From b2fd9b225caf6292e5d9a6f34f07236413684478 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 16:25:28 +0100
Subject: [PATCH 001/285] gradle config updates

---
 gradle.properties   | 4 ++--
 settings.gradle.kts | 1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index 3bf9414dba..f52218fcc2 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,8 +4,8 @@ mcVersion=1.21.4
 
 # Set to true while updating Minecraft version
 updatingMinecraft=false
-#cleanPaperRepo=~/IdeaProjects/Paper/Paper-Server/src/main/java
-updateTaskListIssue=https://github.com/PaperMC/testing/issues/2
+cleanPaperRepo=~/IdeaProjects/Paper/Paper-Server/src/main/java
+updateTaskListIssue=https://github.com/PaperMC/Paper/issues/11736
 
 org.gradle.configuration-cache=true
 org.gradle.caching=true
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2cc62d3e5c..9503dfc6a6 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,7 +3,6 @@ import java.util.Locale
 pluginManagement {
     repositories {
         gradlePluginPortal()
-        mavenLocal()
         maven("https://repo.papermc.io/repository/maven-public/")
     }
 }

From f2ff5966a6f578690059489071f25804f60e6ec7 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 16:52:45 +0100
Subject: [PATCH 002/285] First few files and initial rebuild

---
 build-data/paper.at                           | 23 +++++--------------
 paper-server/patches/features/.gitkeep        |  0
 .../intersection_barrel.json.patch            |  2 +-
 .../CommandSyntaxException.java.patch         |  4 ++--
 .../datafixers/DataFixerBuilder.java.patch    |  4 ++--
 .../mojang/datafixers/util/Either.java.patch  |  8 +++----
 .../com/mojang/logging/LogUtils.java.patch    |  2 +-
 .../mojang/math/OctahedralGroup.java.patch    | 12 +++++-----
 8 files changed, 22 insertions(+), 33 deletions(-)
 delete mode 100644 paper-server/patches/features/.gitkeep
 rename paper-server/patches/{unapplied => sources}/com/mojang/datafixers/DataFixerBuilder.java.patch (97%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/datafixers/util/Either.java.patch (94%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/logging/LogUtils.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/math/OctahedralGroup.java.patch (74%)

diff --git a/build-data/paper.at b/build-data/paper.at
index 48a44de360..da400007ff 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -1,18 +1,7 @@
-# You can use this file to change the access modifiers on a member
-# This line would make the field rollAmount public in Bee
-#public net.minecraft.world.entity.animal.Bee rollAmount
-# This line would make the field public and remove the final modifier
-#public-f net.minecraft.network.protocol.game.ClientboundChatPacket sender
-# Leave out the member and it will apply to the class itself
-# More info, see here https://mcforge.readthedocs.io/en/latest/advanced/accesstransformers/#access-modifiers
-
-# Remap/Decompile fix (unclear why this is happening)
-public net.minecraft.server.MinecraftServer doRunTask(Lnet/minecraft/server/TickTask;)V
-
-# AT remap issue? todo 1.18
-public net.minecraft.world.level.dimension.end.EndDragonFight findExitPortal()Lnet/minecraft/world/level/block/state/pattern/BlockPattern$BlockPatternMatch;
+# This file is auto generated, any changes may be overridden!
+# See CONTRIBUTING.md on how to add access transformers.
 public net.minecraft.nbt.TagParser readArrayTag()Lnet/minecraft/nbt/Tag;
-
-# TODO 1.20 remapSpigotAt.at doesn't remap the return type for this method for some reason
-public net/minecraft/world/entity/Display$TextDisplay getText()Lnet/minecraft/network/chat/Component;
-public net/minecraft/world/entity/Display$BlockDisplay getBlockState()Lnet/minecraft/world/level/block/state/BlockState;
+public net.minecraft.server.MinecraftServer doRunTask(Lnet/minecraft/server/TickTask;)V
+public net.minecraft.world.entity.Display$BlockDisplay getBlockState()Lnet/minecraft/world/level/block/state/BlockState;
+public net.minecraft.world.entity.Display$TextDisplay getText()Lnet/minecraft/network/chat/Component;
+public net.minecraft.world.level.dimension.end.EndDragonFight findExitPortal()Lnet/minecraft/world/level/block/state/pattern/BlockPattern$BlockPatternMatch;
diff --git a/paper-server/patches/features/.gitkeep b/paper-server/patches/features/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch b/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch
index 572f83673b..7eb4e3632c 100644
--- a/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch
+++ b/paper-server/patches/resources/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json.patch
@@ -1,6 +1,6 @@
 --- a/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json
 +++ b/data/minecraft/loot_table/chests/trial_chambers/intersection_barrel.json
-@@ -70,15 +70,6 @@
+@@ -70,15 +_,6 @@
                "add": false,
                "count": 1.0,
                "function": "minecraft:set_count"
diff --git a/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch b/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch
index b3bbcc46e5..5a9ff8c8d0 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/exceptions/CommandSyntaxException.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/brigadier/exceptions/CommandSyntaxException.java
 +++ b/com/mojang/brigadier/exceptions/CommandSyntaxException.java
-@@ -5,7 +5,7 @@
+@@ -5,7 +_,7 @@
  
  import com.mojang.brigadier.Message;
  
@@ -9,7 +9,7 @@
      public static final int CONTEXT_AMOUNT = 10;
      public static boolean ENABLE_COMMAND_STACK_TRACES = true;
      public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions();
-@@ -73,4 +73,11 @@
+@@ -73,4 +_,11 @@
      public int getCursor() {
          return cursor;
      }
diff --git a/paper-server/patches/unapplied/com/mojang/datafixers/DataFixerBuilder.java.patch b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/com/mojang/datafixers/DataFixerBuilder.java.patch
rename to paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
index 6d1be183b0..70f040476a 100644
--- a/paper-server/patches/unapplied/com/mojang/datafixers/DataFixerBuilder.java.patch
+++ b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/datafixers/DataFixerBuilder.java
 +++ b/com/mojang/datafixers/DataFixerBuilder.java
-@@ -29,8 +29,10 @@
+@@ -29,8 +_,10 @@
      private final Int2ObjectSortedMap<Schema> schemas = new Int2ObjectAVLTreeMap<>();
      private final List<DataFix> globalList = new ArrayList<>();
      private final IntSortedSet fixerVersions = new IntAVLTreeSet();
@@ -11,7 +11,7 @@
          this.dataVersion = dataVersion;
      }
  
-@@ -88,6 +90,7 @@
+@@ -88,6 +_,7 @@
              final IntIterator iterator = fixerUpper.fixerVersions().iterator();
              while (iterator.hasNext()) {
                  final int versionKey = iterator.nextInt();
diff --git a/paper-server/patches/unapplied/com/mojang/datafixers/util/Either.java.patch b/paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/com/mojang/datafixers/util/Either.java.patch
rename to paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch
index 810603f8a3..7f682f9002 100644
--- a/paper-server/patches/unapplied/com/mojang/datafixers/util/Either.java.patch
+++ b/paper-server/patches/sources/com/mojang/datafixers/util/Either.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/datafixers/util/Either.java
 +++ b/com/mojang/datafixers/util/Either.java
-@@ -22,7 +22,7 @@
+@@ -22,7 +_,7 @@
      }
  
      private static final class Left<L, R> extends Either<L, R> {
@@ -9,7 +9,7 @@
  
          public Left(final L value) {
              this.value = value;
-@@ -51,7 +51,7 @@
+@@ -51,7 +_,7 @@
  
          @Override
          public Optional<L> left() {
@@ -18,7 +18,7 @@
          }
  
          @Override
-@@ -83,7 +83,7 @@
+@@ -83,7 +_,7 @@
      }
  
      private static final class Right<L, R> extends Either<L, R> {
@@ -27,7 +27,7 @@
  
          public Right(final R value) {
              this.value = value;
-@@ -117,7 +117,7 @@
+@@ -117,7 +_,7 @@
  
          @Override
          public Optional<R> right() {
diff --git a/paper-server/patches/unapplied/com/mojang/logging/LogUtils.java.patch b/paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/com/mojang/logging/LogUtils.java.patch
rename to paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch
index 1cabed3cc3..b05ea2c1ed 100644
--- a/paper-server/patches/unapplied/com/mojang/logging/LogUtils.java.patch
+++ b/paper-server/patches/sources/com/mojang/logging/LogUtils.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/logging/LogUtils.java
 +++ b/com/mojang/logging/LogUtils.java
-@@ -61,4 +61,9 @@
+@@ -61,4 +_,9 @@
      public static Logger getLogger() {
          return LoggerFactory.getLogger(STACK_WALKER.getCallerClass());
      }
diff --git a/paper-server/patches/unapplied/com/mojang/math/OctahedralGroup.java.patch b/paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch
similarity index 74%
rename from paper-server/patches/unapplied/com/mojang/math/OctahedralGroup.java.patch
rename to paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch
index 64c7d5d058..671c030fac 100644
--- a/paper-server/patches/unapplied/com/mojang/math/OctahedralGroup.java.patch
+++ b/paper-server/patches/sources/com/mojang/math/OctahedralGroup.java.patch
@@ -1,14 +1,14 @@
 --- a/com/mojang/math/OctahedralGroup.java
 +++ b/com/mojang/math/OctahedralGroup.java
-@@ -110,6 +110,7 @@
-         this.permutation = axisTransformation;
-         this.transformation = new Matrix3f().scaling(flipX ? -1.0F : 1.0F, flipY ? -1.0F : 1.0F, flipZ ? -1.0F : 1.0F);
-         this.transformation.mul(axisTransformation.transformation());
+@@ -111,6 +_,7 @@
+         this.permutation = permutation;
+         this.transformation = new Matrix3f().scaling(invertX ? -1.0F : 1.0F, invertY ? -1.0F : 1.0F, invertZ ? -1.0F : 1.0F);
+         this.transformation.mul(permutation.transformation());
 +        this.initializeRotationDirections(); // Paper - Avoid Lazy Initialization for Enum Fields
      }
  
      private BooleanList packInversions() {
-@@ -138,7 +139,7 @@
+@@ -139,7 +_,7 @@
          return this.name;
      }
  
@@ -17,7 +17,7 @@
          if (this.rotatedDirections == null) {
              this.rotatedDirections = Maps.newEnumMap(Direction.class);
              Direction.Axis[] axiss = Direction.Axis.values();
-@@ -153,6 +154,10 @@
+@@ -154,6 +_,10 @@
              }
          }
  

From 8324be321ac942afaa6829811772f308fb4bd81e Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:02:13 -0500
Subject: [PATCH 003/285] brig stuff

---
 .../brigadier/CommandDispatcher.java.patch    | 18 +++++------
 .../builder/ArgumentBuilder.java.patch        |  2 +-
 .../brigadier/tree/CommandNode.java.patch     | 30 +++++++++----------
 .../tree/LiteralCommandNode.java.patch        |  8 ++---
 4 files changed, 29 insertions(+), 29 deletions(-)
 rename paper-server/patches/{unapplied => sources}/com/mojang/brigadier/CommandDispatcher.java.patch (88%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/brigadier/builder/ArgumentBuilder.java.patch (98%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/brigadier/tree/CommandNode.java.patch (88%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/brigadier/tree/LiteralCommandNode.java.patch (97%)

diff --git a/paper-server/patches/unapplied/com/mojang/brigadier/CommandDispatcher.java.patch b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/com/mojang/brigadier/CommandDispatcher.java.patch
rename to paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
index ba95e196e5..d5b323d68e 100644
--- a/paper-server/patches/unapplied/com/mojang/brigadier/CommandDispatcher.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/brigadier/CommandDispatcher.java
 +++ b/com/mojang/brigadier/CommandDispatcher.java
-@@ -3,6 +3,7 @@
+@@ -3,6 +_,7 @@
  
  package com.mojang.brigadier;
  
@@ -8,7 +8,7 @@
  import com.mojang.brigadier.builder.LiteralArgumentBuilder;
  import com.mojang.brigadier.context.CommandContext;
  import com.mojang.brigadier.context.CommandContextBuilder;
-@@ -297,15 +298,21 @@
+@@ -297,15 +_,21 @@
          List<ParseResults<S>> potentials = null;
          final int cursor = originalReader.getCursor();
  
@@ -31,7 +31,7 @@
                  } catch (final RuntimeException ex) {
                      throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage());
                  }
-@@ -320,6 +327,7 @@
+@@ -320,6 +_,7 @@
                  }
                  errors.put(child, ex);
                  reader.setCursor(cursor);
@@ -39,7 +39,7 @@
                  continue;
              }
  
-@@ -451,7 +459,7 @@
+@@ -451,7 +_,7 @@
      }
  
      private String getSmartUsage(final CommandNode<S> node, final S source, final boolean optional, final boolean deep) {
@@ -48,16 +48,16 @@
              return null;
          }
  
-@@ -465,7 +473,7 @@
-                 final String redirect = node.getRedirect() == this.root ? "..." : "-> " + node.getRedirect().getUsageText();
-                 return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect;
+@@ -465,7 +_,7 @@
+                 final String redirect = node.getRedirect() == root ? "..." : "-> " + node.getRedirect().getUsageText();
+                 return self + ARGUMENT_SEPARATOR + redirect;
              } else {
 -                final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList());
 +                final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> source == null || c.canUse(source)).collect(Collectors.toList()); // Paper
                  if (children.size() == 1) {
-                     final String usage = this.getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
+                     final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
                      if (usage != null) {
-@@ -537,10 +545,14 @@
+@@ -537,10 +_,14 @@
          int i = 0;
          for (final CommandNode<S> node : parent.getChildren()) {
              CompletableFuture<Suggestions> future = Suggestions.empty();
diff --git a/paper-server/patches/unapplied/com/mojang/brigadier/builder/ArgumentBuilder.java.patch b/paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch
similarity index 98%
rename from paper-server/patches/unapplied/com/mojang/brigadier/builder/ArgumentBuilder.java.patch
rename to paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch
index 16daaa3a3c..e778d183d1 100644
--- a/paper-server/patches/unapplied/com/mojang/brigadier/builder/ArgumentBuilder.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/builder/ArgumentBuilder.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/brigadier/builder/ArgumentBuilder.java
 +++ b/com/mojang/brigadier/builder/ArgumentBuilder.java
-@@ -14,9 +14,17 @@
+@@ -14,9 +_,17 @@
  import java.util.function.Predicate;
  
  public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
diff --git a/paper-server/patches/unapplied/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/com/mojang/brigadier/tree/CommandNode.java.patch
rename to paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
index 179686de6f..39c18089dd 100644
--- a/paper-server/patches/unapplied/com/mojang/brigadier/tree/CommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/brigadier/tree/CommandNode.java
 +++ b/com/mojang/brigadier/tree/CommandNode.java
-@@ -3,6 +3,7 @@
+@@ -3,6 +_,7 @@
  
  package com.mojang.brigadier.tree;
  
@@ -8,7 +8,7 @@
  import com.mojang.brigadier.AmbiguityConsumer;
  import com.mojang.brigadier.Command;
  import com.mojang.brigadier.RedirectModifier;
-@@ -22,6 +23,7 @@
+@@ -22,6 +_,7 @@
  import java.util.Set;
  import java.util.concurrent.CompletableFuture;
  import java.util.function.Predicate;
@@ -16,7 +16,7 @@
  
  public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
      private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
-@@ -32,6 +34,16 @@
+@@ -32,6 +_,16 @@
      private final RedirectModifier<S> modifier;
      private final boolean forks;
      private Command<S> command;
@@ -33,12 +33,12 @@
  
      protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
          this.command = command;
-@@ -61,7 +73,17 @@
-         return this.modifier;
+@@ -61,7 +_,17 @@
+         return modifier;
      }
  
 -    public boolean canUse(final S source) {
-+    // CraftBukkit start
++    // Paper start
 +    public synchronized boolean canUse(final S source) {
 +        if (source instanceof CommandSourceStack) {
 +            try {
@@ -48,28 +48,28 @@
 +                ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking
 +            }
 +        }
-+        // CraftBukkit end
-         return this.requirement.test(source);
++        // Paper end
+         return requirement.test(source);
      }
  
-@@ -151,6 +173,12 @@
+@@ -151,6 +_,12 @@
      protected abstract String getSortedKey();
  
      public Collection<? extends CommandNode<S>> getRelevantNodes(final StringReader input) {
-+        // Paper start - prioritize mc commands in function parsing
++    // Paper start - prioritize mc commands in function parsing
 +        return this.getRelevantNodes(input, null);
 +    }
 +    @org.jetbrains.annotations.ApiStatus.Internal
 +    public Collection<? extends CommandNode<S>> getRelevantNodes(final StringReader input, final Object source) {
-+        // Paper end - prioritize mc commands in function parsing
-         if (this.literals.size() > 0) {
++     // Paper end - prioritize mc commands in function parsing
+         if (literals.size() > 0) {
              final int cursor = input.getCursor();
              while (input.canRead() && input.peek() != ' ') {
-@@ -158,7 +186,21 @@
+@@ -158,7 +_,21 @@
              }
              final String text = input.getString().substring(cursor, input.getCursor());
              input.setCursor(cursor);
--            final LiteralCommandNode<S> literal = this.literals.get(text);
+-            final LiteralCommandNode<S> literal = literals.get(text);
 +            // Paper start - prioritize mc commands in function parsing
 +            LiteralCommandNode<S> literal = null;
 +            if (source instanceof CommandSourceStack css && css.source == net.minecraft.commands.CommandSource.NULL) {
@@ -88,7 +88,7 @@
              if (literal != null) {
                  return Collections.singleton(literal);
              } else {
-@@ -183,4 +225,11 @@
+@@ -183,4 +_,11 @@
      }
  
      public abstract Collection<String> getExamples();
diff --git a/paper-server/patches/unapplied/com/mojang/brigadier/tree/LiteralCommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/com/mojang/brigadier/tree/LiteralCommandNode.java.patch
rename to paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch
index 15d0b7b976..f08f887a30 100644
--- a/paper-server/patches/unapplied/com/mojang/brigadier/tree/LiteralCommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/LiteralCommandNode.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/brigadier/tree/LiteralCommandNode.java
 +++ b/com/mojang/brigadier/tree/LiteralCommandNode.java
-@@ -23,11 +23,19 @@
+@@ -23,11 +_,19 @@
  public class LiteralCommandNode<S> extends CommandNode<S> {
      private final String literal;
      private final String literalLowerCase;
@@ -20,7 +20,7 @@
      }
  
      public String getLiteral() {
-@@ -42,7 +50,12 @@
+@@ -42,7 +_,12 @@
      @Override
      public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
          final int start = reader.getCursor();
@@ -34,7 +34,7 @@
          if (end > -1) {
              contextBuilder.withNode(this, StringRange.between(start, end));
              return;
-@@ -51,7 +64,10 @@
+@@ -51,7 +_,10 @@
          throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literal);
      }
  
@@ -46,7 +46,7 @@
          final int start = reader.getCursor();
          if (reader.canRead(literal.length())) {
              final int end = start + literal.length();
-@@ -78,7 +94,7 @@
+@@ -78,7 +_,7 @@
  
      @Override
      public boolean isValidInput(final String input) {

From 344088ae5b7513ec2ba11d91ccfbca0795e52182 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 17:04:25 +0100
Subject: [PATCH 004/285] Choose tasks at random

---
 build.gradle.kts | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 5f01c51835..2540a7c069 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,6 +8,7 @@ import java.nio.file.Files
 import java.nio.file.SimpleFileVisitor
 import kotlin.io.path.*
 import java.nio.file.Path
+import kotlin.random.Random
 
 plugins {
     id("io.papermc.paperweight.core") version "2.0.0-SNAPSHOT" apply false
@@ -98,7 +99,13 @@ tasks.register("gibWork") {
         val end = html.indexOf("```", start + beginMarker.length)
         val taskList = html.substring(start + beginMarker.length, end)
 
-        val next = taskList.split("\\n").first { it.startsWith("- [ ]") }.replace("- [ ] ", "")
+        // Extract all incomplete tasks and select a random one
+        val incompleteTasks = taskList.split("\\n").filter { it.startsWith("- [ ]") }.map { it.replace("- [ ] ", "") }
+        if (incompleteTasks.isEmpty()) {
+            error("No incomplete tasks found in the task list.")
+        }
+
+        val next = incompleteTasks[Random.nextInt(incompleteTasks.size)]
 
         println("checking out $next...")
         val dir = patchesFolder.resolve("unapplied").resolve(next)

From 92ef45d1663165d0317662ce1b58088e0cb2144d Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Fri, 13 Dec 2024 17:14:25 +0100
Subject: [PATCH 005/285] First attempt

---
 .../component/DataComponentPatch.java.patch   | 34 +++++++------------
 .../core/component/DataComponents.java.patch  |  4 +--
 2 files changed, 14 insertions(+), 24 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/component/DataComponentPatch.java.patch (62%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/component/DataComponents.java.patch (98%)

diff --git a/paper-server/patches/unapplied/net/minecraft/core/component/DataComponentPatch.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/core/component/DataComponentPatch.java.patch
rename to paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
index 53846b237e..272f5afdc5 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/component/DataComponentPatch.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
@@ -1,33 +1,24 @@
 --- a/net/minecraft/core/component/DataComponentPatch.java
 +++ b/net/minecraft/core/component/DataComponentPatch.java
-@@ -61,7 +61,7 @@
-             }
+@@ -125,7 +_,13 @@
          }
  
--        return reference2objectmap;
-+        return (Reference2ObjectMap) reference2objectmap; // CraftBukkit - decompile error
-     });
-     public static final StreamCodec<RegistryFriendlyByteBuf, DataComponentPatch> STREAM_CODEC = new StreamCodec<RegistryFriendlyByteBuf, DataComponentPatch>() {
-         public DataComponentPatch decode(RegistryFriendlyByteBuf registryfriendlybytebuf) {
-@@ -144,7 +144,13 @@
-         }
- 
-         private static <T> void encodeComponent(RegistryFriendlyByteBuf buf, DataComponentType<T> type, Object value) {
--            type.streamCodec().encode(buf, value);
+         private static <T> void encodeComponent(RegistryFriendlyByteBuf buffer, DataComponentType<T> component, Object value) {
+-            component.streamCodec().encode(buffer, (T)value);
 +            // Paper start - codec errors of random anonymous classes are useless
 +            try {
-+            type.streamCodec().encode(buf, (T) value); // CraftBukkit - decompile error
++                component.streamCodec().encode(buffer, (T) value); // CraftBukkit - decompile error
 +            } catch (final Exception e) {
-+                throw new RuntimeException("Error encoding component " + type, e);
++                throw new RuntimeException("Error encoding component " + component, e);
 +            }
 +            // Paper end - codec errors of random anonymous classes are useless
          }
      };
      private static final String REMOVED_PREFIX = "!";
-@@ -270,7 +276,43 @@
-         private final Reference2ObjectMap<DataComponentType<?>, Optional<?>> map = new Reference2ObjectArrayMap();
+@@ -230,6 +_,42 @@
  
-         Builder() {}
+         Builder() {
+         }
 +
 +        // CraftBukkit start
 +        public void copy(DataComponentPatch orig) {
@@ -45,7 +36,7 @@
 +        public boolean isEmpty() {
 +            return this.map.isEmpty();
 +        }
- 
++
 +        @Override
 +        public boolean equals(Object object) {
 +            if (this == object) {
@@ -64,7 +55,6 @@
 +            return this.map.hashCode();
 +        }
 +        // CraftBukkit end
-+
-         public <T> DataComponentPatch.Builder set(DataComponentType<T> type, T value) {
-             this.map.put(type, Optional.of(value));
-             return this;
+ 
+         public <T> DataComponentPatch.Builder set(DataComponentType<T> component, T value) {
+             this.map.put(component, Optional.of(value));
diff --git a/paper-server/patches/unapplied/net/minecraft/core/component/DataComponents.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch
similarity index 98%
rename from paper-server/patches/unapplied/net/minecraft/core/component/DataComponents.java.patch
rename to paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch
index eb470da08e..c1f061a0af 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/component/DataComponents.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/component/DataComponents.java
 +++ b/net/minecraft/core/component/DataComponents.java
-@@ -180,10 +180,10 @@
+@@ -180,10 +_,10 @@
          "map_post_processing", builder -> builder.networkSynchronized(MapPostProcessing.STREAM_CODEC)
      );
      public static final DataComponentType<ChargedProjectiles> CHARGED_PROJECTILES = register(
@@ -13,7 +13,7 @@
      );
      public static final DataComponentType<PotionContents> POTION_CONTENTS = register(
          "potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding()
-@@ -250,7 +250,7 @@
+@@ -250,7 +_,7 @@
          "pot_decorations", builder -> builder.persistent(PotDecorations.CODEC).networkSynchronized(PotDecorations.STREAM_CODEC).cacheEncoding()
      );
      public static final DataComponentType<ItemContainerContents> CONTAINER = register(

From ee51737be669f84c9555f26e1b0fe1827250dd73 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Fri, 13 Dec 2024 17:24:35 +0100
Subject: [PATCH 006/285] More work

---
 gradle.properties                             |  1 -
 .../target/DefendVillageTargetGoal.java.patch |  2 +-
 .../goal/target/HurtByTargetGoal.java.patch   |  4 +--
 .../NearestAttackableTargetGoal.java.patch    |  2 +-
 .../target/OwnerHurtByTargetGoal.java.patch   |  8 ++---
 .../target/OwnerHurtTargetGoal.java.patch     |  8 ++---
 .../ai/goal/target/TargetGoal.java.patch      | 20 +++++++++++++
 .../ai/goal/target/TargetGoal.java.patch      | 30 -------------------
 8 files changed, 32 insertions(+), 43 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch (70%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch (70%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch

diff --git a/gradle.properties b/gradle.properties
index f52218fcc2..27969fea17 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,7 +4,6 @@ mcVersion=1.21.4
 
 # Set to true while updating Minecraft version
 updatingMinecraft=false
-cleanPaperRepo=~/IdeaProjects/Paper/Paper-Server/src/main/java
 updateTaskListIssue=https://github.com/PaperMC/Paper/issues/11736
 
 org.gradle.configuration-cache=true
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch
index 3a2b688e7b..8144be0e41 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java
-@@ -61,7 +61,7 @@
+@@ -48,7 +_,7 @@
  
      @Override
      public void start() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch
index 3b4092a6f3..1fd9b780b6 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java
-@@ -67,7 +67,7 @@
+@@ -59,7 +_,7 @@
  
      @Override
      public void start() {
@@ -9,7 +9,7 @@
          this.targetMob = this.mob.getTarget();
          this.timestamp = this.mob.getLastHurtByMobTimestamp();
          this.unseenMemoryTicks = 300;
-@@ -114,6 +114,6 @@
+@@ -114,6 +_,6 @@
      }
  
      protected void alertOther(Mob mob, LivingEntity target) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch
index 5d304f4ac8..a6474a3a6b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
-@@ -70,7 +70,7 @@
+@@ -73,7 +_,7 @@
  
      @Override
      public void start() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch
index edaee8e182..e411caa1e3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/target/OwnerHurtByTargetGoal.java
-@@ -38,7 +38,7 @@
+@@ -37,7 +_,7 @@
  
      @Override
      public void start() {
 -        this.mob.setTarget(this.ownerLastHurtBy);
 +        this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit - reason
-         LivingEntity entityliving = this.tameAnimal.getOwner();
- 
-         if (entityliving != null) {
+         LivingEntity owner = this.tameAnimal.getOwner();
+         if (owner != null) {
+             this.timestamp = owner.getLastHurtByMobTimestamp();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch
index 696e8ea9fa..d7274fc9a1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/target/OwnerHurtTargetGoal.java
-@@ -38,7 +38,7 @@
+@@ -37,7 +_,7 @@
  
      @Override
      public void start() {
 -        this.mob.setTarget(this.ownerLastHurt);
 +        this.mob.setTarget(this.ownerLastHurt, org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit - reason
-         LivingEntity entityliving = this.tameAnimal.getOwner();
- 
-         if (entityliving != null) {
+         LivingEntity owner = this.tameAnimal.getOwner();
+         if (owner != null) {
+             this.timestamp = owner.getLastHurtMobTimestamp();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
new file mode 100644
index 0000000000..9973960f95
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/ai/goal/target/TargetGoal.java
++++ b/net/minecraft/world/entity/ai/goal/target/TargetGoal.java
+@@ -63,7 +_,7 @@
+                         }
+                     }
+ 
+-                    this.mob.setTarget(target);
++                    this.mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit
+                     return true;
+                 }
+             }
+@@ -83,7 +_,7 @@
+ 
+     @Override
+     public void stop() {
+-        this.mob.setTarget(null);
++        this.mob.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
+         this.targetMob = null;
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
deleted file mode 100644
index ab51c6b369..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/entity/ai/goal/target/TargetGoal.java
-+++ b/net/minecraft/world/entity/ai/goal/target/TargetGoal.java
-@@ -10,6 +10,9 @@
- import net.minecraft.world.level.pathfinder.Node;
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.scores.PlayerTeam;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public abstract class TargetGoal extends Goal {
- 
-@@ -69,7 +72,7 @@
-                         }
-                     }
- 
--                    this.mob.setTarget(entityliving);
-+                    this.mob.setTarget(entityliving, EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit
-                     return true;
-                 }
-             }
-@@ -89,7 +92,7 @@
- 
-     @Override
-     public void stop() {
--        this.mob.setTarget((LivingEntity) null);
-+        this.mob.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
-         this.targetMob = null;
-     }
- 

From a68b56a8647bfdebbc5b91c90f1e67f1203c8994 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:38:42 -0500
Subject: [PATCH 007/285] advancement stuff

---
 build.gradle.kts                              |  1 +
 .../advancements/AdvancementHolder.java.patch | 13 +++++++
 .../advancements/AdvancementTree.java.patch   | 20 +++++++++++
 .../advancements/DisplayInfo.java.patch       |  2 +-
 .../critereon/LocationPredicate.java.patch    |  8 ++---
 .../SimpleCriterionTrigger.java.patch         | 36 +++++++++----------
 .../advancements/AdvancementHolder.java.patch | 24 -------------
 .../advancements/AdvancementTree.java.patch   | 20 -----------
 8 files changed, 57 insertions(+), 67 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/advancements/DisplayInfo.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/advancements/critereon/LocationPredicate.java.patch (72%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch (63%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/advancements/AdvancementHolder.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/advancements/AdvancementTree.java.patch

diff --git a/build.gradle.kts b/build.gradle.kts
index 2540a7c069..28912b4343 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -157,6 +157,7 @@ tasks.register("checkWork") {
             val relative = patchFolder.relativize(it).toString().replace(".patch", "")
             val source = sourceFolder.resolve(relative)
             val target = targetFolder.resolve(relative)
+            if (target.isDirectory()) { return@forEach }
             if (back) {
                 target.copyTo(source, overwrite = true)
             } else {
diff --git a/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch b/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch
new file mode 100644
index 0000000000..6450499bcb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/advancements/AdvancementHolder.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/advancements/AdvancementHolder.java
++++ b/net/minecraft/advancements/AdvancementHolder.java
+@@ -26,4 +_,10 @@
+     public String toString() {
+         return this.id.toString();
+     }
++
++    // CraftBukkit start
++    public final org.bukkit.advancement.Advancement toBukkit() {
++        return new org.bukkit.craftbukkit.advancement.CraftAdvancement(this);
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch b/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch
new file mode 100644
index 0000000000..aeb4408967
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/advancements/AdvancementTree.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/advancements/AdvancementTree.java
++++ b/net/minecraft/advancements/AdvancementTree.java
+@@ -26,7 +_,7 @@
+             this.remove(advancementNode);
+         }
+ 
+-        LOGGER.info("Forgot about advancement {}", node.holder());
++        LOGGER.debug("Forgot about advancement {}", node.holder()); // Paper - Improve logging and errors
+         this.nodes.remove(node.holder().id());
+         if (node.parent() == null) {
+             this.roots.remove(node);
+@@ -62,7 +_,7 @@
+             }
+         }
+ 
+-        LOGGER.info("Loaded {} advancements", this.nodes.size());
++        // LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement
+     }
+ 
+     private boolean tryInsert(AdvancementHolder advancement) {
diff --git a/paper-server/patches/unapplied/net/minecraft/advancements/DisplayInfo.java.patch b/paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/advancements/DisplayInfo.java.patch
rename to paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch
index a0fdd33f43..c7956c2de5 100644
--- a/paper-server/patches/unapplied/net/minecraft/advancements/DisplayInfo.java.patch
+++ b/paper-server/patches/sources/net/minecraft/advancements/DisplayInfo.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/advancements/DisplayInfo.java
 +++ b/net/minecraft/advancements/DisplayInfo.java
-@@ -37,6 +37,7 @@
+@@ -37,6 +_,7 @@
      private final boolean hidden;
      private float x;
      private float y;
diff --git a/paper-server/patches/unapplied/net/minecraft/advancements/critereon/LocationPredicate.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch
similarity index 72%
rename from paper-server/patches/unapplied/net/minecraft/advancements/critereon/LocationPredicate.java.patch
rename to paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch
index 7f7873fc42..e43dabe617 100644
--- a/paper-server/patches/unapplied/net/minecraft/advancements/critereon/LocationPredicate.java.patch
+++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/LocationPredicate.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/advancements/critereon/LocationPredicate.java
 +++ b/net/minecraft/advancements/critereon/LocationPredicate.java
-@@ -44,7 +44,7 @@
-     public boolean matches(ServerLevel world, double x, double y, double z) {
+@@ -44,7 +_,7 @@
+     public boolean matches(ServerLevel level, double x, double y, double z) {
          if (this.position.isPresent() && !this.position.get().matches(x, y, z)) {
              return false;
--        } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) {
-+        } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks
+-        } else if (this.dimension.isPresent() && this.dimension.get() != level.dimension()) {
++        } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? level.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(level))) { // Paper - Add option for strict advancement dimension checks
              return false;
          } else {
              BlockPos blockPos = BlockPos.containing(x, y, z);
diff --git a/paper-server/patches/unapplied/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
rename to paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
index d080616002..f953971ef6 100644
--- a/paper-server/patches/unapplied/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
+++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java
 +++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java
-@@ -15,41 +15,41 @@
+@@ -15,41 +_,41 @@
  import net.minecraft.world.level.storage.loot.LootContext;
  
  public abstract class SimpleCriterionTrigger<T extends SimpleCriterionTrigger.SimpleInstance> implements CriterionTrigger<T> {
@@ -8,34 +8,34 @@
 +    // private final Map<PlayerAdvancements, Set<CriterionTrigger.Listener<T>>> players = Maps.newIdentityHashMap(); // Paper - fix AdvancementDataPlayer leak; moved into AdvancementDataPlayer to fix memory leak
  
      @Override
-     public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener<T> conditions) {
--        this.players.computeIfAbsent(manager, managerx -> Sets.newHashSet()).add(conditions);
-+        manager.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(conditions);  // Paper - fix AdvancementDataPlayer leak
+     public final void addPlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener<T> listener) {
+-        this.players.computeIfAbsent(playerAdvancements, advancements -> Sets.newHashSet()).add(listener);
++        playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener);  // Paper - fix AdvancementDataPlayer leak
      }
  
      @Override
-     public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener<T> conditions) {
--        Set<CriterionTrigger.Listener<T>> set = this.players.get(manager);
-+        Set<CriterionTrigger.Listener<T>> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
+     public final void removePlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener<T> listener) {
+-        Set<CriterionTrigger.Listener<T>> set = this.players.get(playerAdvancements);
++        Set<CriterionTrigger.Listener<T>> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
          if (set != null) {
-             set.remove(conditions);
+             set.remove(listener);
              if (set.isEmpty()) {
--                this.players.remove(manager);
-+                manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
+-                this.players.remove(playerAdvancements);
++                playerAdvancements.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
              }
          }
      }
  
      @Override
-     public final void removePlayerListeners(PlayerAdvancements tracker) {
--        this.players.remove(tracker);
-+        tracker.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
+     public final void removePlayerListeners(PlayerAdvancements playerAdvancements) {
+-        this.players.remove(playerAdvancements);
++        playerAdvancements.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
      }
  
-     protected void trigger(ServerPlayer player, Predicate<T> predicate) {
-         PlayerAdvancements playerAdvancements = player.getAdvancements();
--        Set<CriterionTrigger.Listener<T>> set = this.players.get(playerAdvancements);
-+        Set<CriterionTrigger.Listener<T>> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
+     protected void trigger(ServerPlayer player, Predicate<T> testTrigger) {
+         PlayerAdvancements advancements = player.getAdvancements();
+-        Set<CriterionTrigger.Listener<T>> set = this.players.get(advancements);
++        Set<CriterionTrigger.Listener<T>> set = (Set) advancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
          if (set != null && !set.isEmpty()) {
 -            LootContext lootContext = EntityPredicate.createContext(player, player);
 +            LootContext lootContext = null; // EntityPredicate.createContext(player, player); // Paper - Perf: lazily create LootContext for criterions
@@ -43,7 +43,7 @@
  
              for (CriterionTrigger.Listener<T> listener : set) {
                  T simpleInstance = listener.trigger();
-                 if (predicate.test(simpleInstance)) {
+                 if (testTrigger.test(simpleInstance)) {
                      Optional<ContextAwarePredicate> optional = simpleInstance.player();
 -                    if (optional.isEmpty() || optional.get().matches(lootContext)) {
 +                    if (optional.isEmpty() || optional.get().matches(lootContext = (lootContext == null ? EntityPredicate.createContext(player, player) : lootContext))) { // Paper - Perf: lazily create LootContext for criterions
diff --git a/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementHolder.java.patch b/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementHolder.java.patch
deleted file mode 100644
index d41639df88..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementHolder.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/advancements/AdvancementHolder.java
-+++ b/net/minecraft/advancements/AdvancementHolder.java
-@@ -5,6 +5,10 @@
- import net.minecraft.network.codec.ByteBufCodecs;
- import net.minecraft.network.codec.StreamCodec;
- import net.minecraft.resources.ResourceLocation;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.advancement.CraftAdvancement;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
-+// CraftBukkit end
- 
- public record AdvancementHolder(ResourceLocation id, Advancement value) {
- 
-@@ -38,4 +42,10 @@
-     public String toString() {
-         return this.id.toString();
-     }
-+
-+    // CraftBukkit start
-+    public final org.bukkit.advancement.Advancement toBukkit() {
-+        return new CraftAdvancement(this);
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementTree.java.patch b/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementTree.java.patch
deleted file mode 100644
index 4bec8c46eb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/advancements/AdvancementTree.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/advancements/AdvancementTree.java
-+++ b/net/minecraft/advancements/AdvancementTree.java
-@@ -35,7 +35,7 @@
-             this.remove(advancementnode1);
-         }
- 
--        AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder());
-+        AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors
-         this.nodes.remove(advancement.holder().id());
-         if (advancement.parent() == null) {
-             this.roots.remove(advancement);
-@@ -77,7 +77,7 @@
-             }
-         }
- 
--        AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size());
-+        // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement
-     }
- 
-     private boolean tryInsert(AdvancementHolder advancement) {

From 83b73701310c6eaf4b6000e34dc82d9f6ef3e218 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 18:06:27 +0100
Subject: [PATCH 008/285] Players directory

---
 .../server/players/BanListEntry.java.patch    |  16 +-
 .../players/GameProfileCache.java.patch       | 212 +++++++++++++++++
 .../players/OldUsersConverter.java.patch      |  76 +++---
 .../server/players/SleepStatus.java.patch     |  41 ++++
 .../server/players/StoredUserList.java.patch  |  81 +++++++
 .../players/UserBanListEntry.java.patch       |  11 +-
 .../server/players/UserWhiteList.java.patch   |   6 +-
 .../players/GameProfileCache.java.patch       | 217 ------------------
 .../server/players/SleepStatus.java.patch     |  44 ----
 .../server/players/StoredUserList.java.patch  |  96 --------
 10 files changed, 387 insertions(+), 413 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/players/BanListEntry.java.patch (65%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/players/OldUsersConverter.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/players/UserBanListEntry.java.patch (85%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/players/UserWhiteList.java.patch (88%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch
rename to paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch
index 205eaad746..3e100a7758 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch
@@ -1,17 +1,17 @@
 --- a/net/minecraft/server/players/BanListEntry.java
 +++ b/net/minecraft/server/players/BanListEntry.java
-@@ -27,7 +27,7 @@
+@@ -26,7 +_,7 @@
      }
  
-     protected BanListEntry(@Nullable T key, JsonObject json) {
--        super(key);
-+        super(BanListEntry.checkExpiry(key, json)); // CraftBukkit
+     protected BanListEntry(@Nullable T user, JsonObject entryData) {
+-        super(user);
++        super(BanListEntry.checkExpiry(user, entryData)); // CraftBukkit
  
          Date date;
- 
-@@ -83,4 +83,22 @@
-         json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires));
-         json.addProperty("reason", this.reason);
+         try {
+@@ -80,4 +_,22 @@
+         data.addProperty("expires", this.expires == null ? "forever" : DATE_FORMAT.format(this.expires));
+         data.addProperty("reason", this.reason);
      }
 +
 +    // CraftBukkit start
diff --git a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
new file mode 100644
index 0000000000..aa0d442fc8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
@@ -0,0 +1,212 @@
+--- a/net/minecraft/server/players/GameProfileCache.java
++++ b/net/minecraft/server/players/GameProfileCache.java
+@@ -17,8 +_,6 @@
+ import java.io.File;
+ import java.io.FileNotFoundException;
+ import java.io.IOException;
+-import java.io.Reader;
+-import java.io.Writer;
+ import java.nio.charset.StandardCharsets;
+ import java.text.DateFormat;
+ import java.text.ParseException;
+@@ -56,6 +_,10 @@
+     private final AtomicLong operationCount = new AtomicLong();
+     @Nullable
+     private Executor executor;
++    // Paper start - Fix GameProfileCache concurrency
++    protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock();
++    protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock();
++    // Paper end - Fix GameProfileCache concurrency
+ 
+     public GameProfileCache(GameProfileRepository profileRepository, File file) {
+         this.profileRepository = profileRepository;
+@@ -64,10 +_,12 @@
+     }
+ 
+     private void safeAdd(GameProfileCache.GameProfileInfo profile) {
++        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+         GameProfile profile1 = profile.getProfile();
+         profile.setLastAccess(this.getNextOperation());
+         this.profilesByName.put(profile1.getName().toLowerCase(Locale.ROOT), profile);
+         this.profilesByUUID.put(profile1.getId(), profile);
++        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+     }
+ 
+     private static Optional<GameProfile> lookupGameProfile(GameProfileRepository profileRepo, String name) {
+@@ -86,6 +_,8 @@
+                     atomicReference.set(null);
+                 }
+             };
++            if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name
++                && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status
+             profileRepo.findProfilesByNames(new String[]{name}, profileLookupCallback);
+             GameProfile gameProfile = atomicReference.get();
+             return gameProfile != null ? Optional.of(gameProfile) : createUnknownProfile(name);
+@@ -101,7 +_,7 @@
+     }
+ 
+     private static boolean usesAuthentication() {
+-        return usesAuthentication;
++        return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status
+     }
+ 
+     public void add(GameProfile gameProfile) {
+@@ -111,15 +_,29 @@
+         Date time = instance.getTime();
+         GameProfileCache.GameProfileInfo gameProfileInfo = new GameProfileCache.GameProfileInfo(gameProfile, time);
+         this.safeAdd(gameProfileInfo);
+-        this.save();
++        if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving
+     }
+ 
+     private long getNextOperation() {
+         return this.operationCount.incrementAndGet();
+     }
+ 
++    // Paper start
++    public @Nullable GameProfile getProfileIfCached(String name) {
++        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
++        GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT));
++        if (entry == null) {
++            return null;
++        }
++        entry.setLastAccess(this.getNextOperation());
++        return entry.getProfile();
++        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
++    }
++    // Paper end
++
+     public Optional<GameProfile> get(String name) {
+         String string = name.toLowerCase(Locale.ROOT);
++        boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+         GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByName.get(string);
+         boolean flag = false;
+         if (gameProfileInfo != null && new Date().getTime() >= gameProfileInfo.expirationDate.getTime()) {
+@@ -133,19 +_,24 @@
+         if (gameProfileInfo != null) {
+             gameProfileInfo.setLastAccess(this.getNextOperation());
+             optional = Optional.of(gameProfileInfo.getProfile());
++            stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
+         } else {
+-            optional = lookupGameProfile(this.profileRepository, string);
++            stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
++            try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency
++            optional = lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players
++            } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+             if (optional.isPresent()) {
+                 this.add(optional.get());
+                 flag = false;
+             }
+         }
+ 
+-        if (flag) {
+-            this.save();
++        if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled
++            this.save(true); // Paper - Perf: Async GameProfileCache saving
+         }
+ 
+         return optional;
++        } finally { if (stateLocked) {  this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency
+     }
+ 
+     public CompletableFuture<Optional<GameProfile>> getAsync(String name) {
+@@ -157,7 +_,7 @@
+                 return completableFuture;
+             } else {
+                 CompletableFuture<Optional<GameProfile>> completableFuture1 = CompletableFuture.<Optional<GameProfile>>supplyAsync(
+-                        () -> this.get(name), Util.backgroundExecutor().forName("getProfile")
++                        () -> this.get(name), Util.PROFILE_EXECUTOR // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
+                     )
+                     .whenCompleteAsync((gameProfile, exception) -> this.requests.remove(name), this.executor);
+                 this.requests.put(name, completableFuture1);
+@@ -167,6 +_,7 @@
+     }
+ 
+     public Optional<GameProfile> get(UUID uuid) {
++        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+         GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByUUID.get(uuid);
+         if (gameProfileInfo == null) {
+             return Optional.empty();
+@@ -174,6 +_,7 @@
+             gameProfileInfo.setLastAccess(this.getNextOperation());
+             return Optional.of(gameProfileInfo.getProfile());
+         }
++        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+     }
+ 
+     public void setExecutor(Executor exectutor) {
+@@ -193,7 +_,7 @@
+ 
+         try {
+             Object var9;
+-            try (Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) {
++            try (java.io.Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) {
+                 JsonArray jsonArray = this.gson.fromJson(reader, JsonArray.class);
+                 if (jsonArray != null) {
+                     DateFormat dateFormat = createDateFormat();
+@@ -206,6 +_,11 @@
+ 
+             return (List<GameProfileCache.GameProfileInfo>)var9;
+         } catch (FileNotFoundException var7) {
++        // Spigot Start
++        } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) {
++            GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." );
++            this.file.delete();
++        // Spigot End
+         } catch (JsonParseException | IOException var8) {
+             LOGGER.warn("Failed to load profile cache {}", this.file, var8);
+         }
+@@ -213,24 +_,45 @@
+         return list;
+     }
+ 
+-    public void save() {
++    public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving
+         JsonArray jsonArray = new JsonArray();
+         DateFormat dateFormat = createDateFormat();
+-        this.getTopMRUProfiles(1000).forEach(info -> jsonArray.add(writeGameProfile(info, dateFormat)));
++        this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((info) -> jsonArray.add(writeGameProfile(info, dateFormat))); // Spigot // Paper - Fix GameProfileCache concurrency
+         String string = this.gson.toJson((JsonElement)jsonArray);
++        Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving
+ 
+-        try (Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
++        try (java.io.Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
+             writer.write(string);
+         } catch (IOException var9) {
+         }
++        // Paper start - Perf: Async GameProfileCache saving
++        };
++        if (asyncSave) {
++            io.papermc.paper.util.MCUtil.scheduleAsyncTask(save);
++        } else {
++            save.run();
++        }
++        // Paper end - Perf: Async GameProfileCache saving
+     }
+ 
+     private Stream<GameProfileCache.GameProfileInfo> getTopMRUProfiles(int limit) {
+-        return ImmutableList.copyOf(this.profilesByUUID.values())
+-            .stream()
+-            .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed())
+-            .limit(limit);
+-    }
++       // Paper start - Fix GameProfileCache concurrency
++       return this.listTopMRUProfiles(limit).stream();
++    }
++
++    private List<GameProfileCache.GameProfileInfo> listTopMRUProfiles(int limit) {
++        try {
++            this.stateLock.lock();
++            return this.profilesByUUID.values()
++                .stream()
++                .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed())
++                .limit(limit)
++                .toList();
++        } finally {
++            this.stateLock.unlock();
++        }
++    }
++    // Paper end - Fix GameProfileCache concurrency
+ 
+     private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo profileInfo, DateFormat dateFormat) {
+         JsonObject jsonObject = new JsonObject();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch
rename to paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch
index 7f3147ad37..cf078d7683 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/players/OldUsersConverter.java
 +++ b/net/minecraft/server/players/OldUsersConverter.java
-@@ -21,6 +21,9 @@
+@@ -20,6 +_,9 @@
  import java.util.UUID;
  import javax.annotation.Nullable;
  import net.minecraft.core.UUIDUtil;
@@ -10,62 +10,61 @@
  import net.minecraft.server.MinecraftServer;
  import net.minecraft.server.dedicated.DedicatedServer;
  import net.minecraft.util.StringUtil;
-@@ -62,7 +65,8 @@
-             return new String[i];
-         });
+@@ -49,7 +_,8 @@
  
+     private static void lookupPlayers(MinecraftServer server, Collection<String> names, ProfileLookupCallback callback) {
+         String[] strings = names.stream().filter(name -> !StringUtil.isNullOrEmpty(name)).toArray(String[]::new);
 -        if (server.usesAuthentication()) {
 +        if (server.usesAuthentication() ||
 +            (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now.  // Paper - Add setting for proxy online mode status
-             server.getProfileRepository().findProfilesByNames(astring, callback);
+             server.getProfileRepository().findProfilesByNames(strings, callback);
          } else {
-             String[] astring1 = astring;
-@@ -85,7 +89,7 @@
+             for (String string : strings) {
+@@ -65,7 +_,7 @@
                  try {
-                     gameprofilebanlist.load();
-                 } catch (IOException ioexception) {
--                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName(), ioexception);
-+                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+                     userBanList.load();
+                 } catch (IOException var6) {
+-                    LOGGER.warn("Could not load existing file {}", userBanList.getFile().getName(), var6);
++                    LOGGER.warn("Could not load existing file {}", userBanList.getFile().getName()); // CraftBukkit - don't print stacktrace
                  }
              }
  
-@@ -143,7 +147,7 @@
+@@ -120,7 +_,7 @@
                  try {
-                     ipbanlist.load();
-                 } catch (IOException ioexception) {
--                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName(), ioexception);
-+                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+                     ipBanList.load();
+                 } catch (IOException var11) {
+-                    LOGGER.warn("Could not load existing file {}", ipBanList.getFile().getName(), var11);
++                    LOGGER.warn("Could not load existing file {}", ipBanList.getFile().getName()); // CraftBukkit - don't print stacktrace
                  }
              }
  
-@@ -184,7 +188,7 @@
+@@ -156,7 +_,7 @@
                  try {
-                     oplist.load();
-                 } catch (IOException ioexception) {
--                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName(), ioexception);
-+                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace
+                     serverOpList.load();
+                 } catch (IOException var6) {
+-                    LOGGER.warn("Could not load existing file {}", serverOpList.getFile().getName(), var6);
++                    LOGGER.warn("Could not load existing file {}", serverOpList.getFile().getName()); // CraftBukkit - don't print stacktrace
                  }
              }
  
-@@ -228,7 +232,7 @@
+@@ -200,7 +_,7 @@
                  try {
-                     whitelist.load();
-                 } catch (IOException ioexception) {
--                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName(), ioexception);
-+                    OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace
+                     userWhiteList.load();
+                 } catch (IOException var6) {
+-                    LOGGER.warn("Could not load existing file {}", userWhiteList.getFile().getName(), var6);
++                    LOGGER.warn("Could not load existing file {}", userWhiteList.getFile().getName()); // CraftBukkit - don't print stacktrace
                  }
              }
  
-@@ -346,7 +350,39 @@
-                     private void movePlayerFile(File playerDataFolder, String fileName, String uuid) {
-                         File file5 = new File(file, fileName + ".dat");
-                         File file6 = new File(playerDataFolder, uuid + ".dat");
-+
+@@ -313,6 +_,37 @@
+                     private void movePlayerFile(File file3, String oldFileName, String newFileName) {
+                         File file4 = new File(worldPlayersDirectory, oldFileName + ".dat");
+                         File file5 = new File(file3, newFileName + ".dat");
 +                        // CraftBukkit start - Use old file name to seed lastKnownName
 +                        CompoundTag root = null;
 +
 +                        try {
-+                            root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
++                            root = NbtIo.readCompressed(new java.io.FileInputStream(file4), NbtAccounter.unlimitedHeap());
 +                        } catch (Exception exception) {
 +                            // Paper start
 +                            io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception);
@@ -73,16 +72,16 @@
 +                            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception);
 +                            // Paper end
 +                        }
- 
++
 +                        if (root != null) {
 +                            if (!root.contains("bukkit")) {
 +                                root.put("bukkit", new CompoundTag());
 +                            }
 +                            CompoundTag data = root.getCompound("bukkit");
-+                            data.putString("lastKnownName", fileName);
++                            data.putString("lastKnownName", oldFileName);
 +
 +                            try {
-+                                NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
++                                NbtIo.writeCompressed(root, new java.io.FileOutputStream(file1));
 +                            } catch (Exception exception) {
 +                                // Paper start
 +                                io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception);
@@ -92,7 +91,6 @@
 +                            }
 +                       }
 +                        // CraftBukkit end
-+
-                         OldUsersConverter.ensureDirectoryExists(playerDataFolder);
-                         if (!file5.renameTo(file6)) {
-                             throw new OldUsersConverter.ConversionError("Could not convert file for " + fileName);
+                         OldUsersConverter.ensureDirectoryExists(file3);
+                         if (!file4.renameTo(file5)) {
+                             throw new OldUsersConverter.ConversionError("Could not convert file for " + oldFileName);
diff --git a/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch
new file mode 100644
index 0000000000..feab2a6264
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/server/players/SleepStatus.java
++++ b/net/minecraft/server/players/SleepStatus.java
+@@ -14,8 +_,11 @@
+     }
+ 
+     public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List<ServerPlayer> sleepingPlayers) {
+-        int i = (int)sleepingPlayers.stream().filter(Player::isSleepingLongEnough).count();
+-        return i >= this.sleepersNeeded(requiredSleepPercentage);
++        // CraftBukkit start
++        int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count();
++        boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough);
++        return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage);
++        // CraftBukkit end
+     }
+ 
+     public int sleepersNeeded(int requiredSleepPercentage) {
+@@ -35,16 +_,22 @@
+         int i1 = this.sleepingPlayers;
+         this.activePlayers = 0;
+         this.sleepingPlayers = 0;
++        boolean anySleep = false; // CraftBukkit
+ 
+         for (ServerPlayer serverPlayer : players) {
+             if (!serverPlayer.isSpectator()) {
+                 this.activePlayers++;
+-                if (serverPlayer.isSleeping()) {
++                if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping) { // CraftBukkit
+                     this.sleepingPlayers++;
+                 }
++                // CraftBukkit start
++                if (serverPlayer.isSleeping()) {
++                    anySleep = true;
++                }
++                // CraftBukkit end
+             }
+         }
+ 
+-        return (i1 > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || i1 != this.sleepingPlayers);
++        return anySleep && (i1 > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || i1 != this.sleepingPlayers); // CraftBukkit
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
new file mode 100644
index 0000000000..88ab6f0915
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
@@ -0,0 +1,81 @@
+--- a/net/minecraft/server/players/StoredUserList.java
++++ b/net/minecraft/server/players/StoredUserList.java
+@@ -26,7 +_,7 @@
+     private static final Logger LOGGER = LogUtils.getLogger();
+     private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+     private final File file;
+-    private final Map<String, V> map = Maps.newHashMap();
++    private final Map<String, V> map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList
+ 
+     public StoredUserList(File file) {
+         this.file = file;
+@@ -48,8 +_,11 @@
+ 
+     @Nullable
+     public V get(K obj) {
+-        this.removeExpired();
+-        return this.map.get(this.getKeyForUser(obj));
++        // Paper start - Use ConcurrentHashMap in JsonList
++        return this.map.computeIfPresent(this.getKeyForUser(obj), (k, v) -> {
++            return v.hasExpired() ? null : v;
++        });
++        // Paper end - Use ConcurrentHashMap in JsonList
+     }
+ 
+     public void remove(K user) {
+@@ -71,7 +_,7 @@
+     }
+ 
+     public boolean isEmpty() {
+-        return this.map.size() < 1;
++        return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList
+     }
+ 
+     protected String getKeyForUser(K obj) {
+@@ -79,21 +_,12 @@
+     }
+ 
+     protected boolean contains(K entry) {
++        this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...)
+         return this.map.containsKey(this.getKeyForUser(entry));
+     }
+ 
+     private void removeExpired() {
+-        List<K> list = Lists.newArrayList();
+-
+-        for (V storedUserEntry : this.map.values()) {
+-            if (storedUserEntry.hasExpired()) {
+-                list.add(storedUserEntry.getUser());
+-            }
+-        }
+-
+-        for (K object : list) {
+-            this.map.remove(this.getKeyForUser(object));
+-        }
++        this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList
+     }
+ 
+     protected abstract StoredUserEntry<K> createEntry(JsonObject entryData);
+@@ -103,6 +_,7 @@
+     }
+ 
+     public void save() throws IOException {
++        this.removeExpired(); // Paper - remove expired values before saving
+         JsonArray jsonArray = new JsonArray();
+         this.map.values().stream().map(storedEntry -> Util.make(new JsonObject(), storedEntry::serialize)).forEach(jsonArray::add);
+ 
+@@ -127,7 +_,14 @@
+                         this.map.put(this.getKeyForUser(storedUserEntry.getUser()), (V)storedUserEntry);
+                     }
+                 }
++            // Spigot Start
++            } catch (com.google.gson.JsonParseException | NullPointerException ex) {
++                org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex);
++                File backup = new File(this.file + ".backup");
++                this.file.renameTo(backup);
++                this.file.delete();
+             }
++            // Spigot End
+         }
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
similarity index 85%
rename from paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch
rename to paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
index df038411ce..facd6c1cb4 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/players/UserBanListEntry.java
 +++ b/net/minecraft/server/players/UserBanListEntry.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.server.players;
  
  import com.google.gson.JsonObject;
-@@ -39,20 +40,29 @@
+@@ -37,19 +_,29 @@
  
      @Nullable
      private static GameProfile createGameProfile(JsonObject json) {
@@ -15,13 +15,12 @@
 +        UUID uuid = null;
 +        String name = null;
 +        if (json.has("uuid")) {
-             String s = json.get("uuid").getAsString();
+             String asString = json.get("uuid").getAsString();
  
 -            UUID uuid;
--
              try {
-                 uuid = UUID.fromString(s);
-             } catch (Throwable throwable) {
+                 uuid = UUID.fromString(asString);
+             } catch (Throwable var4) {
 -                return null;
              }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch
rename to paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch
index 4cef2c2bc4..9f81bd284e 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/server/players/UserWhiteList.java
 +++ b/net/minecraft/server/players/UserWhiteList.java
-@@ -28,4 +28,23 @@
-     protected String getKeyForUser(GameProfile gameProfile) {
-         return gameProfile.getId().toString();
+@@ -28,4 +_,23 @@
+     protected String getKeyForUser(GameProfile obj) {
+         return obj.getId().toString();
      }
 +    // Paper start - Add whitelist events
 +    @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch
deleted file mode 100644
index 8b31e2142b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch
+++ /dev/null
@@ -1,217 +0,0 @@
---- a/net/minecraft/server/players/GameProfileCache.java
-+++ b/net/minecraft/server/players/GameProfileCache.java
-@@ -60,6 +60,11 @@
-     @Nullable
-     private Executor executor;
- 
-+    // Paper start - Fix GameProfileCache concurrency
-+    protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock();
-+    protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock();
-+    // Paper end - Fix GameProfileCache concurrency
-+
-     public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) {
-         this.profileRepository = profileRepository;
-         this.file = cacheFile;
-@@ -67,11 +72,13 @@
-     }
- 
-     private void safeAdd(GameProfileCache.GameProfileInfo entry) {
-+        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
-         GameProfile gameprofile = entry.getProfile();
- 
-         entry.setLastAccess(this.getNextOperation());
-         this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry);
-         this.profilesByUUID.put(gameprofile.getId(), entry);
-+        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
-     }
- 
-     private static Optional<GameProfile> lookupGameProfile(GameProfileRepository repository, String name) {
-@@ -85,10 +92,12 @@
-                 }
- 
-                 public void onProfileLookupFailed(String s1, Exception exception) {
--                    atomicreference.set((Object) null);
-+                    atomicreference.set(null); // CraftBukkit - decompile error
-                 }
-             };
- 
-+        if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name
-+                && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status
-             repository.findProfilesByNames(new String[]{name}, profilelookupcallback);
-             GameProfile gameprofile = (GameProfile) atomicreference.get();
- 
-@@ -105,7 +114,7 @@
-     }
- 
-     private static boolean usesAuthentication() {
--        return GameProfileCache.usesAuthentication;
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status
-     }
- 
-     public void add(GameProfile profile) {
-@@ -117,15 +126,29 @@
-         GameProfileCache.GameProfileInfo usercache_usercacheentry = new GameProfileCache.GameProfileInfo(profile, date);
- 
-         this.safeAdd(usercache_usercacheentry);
--        this.save();
-+        if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving
-     }
- 
-     private long getNextOperation() {
-         return this.operationCount.incrementAndGet();
-     }
- 
-+    // Paper start
-+    public @Nullable GameProfile getProfileIfCached(String name) {
-+        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
-+        GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT));
-+        if (entry == null) {
-+            return null;
-+        }
-+        entry.setLastAccess(this.getNextOperation());
-+        return entry.getProfile();
-+        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
-+    }
-+    // Paper end
-+
-     public Optional<GameProfile> get(String name) {
-         String s1 = name.toLowerCase(Locale.ROOT);
-+        boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
-         GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1);
-         boolean flag = false;
- 
-@@ -141,19 +164,24 @@
-         if (usercache_usercacheentry != null) {
-             usercache_usercacheentry.setLastAccess(this.getNextOperation());
-             optional = Optional.of(usercache_usercacheentry.getProfile());
-+            stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
-         } else {
--            optional = GameProfileCache.lookupGameProfile(this.profileRepository, s1);
-+            stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
-+            try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency
-+            optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players
-+            } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency
-             if (optional.isPresent()) {
-                 this.add((GameProfile) optional.get());
-                 flag = false;
-             }
-         }
- 
--        if (flag) {
--            this.save();
-+        if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled
-+            this.save(true); // Paper - Perf: Async GameProfileCache saving
-         }
- 
-         return optional;
-+        } finally { if (stateLocked) {  this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency
-     }
- 
-     public CompletableFuture<Optional<GameProfile>> getAsync(String username) {
-@@ -167,7 +195,7 @@
-             } else {
-                 CompletableFuture<Optional<GameProfile>> completablefuture1 = CompletableFuture.supplyAsync(() -> {
-                     return this.get(username);
--                }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> {
-+                }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
-                     this.requests.remove(username);
-                 }, this.executor);
- 
-@@ -178,6 +206,7 @@
-     }
- 
-     public Optional<GameProfile> get(UUID uuid) {
-+        try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
-         GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid);
- 
-         if (usercache_usercacheentry == null) {
-@@ -186,6 +215,7 @@
-             usercache_usercacheentry.setLastAccess(this.getNextOperation());
-             return Optional.of(usercache_usercacheentry.getProfile());
-         }
-+        } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
-     }
- 
-     public void setExecutor(Executor executor) {
-@@ -208,7 +238,7 @@
- 
-             label54:
-             {
--                ArrayList arraylist;
-+                List<GameProfileCache.GameProfileInfo> arraylist; // CraftBukkit - decompile error
- 
-                 try {
-                     JsonArray jsonarray = (JsonArray) this.gson.fromJson(bufferedreader, JsonArray.class);
-@@ -217,7 +247,7 @@
-                         DateFormat dateformat = GameProfileCache.createDateFormat();
- 
-                         jsonarray.forEach((jsonelement) -> {
--                            Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat);
-+                            Optional<GameProfileCache.GameProfileInfo> optional = GameProfileCache.readGameProfile(jsonelement, dateformat); // CraftBukkit - decompile error
- 
-                             Objects.requireNonNull(list);
-                             optional.ifPresent(list::add);
-@@ -250,6 +280,11 @@
-             }
-         } catch (FileNotFoundException filenotfoundexception) {
-             ;
-+        // Spigot Start
-+        } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) {
-+            GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." );
-+            this.file.delete();
-+        // Spigot End
-         } catch (JsonParseException | IOException ioexception) {
-             GameProfileCache.LOGGER.warn("Failed to load profile cache {}", this.file, ioexception);
-         }
-@@ -257,14 +292,15 @@
-         return list;
-     }
- 
--    public void save() {
-+    public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving
-         JsonArray jsonarray = new JsonArray();
-         DateFormat dateformat = GameProfileCache.createDateFormat();
- 
--        this.getTopMRUProfiles(1000).forEach((usercache_usercacheentry) -> {
-+        this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency
-             jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat));
-         });
-         String s = this.gson.toJson(jsonarray);
-+        Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving
- 
-         try {
-             BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8);
-@@ -289,13 +325,32 @@
-         } catch (IOException ioexception) {
-             ;
-         }
-+        // Paper start - Perf: Async GameProfileCache saving
-+        };
-+        if (asyncSave) {
-+            io.papermc.paper.util.MCUtil.scheduleAsyncTask(save);
-+        } else {
-+            save.run();
-+        }
-+        // Paper end - Perf: Async GameProfileCache saving
- 
-     }
- 
-     private Stream<GameProfileCache.GameProfileInfo> getTopMRUProfiles(int limit) {
--        return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit);
-+        // Paper start - Fix GameProfileCache concurrency
-+        return this.listTopMRUProfiles(limit).stream();
-     }
- 
-+    private List<GameProfileCache.GameProfileInfo> listTopMRUProfiles(int limit) {
-+        try {
-+            this.stateLock.lock();
-+            return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList();
-+        } finally {
-+            this.stateLock.unlock();
-+        }
-+    }
-+    // Paper end - Fix GameProfileCache concurrency
-+
-     private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) {
-         JsonObject jsonobject = new JsonObject();
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch
deleted file mode 100644
index 568224fe25..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/net/minecraft/server/players/SleepStatus.java
-+++ b/net/minecraft/server/players/SleepStatus.java
-@@ -18,9 +18,12 @@
-     }
- 
-     public boolean areEnoughDeepSleeping(int percentage, List<ServerPlayer> players) {
--        int j = (int) players.stream().filter(Player::isSleepingLongEnough).count();
-+        // CraftBukkit start
-+        int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
-+        boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough);
- 
--        return j >= this.sleepersNeeded(percentage);
-+        return anyDeepSleep && j >= this.sleepersNeeded(percentage);
-+        // CraftBukkit end
-     }
- 
-     public int sleepersNeeded(int percentage) {
-@@ -42,18 +45,24 @@
-         this.activePlayers = 0;
-         this.sleepingPlayers = 0;
-         Iterator iterator = players.iterator();
-+        boolean anySleep = false; // CraftBukkit
- 
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
-             if (!entityplayer.isSpectator()) {
-                 ++this.activePlayers;
--                if (entityplayer.isSleeping()) {
-+                if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
-                     ++this.sleepingPlayers;
-                 }
-+                // CraftBukkit start
-+                if (entityplayer.isSleeping()) {
-+                    anySleep = true;
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
--        return (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers);
-+        return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch
deleted file mode 100644
index bea194db95..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch
+++ /dev/null
@@ -1,96 +0,0 @@
---- a/net/minecraft/server/players/StoredUserList.java
-+++ b/net/minecraft/server/players/StoredUserList.java
-@@ -30,7 +30,7 @@
-     private static final Logger LOGGER = LogUtils.getLogger();
-     private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
-     private final File file;
--    private final Map<String, V> map = Maps.newHashMap();
-+    private final Map<String, V> map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList
- 
-     public StoredUserList(File file) {
-         this.file = file;
-@@ -53,8 +53,11 @@
- 
-     @Nullable
-     public V get(K key) {
--        this.removeExpired();
--        return (StoredUserEntry) this.map.get(this.getKeyForUser(key));
-+        // Paper start - Use ConcurrentHashMap in JsonList
-+        return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> {
-+            return v.hasExpired() ? null : v;
-+        });
-+        // Paper end - Use ConcurrentHashMap in JsonList
-     }
- 
-     public void remove(K key) {
-@@ -77,7 +80,7 @@
-     }
- 
-     public boolean isEmpty() {
--        return this.map.size() < 1;
-+        return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList
-     }
- 
-     protected String getKeyForUser(K profile) {
-@@ -85,29 +88,12 @@
-     }
- 
-     protected boolean contains(K k0) {
-+        this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...)
-         return this.map.containsKey(this.getKeyForUser(k0));
-     }
- 
-     private void removeExpired() {
--        List<K> list = Lists.newArrayList();
--        Iterator iterator = this.map.values().iterator();
--
--        while (iterator.hasNext()) {
--            V v0 = (StoredUserEntry) iterator.next();
--
--            if (v0.hasExpired()) {
--                list.add(v0.getUser());
--            }
--        }
--
--        iterator = list.iterator();
--
--        while (iterator.hasNext()) {
--            K k0 = iterator.next();
--
--            this.map.remove(this.getKeyForUser(k0));
--        }
--
-+        this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList
-     }
- 
-     protected abstract StoredUserEntry<K> createEntry(JsonObject json);
-@@ -117,8 +103,9 @@
-     }
- 
-     public void save() throws IOException {
-+        this.removeExpired(); // Paper - remove expired values before saving
-         JsonArray jsonarray = new JsonArray();
--        Stream stream = this.map.values().stream().map((jsonlistentry) -> {
-+        Stream<JsonObject> stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error
-             JsonObject jsonobject = new JsonObject();
- 
-             Objects.requireNonNull(jsonlistentry);
-@@ -171,9 +158,17 @@
-                         StoredUserEntry<K> jsonlistentry = this.createEntry(jsonobject);
- 
-                         if (jsonlistentry.getUser() != null) {
--                            this.map.put(this.getKeyForUser(jsonlistentry.getUser()), jsonlistentry);
-+                            this.map.put(this.getKeyForUser(jsonlistentry.getUser()), (V) jsonlistentry); // CraftBukkit - decompile error
-                         }
-                     }
-+                // Spigot Start
-+                } catch ( com.google.gson.JsonParseException | NullPointerException ex )
-+                {
-+                    org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex );
-+                    File backup = new File( this.file + ".backup" );
-+                    this.file.renameTo( backup );
-+                    this.file.delete();
-+                // Spigot End
-                 } catch (Throwable throwable) {
-                     if (bufferedreader != null) {
-                         try {

From 5c68cc7cefa17a27a410fb53e9babb56356fd00b Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Fri, 13 Dec 2024 18:28:03 +0100
Subject: [PATCH 009/285] don't copy patch folders recursively in gibWork

---
 build.gradle.kts | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 28912b4343..3453bf98f2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -90,7 +90,6 @@ tasks.register("gibWork") {
     val patchesFolder = layout.projectDirectory.dir("paper-server/patches/").convertToPath()
     val storage = layout.cache.resolve("last-updating-folder").also { it.parent.createDirectories() }
 
-    @OptIn(ExperimentalPathApi::class)
     doLast {
         val html = URI(issue).toURL().readText()
 
@@ -112,11 +111,13 @@ tasks.register("gibWork") {
         if (!dir.exists()) {
             error("Unapplied patch folder $next does not exist, did someone else already check it out and forgot to mark it?")
         }
-        dir.copyToRecursively(
-            patchesFolder.resolve("sources").resolve(next)
-                .also { it.createDirectories() }, overwrite = true, followLinks = false
-        )
-        dir.deleteRecursively()
+        dir.listDirectoryEntries("*.patch").forEach { patch ->
+            patch.copyTo(patchesFolder.resolve("sources").resolve(next).resolve(patch.fileName).also { it.createDirectories() }, overwrite = true)
+            patch.deleteIfExists()
+        }
+        if (dir.listDirectoryEntries().isEmpty()) {
+            dir.deleteIfExists()
+        }
 
         storage.writeText(next)
         println("please tick the box in the issue: $issue")

From 1ed5242f38700facce342003bf0fb45e7e210df3 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Fri, 13 Dec 2024 18:36:26 +0100
Subject: [PATCH 010/285] Ender dragon

---
 .../boss/enderdragon/EndCrystal.java.patch    |  79 +++++
 .../boss/enderdragon/EnderDragon.java.patch   | 283 +++++++++++++++
 .../phases/DragonDeathPhase.java.patch        |  11 +
 .../DragonHoldingPatternPhase.java.patch      |   8 +-
 .../DragonLandingApproachPhase.java.patch     |  11 +
 .../phases/DragonLandingPhase.java.patch      |   8 +-
 .../DragonSittingFlamingPhase.java.patch      |  26 +-
 .../phases/DragonStrafePlayerPhase.java.patch |   8 +-
 .../phases/DragonTakeoffPhase.java.patch      |  11 +
 .../phases/EnderDragonPhaseManager.java.patch |  22 ++
 .../dimension/end/EndDragonFight.java.patch   |  11 +
 .../boss/enderdragon/EndCrystal.java.patch    |  91 -----
 .../boss/enderdragon/EnderDragon.java.patch   | 331 ------------------
 .../phases/DragonDeathPhase.java.patch        |  11 -
 .../DragonLandingApproachPhase.java.patch     |  11 -
 .../phases/DragonTakeoffPhase.java.patch      |  11 -
 .../phases/EnderDragonPhaseManager.java.patch |  42 ---
 17 files changed, 448 insertions(+), 527 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch (70%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch (57%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch (81%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
new file mode 100644
index 0000000000..d69e9ebf10
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+@@ -25,6 +_,7 @@
+     );
+     private static final EntityDataAccessor<Boolean> DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
+     public int time;
++    public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
+ 
+     public EndCrystal(EntityType<? extends EndCrystal> entityType, Level level) {
+         super(entityType, level);
+@@ -56,9 +_,23 @@
+         if (this.level() instanceof ServerLevel) {
+             BlockPos blockPos = this.blockPosition();
+             if (((ServerLevel)this.level()).getDragonFight() != null && this.level().getBlockState(blockPos).isAir()) {
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockPos, this).isCancelled()) { // Paper
+                 this.level().setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level(), blockPos));
+-            }
+-        }
++                } // Paper
++            }
++        }
++
++        // Paper start - Fix invulnerable end crystals
++        if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) {
++            if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld())
++                || ((ServerLevel) this.level()).getDragonFight() == null
++                || ((ServerLevel) this.level()).getDragonFight().respawnStage == null
++                || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) {
++                this.setInvulnerable(false);
++                this.setBeamTarget(null);
++            }
++        }
++        // Paper end - Fix invulnerable end crystals
+     }
+ 
+     @Override
+@@ -68,6 +_,7 @@
+         }
+ 
+         compound.putBoolean("ShowBottom", this.showsBottom());
++        if (this.generatedByDragonFight) compound.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals
+     }
+ 
+     @Override
+@@ -76,6 +_,7 @@
+         if (compound.contains("ShowBottom", 1)) {
+             this.setShowBottom(compound.getBoolean("ShowBottom"));
+         }
++        if (compound.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = compound.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals
+     }
+ 
+     @Override
+@@ -96,10 +_,24 @@
+             return false;
+         } else {
+             if (!this.isRemoved()) {
+-                this.remove(Entity.RemovalReason.KILLED);
++                // Paper start - All non-living entities need this
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false)) {
++                    return false;
++                }
++                // Paper end
+                 if (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
+                     DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.getEntity()) : null;
+-                    level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK);
++                    // Paper start
++                    org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
++                    if (event.isCancelled()) {
++                        return false;
++                    }
++
++                    this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // Paper - add Bukkit remove cause
++                    level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
++                } else {
++                    this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
++                    // Paper end
+                 }
+ 
+                 this.onDestroyedBy(level, damageSource);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
new file mode 100644
index 0000000000..95f6a4e90e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -0,0 +1,283 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+@@ -86,6 +_,10 @@
+     private final Node[] nodes = new Node[24];
+     private final int[] nodeAdjacency = new int[24];
+     private final BinaryHeap openSet = new BinaryHeap();
++    // Paper start
++    private final net.minecraft.world.level.Explosion explosionSource; // Paper - reusable source for CraftTNTPrimed.getSource()
++    @Nullable private BlockPos podium;
++    // Paper end
+ 
+     public EnderDragon(EntityType<? extends EnderDragon> entityType, Level level) {
+         super(EntityType.ENDER_DRAGON, level);
+@@ -101,6 +_,7 @@
+         this.setHealth(this.getMaxHealth());
+         this.noPhysics = true;
+         this.phaseManager = new EnderDragonPhaseManager(this);
++        this.explosionSource = new net.minecraft.world.level.ServerExplosion(level.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, net.minecraft.world.level.Explosion.BlockInteraction.DESTROY); // Paper
+     }
+ 
+     public void setDragonFight(EndDragonFight dragonFight) {
+@@ -119,6 +_,19 @@
+         return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0);
+     }
+ 
++    // Paper start - Allow changing the EnderDragon podium
++    public BlockPos getPodium() {
++        if (this.podium == null) {
++            return EndPodiumFeature.getLocation(this.getFightOrigin());
++        }
++        return this.podium;
++    }
++
++    public void setPodium(@Nullable BlockPos blockPos) {
++        this.podium = blockPos;
++    }
++    // Paper end
++
+     @Override
+     public boolean isFlapping() {
+         float cos = Mth.cos(this.flapTime * (float) (Math.PI * 2));
+@@ -210,7 +_,7 @@
+                     }
+ 
+                     Vec3 flyTargetLocation = currentPhase.getFlyTargetLocation();
+-                    if (flyTargetLocation != null) {
++                    if (flyTargetLocation != null && currentPhase.getPhase() != EnderDragonPhase.HOVERING) { // Paper - Don't move when hovering
+                         double d = flyTargetLocation.x - this.getX();
+                         double d1 = flyTargetLocation.y - this.getY();
+                         double d2 = flyTargetLocation.z - this.getZ();
+@@ -369,7 +_,14 @@
+             if (this.nearestCrystal.isRemoved()) {
+                 this.nearestCrystal = null;
+             } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
+-                this.setHealth(this.getHealth() + 1.0F);
++                // Paper start
++                org.bukkit.event.entity.EntityRegainHealthEvent event = new org.bukkit.event.entity.EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
++                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (!event.isCancelled()) {
++                    this.setHealth((float) (this.getHealth() + event.getAmount()));
++                }
++                // Paper end
+             }
+         }
+ 
+@@ -400,7 +_,7 @@
+                 double d2 = entity.getX() - d;
+                 double d3 = entity.getZ() - d1;
+                 double max = Math.max(d2 * d2 + d3 * d3, 0.1);
+-                entity.push(d2 / max * 4.0, 0.2F, d3 / max * 4.0);
++                entity.push(d2 / max * 4.0, 0.2F, d3 / max * 4.0, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+                 if (!this.phaseManager.getCurrentPhase().isSitting() && livingEntity.getLastHurtByMobTimestamp() < entity.tickCount - 2) {
+                     DamageSource damageSource = this.damageSources().mobAttack(this);
+                     entity.hurtServer(level, damageSource, 5.0F);
+@@ -433,6 +_,7 @@
+         int floor5 = Mth.floor(box.maxZ);
+         boolean flag = false;
+         boolean flag1 = false;
++        List<org.bukkit.block.Block> destroyedBlocks = new java.util.ArrayList<>(); // Paper - Create a list to hold all the destroyed blocks
+ 
+         for (int i = floor; i <= floor3; i++) {
+             for (int i1 = floor1; i1 <= floor4; i1++) {
+@@ -441,7 +_,11 @@
+                     BlockState blockState = level.getBlockState(blockPos);
+                     if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) {
+                         if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) {
+-                            flag1 = level.removeBlock(blockPos, false) || flag1;
++                            // Paper start - Add blocks to list rather than destroying them
++                            //flag1 = level.removeBlock(blockPos, false) || flag1;
++                            flag1 = true;
++                            destroyedBlocks.add(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos));
++                            // Paper end
+                         } else {
+                             flag = true;
+                         }
+@@ -450,6 +_,54 @@
+             }
+         }
+ 
++        // Paper start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
++        // SPIGOT-4882: don't fire event if nothing hit
++        if (!flag1) {
++            return flag;
++        }
++
++        org.bukkit.event.entity.EntityExplodeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityExplodeEvent(this, destroyedBlocks, 0F, this.explosionSource.getBlockInteraction());
++        if (event.isCancelled()) {
++            // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down.
++            // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled.
++            return flag;
++        } else if (event.getYield() == 0F) {
++            // Yield zero ==> no drops
++            for (org.bukkit.block.Block block : event.blockList()) {
++                this.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false);
++            }
++        } else {
++            for (org.bukkit.block.Block block : event.blockList()) {
++                org.bukkit.Material blockId = block.getType();
++                if (blockId.isAir()) {
++                    continue;
++                }
++
++                org.bukkit.craftbukkit.block.CraftBlock craftBlock = ((org.bukkit.craftbukkit.block.CraftBlock) block);
++                BlockPos blockposition = craftBlock.getPosition();
++
++                net.minecraft.world.level.block.Block nmsBlock = craftBlock.getNMS().getBlock();
++                if (nmsBlock.dropFromExplosion(this.explosionSource)) {
++                    net.minecraft.world.level.block.entity.BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null;
++                    net.minecraft.world.level.storage.loot.LootParams.Builder loottableinfo_builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((ServerLevel) this.level())).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.TOOL, net.minecraft.world.item.ItemStack.EMPTY).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.BLOCK_ENTITY, tileentity);
++
++                    craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> {
++                        net.minecraft.world.level.block.Block.popResource(this.level(), blockposition, itemstack);
++                    });
++                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, net.minecraft.world.item.ItemStack.EMPTY, false);
++                }
++                // Paper start - TNTPrimeEvent
++                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level(), blockposition);
++                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent())
++                    continue;
++                // Paper end - TNTPrimeEvent
++                nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource);
++
++                this.level().removeBlock(blockposition, false);
++            }
++        }
++        // Paper end
++
+         if (flag1) {
+             BlockPos blockPos1 = new BlockPos(
+                 floor + this.random.nextInt(floor3 - floor + 1),
+@@ -507,7 +_,15 @@
+ 
+     @Override
+     public void kill(ServerLevel level) {
+-        this.remove(Entity.RemovalReason.KILLED);
++        // Paper start - Fire entity death event
++        this.silentDeath = true;
++        org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill());
++        if (deathEvent.isCancelled()) {
++            this.silentDeath = false; // Reset to default if event was cancelled
++            return;
++        }
++        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
++        // Paper end - Fire entity death event
+         this.gameEvent(GameEvent.ENTITY_DIE);
+         if (this.dragonFight != null) {
+             this.dragonFight.updateDragon(this);
+@@ -529,18 +_,41 @@
+             this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + f, this.getY() + 2.0 + f1, this.getZ() + f2, 0.0, 0.0, 0.0);
+         }
+ 
++        // Paper start - SPIGOT-2420: Moved up to #getExpReward method
++        /*
+         int i = 500;
+         if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+             i = 12000;
+         }
++         */
++        int i = this.expToDrop;
++        // Paper end
+ 
+         if (this.level() instanceof ServerLevel serverLevel) {
+-            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+-                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F));
++            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) {  // Paper - SPIGOT-2420: Already checked for the game rule when calculating the xp
++                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
+             }
+ 
+             if (this.dragonDeathTime == 1 && !this.isSilent()) {
+-                serverLevel.globalLevelEvent(1028, this.blockPosition(), 0);
++                // Paper start - Use relative location for far away sounds
++                // serverLevel.globalLevelEvent(1028, this.blockPosition(), 0);
++                int viewDistance = serverLevel.getCraftServer().getViewDistance() * 16;
++                for (net.minecraft.server.level.ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
++                    double deltaX = this.getX() - player.getX();
++                    double deltaZ = this.getZ() - player.getZ();
++                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++                    final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule
++                    if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Paper - respect global sound events gamerule
++                    if (distanceSquared > viewDistance * viewDistance) {
++                        double deltaLength = Math.sqrt(distanceSquared);
++                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
++                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
++                    } else {
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
++                    }
++                }
++                // Paper end
+             }
+         }
+ 
+@@ -553,15 +_,15 @@
+         }
+ 
+         if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel serverLevel1) {
+-            if (serverLevel1.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+-                ExperienceOrb.award(serverLevel1, this.position(), Mth.floor(i * 0.2F));
++            if (true) { // Paper - SPIGOT-2420: Already checked for the game rule when calculating the xp
++                ExperienceOrb.award(serverLevel1, this.position(), Mth.floor(i * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
+             }
+ 
+             if (this.dragonFight != null) {
+                 this.dragonFight.setDragonKilled(this);
+             }
+ 
+-            this.remove(Entity.RemovalReason.KILLED);
++            this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
+             this.gameEvent(GameEvent.ENTITY_DIE);
+         }
+     }
+@@ -743,6 +_,7 @@
+         super.addAdditionalSaveData(compound);
+         compound.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
+         compound.putInt("DragonDeathTime", this.dragonDeathTime);
++        compound.putInt("Bukkit.expToDrop", this.expToDrop); // Paper - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
+     }
+ 
+     @Override
+@@ -755,6 +_,12 @@
+         if (compound.contains("DragonDeathTime")) {
+             this.dragonDeathTime = compound.getInt("DragonDeathTime");
+         }
++
++        // Paper start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
++        if (compound.contains("Bukkit.expToDrop")) {
++            this.expToDrop = compound.getInt("Bukkit.expToDrop");
++        }
++        // Paper end
+     }
+ 
+     @Override
+@@ -795,7 +_,7 @@
+         EnderDragonPhase<? extends DragonPhaseInstance> phase = currentPhase.getPhase();
+         Vec3 viewVector;
+         if (phase == EnderDragonPhase.LANDING || phase == EnderDragonPhase.TAKEOFF) {
+-            BlockPos heightmapPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
++            BlockPos heightmapPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium
+             float max = Math.max((float)Math.sqrt(heightmapPos.distToCenterSqr(this.position())) / 4.0F, 1.0F);
+             float f = 6.0F / max;
+             float xRot = this.getXRot();
+@@ -883,4 +_,20 @@
+     protected float sanitizeScale(float scale) {
+         return 1.0F;
+     }
++
++    // Paper start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
++    @Override
++    public int getExpReward(ServerLevel worldserver, Entity entity) {
++        // CraftBukkit - Moved from #tickDeath method
++        boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
++        short short0 = 500;
++
++        if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
++            short0 = 12000;
++        }
++
++        return flag ? short0 : 0;
++    }
++    // Paper end
++
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
new file mode 100644
index 0000000000..3c43f0ec62
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
+@@ -34,7 +_,7 @@
+     public void doServerTick(ServerLevel level) {
+         this.time++;
+         if (this.targetLocation == null) {
+-            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+             this.targetLocation = Vec3.atBottomCenterOf(heightmapPos);
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch
index f07b2b70e3..9652e080af 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java
 +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java
-@@ -53,7 +53,7 @@
+@@ -53,7 +_,7 @@
  
-     private void findNewTarget(ServerLevel world) {
+     private void findNewTarget(ServerLevel level) {
          if (this.currentPath != null && this.currentPath.isDone()) {
--            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
-+            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+-            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
              int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive();
              if (this.dragon.getRandom().nextInt(i + 3) == 0) {
                  this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
new file mode 100644
index 0000000000..bfc7cb63f7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
+@@ -52,7 +_,7 @@
+     private void findNewTarget(ServerLevel level) {
+         if (this.currentPath == null || this.currentPath.isDone()) {
+             int i = this.dragon.findClosestNode();
+-            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+             Player nearestPlayer = level.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, heightmapPos.getX(), heightmapPos.getY(), heightmapPos.getZ());
+             int i1;
+             if (nearestPlayer != null) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch
index 0d9035a506..dcadba5126 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java
 +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java
-@@ -42,7 +42,7 @@
-     public void doServerTick(ServerLevel world) {
+@@ -50,7 +_,7 @@
+     public void doServerTick(ServerLevel level) {
          if (this.targetLocation == null) {
              this.targetLocation = Vec3.atBottomCenterOf(
--                world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))
-+                world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium
+-                level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))
++                level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium
              );
          }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
index 9477328964..f6723920d3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
@@ -1,35 +1,25 @@
 --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java
 +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java
-@@ -10,6 +10,9 @@
- import net.minecraft.world.entity.AreaEffectCloud;
- import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase {
- 
-@@ -86,7 +89,13 @@
+@@ -82,7 +_,13 @@
              this.flame.setDuration(200);
              this.flame.setParticle(ParticleTypes.DRAGON_BREATH);
              this.flame.addEffect(new MobEffectInstance(MobEffects.HARM));
 +            if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events
-             world.addFreshEntity(this.flame);
-+            // Paper start - EnderDragon Events
+             level.addFreshEntity(this.flame);
++            // Paper start
 +            } else {
 +                this.end();
 +            }
-+            // Paper end - EnderDragon Events
++            // Paper end
          }
- 
      }
-@@ -100,7 +109,7 @@
+ 
+@@ -95,7 +_,7 @@
      @Override
      public void end() {
          if (this.flame != null) {
 -            this.flame.discard();
-+            this.flame.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++            this.flame.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // Paper- add Bukkit remove cause
              this.flame = null;
          }
- 
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
similarity index 81%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
index 7a5a567fd1..5556c6a0ae 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
@@ -1,13 +1,13 @@
 --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java
 +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java
-@@ -79,8 +79,11 @@
+@@ -77,8 +_,11 @@
                          }
  
-                         DragonFireball dragonFireball = new DragonFireball(world, this.dragon, vec34.normalize());
+                         DragonFireball dragonFireball = new DragonFireball(level, this.dragon, vec32.normalize());
 +                        dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported
-                         dragonFireball.moveTo(o, p, q, 0.0F, 0.0F);
+                         dragonFireball.moveTo(d2, d3, d4, 0.0F, 0.0F);
 +                        if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events
-                         world.addFreshEntity(dragonFireball);
+                         level.addFreshEntity(dragonFireball);
 +                        else dragonFireball.discard(null); // Paper - EnderDragon Events
                          this.fireballCharge = 0;
                          if (this.currentPath != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
new file mode 100644
index 0000000000..d81a699daa
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
+@@ -24,7 +_,7 @@
+     @Override
+     public void doServerTick(ServerLevel level) {
+         if (!this.firstTick && this.currentPath != null) {
+-            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++            BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+             if (!heightmapPos.closerToCenterThan(this.dragon.position(), 10.0)) {
+                 this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN);
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
new file mode 100644
index 0000000000..8f1877cd14
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
++++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
+@@ -23,6 +_,19 @@
+                 this.currentPhase.end();
+             }
+ 
++            // Paper start - Call EnderDragonChangePhaseEvent
++            org.bukkit.event.entity.EnderDragonChangePhaseEvent event = new org.bukkit.event.entity.EnderDragonChangePhaseEvent(
++                (org.bukkit.craftbukkit.entity.CraftEnderDragon) this.dragon.getBukkitEntity(),
++                (this.currentPhase == null) ? null : org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()),
++                org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(phase)
++            );
++            this.dragon.level().getCraftServer().getPluginManager().callEvent(event);
++            if (event.isCancelled()) {
++                return;
++            }
++            phase = org.bukkit.craftbukkit.entity.CraftEnderDragon.getMinecraftPhase(event.getNewPhase());
++            // Paper end
++
+             this.currentPhase = this.getPhase((EnderDragonPhase<DragonPhaseInstance>)phase);
+             if (!this.dragon.level().isClientSide) {
+                 this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, phase.getId());
diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
new file mode 100644
index 0000000000..c28a5c1792
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
+@@ -92,7 +_,7 @@
+     @Nullable
+     private BlockPos portalLocation;
+     @Nullable
+-    private DragonRespawnAnimation respawnStage;
++    public DragonRespawnAnimation respawnStage;// Paper-At: public net.minecraft.world.level.dimension.end.EndDragonFight respawnStage
+     private int respawnTime;
+     @Nullable
+     private List<EndCrystal> respawnCrystals;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
deleted file mode 100644
index 9f6803d4ee..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
+++ /dev/null
@@ -1,91 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
-@@ -19,12 +19,18 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.BaseFireBlock;
- import net.minecraft.world.level.dimension.end.EndDragonFight;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
- 
- public class EndCrystal extends Entity {
- 
-     private static final EntityDataAccessor<Optional<BlockPos>> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS);
-     private static final EntityDataAccessor<Boolean> DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
-     public int time;
-+    public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
- 
-     public EndCrystal(EntityType<? extends EndCrystal> type, Level world) {
-         super(type, world);
-@@ -57,8 +63,23 @@
-             BlockPos blockposition = this.blockPosition();
- 
-             if (((ServerLevel) this.level()).getDragonFight() != null && this.level().getBlockState(blockposition).isAir()) {
--                this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition));
-+                // CraftBukkit start
-+                if (!CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) {
-+                    this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition));
-+                }
-+                // CraftBukkit end
-             }
-+            // Paper start - Fix invulnerable end crystals
-+            if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) {
-+                if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld())
-+                    || ((ServerLevel) this.level()).getDragonFight() == null
-+                    || ((ServerLevel) this.level()).getDragonFight().respawnStage == null
-+                    || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) {
-+                    this.setInvulnerable(false);
-+                    this.setBeamTarget(null);
-+                }
-+            }
-+            // Paper end - Fix invulnerable end crystals
-         }
- 
-     }
-@@ -70,6 +91,7 @@
-         }
- 
-         nbt.putBoolean("ShowBottom", this.showsBottom());
-+        if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals
-     }
- 
-     @Override
-@@ -78,6 +100,7 @@
-         if (nbt.contains("ShowBottom", 1)) {
-             this.setShowBottom(nbt.getBoolean("ShowBottom"));
-         }
-+        if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals
- 
-     }
- 
-@@ -99,12 +122,26 @@
-             return false;
-         } else {
-             if (!this.isRemoved()) {
--                this.remove(Entity.RemovalReason.KILLED);
-+                // CraftBukkit start - All non-living entities need this
-+                if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
-+                    return false;
-+                }
-+                // CraftBukkit end
-                 if (!source.is(DamageTypeTags.IS_EXPLOSION)) {
-                     DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
- 
--                    world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK);
-+                    // CraftBukkit start
-+                    ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
-+                    if (event.isCancelled()) {
-+                        return false;
-+                    }
-+
-+                    this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
-+                    world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
-+                } else {
-+                    this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-                 }
-+                // CraftBukkit end
- 
-                 this.onDestroyedBy(world, source);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
deleted file mode 100644
index 01ecc5bc3d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
+++ /dev/null
@@ -1,331 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
-@@ -37,20 +37,35 @@
- import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhaseManager;
- import net.minecraft.world.entity.monster.Enemy;
- import net.minecraft.world.entity.player.Player;
--import net.minecraft.world.item.enchantment.EnchantmentHelper;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.Level;
--import net.minecraft.world.level.block.state.BlockState;
--import net.minecraft.world.level.dimension.end.EndDragonFight;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.levelgen.Heightmap;
- import net.minecraft.world.level.levelgen.feature.EndPodiumFeature;
- import net.minecraft.world.level.pathfinder.BinaryHeap;
- import net.minecraft.world.level.pathfinder.Node;
- import net.minecraft.world.level.pathfinder.Path;
-+import org.slf4j.Logger;
-+
-+// CraftBukkit start
-+import net.minecraft.world.item.ItemStack;
-+import net.minecraft.world.item.enchantment.EnchantmentHelper;
-+import net.minecraft.world.level.Explosion;
-+import net.minecraft.world.level.ServerExplosion;
-+import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.entity.BlockEntity;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.dimension.end.EndDragonFight;
-+import net.minecraft.world.level.storage.loot.LootParams;
-+import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
--import org.slf4j.Logger;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.entity.EntityExplodeEvent;
-+import org.bukkit.event.entity.EntityRegainHealthEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class EnderDragon extends Mob implements Enemy {
- 
-@@ -88,6 +103,11 @@
-     private final Node[] nodes;
-     private final int[] nodeAdjacency;
-     private final BinaryHeap openSet;
-+    private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
-+    // Paper start - Allow changing the EnderDragon podium
-+    @Nullable
-+    private BlockPos podium;
-+    // Paper end - Allow changing the EnderDragon podium
- 
-     public EnderDragon(EntityType<? extends EnderDragon> entitytypes, Level world) {
-         super(EntityType.ENDER_DRAGON, world);
-@@ -108,6 +128,7 @@
-         this.setHealth(this.getMaxHealth());
-         this.noPhysics = true;
-         this.phaseManager = new EnderDragonPhaseManager(this);
-+        this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit
-     }
- 
-     public void setDragonFight(EndDragonFight fight) {
-@@ -124,7 +145,20 @@
- 
-     public static AttributeSupplier.Builder createAttributes() {
-         return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
-+    }
-+
-+    // Paper start - Allow changing the EnderDragon podium
-+    public BlockPos getPodium() {
-+        if (this.podium == null) {
-+            return EndPodiumFeature.getLocation(this.getFightOrigin());
-+        }
-+        return this.podium;
-+    }
-+
-+    public void setPodium(@Nullable BlockPos blockPos) {
-+        this.podium = blockPos;
-     }
-+    // Paper end - Allow changing the EnderDragon podium
- 
-     @Override
-     public boolean isFlapping() {
-@@ -218,7 +252,7 @@
- 
-                     Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation();
- 
--                    if (vec3d1 != null) {
-+                    if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering
-                         double d0 = vec3d1.x - this.getX();
-                         double d1 = vec3d1.y - this.getY();
-                         double d2 = vec3d1.z - this.getZ();
-@@ -379,7 +413,14 @@
-             if (this.nearestCrystal.isRemoved()) {
-                 this.nearestCrystal = null;
-             } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
--                this.setHealth(this.getHealth() + 1.0F);
-+                // CraftBukkit start
-+                EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (!event.isCancelled()) {
-+                    this.setHealth((float) (this.getHealth() + event.getAmount()));
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -417,7 +458,7 @@
-                 double d3 = entity.getZ() - d1;
-                 double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D);
- 
--                entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D);
-+                entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-                 if (!this.phaseManager.getCurrentPhase().isSitting() && entityliving.getLastHurtByMobTimestamp() < entity.tickCount - 2) {
-                     DamageSource damagesource = this.damageSources().mobAttack(this);
- 
-@@ -458,6 +499,9 @@
-         int j1 = Mth.floor(box.maxZ);
-         boolean flag = false;
-         boolean flag1 = false;
-+        // CraftBukkit start - Create a list to hold all the destroyed blocks
-+        List<org.bukkit.block.Block> destroyedBlocks = new java.util.ArrayList<org.bukkit.block.Block>();
-+        // CraftBukkit end
- 
-         for (int k1 = i; k1 <= l; ++k1) {
-             for (int l1 = j; l1 <= i1; ++l1) {
-@@ -467,14 +511,66 @@
- 
-                     if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) {
-                         if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) {
--                            flag1 = world.removeBlock(blockposition, false) || flag1;
-+                            // CraftBukkit start - Add blocks to list rather than destroying them
-+                            // flag1 = worldserver.removeBlock(blockposition, false) || flag1;
-+                            flag1 = true;
-+                            destroyedBlocks.add(CraftBlock.at(world, blockposition));
-+                            // CraftBukkit end
-                         } else {
-                             flag = true;
-                         }
-                     }
-+                }
-+            }
-+        }
-+
-+        // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
-+        // SPIGOT-4882: don't fire event if nothing hit
-+        if (!flag1) {
-+            return flag;
-+        }
-+
-+        EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this, destroyedBlocks, 0F, this.explosionSource.getBlockInteraction());
-+        if (event.isCancelled()) {
-+            // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down.
-+            // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled.
-+            return flag;
-+        } else if (event.getYield() == 0F) {
-+            // Yield zero ==> no drops
-+            for (org.bukkit.block.Block block : event.blockList()) {
-+                this.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false);
-+            }
-+        } else {
-+            for (org.bukkit.block.Block block : event.blockList()) {
-+                org.bukkit.Material blockId = block.getType();
-+                if (blockId.isAir()) {
-+                    continue;
-+                }
-+
-+                CraftBlock craftBlock = ((CraftBlock) block);
-+                BlockPos blockposition = craftBlock.getPosition();
-+
-+                Block nmsBlock = craftBlock.getNMS().getBlock();
-+                if (nmsBlock.dropFromExplosion(this.explosionSource)) {
-+                    BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null;
-+                    LootParams.Builder loottableinfo_builder = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity);
-+
-+                    craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> {
-+                        Block.popResource(this.level(), blockposition, itemstack);
-+                    });
-+                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false);
-                 }
-+                // Paper start - TNTPrimeEvent
-+                org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition);
-+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent())
-+                    continue;
-+                // Paper end - TNTPrimeEvent
-+                nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource);
-+
-+                this.level().removeBlock(blockposition, false);
-             }
-         }
-+        // CraftBukkit end
- 
-         if (flag1) {
-             BlockPos blockposition1 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(i1 - j + 1), k + this.random.nextInt(j1 - k + 1));
-@@ -531,7 +627,15 @@
- 
-     @Override
-     public void kill(ServerLevel world) {
--        this.remove(Entity.RemovalReason.KILLED);
-+        // Paper start - Fire entity death event
-+        this.silentDeath = true;
-+        org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill());
-+        if (deathEvent.isCancelled()) {
-+            this.silentDeath = false; // Reset to default if event was cancelled
-+            return;
-+        }
-+        // Paper end - Fire entity death event
-+        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-         this.gameEvent(GameEvent.ENTITY_DIE);
-         if (this.dragonFight != null) {
-             this.dragonFight.updateDragon(this);
-@@ -540,7 +644,22 @@
- 
-     }
- 
-+    // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
-     @Override
-+    public int getExpReward(ServerLevel worldserver, Entity entity) {
-+        // CraftBukkit - Moved from #tickDeath method
-+        boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
-+        short short0 = 500;
-+
-+        if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
-+            short0 = 12000;
-+        }
-+
-+        return flag ? short0 : 0;
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     protected void tickDeath() {
-         if (this.dragonFight != null) {
-             this.dragonFight.updateDragon(this);
-@@ -555,21 +674,44 @@
-             this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double) f, this.getY() + 2.0D + (double) f1, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D);
-         }
- 
-+        // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method
-+        /*
-         short short0 = 500;
- 
-         if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
-             short0 = 12000;
-         }
-+        */
-+        int short0 = this.expToDrop;
-+        // CraftBukkit end
- 
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
--            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
--                ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F));
-+            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) {  // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
-+                ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
-             }
- 
-             if (this.dragonDeathTime == 1 && !this.isSilent()) {
--                worldserver.globalLevelEvent(1028, this.blockPosition(), 0);
-+                // CraftBukkit start - Use relative location for far away sounds
-+                // worldserver.globalLevelEvent(1028, this.blockPosition(), 0);
-+                int viewDistance = worldserver.getCraftServer().getViewDistance() * 16;
-+                for (net.minecraft.server.level.ServerPlayer player : worldserver.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
-+                    double deltaX = this.getX() - player.getX();
-+                    double deltaZ = this.getZ() - player.getZ();
-+                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-+                    final double soundRadiusSquared = worldserver.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule
-+                    if ( !worldserver.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule
-+                    if (distanceSquared > viewDistance * viewDistance) {
-+                        double deltaLength = Math.sqrt(distanceSquared);
-+                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
-+                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
-+                    } else {
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
-+                    }
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -592,15 +734,15 @@
-             if (world1 instanceof ServerLevel) {
-                 ServerLevel worldserver1 = (ServerLevel) world1;
- 
--                if (worldserver1.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
--                    ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F));
-+                if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
-+                    ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
-                 }
- 
-                 if (this.dragonFight != null) {
-                     this.dragonFight.setDragonKilled(this);
-                 }
- 
--                this.remove(Entity.RemovalReason.KILLED);
-+                this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-                 this.gameEvent(GameEvent.ENTITY_DIE);
-             }
-         }
-@@ -814,6 +956,7 @@
-         super.addAdditionalSaveData(nbt);
-         nbt.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
-         nbt.putInt("DragonDeathTime", this.dragonDeathTime);
-+        nbt.putInt("Bukkit.expToDrop", this.expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
-     }
- 
-     @Override
-@@ -827,6 +970,11 @@
-             this.dragonDeathTime = nbt.getInt("DragonDeathTime");
-         }
- 
-+        // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
-+        if (nbt.contains("Bukkit.expToDrop")) {
-+            this.expToDrop = nbt.getInt("Bukkit.expToDrop");
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -879,7 +1027,7 @@
-                 vec3d = this.getViewVector(tickDelta);
-             }
-         } else {
--            BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
-+            BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium
- 
-             f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F);
-             float f3 = 6.0F / f1;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
deleted file mode 100644
index 2dcf3ac15b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
-@@ -42,7 +42,7 @@
-     public void doServerTick(ServerLevel world) {
-         this.time++;
-         if (this.targetLocation == null) {
--            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
-+            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
-             this.targetLocation = Vec3.atBottomCenterOf(blockPos);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
deleted file mode 100644
index ce24273215..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
-@@ -52,7 +52,7 @@
-     private void findNewTarget(ServerLevel world) {
-         if (this.currentPath == null || this.currentPath.isDone()) {
-             int i = this.dragon.findClosestNode();
--            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
-+            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
-             Player player = world.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ());
-             int j;
-             if (player != null) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
deleted file mode 100644
index 54fe962df5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
-@@ -24,7 +24,7 @@
-     @Override
-     public void doServerTick(ServerLevel world) {
-         if (!this.firstTick && this.currentPath != null) {
--            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
-+            BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
-             if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) {
-                 this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
deleted file mode 100644
index d39507da58..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
-+++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
-@@ -5,6 +5,11 @@
- import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftEnderDragon;
-+import org.bukkit.event.entity.EnderDragonChangePhaseEvent;
-+// CraftBukkit end
-+
- public class EnderDragonPhaseManager {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -24,6 +29,19 @@
-                 this.currentPhase.end();
-             }
- 
-+            // CraftBukkit start - Call EnderDragonChangePhaseEvent
-+            EnderDragonChangePhaseEvent event = new EnderDragonChangePhaseEvent(
-+                    (CraftEnderDragon) this.dragon.getBukkitEntity(),
-+                    (this.currentPhase == null) ? null : CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()),
-+                    CraftEnderDragon.getBukkitPhase(type)
-+            );
-+            this.dragon.level().getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+            type = CraftEnderDragon.getMinecraftPhase(event.getNewPhase());
-+            // CraftBukkit end
-+
-             this.currentPhase = this.getPhase(type);
-             if (!this.dragon.level().isClientSide) {
-                 this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, type.getId());
-@@ -45,6 +63,6 @@
-             this.phases[i] = type.createInstance(this.dragon);
-         }
- 
--        return this.phases[i];
-+        return (T) this.phases[i]; // CraftBukkit - decompile error
-     }
- }

From e9b739bc487649ba05679ca3be6afe5bd831b4e7 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 09:24:26 -0800
Subject: [PATCH 011/285] some more directories

---
 build.gradle.kts                              |  1 +
 .../syncher/SynchedEntityData.java.patch      | 27 ++----
 .../world/entity/animal/frog/Frog.java.patch  | 10 +-
 .../entity/animal/frog/ShootTongue.java.patch | 11 +++
 .../entity/animal/frog/Tadpole.java.patch     | 84 +++++++++++++++++
 .../vibrations/VibrationSystem.java.patch     | 18 ++++
 .../placement/StructurePlacement.java.patch   | 93 +++++++++++++++++++
 .../placement/StructurePlacement.java.patch   | 93 -------------------
 8 files changed, 222 insertions(+), 115 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/syncher/SynchedEntityData.java.patch (56%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/frog/Frog.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch

diff --git a/build.gradle.kts b/build.gradle.kts
index 3453bf98f2..f57497cb0b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -141,6 +141,7 @@ tasks.register("showWork") {
 }
 
 tasks.register("checkWork") {
+    notCompatibleWithConfigurationCache("This task is interactive")
     fun expandUserHome(path: String): Path {
         return Path.of(path.replaceFirst("^~".toRegex(), System.getProperty("user.home")))
     }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/syncher/SynchedEntityData.java.patch b/paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/network/syncher/SynchedEntityData.java.patch
rename to paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch
index d2519790c5..ac8c137be4 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/syncher/SynchedEntityData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/syncher/SynchedEntityData.java.patch
@@ -1,23 +1,21 @@
 --- a/net/minecraft/network/syncher/SynchedEntityData.java
 +++ b/net/minecraft/network/syncher/SynchedEntityData.java
-@@ -50,8 +50,8 @@
+@@ -45,7 +_,7 @@
          }
      }
  
 -    private <T> SynchedEntityData.DataItem<T> getItem(EntityDataAccessor<T> key) {
--        return this.itemsById[key.id()];
 +    public <T> SynchedEntityData.DataItem<T> getItem(EntityDataAccessor<T> key) { // Paper - public
-+        return (SynchedEntityData.DataItem<T>) this.itemsById[key.id()]; // CraftBukkit - decompile error
+         return (SynchedEntityData.DataItem<T>)this.itemsById[key.id()];
      }
  
-     public <T> T get(EntityDataAccessor<T> data) {
-@@ -74,6 +74,13 @@
- 
+@@ -67,6 +_,13 @@
+         }
      }
  
 +    // CraftBukkit start - add method from above
-+    public <T> void markDirty(EntityDataAccessor<T> datawatcherobject) {
-+        this.getItem(datawatcherobject).setDirty(true);
++    public <T> void markDirty(final EntityDataAccessor<T> entityDataAccessor) {
++        this.getItem(entityDataAccessor).setDirty(true);
 +        this.isDirty = true;
 +    }
 +    // CraftBukkit end
@@ -25,15 +23,11 @@
      public boolean isDirty() {
          return this.isDirty;
      }
-@@ -140,10 +147,24 @@
-         if (!Objects.equals(from.serializer(), to.accessor.serializer())) {
-             throw new IllegalStateException(String.format(Locale.ROOT, "Invalid entity data item type for field %d on entity %s: old=%s(%s), new=%s(%s)", to.accessor.id(), this.entity, to.value, to.value.getClass(), from.value, from.value.getClass()));
-         } else {
--            to.setValue(from.value);
-+            to.setValue((T) from.value); // CraftBukkit - decompile error
+@@ -169,6 +_,20 @@
+             return new SynchedEntityData(this.entity, this.itemsById);
          }
      }
- 
++
 +    // Paper start
 +    // We need to pack all as we cannot rely on "non default values" or "dirty" ones.
 +    // Because these values can possibly be desynced on the client.
@@ -47,7 +41,6 @@
 +        return list;
 +    }
 +    // Paper end
-+
-     public static class DataItem<T> {
  
+     public static class DataItem<T> {
          final EntityDataAccessor<T> accessor;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Frog.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Frog.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch
index f849bb1ffc..f46b1c8ba3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Frog.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Frog.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/world/entity/animal/frog/Frog.java
 +++ b/net/minecraft/world/entity/animal/frog/Frog.java
-@@ -270,7 +270,12 @@
+@@ -270,7 +_,12 @@
  
      @Override
-     public void spawnChildFromBreeding(ServerLevel world, Animal other) {
--        this.finalizeSpawnChildFromBreeding(world, other, null);
+     public void spawnChildFromBreeding(ServerLevel level, Animal mate) {
+-        this.finalizeSpawnChildFromBreeding(level, mate, null);
 +        // Paper start - Add EntityFertilizeEggEvent event
-+        final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other);
++        final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, mate);
 +        if (result.isCancelled()) return;
 +
-+        this.finalizeSpawnChildFromBreeding(world, other, null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount
++        this.finalizeSpawnChildFromBreeding(level, mate, null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount
 +        // Paper end - Add EntityFertilizeEggEvent event
          this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE);
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
new file mode 100644
index 0000000000..f503b6b310
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/frog/ShootTongue.java
++++ b/net/minecraft/world/entity/animal/frog/ShootTongue.java
+@@ -96,7 +_,7 @@
+             if (entity.isAlive()) {
+                 frog.doHurtTarget(level, entity);
+                 if (!entity.isAlive()) {
+-                    entity.remove(Entity.RemovalReason.KILLED);
++                    entity.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
+                 }
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
new file mode 100644
index 0000000000..86db73a415
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
@@ -0,0 +1,84 @@
+--- a/net/minecraft/world/entity/animal/frog/Tadpole.java
++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
+@@ -62,6 +_,7 @@
+         MemoryModuleType.BREED_TARGET,
+         MemoryModuleType.IS_PANICKING
+     );
++    public boolean ageLocked; // Paper
+ 
+     public Tadpole(EntityType<? extends AbstractFish> entityType, Level level) {
+         super(entityType, level);
+@@ -113,7 +_,7 @@
+     @Override
+     public void aiStep() {
+         super.aiStep();
+-        if (!this.level().isClientSide) {
++        if (!this.level().isClientSide && !this.ageLocked) { // Paper
+             this.setAge(this.age + 1);
+         }
+     }
+@@ -122,12 +_,14 @@
+     public void addAdditionalSaveData(CompoundTag tag) {
+         super.addAdditionalSaveData(tag);
+         tag.putInt("Age", this.age);
++        tag.putBoolean("AgeLocked", this.ageLocked); // Paper
+     }
+ 
+     @Override
+     public void readAdditionalSaveData(CompoundTag tag) {
+         super.readAdditionalSaveData(tag);
+         this.setAge(tag.getInt("Age"));
++        this.ageLocked = tag.getBoolean("AgeLocked"); // Paper
+     }
+ 
+     @Nullable
+@@ -177,7 +_,12 @@
+     @Override
+     public void saveToBucketTag(ItemStack stack) {
+         Bucketable.saveDefaultDataToBucketTag(this, stack);
+-        CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, compoundTag -> compoundTag.putInt("Age", this.getAge()));
++        // Paper start - Save tadpole age
++        CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, compoundTag -> {
++            compoundTag.putInt("Age", this.getAge());
++            compoundTag.putBoolean("AgeLocked", this.ageLocked);
++        });
++        // Paper end - Save tadpole age
+     }
+ 
+     @Override
+@@ -186,6 +_,7 @@
+         if (tag.contains("Age")) {
+             this.setAge(tag.getInt("Age"));
+         }
++        this.ageLocked = tag.getBoolean("AgeLocked"); // Paper
+     }
+ 
+     @Override
+@@ -217,6 +_,7 @@
+     }
+ 
+     private void ageUp(int offset) {
++        if (this.ageLocked) return; // Paper
+         this.setAge(this.age + offset * 20);
+     }
+ 
+@@ -229,12 +_,17 @@
+ 
+     private void ageUp() {
+         if (this.level() instanceof ServerLevel serverLevel) {
+-            this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> {
++            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> { // CraftBukkit
+                 mob.finalizeSpawn(serverLevel, this.level().getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, null);
+                 mob.setPersistenceRequired();
+                 mob.fudgePositionAfterSizeChange(this.getDimensions(this.getPose()));
+                 this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
+-            });
++            // CraftBukkit start
++            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS);
++            if (converted == null) {
++                this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled
++            }
++            // CraftBukkit end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
new file mode 100644
index 0000000000..36b61e1dd6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
++++ b/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
+@@ -214,7 +_,14 @@
+                     return false;
+                 } else {
+                     Vec3 vec3 = position.get();
+-                    if (!vibrationUser.canReceiveVibration(level, BlockPos.containing(pos), gameEvent, context)) {
++                    // CraftBukkit start
++                    boolean defaultCancel = !vibrationUser.canReceiveVibration(level, BlockPos.containing(pos), gameEvent, context);
++                    Entity entity = context.sourceEntity();
++                    org.bukkit.event.block.BlockReceiveGameEvent event1 = new org.bukkit.event.block.BlockReceiveGameEvent(org.bukkit.craftbukkit.CraftGameEvent.minecraftToBukkit(gameEvent.value()), org.bukkit.craftbukkit.block.CraftBlock.at(level, BlockPos.containing(vec3)), (entity == null) ? null : entity.getBukkitEntity());
++                    event1.setCancelled(defaultCancel);
++                    level.getCraftServer().getPluginManager().callEvent(event1);
++                    if (event1.isCancelled()) {
++                        // CraftBukkit end
+                         return false;
+                     } else if (isOccluded(level, pos, vec3)) {
+                         return false;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch
new file mode 100644
index 0000000000..d79551ea59
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch
@@ -0,0 +1,93 @@
+--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
+@@ -79,14 +_,30 @@
+         return this.exclusionZone;
+     }
+ 
++    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs
+     public boolean isStructureChunk(ChunkGeneratorStructureState structureState, int x, int z) {
++        // Paper start - Add missing structure set seed configs
++        return this.isStructureChunk(structureState, x, z, null);
++    }
++    public boolean isStructureChunk(ChunkGeneratorStructureState structureState, int x, int z, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey<StructureSet> structureSetKey) {
++        Integer saltOverride = null;
++        if (structureSetKey != null) {
++            if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) {
++                saltOverride = structureState.conf.mineshaftSeed;
++            } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) {
++                saltOverride = structureState.conf.buriedTreasureSeed;
++            }
++        }
++        // Paper end - Add missing structure set seed configs
+         return this.isPlacementChunk(structureState, x, z)
+-            && this.applyAdditionalChunkRestrictions(x, z, structureState.getLevelSeed())
++            && this.applyAdditionalChunkRestrictions(x, z, structureState.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs
+             && this.applyInteractionsWithOtherStructures(structureState, x, z);
+     }
+ 
+-    public boolean applyAdditionalChunkRestrictions(int regionX, int regionZ, long levelSeed) {
+-        return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(levelSeed, this.salt, regionX, regionZ, this.frequency);
++    // Paper start - Add missing structure set seed configs
++    public boolean applyAdditionalChunkRestrictions(int regionX, int regionZ, long levelSeed, @org.jetbrains.annotations.Nullable Integer saltOverride) {
++        return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(levelSeed, this.salt, regionX, regionZ, this.frequency, saltOverride);
++        // Paper end - Add missing structure set seed configs
+     }
+ 
+     public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState structureState, int x, int z) {
+@@ -101,25 +_,31 @@
+ 
+     public abstract StructurePlacementType<?> type();
+ 
+-    private static boolean probabilityReducer(long levelSeed, int regionX, int regionZ, int salt, float probability) {
++    private static boolean probabilityReducer(long levelSeed, int regionX, int regionZ, int salt, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
+         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+         worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, salt);
+         return worldgenRandom.nextFloat() < probability;
+     }
+ 
+-    private static boolean legacyProbabilityReducerWithDouble(long baseSeed, int salt, int chunkX, int chunkZ, float probability) {
++    private static boolean legacyProbabilityReducerWithDouble(long baseSeed, int salt, int chunkX, int chunkZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
+         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
++        if (saltOverride == null) { // Paper - Add missing structure set seed configs
+         worldgenRandom.setLargeFeatureSeed(baseSeed, chunkX, chunkZ);
++        // Paper start - Add missing structure set seed configs
++        } else {
++            worldgenRandom.setLargeFeatureWithSalt(baseSeed, chunkX, chunkZ, saltOverride);
++        }
++        // Paper end - Add missing structure set seed configs
+         return worldgenRandom.nextDouble() < probability;
+     }
+ 
+-    private static boolean legacyArbitrarySaltProbabilityReducer(long levelSeed, int salt, int regionX, int regionZ, float probability) {
++    private static boolean legacyArbitrarySaltProbabilityReducer(long levelSeed, int salt, int regionX, int regionZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
+         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+-        worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, 10387320);
++        worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs
+         return worldgenRandom.nextFloat() < probability;
+     }
+ 
+-    private static boolean legacyPillagerOutpostReducer(long levelSeed, int salt, int regionX, int regionZ, float probability) {
++    private static boolean legacyPillagerOutpostReducer(long levelSeed, int salt, int regionX, int regionZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
+         int i = regionX >> 4;
+         int i1 = regionZ >> 4;
+         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+@@ -147,7 +_,7 @@
+ 
+     @FunctionalInterface
+     public interface FrequencyReducer {
+-        boolean shouldGenerate(long levelSeed, int i, int salt, int regionX, float regionZ);
++        boolean shouldGenerate(long levelSeed, int i, int salt, int regionX, float regionZ, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs
+     }
+ 
+     public static enum FrequencyReductionMethod implements StringRepresentable {
+@@ -167,8 +_,8 @@
+             this.reducer = reducer;
+         }
+ 
+-        public boolean shouldGenerate(long levelSeed, int salt, int regionX, int regionZ, float probability) {
+-            return this.reducer.shouldGenerate(levelSeed, salt, regionX, regionZ, probability);
++        public boolean shouldGenerate(long levelSeed, int salt, int regionX, int regionZ, float probability, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
++            return this.reducer.shouldGenerate(levelSeed, salt, regionX, regionZ, probability, saltOverride); // Paper - Add missing structure set seed configs
+         }
+ 
+         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch
deleted file mode 100644
index 8abda3baff..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch
+++ /dev/null
@@ -1,93 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
-+++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
-@@ -79,14 +79,30 @@
-         return this.exclusionZone;
-     }
- 
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs
-     public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) {
-+        // Paper start - Add missing structure set seed configs
-+        return this.isStructureChunk(calculator, chunkX, chunkZ, null);
-+    }
-+    public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey<StructureSet> structureSetKey) {
-+        Integer saltOverride = null;
-+        if (structureSetKey != null) {
-+            if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) {
-+                saltOverride = calculator.conf.mineshaftSeed;
-+            } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) {
-+                saltOverride = calculator.conf.buriedTreasureSeed;
-+            }
-+        }
-+        // Paper end - Add missing structure set seed configs
-         return this.isPlacementChunk(calculator, chunkX, chunkZ)
--            && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed())
-+            && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs
-             && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ);
-     }
- 
--    public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) {
--        return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency);
-+    // Paper start - Add missing structure set seed configs
-+    public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) {
-+        return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride);
-+        // Paper end - Add missing structure set seed configs
-     }
- 
-     public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) {
-@@ -101,25 +117,31 @@
- 
-     public abstract StructurePlacementType<?> type();
- 
--    private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
-+    private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
-         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
-         worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ);
-         return worldgenRandom.nextFloat() < frequency;
-     }
- 
--    private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) {
-+    private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
-         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
-+        if (saltOverride == null) { // Paper - Add missing structure set seed configs
-         worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ);
-+        // Paper start - Add missing structure set seed configs
-+        } else {
-+            worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride);
-+        }
-+        // Paper end - Add missing structure set seed configs
-         return worldgenRandom.nextDouble() < (double)frequency;
-     }
- 
--    private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
-+    private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
-         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
--        worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320);
-+        worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs
-         return worldgenRandom.nextFloat() < frequency;
-     }
- 
--    private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
-+    private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
-         int i = chunkX >> 4;
-         int j = chunkZ >> 4;
-         WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
-@@ -147,7 +169,7 @@
- 
-     @FunctionalInterface
-     public interface FrequencyReducer {
--        boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance);
-+        boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs
-     }
- 
-     public static enum FrequencyReductionMethod implements StringRepresentable {
-@@ -167,8 +189,8 @@
-             this.reducer = generationPredicate;
-         }
- 
--        public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) {
--            return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance);
-+        public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
-+            return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs
-         }
- 
-         @Override

From aa998246f7e6ec582ddd3f8fa0302a28d1702bb7 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 18:40:47 +0100
Subject: [PATCH 012/285] More work

---
 .../server/rcon/RconConsoleSource.java.patch  | 35 +++++++++++++
 .../effects/ApplyMobEffect.java.patch         | 11 +++++
 .../effects/ChangeItemDamage.java.patch       | 14 ++++++
 .../enchantment/effects/Ignite.java.patch     | 25 ++++++++++
 .../effects/ReplaceBlock.java.patch           | 13 +++++
 .../effects/ReplaceDisk.java.patch            | 11 +++++
 .../effects/SummonEntityEffect.java.patch     | 20 ++++++++
 .../server/rcon/RconConsoleSource.java.patch  | 49 -------------------
 .../effects/ApplyMobEffect.java.patch         | 11 -----
 .../effects/ChangeItemDamage.java.patch       | 14 ------
 .../enchantment/effects/Ignite.java.patch     | 36 --------------
 .../effects/ReplaceBlock.java.patch           | 11 -----
 .../effects/ReplaceDisk.java.patch            | 11 -----
 .../effects/SummonEntityEffect.java.patch     | 35 -------------
 14 files changed, 129 insertions(+), 167 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/rcon/RconConsoleSource.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch
new file mode 100644
index 0000000000..7ad11d0409
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/rcon/RconConsoleSource.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/server/rcon/RconConsoleSource.java
++++ b/net/minecraft/server/rcon/RconConsoleSource.java
+@@ -13,8 +_,13 @@
+     private static final Component RCON_COMPONENT = Component.literal("Rcon");
+     private final StringBuffer buffer = new StringBuffer();
+     private final MinecraftServer server;
++    // CraftBukkit start
++    public final java.net.SocketAddress socketAddress;
++    private final org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender remoteConsole = new org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender(this);
+ 
+-    public RconConsoleSource(MinecraftServer server) {
++    public RconConsoleSource(MinecraftServer server, java.net.SocketAddress socketAddress) {
++        this.socketAddress = socketAddress;
++        // CraftBukkit end
+         this.server = server;
+     }
+ 
+@@ -32,6 +_,17 @@
+             this, Vec3.atLowerCornerOf(serverLevel.getSharedSpawnPos()), Vec2.ZERO, serverLevel, 4, "Rcon", RCON_COMPONENT, this.server, null
+         );
+     }
++
++    // CraftBukkit start - Send a String
++    public void sendMessage(String message) {
++        this.buffer.append(message);
++    }
++
++    @Override
++    public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++        return this.remoteConsole;
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public void sendSystemMessage(Component component) {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
new file mode 100644
index 0000000000..47f39569b2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java
++++ b/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java
+@@ -44,7 +_,7 @@
+                 int max = Math.max(
+                     0, Math.round(Mth.randomBetween(random, this.minAmplifier.calculate(enchantmentLevel), this.maxAmplifier.calculate(enchantmentLevel)))
+                 );
+-                livingEntity.addEffect(new MobEffectInstance(randomElement.get(), rounded, max));
++                livingEntity.addEffect(new MobEffectInstance(randomElement.get(), rounded, max), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
new file mode 100644
index 0000000000..0de21ed24c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
++++ b/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
+@@ -21,9 +_,9 @@
+     public void apply(ServerLevel level, int enchantmentLevel, EnchantedItemInUse item, Entity entity, Vec3 origin) {
+         ItemStack itemStack = item.itemStack();
+         if (itemStack.has(DataComponents.MAX_DAMAGE) && itemStack.has(DataComponents.DAMAGE)) {
+-            ServerPlayer serverPlayer1 = item.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null;
++            //ServerPlayer serverPlayer1 = item.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - EntityDamageItemEvent - always pass in entity
+             int i = (int)this.amount.calculate(enchantmentLevel);
+-            itemStack.hurtAndBreak(i, level, serverPlayer1, item.onBreak());
++            itemStack.hurtAndBreak(i, level, item.owner(), item.onBreak()); // Paper - EntityDamageItemEvent - always pass in entity
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
new file mode 100644
index 0000000000..e1daa636a5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/item/enchantment/effects/Ignite.java
++++ b/net/minecraft/world/item/enchantment/effects/Ignite.java
+@@ -15,7 +_,21 @@
+ 
+     @Override
+     public void apply(ServerLevel level, int enchantmentLevel, EnchantedItemInUse item, Entity entity, Vec3 origin) {
+-        entity.igniteForSeconds(this.duration.calculate(enchantmentLevel));
++        // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++        org.bukkit.event.entity.EntityCombustEvent entityCombustEvent;
++        if (item.owner() != null) {
++            entityCombustEvent = new org.bukkit.event.entity.EntityCombustByEntityEvent(item.owner().getBukkitEntity(), entity.getBukkitEntity(), this.duration.calculate(enchantmentLevel));
++        } else {
++            entityCombustEvent = new org.bukkit.event.entity.EntityCombustEvent(entity.getBukkitEntity(), this.duration.calculate(enchantmentLevel));
++        }
++
++        org.bukkit.Bukkit.getPluginManager().callEvent(entityCombustEvent);
++        if (entityCombustEvent.isCancelled()) {
++            return;
++        }
++
++        entity.igniteForSeconds(entityCombustEvent.getDuration(), false);
++        // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
new file mode 100644
index 0000000000..497dec7455
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
++++ b/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
+@@ -29,8 +_,9 @@
+     @Override
+     public void apply(ServerLevel level, int enchantmentLevel, EnchantedItemInUse item, Entity entity, Vec3 origin) {
+         BlockPos blockPos = BlockPos.containing(origin).offset(this.offset);
++        // }).orElse(true) &&
+         if (this.predicate.map(blockPredicate -> blockPredicate.test(level, blockPos)).orElse(true)
+-            && level.setBlockAndUpdate(blockPos, this.blockState.getState(entity.getRandom(), blockPos))) {
++            && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, blockPos, this.blockState.getState(entity.getRandom(), blockPos), entity)) { // CraftBukkit - Call EntityBlockFormEvent
+             this.triggerGameEvent.ifPresent(holder -> level.gameEvent(entity, (Holder<GameEvent>)holder, blockPos));
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
new file mode 100644
index 0000000000..08b184622e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java
++++ b/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java
+@@ -47,7 +_,7 @@
+         for (BlockPos blockPos1 : BlockPos.betweenClosed(blockPos.offset(-i, 0, -i), blockPos.offset(i, Math.min(i1 - 1, 0), i))) {
+             if (blockPos1.distToCenterSqr(origin.x(), blockPos1.getY() + 0.5, origin.z()) < Mth.square(i)
+                 && this.predicate.map(predicate -> predicate.test(level, blockPos1)).orElse(true)
+-                && level.setBlockAndUpdate(blockPos1, this.blockState.getState(random, blockPos1))) {
++                && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, blockPos1, this.blockState.getState(random, blockPos1), entity)) { // CraftBukkit - Call EntityBlockFormEvent for Frost Walker
+                 this.triggerGameEvent.ifPresent(event -> level.gameEvent(entity, (Holder<GameEvent>)event, blockPos1));
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
new file mode 100644
index 0000000000..913d9aeec7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
++++ b/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
+@@ -34,11 +_,16 @@
+         if (Level.isInSpawnableBounds(blockPos)) {
+             Optional<Holder<EntityType<?>>> randomElement = this.entityTypes().getRandomElement(level.getRandom());
+             if (!randomElement.isEmpty()) {
+-                Entity entity1 = randomElement.get().value().spawn(level, blockPos, EntitySpawnReason.TRIGGERED);
++                Entity entity1 = randomElement.get().value().create(level, null, blockPos, EntitySpawnReason.TRIGGERED, false, false); // CraftBukkit
+                 if (entity1 != null) {
+                     if (entity1 instanceof LightningBolt lightningBolt && item.owner() instanceof ServerPlayer serverPlayer) {
+                         lightningBolt.setCause(serverPlayer);
+                     }
++                    // CraftBukkit start
++                    level.strikeLightning(entity1, (item.itemStack().getItem() == net.minecraft.world.item.Items.TRIDENT) ? org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT : org.bukkit.event.weather.LightningStrikeEvent.Cause.ENCHANTMENT);
++                } else {
++                    level.addFreshEntityWithPassengers(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENCHANTMENT);
++                    // CraftBukkit end
+ 
+                     if (this.joinTeam && entity.getTeam() != null) {
+                         level.getScoreboard().addPlayerToTeam(entity1.getScoreboardName(), entity.getTeam());
diff --git a/paper-server/patches/unapplied/net/minecraft/server/rcon/RconConsoleSource.java.patch b/paper-server/patches/unapplied/net/minecraft/server/rcon/RconConsoleSource.java.patch
deleted file mode 100644
index 589c29e384..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/rcon/RconConsoleSource.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/server/rcon/RconConsoleSource.java
-+++ b/net/minecraft/server/rcon/RconConsoleSource.java
-@@ -8,16 +8,24 @@
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
--
-+// CraftBukkit start
-+import java.net.SocketAddress;
-+import org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender;
-+// CraftBukkit end
- public class RconConsoleSource implements CommandSource {
- 
-     private static final String RCON = "Rcon";
-     private static final Component RCON_COMPONENT = Component.literal("Rcon");
-     private final StringBuffer buffer = new StringBuffer();
-     private final MinecraftServer server;
-+    // CraftBukkit start
-+    public final SocketAddress socketAddress;
-+    private final CraftRemoteConsoleCommandSender remoteConsole = new CraftRemoteConsoleCommandSender(this);
- 
--    public RconConsoleSource(MinecraftServer server) {
--        this.server = server;
-+    public RconConsoleSource(MinecraftServer minecraftserver, SocketAddress socketAddress) {
-+        this.socketAddress = socketAddress;
-+        // CraftBukkit end
-+        this.server = minecraftserver;
-     }
- 
-     public void prepareForCommand() {
-@@ -34,7 +42,18 @@
-         return new CommandSourceStack(this, Vec3.atLowerCornerOf(worldserver.getSharedSpawnPos()), Vec2.ZERO, worldserver, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null);
-     }
- 
-+    // CraftBukkit start - Send a String
-+    public void sendMessage(String message) {
-+        this.buffer.append(message);
-+    }
-+
-     @Override
-+    public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+        return this.remoteConsole;
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public void sendSystemMessage(Component message) {
-         this.buffer.append(message.getString());
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
deleted file mode 100644
index abe8774935..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java
-+++ b/net/minecraft/world/item/enchantment/effects/ApplyMobEffect.java
-@@ -34,7 +34,7 @@
-                 int j = Math.round(Mth.randomBetween(randomsource, this.minDuration.calculate(level), this.maxDuration.calculate(level)) * 20.0F);
-                 int k = Math.max(0, Math.round(Mth.randomBetween(randomsource, this.minAmplifier.calculate(level), this.maxAmplifier.calculate(level))));
- 
--                entityliving.addEffect(new MobEffectInstance((Holder) optional.get(), j, k));
-+                entityliving.addEffect(new MobEffectInstance((Holder) optional.get(), j, k), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-             }
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
deleted file mode 100644
index 711eebddb2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
-+++ b/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
-@@ -21,9 +21,9 @@
-     public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) {
-         ItemStack itemStack = context.itemStack();
-         if (itemStack.has(DataComponents.MAX_DAMAGE) && itemStack.has(DataComponents.DAMAGE)) {
--            ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null;
-+            // ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - EntityDamageItemEvent - always pass in entity
-             int i = (int)this.amount.calculate(level);
--            itemStack.hurtAndBreak(i, world, serverPlayer2, context.onBreak());
-+            itemStack.hurtAndBreak(i, world, context.owner(), context.onBreak()); // Paper - EntityDamageItemEvent - always pass in entity
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/Ignite.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
deleted file mode 100644
index 80efcc0872..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/Ignite.java.patch
+++ /dev/null
@@ -1,36 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/Ignite.java
-+++ b/net/minecraft/world/item/enchantment/effects/Ignite.java
-@@ -7,6 +7,10 @@
- import net.minecraft.world.item.enchantment.EnchantedItemInUse;
- import net.minecraft.world.item.enchantment.LevelBasedValue;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityCombustByEntityEvent;
-+import org.bukkit.event.entity.EntityCombustEvent;
-+// CraftBukkit end
- 
- public record Ignite(LevelBasedValue duration) implements EnchantmentEntityEffect {
- 
-@@ -18,7 +22,21 @@
- 
-     @Override
-     public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) {
--        user.igniteForSeconds(this.duration.calculate(level));
-+        // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
-+        EntityCombustEvent entityCombustEvent;
-+        if (context.owner() != null) {
-+            entityCombustEvent = new EntityCombustByEntityEvent(context.owner().getBukkitEntity(), user.getBukkitEntity(), this.duration.calculate(level));
-+        } else {
-+            entityCombustEvent = new EntityCombustEvent(user.getBukkitEntity(), this.duration.calculate(level));
-+        }
-+
-+        org.bukkit.Bukkit.getPluginManager().callEvent(entityCombustEvent);
-+        if (entityCombustEvent.isCancelled()) {
-+            return;
-+        }
-+
-+        user.igniteForSeconds(entityCombustEvent.getDuration(), false);
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
deleted file mode 100644
index 78dc09a7de..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
-+++ b/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
-@@ -26,7 +26,7 @@
- 
-         if ((Boolean) this.predicate.map((blockpredicate) -> {
-             return blockpredicate.test(world, blockposition);
--        }).orElse(true) && world.setBlockAndUpdate(blockposition, this.blockState.getState(user.getRandom(), blockposition))) {
-+        }).orElse(true) && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, blockposition, this.blockState.getState(user.getRandom(), blockposition), user)) { // CraftBukkit - Call EntityBlockFormEvent
-             this.triggerGameEvent.ifPresent((holder) -> {
-                 world.gameEvent(user, holder, blockposition);
-             });
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
deleted file mode 100644
index 2a0ad9e549..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java
-+++ b/net/minecraft/world/item/enchantment/effects/ReplaceDisk.java
-@@ -37,7 +37,7 @@
- 
-             if (blockposition1.distToCenterSqr(pos.x(), (double) blockposition1.getY() + 0.5D, pos.z()) < (double) Mth.square(j) && (Boolean) this.predicate.map((blockpredicate) -> {
-                 return blockpredicate.test(world, blockposition1);
--            }).orElse(true) && world.setBlockAndUpdate(blockposition1, this.blockState.getState(randomsource, blockposition1))) {
-+            }).orElse(true) && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, blockposition1,  this.blockState.getState(randomsource, blockposition1), user)) { // CraftBukkit - Call EntityBlockFormEvent for Frost Walker
-                 this.triggerGameEvent.ifPresent((holder) -> {
-                     world.gameEvent(user, holder, blockposition1);
-                 });
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
deleted file mode 100644
index 8ae0ac6d6e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
-+++ b/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
-@@ -19,6 +19,11 @@
- import net.minecraft.world.item.enchantment.EnchantedItemInUse;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import net.minecraft.world.item.Items;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.weather.LightningStrikeEvent;
-+// CraftBukkit end
- 
- public record SummonEntityEffect(HolderSet<EntityType<?>> entityTypes, boolean joinTeam) implements EnchantmentEntityEffect {
- 
-@@ -34,7 +39,7 @@
-             Optional<Holder<EntityType<?>>> optional = this.entityTypes().getRandomElement(world.getRandom());
- 
-             if (!optional.isEmpty()) {
--                Entity entity1 = ((EntityType) ((Holder) optional.get()).value()).spawn(world, blockposition, EntitySpawnReason.TRIGGERED);
-+                Entity entity1 = ((EntityType) ((Holder) optional.get()).value()).create(world, null, blockposition, EntitySpawnReason.TRIGGERED, false, false); // CraftBukkit
- 
-                 if (entity1 != null) {
-                     if (entity1 instanceof LightningBolt) {
-@@ -46,6 +51,11 @@
- 
-                             entitylightning.setCause(entityplayer);
-                         }
-+                        // CraftBukkit start
-+                        world.strikeLightning(entity1, (context.itemStack().getItem() == Items.TRIDENT) ? LightningStrikeEvent.Cause.TRIDENT : LightningStrikeEvent.Cause.ENCHANTMENT);
-+                    } else {
-+                        world.addFreshEntityWithPassengers(entity1, CreatureSpawnEvent.SpawnReason.ENCHANTMENT);
-+                        // CraftBukkit end
-                     }
- 
-                     if (this.joinTeam && user.getTeam() != null) {

From b97663fdf9773a7dac649a2714aff90b2900859a Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 09:44:20 -0800
Subject: [PATCH 013/285] remove applied patches

---
 .../entity/animal/frog/ShootTongue.java.patch | 39 ---------
 .../entity/animal/frog/Tadpole.java.patch     | 87 -------------------
 .../vibrations/VibrationSystem.java.patch     | 52 -----------
 3 files changed, 178 deletions(-)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
deleted file mode 100644
index 354d8a45c5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/ShootTongue.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/entity/animal/frog/ShootTongue.java
-+++ b/net/minecraft/world/entity/animal/frog/ShootTongue.java
-@@ -19,6 +19,9 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class ShootTongue extends Behavior<Frog> {
- 
-@@ -64,7 +67,7 @@
- 
-         BehaviorUtils.lookAtEntity(frog, entityliving);
-         frog.setTongueTarget(entityliving);
--        frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(entityliving.position(), 2.0F, 0)));
-+        frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(entityliving.position(), 2.0F, 0))); // CraftBukkit - decompile error
-         this.calculatePathCounter = 10;
-         this.state = ShootTongue.State.MOVE_TO_TARGET;
-     }
-@@ -85,7 +88,7 @@
-             if (entity.isAlive()) {
-                 frog.doHurtTarget(world, entity);
-                 if (!entity.isAlive()) {
--                    entity.remove(Entity.RemovalReason.KILLED);
-+                    entity.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-                 }
-             }
-         }
-@@ -106,7 +109,7 @@
-                     this.eatAnimationTimer = 0;
-                     this.state = ShootTongue.State.CATCH_ANIMATION;
-                 } else if (this.calculatePathCounter <= 0) {
--                    frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(entityliving.position(), 2.0F, 0)));
-+                    frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(entityliving.position(), 2.0F, 0))); // CraftBukkit - decompile error
-                     this.calculatePathCounter = 10;
-                 } else {
-                     --this.calculatePathCounter;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
deleted file mode 100644
index 636405ea80..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
+++ /dev/null
@@ -1,87 +0,0 @@
---- a/net/minecraft/world/entity/animal/frog/Tadpole.java
-+++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
-@@ -50,6 +50,7 @@
-     public int age;
-     protected static final ImmutableList<SensorType<? extends Sensor<? super Tadpole>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS);
-     protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING);
-+    public boolean ageLocked; // Paper
- 
-     public Tadpole(EntityType<? extends AbstractFish> type, Level world) {
-         super(type, world);
-@@ -74,7 +75,7 @@
- 
-     @Override
-     public Brain<Tadpole> getBrain() {
--        return super.getBrain();
-+        return (Brain<Tadpole>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -102,7 +103,7 @@
-     @Override
-     public void aiStep() {
-         super.aiStep();
--        if (!this.level().isClientSide) {
-+        if (!this.level().isClientSide && !this.ageLocked) { // Paper
-             this.setAge(this.age + 1);
-         }
- 
-@@ -112,12 +113,14 @@
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
-         nbt.putInt("Age", this.age);
-+        nbt.putBoolean("AgeLocked", this.ageLocked); // Paper
-     }
- 
-     @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         this.setAge(nbt.getInt("Age"));
-+        this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper
-     }
- 
-     @Nullable
-@@ -169,6 +172,7 @@
-         Bucketable.saveDefaultDataToBucketTag(this, stack);
-         CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, (nbttagcompound) -> {
-             nbttagcompound.putInt("Age", this.getAge());
-+            nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper
-         });
-     }
- 
-@@ -179,6 +183,7 @@
-             this.setAge(nbt.getInt("Age"));
-         }
- 
-+        this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper
-     }
- 
-     @Override
-@@ -210,6 +215,7 @@
-     }
- 
-     private void ageUp(int seconds) {
-+        if (this.ageLocked) return; // Paper
-         this.setAge(this.age + seconds * 20);
-     }
- 
-@@ -225,12 +231,17 @@
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
--            this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), (frog) -> {
-+            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), (frog) -> { // CraftBukkit
-                 frog.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(frog.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null);
-                 frog.setPersistenceRequired();
-                 frog.fudgePositionAfterSizeChange(this.getDimensions(this.getPose()));
-                 this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
--            });
-+            // CraftBukkit start
-+            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS);
-+            if (converted == null) {
-+                this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
deleted file mode 100644
index d96bdd56da..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
+++ /dev/null
@@ -1,52 +0,0 @@
---- a/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
-+++ b/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
-@@ -30,6 +30,11 @@
- import net.minecraft.world.level.gameevent.PositionSource;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.CraftGameEvent;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockReceiveGameEvent;
-+// CraftBukkit end
- 
- public interface VibrationSystem {
- 
-@@ -233,7 +238,8 @@
-             if (callback.requiresAdjacentChunksToBeTicking() && !Ticker.areAdjacentChunksTicking(world, blockposition1)) {
-                 return false;
-             } else {
--                callback.onReceiveVibration(world, blockposition, vibration.gameEvent(), (Entity) vibration.getEntity(world).orElse((Object) null), (Entity) vibration.getProjectileOwner(world).orElse((Object) null), VibrationSystem.Listener.distanceBetweenInBlocks(blockposition, blockposition1));
-+                // CraftBukkit - decompile error
-+                callback.onReceiveVibration(world, blockposition, vibration.gameEvent(), (Entity) vibration.getEntity(world).orElse(null), (Entity) vibration.getProjectileOwner(world).orElse(null), VibrationSystem.Listener.distanceBetweenInBlocks(blockposition, blockposition1));
-                 listenerData.setCurrentVibration((VibrationInfo) null);
-                 return true;
-             }
-@@ -288,8 +294,14 @@
-                     return false;
-                 } else {
-                     Vec3 vec3d1 = (Vec3) optional.get();
--
--                    if (!vibrationsystem_d.canReceiveVibration(world, BlockPos.containing(emitterPos), event, emitter)) {
-+                    // CraftBukkit start
-+                    boolean defaultCancel = !vibrationsystem_d.canReceiveVibration(world, BlockPos.containing(emitterPos), event, emitter);
-+                    Entity entity = emitter.sourceEntity();
-+                    BlockReceiveGameEvent event1 = new BlockReceiveGameEvent(CraftGameEvent.minecraftToBukkit(event.value()), CraftBlock.at(world, BlockPos.containing(vec3d1)), (entity == null) ? null : entity.getBukkitEntity());
-+                    event1.setCancelled(defaultCancel);
-+                    world.getCraftServer().getPluginManager().callEvent(event1);
-+                    if (event1.isCancelled()) {
-+                        // CraftBukkit end
-                         return false;
-                     } else if (Listener.isOccluded(world, emitterPos, vec3d1)) {
-                         return false;
-@@ -341,8 +353,8 @@
-         public static Codec<VibrationSystem.Data> CODEC = RecordCodecBuilder.create((instance) -> {
-             return instance.group(VibrationInfo.CODEC.lenientOptionalFieldOf("event").forGetter((vibrationsystem_a) -> {
-                 return Optional.ofNullable(vibrationsystem_a.currentVibration);
--            }), VibrationSelector.CODEC.fieldOf("selector").forGetter(VibrationSystem.Data::getSelectionStrategy), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("event_delay").orElse(0).forGetter(VibrationSystem.Data::getTravelTimeInTicks)).apply(instance, (optional, vibrationselector, integer) -> {
--                return new VibrationSystem.Data((VibrationInfo) optional.orElse((Object) null), vibrationselector, integer, true);
-+            }), VibrationSelector.CODEC.optionalFieldOf("selector").xmap(o -> o.orElseGet(VibrationSelector::new), Optional::of).forGetter(VibrationSystem.Data::getSelectionStrategy), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("event_delay").orElse(0).forGetter(VibrationSystem.Data::getTravelTimeInTicks)).apply(instance, (optional, vibrationselector, integer) -> { // Paper - fix MapLike spam for missing "selector" in 1.19.2
-+                return new VibrationSystem.Data((VibrationInfo) optional.orElse(null), vibrationselector, integer, true); // CraftBukkit - decompile error
-             });
-         });
-         public static final String NBT_TAG_KEY = "listener";

From e20952c643372f6a328c69ce4bdb2bbbce79e21a Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 18:55:47 +0100
Subject: [PATCH 014/285] Make Tadpole apply

---
 .../entity/animal/frog/Tadpole.java.patch     | 16 +++++++-------
 .../LootPoolSingletonContainer.java.patch     | 21 ++++++++++++-------
 2 files changed, 22 insertions(+), 15 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch (77%)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
index 86db73a415..215b78b19d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
@@ -18,17 +18,17 @@
          }
      }
 @@ -122,12 +_,14 @@
-     public void addAdditionalSaveData(CompoundTag tag) {
-         super.addAdditionalSaveData(tag);
-         tag.putInt("Age", this.age);
-+        tag.putBoolean("AgeLocked", this.ageLocked); // Paper
+     public void addAdditionalSaveData(CompoundTag compound) {
+         super.addAdditionalSaveData(compound);
+         compound.putInt("Age", this.age);
++        compound.putBoolean("AgeLocked", this.ageLocked); // Paper
      }
  
      @Override
-     public void readAdditionalSaveData(CompoundTag tag) {
-         super.readAdditionalSaveData(tag);
-         this.setAge(tag.getInt("Age"));
-+        this.ageLocked = tag.getBoolean("AgeLocked"); // Paper
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+         this.setAge(compound.getInt("Age"));
++        this.ageLocked = compound.getBoolean("AgeLocked"); // Paper
      }
  
      @Nullable
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch
index f97db2bfbc..baa99f7a0b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java.patch
@@ -1,10 +1,21 @@
 --- a/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java
 +++ b/net/minecraft/world/level/storage/loot/entries/LootPoolSingletonContainer.java
-@@ -126,9 +126,35 @@
+@@ -32,6 +_,10 @@
+             );
+         }
+     };
++    // Paper start - Configurable LootPool luck formula
++    private Float lastLuck;
++    private int lastWeight;
++    // Paper end - Configurable LootPool luck formula
+ 
+     protected LootPoolSingletonContainer(int weight, int quality, List<LootItemCondition> conditions, List<LootItemFunction> functions) {
+         super(conditions);
+@@ -126,7 +_,31 @@
      protected abstract class EntryBase implements LootPoolEntry {
          @Override
          public int getWeight(float luck) {
--            return Math.max(Mth.floor((float)LootPoolSingletonContainer.this.weight + (float)LootPoolSingletonContainer.this.quality * luck), 0);
+-            return Math.max(Mth.floor(LootPoolSingletonContainer.this.weight + LootPoolSingletonContainer.this.quality * luck), 0);
 +            // Paper start - Configurable LootPool luck formula
 +            // SEE: https://luckformula.emc.gs for details and data
 +            if (LootPoolSingletonContainer.this.lastLuck != null && LootPoolSingletonContainer.this.lastLuck == luck) {
@@ -29,11 +40,7 @@
 +            LootPoolSingletonContainer.this.lastLuck = luck;
 +            LootPoolSingletonContainer.this.lastWeight = (int) Math.max(Math.floor(baseWeight), 0);
 +            return lastWeight;
++            // Paper end - Configurable LootPool luck formula
          }
      }
-+    private Float lastLuck = null;
-+    private int lastWeight = 0;
-+    // Paper end - Configurable LootPool luck formula
  
-     @FunctionalInterface
-     protected interface EntryConstructor {

From 18a25937bc79b4d7ab93799221a1e731ea2a37ef Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Fri, 13 Dec 2024 18:56:07 +0100
Subject: [PATCH 015/285] pathfinding, packet utils

---
 .../network/protocol/Packet.java.patch        |  4 +--
 .../network/protocol/PacketUtils.java.patch   | 10 +++++++
 .../world/level/pathfinder/Path.java.patch    |  4 +--
 .../pathfinder/WalkNodeEvaluator.java.patch   |  8 +++---
 .../network/protocol/PacketUtils.java.patch   | 27 -------------------
 5 files changed, 18 insertions(+), 35 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/Packet.java.patch (91%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/pathfinder/Path.java.patch (93%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch (74%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/PacketUtils.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/Packet.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch
similarity index 91%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/Packet.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch
index ba40a5940e..221a5587d3 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/Packet.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/Packet.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/network/protocol/Packet.java
 +++ b/net/minecraft/network/protocol/Packet.java
-@@ -11,6 +11,19 @@
+@@ -11,6 +_,19 @@
  
-     void handle(T listener);
+     void handle(T handler);
  
 +    // Paper start
 +    default boolean hasLargePacketFallback() {
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
new file mode 100644
index 0000000000..0eaf929602
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/network/protocol/PacketUtils.java
++++ b/net/minecraft/network/protocol/PacketUtils.java
+@@ -21,6 +_,7 @@
+     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
+         if (!executor.isSameThread()) {
+             executor.executeIfPossible(() -> {
++                if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
+                 if (processor.shouldHandleMessage(packet)) {
+                     try {
+                         packet.handle(processor);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/Path.java.patch b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch
similarity index 93%
rename from paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/Path.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch
index 94eb8f4856..7238ea6726 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/Path.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/pathfinder/Path.java
 +++ b/net/minecraft/world/level/pathfinder/Path.java
-@@ -18,6 +18,7 @@
+@@ -18,6 +_,7 @@
      private final BlockPos target;
      private final float distToTarget;
      private final boolean reached;
 +    public boolean hasNext() { return getNextNodeIndex() < this.nodes.size(); } // Paper - Mob Pathfinding API
  
-     public Path(List<Node> nodes, BlockPos target, boolean reachesTarget) {
+     public Path(List<Node> nodes, BlockPos target, boolean reached) {
          this.nodes = nodes;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch
similarity index 74%
rename from paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch
index 96be982191..94a7ed2e86 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
 +++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
-@@ -478,7 +478,12 @@
+@@ -480,7 +_,12 @@
      }
  
-     protected static PathType getPathTypeFromState(BlockGetter world, BlockPos pos) {
--        BlockState blockState = world.getBlockState(pos);
+     protected static PathType getPathTypeFromState(BlockGetter level, BlockPos pos) {
+-        BlockState blockState = level.getBlockState(pos);
 +        // Paper start - Do not load chunks during pathfinding
-+        BlockState blockState = world.getBlockStateIfLoaded(pos);
++        BlockState blockState = level.getBlockStateIfLoaded(pos);
 +        if (blockState == null) {
 +            return PathType.BLOCKED;
 +        }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/PacketUtils.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/PacketUtils.java.patch
deleted file mode 100644
index 67d49cd3f7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/PacketUtils.java.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- a/net/minecraft/network/protocol/PacketUtils.java
-+++ b/net/minecraft/network/protocol/PacketUtils.java
-@@ -6,10 +6,15 @@
- import net.minecraft.CrashReportCategory;
- import net.minecraft.ReportedException;
- import net.minecraft.network.PacketListener;
-+import org.slf4j.Logger;
-+
-+// CraftBukkit start
-+import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.RunningOnDifferentThreadException;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.network.ServerCommonPacketListenerImpl;
-+// CraftBukkit end
- import net.minecraft.util.thread.BlockableEventLoop;
--import org.slf4j.Logger;
- 
- public class PacketUtils {
- 
-@@ -24,6 +29,7 @@
-     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, BlockableEventLoop<?> engine) throws RunningOnDifferentThreadException {
-         if (!engine.isSameThread()) {
-             engine.executeIfPossible(() -> {
-+                if (listener instanceof ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // CraftBukkit - Don't handle sync packets for kicked players
-                 if (listener.shouldHandleMessage(packet)) {
-                     try {
-                         packet.handle(listener);

From 7d42b87010153b1ed1014c1d66c3eb136f5990e8 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Fri, 13 Dec 2024 19:17:03 +0100
Subject: [PATCH 016/285] net/minecraft/world + Tadpole?

---
 .../net/minecraft/world/BossEvent.java.patch  | 32 ++++----
 .../world/CompoundContainer.java.patch        | 60 +++++++++++++++
 .../net/minecraft/world/Container.java.patch  | 36 +++++++++
 .../world/RandomizableContainer.java.patch    | 64 ++++++++--------
 .../world/SimpleContainer.java.patch          | 54 +++++---------
 .../entity/animal/frog/Tadpole.java.patch     | 22 +++---
 .../world/CompoundContainer.java.patch        | 74 -------------------
 .../net/minecraft/world/Container.java.patch  | 49 ------------
 8 files changed, 173 insertions(+), 218 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/BossEvent.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/Container.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/RandomizableContainer.java.patch (65%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/SimpleContainer.java.patch (68%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/CompoundContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/Container.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/BossEvent.java.patch b/paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/BossEvent.java.patch
rename to paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch
index 3210ddc320..ae696def49 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/BossEvent.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/BossEvent.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/BossEvent.java
 +++ b/net/minecraft/world/BossEvent.java
-@@ -13,6 +13,7 @@
+@@ -13,6 +_,7 @@
      protected boolean darkenScreen;
      protected boolean playBossMusic;
      protected boolean createWorldFog;
 +    public net.kyori.adventure.bossbar.BossBar adventure; // Paper
  
-     public BossEvent(UUID uuid, Component name, BossEvent.BossBarColor color, BossEvent.BossBarOverlay style) {
-         this.id = uuid;
-@@ -27,61 +28,75 @@
+     public BossEvent(UUID id, Component name, BossEvent.BossBarColor color, BossEvent.BossBarOverlay overlay) {
+         this.id = id;
+@@ -27,61 +_,75 @@
      }
  
      public Component getName() {
@@ -26,9 +26,9 @@
          return this.progress;
      }
  
-     public void setProgress(float percent) {
-+        if (this.adventure != null) this.adventure.progress(percent); // Paper
-         this.progress = percent;
+     public void setProgress(float progress) {
++        if (this.adventure != null) this.adventure.progress(progress); // Paper
+         this.progress = progress;
      }
  
      public BossEvent.BossBarColor getColor() {
@@ -46,9 +46,9 @@
          return this.overlay;
      }
  
-     public void setOverlay(BossEvent.BossBarOverlay style) {
-+        if (this.adventure != null) this.adventure.overlay(io.papermc.paper.adventure.PaperAdventure.asAdventure(style)); // Paper
-         this.overlay = style;
+     public void setOverlay(BossEvent.BossBarOverlay overlay) {
++        if (this.adventure != null) this.adventure.overlay(io.papermc.paper.adventure.PaperAdventure.asAdventure(overlay)); // Paper
+         this.overlay = overlay;
      }
  
      public boolean shouldDarkenScreen() {
@@ -67,15 +67,15 @@
          return this.playBossMusic;
      }
  
-     public BossEvent setPlayBossMusic(boolean dragonMusic) {
-+        if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC, dragonMusic); // Paper
-         this.playBossMusic = dragonMusic;
+     public BossEvent setPlayBossMusic(boolean playEndBossMusic) {
++        if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.PLAY_BOSS_MUSIC, playEndBossMusic); // Paper
+         this.playBossMusic = playEndBossMusic;
          return this;
      }
  
-     public BossEvent setCreateWorldFog(boolean thickenFog) {
-+        if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG, thickenFog); // Paper
-         this.createWorldFog = thickenFog;
+     public BossEvent setCreateWorldFog(boolean createFog) {
++        if (this.adventure != null) io.papermc.paper.adventure.PaperAdventure.setFlag(this.adventure, net.kyori.adventure.bossbar.BossBar.Flag.CREATE_WORLD_FOG, createFog); // Paper
+         this.createWorldFog = createFog;
          return this;
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
new file mode 100644
index 0000000000..ffcea4eeab
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/CompoundContainer.java
++++ b/net/minecraft/world/CompoundContainer.java
+@@ -7,6 +_,48 @@
+     private final Container container1;
+     private final Container container2;
+ 
++    // Paper start - add fields and methods
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
++
++    public java.util.List<ItemStack> getContents() {
++        java.util.List<ItemStack> result = new java.util.ArrayList<>(this.getContainerSize());
++        for (int i = 0; i < this.getContainerSize(); i++) {
++            result.add(this.getItem(i));
++        }
++        return result;
++    }
++
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.container1.onOpen(who);
++        this.container2.onOpen(who);
++        this.transaction.add(who);
++    }
++
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.container1.onClose(who);
++        this.container2.onClose(who);
++        this.transaction.remove(who);
++    }
++
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    public org.bukkit.inventory.InventoryHolder getOwner() {
++        return null; // This method won't be called since CraftInventoryDoubleChest doesn't defer to here
++    }
++
++    public void setMaxStackSize(int size) {
++        this.container1.setMaxStackSize(size);
++        this.container2.setMaxStackSize(size);
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        return this.container1.getLocation(); // TODO: right?
++    }
++    // Paper end
++
+     public CompoundContainer(Container container1, Container container2) {
+         this.container1 = container1;
+         this.container2 = container2;
+@@ -58,7 +_,7 @@
+ 
+     @Override
+     public int getMaxStackSize() {
+-        return this.container1.getMaxStackSize();
++        return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // Paper - check both sides
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/Container.java.patch b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
new file mode 100644
index 0000000000..ac7f19ed19
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/Container.java
++++ b/net/minecraft/world/Container.java
+@@ -24,9 +_,7 @@
+ 
+     void setItem(int slot, ItemStack stack);
+ 
+-    default int getMaxStackSize() {
+-        return 99;
+-    }
++    int getMaxStackSize(); // Paper
+ 
+     default int getMaxStackSize(ItemStack stack) {
+         return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
+@@ -87,4 +_,22 @@
+         BlockPos blockPos = blockEntity.getBlockPos();
+         return level != null && level.getBlockEntity(blockPos) == blockEntity && player.canInteractWithBlock(blockPos, distance);
+     }
++
++    // Paper start
++    java.util.List<ItemStack> getContents();
++
++    void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who);
++
++    void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who);
++
++    java.util.List<org.bukkit.entity.HumanEntity> getViewers();
++
++    org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation
++
++    void setMaxStackSize(int size);
++
++    org.bukkit.Location getLocation();
++
++    int MAX_STACK = 99;
++    // Paper end
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/RandomizableContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/RandomizableContainer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch
index a79a33da44..8467c86645 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/RandomizableContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/RandomizableContainer.java.patch
@@ -1,23 +1,23 @@
 --- a/net/minecraft/world/RandomizableContainer.java
 +++ b/net/minecraft/world/RandomizableContainer.java
-@@ -28,7 +28,7 @@
+@@ -28,7 +_,7 @@
  
      void setLootTable(@Nullable ResourceKey<LootTable> lootTable);
  
--    default void setLootTable(ResourceKey<LootTable> lootTableId, long lootTableSeed) {
-+    default void setLootTable(@Nullable ResourceKey<LootTable> lootTableId, long lootTableSeed) { // Paper - add nullable
-         this.setLootTable(lootTableId);
-         this.setLootTableSeed(lootTableSeed);
+-    default void setLootTable(ResourceKey<LootTable> lootTable, long seed) {
++    default void setLootTable(@Nullable ResourceKey<LootTable> lootTable, long seed) { // Paper - add nullable
+         this.setLootTable(lootTable);
+         this.setLootTableSeed(seed);
      }
-@@ -50,14 +50,15 @@
+@@ -50,14 +_,15 @@
  
-     default boolean tryLoadLootTable(CompoundTag nbt) {
-         if (nbt.contains("LootTable", 8)) {
--            this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))));
-+            this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation
-+            if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API
-             if (nbt.contains("LootTableSeed", 4)) {
-                 this.setLootTableSeed(nbt.getLong("LootTableSeed"));
+     default boolean tryLoadLootTable(CompoundTag tag) {
+         if (tag.contains("LootTable", 8)) {
+-            this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(tag.getString("LootTable"))));
++            this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(tag.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation
++            if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(tag); // Paper - LootTable API
+             if (tag.contains("LootTableSeed", 4)) {
+                 this.setLootTableSeed(tag.getLong("LootTableSeed"));
              } else {
                  this.setLootTableSeed(0L);
              }
@@ -27,14 +27,14 @@
          } else {
              return false;
          }
-@@ -69,26 +70,44 @@
+@@ -69,26 +_,42 @@
              return false;
          } else {
-             nbt.putString("LootTable", resourceKey.location().toString());
-+            if (this.lootableData() != null) this.lootableData().saveNbt(nbt); // Paper - LootTable API
-             long l = this.getLootTableSeed();
-             if (l != 0L) {
-                 nbt.putLong("LootTableSeed", l);
+             tag.putString("LootTable", lootTable.location().toString());
++            if (this.lootableData() != null) this.lootableData().saveNbt(tag); // Paper - LootTable API
+             long lootTableSeed = this.getLootTableSeed();
+             if (lootTableSeed != 0L) {
+                 tag.putLong("LootTableSeed", lootTableSeed);
              }
  
 -            return true;
@@ -50,10 +50,10 @@
 +        // Paper end - LootTable API
          Level level = this.getLevel();
          BlockPos blockPos = this.getBlockPos();
-         ResourceKey<LootTable> resourceKey = this.getLootTable();
--        if (resourceKey != null && level != null && level.getServer() != null) {
+         ResourceKey<LootTable> lootTable = this.getLootTable();
+-        if (lootTable != null && level != null && level.getServer() != null) {
 +        // Paper start - LootTable API
-+        lootReplenish: if (resourceKey != null && level != null && level.getServer() != null) {
++        lootReplenish: if (lootTable != null && level != null && level.getServer() != null) {
 +            if (this.lootableData() != null && !this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
 +                if (forceClearLootTable) {
 +                    this.setLootTable(null);
@@ -61,27 +61,25 @@
 +                break lootReplenish;
 +            }
 +            // Paper end - LootTable API
-             LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(resourceKey);
+             LootTable lootTable1 = level.getServer().reloadableRegistries().getLootTable(lootTable);
              if (player instanceof ServerPlayer) {
-                 CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, resourceKey);
+                 CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, lootTable);
              }
  
--            this.setLootTable(null);
-+            // Paper start - LootTable API
-+            if (forceClearLootTable || this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
-+                this.setLootTable(null);
-+            }
-+            // Paper end - LootTable API
++            if (forceClearLootTable || this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) { // Paper - LootTable API
+             this.setLootTable(null);
++            } // Paper - LootTable API
              LootParams.Builder builder = new LootParams.Builder((ServerLevel)level).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos));
              if (player != null) {
                  builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
-@@ -97,4 +116,16 @@
-             lootTable.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed());
+@@ -97,4 +_,17 @@
+             lootTable1.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed());
          }
      }
 +
 +    // Paper start - LootTable API
-+    @Nullable @org.jetbrains.annotations.Contract(pure = true)
++    @Nullable
++    @org.jetbrains.annotations.Contract(pure = true)
 +    default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
 +        return null; // some containers don't really have a "replenish" ability like decorated pots
 +    }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/SimpleContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
similarity index 68%
rename from paper-server/patches/unapplied/net/minecraft/world/SimpleContainer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
index ff3587753c..314fec3e3b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/SimpleContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
@@ -1,24 +1,11 @@
 --- a/net/minecraft/world/SimpleContainer.java
 +++ b/net/minecraft/world/SimpleContainer.java
-@@ -14,18 +14,98 @@
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
- public class SimpleContainer implements Container, StackedContentsCompatible {
- 
-     private final int size;
-     public final NonNullList<ItemStack> items;
+@@ -19,7 +_,84 @@
      @Nullable
      private List<ContainerListener> listeners;
-+
-+    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
+ 
++    // Paper start - add fields and methods
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +    protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation
 +
@@ -26,15 +13,15 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(CraftHumanEntity who) {
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.add(who);
 +    }
 +
-+    public void onClose(CraftHumanEntity who) {
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
@@ -55,9 +42,9 @@
 +        // Paper end - Add missing InventoryHolders
 +        return this.bukkitOwner;
 +    }
- 
++
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        // Paper start - Fix inventories returning null Locations
 +        // When the block inventory does not have a tile state that implements getLocation, e. g. composters
 +        if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) {
@@ -77,27 +64,24 @@
 +            this.items.set(slot, original.items.get(slot).copy());
 +        }
 +    }
++    // Paper end
 +
      public SimpleContainer(int size) {
--        this.size = size;
--        this.items = NonNullList.withSize(size, ItemStack.EMPTY);
 +        this(size, null);
 +    }
++
 +    // Paper start - Add missing InventoryHolders
 +    private @Nullable java.util.function.Supplier<? extends org.bukkit.inventory.InventoryHolder> bukkitOwnerCreator;
++
 +    public SimpleContainer(java.util.function.Supplier<? extends org.bukkit.inventory.InventoryHolder> bukkitOwnerCreator, int size) {
 +        this(size);
 +        this.bukkitOwnerCreator = bukkitOwnerCreator;
-     }
-+    // Paper end - Add missing InventoryHolders
- 
-+    public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) {
-+        this.bukkitOwner = owner;
-+        // CraftBukkit end
-+        this.size = i;
-+        this.items = NonNullList.withSize(i, ItemStack.EMPTY);
 +    }
++    // Paper end - Add missing InventoryHolders
 +
-     public SimpleContainer(ItemStack... items) {
-         this.size = items.length;
-         this.items = NonNullList.of(ItemStack.EMPTY, items);
++    public SimpleContainer(int size, org.bukkit.inventory.InventoryHolder owner) {
++        this.bukkitOwner = owner;
++        // Paper end
+         this.size = size;
+         this.items = NonNullList.withSize(size, ItemStack.EMPTY);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
index 215b78b19d..46c558cd76 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
@@ -18,17 +18,17 @@
          }
      }
 @@ -122,12 +_,14 @@
-     public void addAdditionalSaveData(CompoundTag compound) {
-         super.addAdditionalSaveData(compound);
-         compound.putInt("Age", this.age);
-+        compound.putBoolean("AgeLocked", this.ageLocked); // Paper
+     public void addAdditionalSaveData(CompoundTag tag) {
+         super.addAdditionalSaveData(tag);
+         tag.putInt("Age", this.age);
++        tag.putBoolean("AgeLocked", this.ageLocked); // Paper
      }
  
      @Override
-     public void readAdditionalSaveData(CompoundTag compound) {
-         super.readAdditionalSaveData(compound);
-         this.setAge(compound.getInt("Age"));
-+        this.ageLocked = compound.getBoolean("AgeLocked"); // Paper
+     public void readAdditionalSaveData(CompoundTag tag) {
+         super.readAdditionalSaveData(tag);
+         this.setAge(tag.getInt("Age"));
++        this.ageLocked = tag.getBoolean("AgeLocked"); // Paper
      }
  
      @Nullable
@@ -67,18 +67,18 @@
      private void ageUp() {
          if (this.level() instanceof ServerLevel serverLevel) {
 -            this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> {
-+            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> { // CraftBukkit
++            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> { // Paper
                  mob.finalizeSpawn(serverLevel, this.level().getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, null);
                  mob.setPersistenceRequired();
                  mob.fudgePositionAfterSizeChange(this.getDimensions(this.getPose()));
                  this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
 -            });
-+            // CraftBukkit start
++            // Paper start
 +            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS);
 +            if (converted == null) {
 +                this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled
 +            }
-+            // CraftBukkit end
++            // Paper end
          }
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/CompoundContainer.java.patch
deleted file mode 100644
index 1a360b99ad..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/CompoundContainer.java.patch
+++ /dev/null
@@ -1,74 +0,0 @@
---- a/net/minecraft/world/CompoundContainer.java
-+++ b/net/minecraft/world/CompoundContainer.java
-@@ -3,11 +3,62 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.List;
-+import org.bukkit.Location;
-+
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
- public class CompoundContainer implements Container {
- 
-     public final Container container1;
-     public final Container container2;
- 
-+    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
-+
-+    public List<ItemStack> getContents() {
-+        List<ItemStack> result = new ArrayList<ItemStack>(this.getContainerSize());
-+        for (int i = 0; i < this.getContainerSize(); i++) {
-+            result.add(this.getItem(i));
-+        }
-+        return result;
-+    }
-+
-+    public void onOpen(CraftHumanEntity who) {
-+        this.container1.onOpen(who);
-+        this.container2.onOpen(who);
-+        this.transaction.add(who);
-+    }
-+
-+    public void onClose(CraftHumanEntity who) {
-+        this.container1.onClose(who);
-+        this.container2.onClose(who);
-+        this.transaction.remove(who);
-+    }
-+
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    public org.bukkit.inventory.InventoryHolder getOwner() {
-+        return null; // This method won't be called since CraftInventoryDoubleChest doesn't defer to here
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.container1.setMaxStackSize(size);
-+        this.container2.setMaxStackSize(size);
-+    }
-+
-+    @Override
-+    public Location getLocation() {
-+        return this.container1.getLocation(); // TODO: right?
-+    }
-+    // CraftBukkit end
-+
-     public CompoundContainer(Container first, Container second) {
-         this.container1 = first;
-         this.container2 = second;
-@@ -54,7 +105,7 @@
- 
-     @Override
-     public int getMaxStackSize() {
--        return this.container1.getMaxStackSize();
-+        return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // CraftBukkit - check both sides
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/Container.java.patch b/paper-server/patches/unapplied/net/minecraft/world/Container.java.patch
deleted file mode 100644
index cc552af92d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/Container.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/world/Container.java
-+++ b/net/minecraft/world/Container.java
-@@ -6,8 +6,12 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
-+// CraftBukkit start
-+import net.minecraft.world.item.crafting.RecipeHolder;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.entity.BlockEntity;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+// CraftBukkit end
- 
- public interface Container extends Clearable {
- 
-@@ -25,9 +29,7 @@
- 
-     void setItem(int slot, ItemStack stack);
- 
--    default int getMaxStackSize() {
--        return 99;
--    }
-+    int getMaxStackSize(); // CraftBukkit
- 
-     default int getMaxStackSize(ItemStack stack) {
-         return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
-@@ -91,4 +93,22 @@
- 
-         return world == null ? false : (world.getBlockEntity(blockposition) != blockEntity ? false : player.canInteractWithBlock(blockposition, (double) range));
-     }
-+
-+    // CraftBukkit start
-+    java.util.List<ItemStack> getContents();
-+
-+    void onOpen(CraftHumanEntity who);
-+
-+    void onClose(CraftHumanEntity who);
-+
-+    java.util.List<org.bukkit.entity.HumanEntity> getViewers();
-+
-+    org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation
-+
-+    void setMaxStackSize(int size);
-+
-+    org.bukkit.Location getLocation();
-+
-+    int MAX_STACK = 99;
-+    // CraftBukkit end
- }

From 3ef33943115d1d6dd08e97b8beb20da2edca3d41 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 19:25:16 +0100
Subject: [PATCH 017/285] More mobs

---
 .../entity/animal/axolotl/Axolotl.java.patch  |  36 +++
 .../entity/monster/breeze/Breeze.java.patch   |  11 +-
 .../monster/piglin/AbstractPiglin.java.patch  |  19 ++
 .../entity/monster/piglin/Piglin.java.patch   | 119 ++++++++++
 .../entity/monster/piglin/PiglinAi.java.patch | 174 +++++++++++++++
 .../entity/animal/axolotl/Axolotl.java.patch  |  52 -----
 .../monster/piglin/AbstractPiglin.java.patch  |  20 --
 .../entity/monster/piglin/Piglin.java.patch   | 140 ------------
 .../entity/monster/piglin/PiglinAi.java.patch | 210 ------------------
 9 files changed, 349 insertions(+), 432 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/breeze/Breeze.java.patch (64%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
new file mode 100644
index 0000000000..302c9dd462
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java
++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+@@ -226,7 +_,7 @@
+ 
+     @Override
+     public int getMaxAirSupply() {
+-        return 6000;
++        return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+     }
+ 
+     @Override
+@@ -426,10 +_,10 @@
+         if (effect == null || effect.endsWithin(2399)) {
+             int i = effect != null ? effect.getDuration() : 0;
+             int min = Math.min(2400, 100 + i);
+-            player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, min, 0), this);
++            player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, min, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // CraftBukkit
+         }
+ 
+-        player.removeEffect(MobEffects.DIG_SLOWDOWN);
++        player.removeEffect(MobEffects.DIG_SLOWDOWN, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // Paper - Add missing effect cause
+     }
+ 
+     @Override
+@@ -620,4 +_,11 @@
+             return Util.getRandom(variants, random);
+         }
+     }
++
++    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++    @Override
++    public int getDefaultMaxAirSupply() {
++        return Axolotl.AXOLOTL_TOTAL_AIR_SUPPLY;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/breeze/Breeze.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/breeze/Breeze.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch
index b3c69c8fbb..b0ccae8fcd 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/breeze/Breeze.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/breeze/Breeze.java.patch
@@ -1,15 +1,6 @@
 --- a/net/minecraft/world/entity/monster/breeze/Breeze.java
 +++ b/net/minecraft/world/entity/monster/breeze/Breeze.java
-@@ -77,7 +77,7 @@
- 
-     @Override
-     public Brain<Breeze> getBrain() {
--        return super.getBrain();
-+        return (Brain<Breeze>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -252,6 +252,7 @@
+@@ -250,6 +_,7 @@
  
      @Override
      public boolean canAttackType(EntityType<?> type) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
new file mode 100644
index 0000000000..d17978051b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
++++ b/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
+@@ -99,9 +_,14 @@
+     }
+ 
+     protected void finishConversion(ServerLevel serverLevel) {
+-        this.convertTo(
+-            EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), mob -> mob.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0))
++        net.minecraft.world.entity.Entity converted = this.convertTo( // Paper - Fix issues with mob conversion; reset to prevent event spam
++            EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), mob -> {mob.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));}, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED // CraftBukkit - add spawn and transform reasons
+         );
++        // Paper start - Fix issues with mob conversion; reset to prevent event spam
++        if (converted == null) {
++            this.timeInOverworld = 0;
++        }
++        // Paper end - Fix issues with mob conversion
+     }
+ 
+     public boolean isAdult() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
new file mode 100644
index 0000000000..4ba6bdac5e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
@@ -0,0 +1,119 @@
+--- a/net/minecraft/world/entity/monster/piglin/Piglin.java
++++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
+@@ -4,15 +_,6 @@
+ import com.mojang.serialization.Dynamic;
+ import java.util.List;
+ import javax.annotation.Nullable;
+-import net.minecraft.core.BlockPos;
+-import net.minecraft.nbt.CompoundTag;
+-import net.minecraft.network.syncher.EntityDataAccessor;
+-import net.minecraft.network.syncher.EntityDataSerializers;
+-import net.minecraft.network.syncher.SynchedEntityData;
+-import net.minecraft.resources.ResourceLocation;
+-import net.minecraft.server.level.ServerLevel;
+-import net.minecraft.sounds.SoundEvent;
+-import net.minecraft.sounds.SoundEvents;
+ import net.minecraft.tags.ItemTags;
+ import net.minecraft.tags.TagKey;
+ import net.minecraft.util.RandomSource;
+@@ -59,6 +_,25 @@
+ import net.minecraft.world.level.ServerLevelAccessor;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
++// CraftBukkit start
++import java.util.stream.Collectors;
++import java.util.HashSet;
++import java.util.Set;
++import net.minecraft.core.BlockPos;
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.ListTag;
++import net.minecraft.nbt.StringTag;
++import net.minecraft.nbt.Tag;
++import net.minecraft.network.syncher.EntityDataAccessor;
++import net.minecraft.network.syncher.EntityDataSerializers;
++import net.minecraft.network.syncher.SynchedEntityData;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundEvents;
++import net.minecraft.world.item.Item;
++// CraftBukkit end
+ 
+ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, InventoryCarrier {
+     private static final EntityDataAccessor<Boolean> DATA_BABY_ID = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
+@@ -122,6 +_,10 @@
+         MemoryModuleType.ATE_RECENTLY,
+         MemoryModuleType.NEAREST_REPELLENT
+     );
++    // CraftBukkit start - Custom bartering and interest list
++    public Set<Item> allowedBarterItems = new HashSet<>();
++    public Set<Item> interestItems = new HashSet<>();
++    // CraftBukkit end
+ 
+     public Piglin(EntityType<? extends AbstractPiglin> entityType, Level level) {
+         super(entityType, level);
+@@ -140,6 +_,14 @@
+         }
+ 
+         this.writeInventoryToTag(compound, this.registryAccess());
++        // CraftBukkit start
++        ListTag barterList = new ListTag();
++        this.allowedBarterItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(barterList::add);
++        compound.put("Bukkit.BarterList", barterList);
++        ListTag interestList = new ListTag();
++        this.interestItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(interestList::add);
++        compound.put("Bukkit.InterestList", interestList);
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -148,6 +_,10 @@
+         this.setBaby(compound.getBoolean("IsBaby"));
+         this.setCannotHunt(compound.getBoolean("CannotHunt"));
+         this.readInventoryFromTag(compound, this.registryAccess());
++        // CraftBukkit start
++        this.allowedBarterItems = compound.getList("Bukkit.BarterList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new));
++        this.interestItems = compound.getList("Bukkit.InterestList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new));
++        // CraftBukkit end
+     }
+ 
+     @VisibleForDebug
+@@ -325,7 +_,9 @@
+     @Override
+     protected void finishConversion(ServerLevel serverLevel) {
+         PiglinAi.cancelAdmiring(serverLevel, this);
++        this.forceDrops = true; // Paper - Add missing forceDrop toggles
+         this.inventory.removeAllItems().forEach(itemStack -> this.spawnAtLocation(serverLevel, itemStack));
++        this.forceDrops = false; // Paper - Add missing forceDrop toggles
+         super.finishConversion(serverLevel);
+     }
+ 
+@@ -400,7 +_,7 @@
+     }
+ 
+     protected void holdInOffHand(ItemStack stack) {
+-        if (stack.is(PiglinAi.BARTERING_ITEM)) {
++        if (stack.is(PiglinAi.BARTERING_ITEM) || this.allowedBarterItems.contains(stack.getItem())) { // CraftBukkit - Changes to accept custom payment items
+             this.setItemSlot(EquipmentSlot.OFFHAND, stack);
+             this.setGuaranteedDrop(EquipmentSlot.OFFHAND);
+         } else {
+@@ -425,15 +_,15 @@
+             return false;
+         } else {
+             TagKey<Item> preferredWeaponType = this.getPreferredWeaponType();
+-            boolean flag = PiglinAi.isLovedItem(newItem) || preferredWeaponType != null && newItem.is(preferredWeaponType);
+-            boolean flag1 = PiglinAi.isLovedItem(currentItem) || preferredWeaponType != null && currentItem.is(preferredWeaponType);
++            boolean flag = PiglinAi.isLovedItem(newItem, this) || preferredWeaponType != null && newItem.is(preferredWeaponType); // CraftBukkit
++            boolean flag1 = PiglinAi.isLovedItem(currentItem, this) || preferredWeaponType != null && currentItem.is(preferredWeaponType); // CraftBukkit
+             return flag && !flag1 || (flag || !flag1) && super.canReplaceCurrentItem(newItem, currentItem, slot);
+         }
+     }
+ 
+     @Override
+     protected void pickUpItem(ServerLevel level, ItemEntity entity) {
+-        this.onItemPickup(entity);
++        // this.onItemPickup(entity); // Paper - EntityPickupItemEvent fixes; call in PiglinAi#pickUpItem after EntityPickupItemEvent is fired
+         PiglinAi.pickUpItem(level, this, entity);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
new file mode 100644
index 0000000000..524c238be3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
@@ -0,0 +1,174 @@
+--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
++++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+@@ -70,6 +_,13 @@
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import java.util.stream.Collectors;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.entity.EntityRemoveEvent;
++import org.bukkit.event.entity.PiglinBarterEvent;
++// CraftBukkit end
+ 
+ public class PiglinAi {
+     public static final int REPELLENT_DETECTION_RANGE_HORIZONTAL = 8;
+@@ -328,23 +_,32 @@
+     protected static void pickUpItem(ServerLevel level, Piglin piglin, ItemEntity itemEntity) {
+         stopWalking(piglin);
+         ItemStack item;
+-        if (itemEntity.getItem().is(Items.GOLD_NUGGET)) {
++        // CraftBukkit start
++        // Paper start - EntityPickupItemEvent fixes; fix event firing twice
++        if (itemEntity.getItem().is(Items.GOLD_NUGGET)/* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()*/) { // Paper
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) return;
++            piglin.onItemPickup(itemEntity); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification
++            // Paper end
+             piglin.take(itemEntity, itemEntity.getItem().getCount());
+             item = itemEntity.getItem();
+-            itemEntity.discard();
+-        } else {
++            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
++        } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, itemEntity.getItem().getCount() - 1, false).isCancelled()) {
++            piglin.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; moved from Piglin#pickUpItem - call prior to item entity modification
+             piglin.take(itemEntity, 1);
+             item = removeOneItemFromItemEntity(itemEntity);
++        } else {
++            return;
++            // CraftBukkit end
+         }
+ 
+-        if (isLovedItem(item)) {
++        if (isLovedItem(item, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
+             piglin.getBrain().eraseMemory(MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM);
+             holdInOffhand(level, piglin, item);
+             admireGoldItem(piglin);
+         } else if (isFood(item) && !hasEatenRecently(piglin)) {
+             eat(piglin);
+         } else {
+-            boolean flag = !piglin.equipItemIfPossible(level, item).equals(ItemStack.EMPTY);
++            boolean flag = !piglin.equipItemIfPossible(level, item, null).equals(ItemStack.EMPTY); // CraftBukkit // Paper - pass null item entity to prevent duplicate pickup item event call - called above.
+             if (!flag) {
+                 putInInventory(piglin, item);
+             }
+@@ -353,7 +_,9 @@
+ 
+     private static void holdInOffhand(ServerLevel level, Piglin piglin, ItemStack stack) {
+         if (isHoldingItemInOffHand(piglin)) {
++            piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
+             piglin.spawnAtLocation(level, piglin.getItemInHand(InteractionHand.OFF_HAND));
++            piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
+         }
+ 
+         piglin.holdInOffHand(stack);
+@@ -363,7 +_,7 @@
+         ItemStack item = itemEntity.getItem();
+         ItemStack itemStack = item.split(1);
+         if (item.isEmpty()) {
+-            itemEntity.discard();
++            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+         } else {
+             itemEntity.setItem(item);
+         }
+@@ -375,9 +_,14 @@
+         ItemStack itemInHand = piglin.getItemInHand(InteractionHand.OFF_HAND);
+         piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
+         if (piglin.isAdult()) {
+-            boolean isBarterCurrency = isBarterCurrency(itemInHand);
++            boolean isBarterCurrency = isBarterCurrency(itemInHand, piglin); // CraftBukkit - Changes to allow custom payment for bartering
+             if (barter && isBarterCurrency) {
+-                throwItems(piglin, getBarterResponseItems(piglin));
++                // CraftBukkit start
++                PiglinBarterEvent event = CraftEventFactory.callPiglinBarterEvent(piglin, getBarterResponseItems(piglin), itemInHand);
++                if (!event.isCancelled()) {
++                    throwItems(piglin, event.getOutcome().stream().map(CraftItemStack::asNMSCopy).collect(Collectors.toList()));
++                }
++                // CraftBukkit end
+             } else if (!isBarterCurrency) {
+                 boolean flag = !piglin.equipItemIfPossible(level, itemInHand).isEmpty();
+                 if (!flag) {
+@@ -388,7 +_,7 @@
+             boolean isBarterCurrency = !piglin.equipItemIfPossible(level, itemInHand).isEmpty();
+             if (!isBarterCurrency) {
+                 ItemStack mainHandItem = piglin.getMainHandItem();
+-                if (isLovedItem(mainHandItem)) {
++                if (isLovedItem(mainHandItem, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
+                     putInInventory(piglin, mainHandItem);
+                 } else {
+                     throwItems(piglin, Collections.singletonList(mainHandItem));
+@@ -401,7 +_,9 @@
+ 
+     protected static void cancelAdmiring(ServerLevel level, Piglin piglin) {
+         if (isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) {
++            piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
+             piglin.spawnAtLocation(level, piglin.getOffhandItem());
++            piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
+             piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
+         }
+     }
+@@ -457,7 +_,7 @@
+             return false;
+         } else if (isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) {
+             return false;
+-        } else if (isBarterCurrency(stack)) {
++        } else if (isBarterCurrency(stack, piglin)) { // CraftBukkit
+             return isNotHoldingLovedItemInOffHand(piglin);
+         } else {
+             boolean canAddToInventory = piglin.canAddToInventory(stack);
+@@ -466,11 +_,16 @@
+             } else if (isFood(stack)) {
+                 return !hasEatenRecently(piglin) && canAddToInventory;
+             } else {
+-                return !isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : isNotHoldingLovedItemInOffHand(piglin) && canAddToInventory;
++                return !isLovedItem(stack, piglin) ? piglin.canReplaceCurrentItem(stack) : isNotHoldingLovedItemInOffHand(piglin) && canAddToInventory; // Paper - upstream missed isLovedItem check
+             }
+         }
+     }
+ 
++    // CraftBukkit start - Added method to allow checking for custom payment items
++    protected static boolean isLovedItem(ItemStack item, Piglin piglin) {
++        return PiglinAi.isLovedItem(item) || (piglin.interestItems.contains(item.getItem()) || piglin.allowedBarterItems.contains(item.getItem()));
++    }
++    // CraftBukkit end
+     protected static boolean isLovedItem(ItemStack item) {
+         return item.is(ItemTags.PIGLIN_LOVED);
+     }
+@@ -522,6 +_,7 @@
+     }
+ 
+     public static void angerNearbyPiglins(ServerLevel level, Player player, boolean requireLineOfSight) {
++        if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests
+         List<Piglin> entitiesOfClass = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0));
+         entitiesOfClass.stream().filter(PiglinAi::isIdle).filter(piglin -> !requireLineOfSight || BehaviorUtils.canSee(piglin, player)).forEach(piglin -> {
+             if (level.getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER)) {
+@@ -546,7 +_,7 @@
+     }
+ 
+     protected static boolean canAdmire(Piglin piglin, ItemStack stack) {
+-        return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack);
++        return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack, piglin); // CraftBukkit
+     }
+ 
+     protected static void wasHurtBy(ServerLevel level, Piglin piglin, LivingEntity entity) {
+@@ -794,6 +_,11 @@
+         return piglin.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_ITEM);
+     }
+ 
++    // CraftBukkit start - Changes to allow custom payment for bartering
++    private static boolean isBarterCurrency(ItemStack item, Piglin piglin) {
++        return PiglinAi.isBarterCurrency(item) || piglin.allowedBarterItems.contains(item.getItem());
++    }
++    // CraftBukkit end
+     private static boolean isBarterCurrency(ItemStack stack) {
+         return stack.is(BARTERING_ITEM);
+     }
+@@ -831,7 +_,7 @@
+     }
+ 
+     private static boolean isNotHoldingLovedItemInOffHand(Piglin piglin) {
+-        return piglin.getOffhandItem().isEmpty() || !isLovedItem(piglin.getOffhandItem());
++        return piglin.getOffhandItem().isEmpty() || !isLovedItem(piglin.getOffhandItem(), piglin); // CraftBukkit - Changes to allow custom payment for bartering
+     }
+ 
+     public static boolean isZombified(EntityType<?> entityType) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
deleted file mode 100644
index 3923bd7b5c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
+++ /dev/null
@@ -1,52 +0,0 @@
---- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-+++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-@@ -67,10 +67,17 @@
- 
- public class Axolotl extends Animal implements VariantHolder<Axolotl.Variant>, Bucketable {
- 
-+    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-+    @Override
-+    public int getDefaultMaxAirSupply() {
-+        return Axolotl.AXOLOTL_TOTAL_AIR_SUPPLY;
-+    }
-+    // CraftBukkit end
-     public static final int TOTAL_PLAYDEAD_TIME = 200;
-     private static final int POSE_ANIMATION_TICKS = 10;
-     protected static final ImmutableList<? extends SensorType<? extends Sensor<? super Axolotl>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_ADULT, SensorType.HURT_BY, SensorType.AXOLOTL_ATTACKABLES, SensorType.AXOLOTL_TEMPTATIONS);
--    protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT, new MemoryModuleType[]{MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.PLAY_DEAD_TICKS, MemoryModuleType.NEAREST_ATTACKABLE, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HAS_HUNTING_COOLDOWN, MemoryModuleType.IS_PANICKING});
-+    // CraftBukkit - decompile error
-+    protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.<MemoryModuleType<?>>of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT, new MemoryModuleType[]{MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.PLAY_DEAD_TICKS, MemoryModuleType.NEAREST_ATTACKABLE, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HAS_HUNTING_COOLDOWN, MemoryModuleType.IS_PANICKING});
-     private static final EntityDataAccessor<Integer> DATA_VARIANT = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.INT);
-     private static final EntityDataAccessor<Boolean> DATA_PLAYING_DEAD = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.BOOLEAN);
-     private static final EntityDataAccessor<Boolean> FROM_BUCKET = SynchedEntityData.defineId(Axolotl.class, EntityDataSerializers.BOOLEAN);
-@@ -210,7 +217,7 @@
- 
-     @Override
-     public int getMaxAirSupply() {
--        return 6000;
-+        return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-     }
- 
-     @Override
-@@ -414,10 +421,10 @@
-             int i = mobeffect != null ? mobeffect.getDuration() : 0;
-             int j = Math.min(2400, 100 + i);
- 
--            player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this);
-+            player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // CraftBukkit
-         }
- 
--        player.removeEffect(MobEffects.DIG_SLOWDOWN);
-+        player.removeEffect(MobEffects.DIG_SLOWDOWN, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // Paper - Add missing effect cause
-     }
- 
-     @Override
-@@ -464,7 +471,7 @@
- 
-     @Override
-     public Brain<Axolotl> getBrain() {
--        return super.getBrain();
-+        return (Brain<Axolotl>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
deleted file mode 100644
index 68e189739c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
-+++ b/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
-@@ -100,9 +100,15 @@
-     }
- 
-     protected void finishConversion(ServerLevel world) {
--        this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> {
-+        net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { // Paper - Fix issues with mob conversion; reset to prevent event spam
-             entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
--        });
-+        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
-+
-+        // Paper start - Fix issues with mob conversion; reset to prevent event spam
-+        if (converted == null) {
-+            this.timeInOverworld = 0;
-+        }
-+        // Paper end - Fix issues with mob conversion
-     }
- 
-     public boolean isAdult() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
deleted file mode 100644
index d238fb349f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
+++ /dev/null
@@ -1,140 +0,0 @@
---- a/net/minecraft/world/entity/monster/piglin/Piglin.java
-+++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
-@@ -4,15 +4,6 @@
- import com.mojang.serialization.Dynamic;
- import java.util.List;
- import javax.annotation.Nullable;
--import net.minecraft.core.BlockPos;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.network.syncher.EntityDataAccessor;
--import net.minecraft.network.syncher.EntityDataSerializers;
--import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.resources.ResourceLocation;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundEvents;
- import net.minecraft.tags.ItemTags;
- import net.minecraft.tags.TagKey;
- import net.minecraft.util.RandomSource;
-@@ -59,6 +50,25 @@
- import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import java.util.stream.Collectors;
-+import java.util.HashSet;
-+import java.util.Set;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.ListTag;
-+import net.minecraft.nbt.StringTag;
-+import net.minecraft.nbt.Tag;
-+import net.minecraft.network.syncher.EntityDataAccessor;
-+import net.minecraft.network.syncher.EntityDataSerializers;
-+import net.minecraft.network.syncher.SynchedEntityData;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundEvents;
-+import net.minecraft.world.item.Item;
-+// CraftBukkit end
- 
- public class Piglin extends AbstractPiglin implements CrossbowAttackMob, InventoryCarrier {
- 
-@@ -79,6 +89,10 @@
-     public boolean cannotHunt;
-     protected static final ImmutableList<SensorType<? extends Sensor<? super Piglin>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ITEMS, SensorType.HURT_BY, SensorType.PIGLIN_SPECIFIC_SENSOR);
-     protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.DOORS_TO_CLOSE, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS, MemoryModuleType.NEARBY_ADULT_PIGLINS, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, new MemoryModuleType[]{MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.PATH, MemoryModuleType.ANGRY_AT, MemoryModuleType.UNIVERSAL_ANGER, MemoryModuleType.AVOID_TARGET, MemoryModuleType.ADMIRING_ITEM, MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM, MemoryModuleType.ADMIRING_DISABLED, MemoryModuleType.DISABLE_WALK_TO_ADMIRE_ITEM, MemoryModuleType.CELEBRATE_LOCATION, MemoryModuleType.DANCING, MemoryModuleType.HUNTED_RECENTLY, MemoryModuleType.NEAREST_VISIBLE_BABY_HOGLIN, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, MemoryModuleType.RIDE_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_HUNTABLE_HOGLIN, MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD, MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM, MemoryModuleType.ATE_RECENTLY, MemoryModuleType.NEAREST_REPELLENT});
-+    // CraftBukkit start - Custom bartering and interest list
-+    public Set<Item> allowedBarterItems = new HashSet<>();
-+    public Set<Item> interestItems = new HashSet<>();
-+    // CraftBukkit end
- 
-     public Piglin(EntityType<? extends AbstractPiglin> type, Level world) {
-         super(type, world);
-@@ -97,6 +111,14 @@
-         }
- 
-         this.writeInventoryToTag(nbt, this.registryAccess());
-+        // CraftBukkit start
-+        ListTag barterList = new ListTag();
-+        this.allowedBarterItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(barterList::add);
-+        nbt.put("Bukkit.BarterList", barterList);
-+        ListTag interestList = new ListTag();
-+        this.interestItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(interestList::add);
-+        nbt.put("Bukkit.InterestList", interestList);
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -105,6 +127,10 @@
-         this.setBaby(nbt.getBoolean("IsBaby"));
-         this.setCannotHunt(nbt.getBoolean("CannotHunt"));
-         this.readInventoryFromTag(nbt, this.registryAccess());
-+        // CraftBukkit start
-+        this.allowedBarterItems = nbt.getList("Bukkit.BarterList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new));
-+        this.interestItems = nbt.getList("Bukkit.InterestList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::getValue).collect(Collectors.toCollection(HashSet::new));
-+        // CraftBukkit end
-     }
- 
-     @VisibleForDebug
-@@ -224,7 +250,7 @@
- 
-     @Override
-     public Brain<Piglin> getBrain() {
--        return super.getBrain();
-+        return (Brain<Piglin>) super.getBrain(); // CraftBukkit - Decompile error
-     }
- 
-     @Override
-@@ -300,9 +326,11 @@
-     @Override
-     protected void finishConversion(ServerLevel world) {
-         PiglinAi.cancelAdmiring(world, this);
-+        this.forceDrops = true; // Paper - Add missing forceDrop toggles
-         this.inventory.removeAllItems().forEach((itemstack) -> {
-             this.spawnAtLocation(world, itemstack);
-         });
-+        this.forceDrops = false; // Paper - Add missing forceDrop toggles
-         super.finishConversion(world);
-     }
- 
-@@ -374,7 +402,7 @@
-     }
- 
-     protected void holdInOffHand(ItemStack stack) {
--        if (stack.is(PiglinAi.BARTERING_ITEM)) {
-+        if (stack.is(PiglinAi.BARTERING_ITEM) || this.allowedBarterItems.contains(stack.getItem())) { // CraftBukkit - Changes to accept custom payment items
-             this.setItemSlot(EquipmentSlot.OFFHAND, stack);
-             this.setGuaranteedDrop(EquipmentSlot.OFFHAND);
-         } else {
-@@ -401,8 +429,8 @@
-             return false;
-         } else {
-             TagKey<Item> tagkey = this.getPreferredWeaponType();
--            boolean flag = PiglinAi.isLovedItem(newStack) || tagkey != null && newStack.is(tagkey);
--            boolean flag1 = PiglinAi.isLovedItem(currentStack) || tagkey != null && currentStack.is(tagkey);
-+            boolean flag = PiglinAi.isLovedItem(newStack, this) || tagkey != null && newStack.is(tagkey); // CraftBukkit
-+            boolean flag1 = PiglinAi.isLovedItem(currentStack, this) || tagkey != null && currentStack.is(tagkey); // CraftBukkit
- 
-             return flag && !flag1 ? true : (!flag && flag1 ? false : super.canReplaceCurrentItem(newStack, currentStack, slot));
-         }
-@@ -410,7 +438,7 @@
- 
-     @Override
-     protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) {
--        this.onItemPickup(itemEntity);
-+        // this.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; call in PiglinAi#pickUpItem after EntityPickupItemEvent is fired
-         PiglinAi.pickUpItem(world, this, itemEntity);
-     }
- 
-@@ -431,7 +459,7 @@
- 
-     @Override
-     protected SoundEvent getAmbientSound() {
--        return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse((Object) null);
-+        return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - Decompile error
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
deleted file mode 100644
index 66919128bd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
+++ /dev/null
@@ -1,210 +0,0 @@
---- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
-+++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
-@@ -71,6 +71,13 @@
- import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
- import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import java.util.stream.Collectors;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.PiglinBarterEvent;
-+// CraftBukkit end
- 
- public class PiglinAi {
- 
-@@ -166,7 +173,8 @@
-     }
- 
-     private static void initRideHoglinActivity(Brain<Piglin> brain) {
--        brain.addActivityAndRemoveMemoryWhenStopped(Activity.RIDE, 10, ImmutableList.of(Mount.create(0.8F), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 8.0F), BehaviorBuilder.sequence(BehaviorBuilder.triggerIf(Entity::isPassenger), TriggerGate.triggerOneShuffled(ImmutableList.builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(BehaviorBuilder.triggerIf((entitypiglin) -> {
-+        // CraftBukkit - decompile error
-+        brain.addActivityAndRemoveMemoryWhenStopped(Activity.RIDE, 10, ImmutableList.of(Mount.create(0.8F), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 8.0F), BehaviorBuilder.sequence(BehaviorBuilder.triggerIf(Entity::isPassenger), TriggerGate.triggerOneShuffled(ImmutableList.<Pair<? extends net.minecraft.world.entity.ai.behavior.declarative.Trigger<? super LivingEntity>, Integer>>builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(BehaviorBuilder.triggerIf((entitypiglin) -> {
-             return true;
-         }), 1)).build())), DismountOrSkipMounting.create(8, PiglinAi::wantsToStopRiding)), MemoryModuleType.RIDE_TARGET);
-     }
-@@ -176,7 +184,7 @@
-     }
- 
-     private static RunOne<LivingEntity> createIdleLookBehaviors() {
--        return new RunOne<>(ImmutableList.builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(new DoNothing(30, 60), 1)).build());
-+        return new RunOne<>(ImmutableList.<Pair<? extends BehaviorControl<? super LivingEntity>, Integer>>builder().addAll(PiglinAi.createLookBehaviors()).add(Pair.of(new DoNothing(30, 60), 1)).build()); // CraftBukkit - decompile error
-     }
- 
-     private static RunOne<Piglin> createIdleMovementBehaviors() {
-@@ -197,13 +205,13 @@
- 
-     protected static void updateActivity(Piglin piglin) {
-         Brain<Piglin> behaviorcontroller = piglin.getBrain();
--        Activity activity = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse((Object) null);
-+        Activity activity = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse(null); // CraftBukkit - decompile error
- 
-         behaviorcontroller.setActiveActivityToFirstValid(ImmutableList.of(Activity.ADMIRE_ITEM, Activity.FIGHT, Activity.AVOID, Activity.CELEBRATE, Activity.RIDE, Activity.IDLE));
--        Activity activity1 = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse((Object) null);
-+        Activity activity1 = (Activity) behaviorcontroller.getActiveNonCoreActivity().orElse(null); // CraftBukkit - decompile error
- 
-         if (activity != activity1) {
--            Optional optional = PiglinAi.getSoundForCurrentActivity(piglin);
-+            Optional<SoundEvent> optional = PiglinAi.getSoundForCurrentActivity(piglin); // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(piglin);
-             optional.ifPresent(piglin::makeSound);
-@@ -235,23 +243,32 @@
-         PiglinAi.stopWalking(piglin);
-         ItemStack itemstack;
- 
--        if (itemEntity.getItem().is(Items.GOLD_NUGGET)) {
--            piglin.take(itemEntity, itemEntity.getItem().getCount());
--            itemstack = itemEntity.getItem();
--            itemEntity.discard();
--        } else {
-+        // CraftBukkit start
-+        // Paper start - EntityPickupItemEvent fixes; fix event firing twice
-+        if (itemEntity.getItem().is(Items.GOLD_NUGGET)/* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()*/) { // Paper
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) return;
-+            piglin.onItemPickup(itemEntity); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification
-+            // Paper end
-+            piglin.take(itemEntity, itemEntity.getItem().getCount());
-+            itemstack = itemEntity.getItem();
-+            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-+        } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, itemEntity.getItem().getCount() - 1, false).isCancelled()) {
-+            piglin.onItemPickup(itemEntity); // Paper - EntityPickupItemEvent fixes; moved from Piglin#pickUpItem - call prior to item entity modification
-             piglin.take(itemEntity, 1);
-             itemstack = PiglinAi.removeOneItemFromItemEntity(itemEntity);
-+        } else {
-+            return;
-         }
-+        // CraftBukkit end
- 
--        if (PiglinAi.isLovedItem(itemstack)) {
-+        if (PiglinAi.isLovedItem(itemstack, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
-             piglin.getBrain().eraseMemory(MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM);
-             PiglinAi.holdInOffhand(world, piglin, itemstack);
-             PiglinAi.admireGoldItem(piglin);
-         } else if (PiglinAi.isFood(itemstack) && !PiglinAi.hasEatenRecently(piglin)) {
-             PiglinAi.eat(piglin);
-         } else {
--            boolean flag = !piglin.equipItemIfPossible(world, itemstack).equals(ItemStack.EMPTY);
-+            boolean flag = !piglin.equipItemIfPossible(world, itemstack, null).equals(ItemStack.EMPTY); // CraftBukkit // Paper - pass null item entity to prevent duplicate pickup item event call - called above.
- 
-             if (!flag) {
-                 PiglinAi.putInInventory(piglin, itemstack);
-@@ -261,7 +278,9 @@
- 
-     private static void holdInOffhand(ServerLevel world, Piglin piglin, ItemStack stack) {
-         if (PiglinAi.isHoldingItemInOffHand(piglin)) {
-+            piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
-             piglin.spawnAtLocation(world, piglin.getItemInHand(InteractionHand.OFF_HAND));
-+            piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
-         }
- 
-         piglin.holdInOffHand(stack);
-@@ -272,7 +291,7 @@
-         ItemStack itemstack1 = itemstack.split(1);
- 
-         if (itemstack.isEmpty()) {
--            stack.discard();
-+            stack.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-         } else {
-             stack.setItem(itemstack);
-         }
-@@ -287,9 +306,14 @@
-         boolean flag1;
- 
-         if (piglin.isAdult()) {
--            flag1 = PiglinAi.isBarterCurrency(itemstack);
-+            flag1 = PiglinAi.isBarterCurrency(itemstack, piglin); // CraftBukkit - Changes to allow custom payment for bartering
-             if (barter && flag1) {
--                PiglinAi.throwItems(piglin, PiglinAi.getBarterResponseItems(piglin));
-+                // CraftBukkit start
-+                PiglinBarterEvent event = CraftEventFactory.callPiglinBarterEvent(piglin, PiglinAi.getBarterResponseItems(piglin), itemstack);
-+                if (!event.isCancelled()) {
-+                    PiglinAi.throwItems(piglin, event.getOutcome().stream().map(CraftItemStack::asNMSCopy).collect(Collectors.toList()));
-+                }
-+                // CraftBukkit end
-             } else if (!flag1) {
-                 boolean flag2 = !piglin.equipItemIfPossible(world, itemstack).isEmpty();
- 
-@@ -302,7 +326,7 @@
-             if (!flag1) {
-                 ItemStack itemstack1 = piglin.getMainHandItem();
- 
--                if (PiglinAi.isLovedItem(itemstack1)) {
-+                if (PiglinAi.isLovedItem(itemstack1, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
-                     PiglinAi.putInInventory(piglin, itemstack1);
-                 } else {
-                     PiglinAi.throwItems(piglin, Collections.singletonList(itemstack1));
-@@ -316,7 +340,9 @@
- 
-     protected static void cancelAdmiring(ServerLevel world, Piglin piglin) {
-         if (PiglinAi.isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) {
-+            piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
-             piglin.spawnAtLocation(world, piglin.getOffhandItem());
-+            piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
-             piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
-         }
- 
-@@ -379,15 +405,21 @@
-             return false;
-         } else if (PiglinAi.isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) {
-             return false;
--        } else if (PiglinAi.isBarterCurrency(stack)) {
-+        } else if (PiglinAi.isBarterCurrency(stack, piglin)) { // CraftBukkit
-             return PiglinAi.isNotHoldingLovedItemInOffHand(piglin);
-         } else {
-             boolean flag = piglin.canAddToInventory(stack);
- 
--            return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag));
-+            return stack.is(Items.GOLD_NUGGET) ? flag : (PiglinAi.isFood(stack) ? !PiglinAi.hasEatenRecently(piglin) && flag : (!PiglinAi.isLovedItem(stack, piglin) ? piglin.canReplaceCurrentItem(stack) : PiglinAi.isNotHoldingLovedItemInOffHand(piglin) && flag)); // Paper - upstream missed isLovedItem check
-         }
-     }
- 
-+    // CraftBukkit start - Added method to allow checking for custom payment items
-+    protected static boolean isLovedItem(ItemStack itemstack, Piglin piglin) {
-+        return PiglinAi.isLovedItem(itemstack) || (piglin.interestItems.contains(itemstack.getItem()) || piglin.allowedBarterItems.contains(itemstack.getItem()));
-+    }
-+    // CraftBukkit end
-+
-     protected static boolean isLovedItem(ItemStack stack) {
-         return stack.is(ItemTags.PIGLIN_LOVED);
-     }
-@@ -451,6 +483,7 @@
-     }
- 
-     public static void angerNearbyPiglins(ServerLevel world, Player player, boolean blockOpen) {
-+        if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests
-         List<Piglin> list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D));
- 
-         list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> {
-@@ -481,7 +514,7 @@
-     }
- 
-     protected static boolean canAdmire(Piglin piglin, ItemStack nearbyItems) {
--        return !PiglinAi.isAdmiringDisabled(piglin) && !PiglinAi.isAdmiringItem(piglin) && piglin.isAdult() && PiglinAi.isBarterCurrency(nearbyItems);
-+        return !PiglinAi.isAdmiringDisabled(piglin) && !PiglinAi.isAdmiringItem(piglin) && piglin.isAdult() && PiglinAi.isBarterCurrency(nearbyItems, piglin); // CraftBukkit
-     }
- 
-     protected static void wasHurtBy(ServerLevel world, Piglin piglin, LivingEntity attacker) {
-@@ -735,6 +768,12 @@
-         return entity.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_ITEM);
-     }
- 
-+    // CraftBukkit start - Changes to allow custom payment for bartering
-+    private static boolean isBarterCurrency(ItemStack itemstack, Piglin piglin) {
-+        return PiglinAi.isBarterCurrency(itemstack) || piglin.allowedBarterItems.contains(itemstack.getItem());
-+    }
-+    // CraftBukkit end
-+
-     private static boolean isBarterCurrency(ItemStack stack) {
-         return stack.is(PiglinAi.BARTERING_ITEM);
-     }
-@@ -772,7 +811,7 @@
-     }
- 
-     private static boolean isNotHoldingLovedItemInOffHand(Piglin piglin) {
--        return piglin.getOffhandItem().isEmpty() || !PiglinAi.isLovedItem(piglin.getOffhandItem());
-+        return piglin.getOffhandItem().isEmpty() || !PiglinAi.isLovedItem(piglin.getOffhandItem(), piglin); // CraftBukkit - Changes to allow custom payment for bartering
-     }
- 
-     public static boolean isZombified(EntityType<?> entityType) {

From 0135513d3d8f9049e4bc5f20eaef085bda1b8d00 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 20:01:24 +0100
Subject: [PATCH 018/285] More mobs

---
 .../entity/animal/frog/Tadpole.java.patch     |  22 ++--
 .../horse/AbstractChestedHorse.java.patch     |   8 +-
 .../animal/horse/AbstractHorse.java.patch     | 115 +++++++++---------
 .../entity/animal/horse/Llama.java.patch      |  16 +--
 .../animal/horse/SkeletonHorse.java.patch     |  11 ++
 .../animal/horse/SkeletonTrapGoal.java.patch  |  48 ++++++++
 .../animal/horse/TraderLlama.java.patch       |  20 +++
 .../entity/monster/piglin/Piglin.java.patch   |  25 ++--
 .../animal/horse/SkeletonHorse.java.patch     |  21 ----
 .../animal/horse/SkeletonTrapGoal.java.patch  |  49 --------
 .../animal/horse/TraderLlama.java.patch       |  30 -----
 11 files changed, 166 insertions(+), 199 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch (68%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/horse/Llama.java.patch (80%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
index 46c558cd76..215b78b19d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
@@ -18,17 +18,17 @@
          }
      }
 @@ -122,12 +_,14 @@
-     public void addAdditionalSaveData(CompoundTag tag) {
-         super.addAdditionalSaveData(tag);
-         tag.putInt("Age", this.age);
-+        tag.putBoolean("AgeLocked", this.ageLocked); // Paper
+     public void addAdditionalSaveData(CompoundTag compound) {
+         super.addAdditionalSaveData(compound);
+         compound.putInt("Age", this.age);
++        compound.putBoolean("AgeLocked", this.ageLocked); // Paper
      }
  
      @Override
-     public void readAdditionalSaveData(CompoundTag tag) {
-         super.readAdditionalSaveData(tag);
-         this.setAge(tag.getInt("Age"));
-+        this.ageLocked = tag.getBoolean("AgeLocked"); // Paper
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+         this.setAge(compound.getInt("Age"));
++        this.ageLocked = compound.getBoolean("AgeLocked"); // Paper
      }
  
      @Nullable
@@ -67,18 +67,18 @@
      private void ageUp() {
          if (this.level() instanceof ServerLevel serverLevel) {
 -            this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> {
-+            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> { // Paper
++            Frog converted = this.convertTo(EntityType.FROG, ConversionParams.single(this, false, false), mob -> { // CraftBukkit
                  mob.finalizeSpawn(serverLevel, this.level().getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, null);
                  mob.setPersistenceRequired();
                  mob.fudgePositionAfterSizeChange(this.getDimensions(this.getPose()));
                  this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
 -            });
-+            // Paper start
++            // CraftBukkit start
 +            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS);
 +            if (converted == null) {
 +                this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled
 +            }
-+            // Paper end
++            // CraftBukkit end
          }
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
index 78095235f3..4eadfd7df1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
 +++ b/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
-@@ -69,9 +69,16 @@
-         super.dropEquipment(world);
+@@ -69,9 +_,16 @@
+         super.dropEquipment(level);
          if (this.hasChest()) {
-             this.spawnAtLocation(world, Blocks.CHEST);
+             this.spawnAtLocation(level, Blocks.CHEST);
 +            //this.setChest(false); // Paper - moved to post death logic
 +        }
 +    }
@@ -16,4 +16,4 @@
 +    // Paper end
  
      @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
+     public void addAdditionalSaveData(CompoundTag compound) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
similarity index 68%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
index e975349f91..180a6dcee4 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java
 +++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-@@ -79,6 +79,17 @@
+@@ -77,6 +_,17 @@
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.ticks.ContainerSingleItem;
  
@@ -16,13 +16,12 @@
 +// CraftBukkit end
 +
  public abstract class AbstractHorse extends Animal implements ContainerListener, HasCustomInventoryScreen, OwnableEntity, PlayerRideableJumping, Saddleable {
- 
      public static final int EQUIPMENT_SLOT_OFFSET = 400;
-@@ -166,8 +177,54 @@
-         @Override
+     public static final int CHEST_SLOT_OFFSET = 499;
+@@ -145,7 +_,53 @@
          public boolean stillValid(Player player) {
-             return player.getVehicle() == AbstractHorse.this || player.canInteractWithEntity((Entity) AbstractHorse.this, 4.0D);
-+        }
+             return player.getVehicle() == AbstractHorse.this || player.canInteractWithEntity(AbstractHorse.this, 4.0);
+         }
 +
 +        // CraftBukkit start - add fields and methods
 +        public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
@@ -66,14 +65,14 @@
 +        @Override
 +        public Location getLocation() {
 +            return AbstractHorse.this.getBukkitEntity().getLocation();
-         }
++        }
 +        // CraftBukkit end
      };
 +    public int maxDomestication = 100; // CraftBukkit - store max domestication value
  
-     protected AbstractHorse(EntityType<? extends AbstractHorse> type, Level world) {
-         super(type, world);
-@@ -312,7 +369,7 @@
+     protected AbstractHorse(EntityType<? extends AbstractHorse> entityType, Level level) {
+         super(entityType, level);
+@@ -284,7 +_,7 @@
      }
  
      @Override
@@ -82,16 +81,16 @@
          return !this.isVehicle();
      }
  
-@@ -366,7 +423,7 @@
-     public void createInventory() {
-         SimpleContainer inventorysubcontainer = this.inventory;
+@@ -340,7 +_,7 @@
  
+     protected void createInventory() {
+         SimpleContainer simpleContainer = this.inventory;
 -        this.inventory = new SimpleContainer(this.getInventorySize());
 +        this.inventory = new SimpleContainer(this.getInventorySize(), (org.bukkit.entity.AbstractHorse) this.getBukkitEntity()); // CraftBukkit
-         if (inventorysubcontainer != null) {
-             inventorysubcontainer.removeListener(this);
-             int i = Math.min(inventorysubcontainer.getContainerSize(), this.inventory.getContainerSize());
-@@ -470,7 +527,7 @@
+         if (simpleContainer != null) {
+             simpleContainer.removeListener(this);
+             int min = Math.min(simpleContainer.getContainerSize(), this.inventory.getContainerSize());
+@@ -448,7 +_,7 @@
      }
  
      public int getMaxTemper() {
@@ -100,21 +99,21 @@
      }
  
      @Override
-@@ -528,7 +585,7 @@
-             b0 = 5;
+@@ -503,7 +_,7 @@
+             i1 = 5;
              if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) {
                  flag = true;
 -                this.setInLove(player);
-+                this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying
++                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
              }
-         } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) {
+         } else if (stack.is(Items.GOLDEN_APPLE) || stack.is(Items.ENCHANTED_GOLDEN_APPLE)) {
              f = 10.0F;
-@@ -536,12 +593,12 @@
-             b0 = 10;
+@@ -511,12 +_,12 @@
+             i1 = 10;
              if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) {
                  flag = true;
 -                this.setInLove(player);
-+                this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying
++                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
              }
          }
  
@@ -124,34 +123,33 @@
              flag = true;
          }
  
-@@ -618,7 +675,7 @@
-         if (world instanceof ServerLevel worldserver) {
-             if (this.isAlive()) {
-                 if (this.random.nextInt(900) == 0 && this.deathTime == 0) {
--                    this.heal(1.0F);
-+                    this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
-                 }
- 
-                 if (this.canEatGrass()) {
-@@ -720,7 +777,16 @@
+@@ -587,7 +_,7 @@
+         super.aiStep();
+         if (this.level() instanceof ServerLevel serverLevel && this.isAlive()) {
+             if (this.random.nextInt(900) == 0 && this.deathTime == 0) {
+-                this.heal(1.0F);
++                this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
              }
-         }
  
-+    }
-+
+             if (this.canEatGrass()) {
+@@ -690,6 +_,15 @@
+         }
+     }
+ 
 +    // Paper start - Horse API
 +    public void setMouthOpen(boolean open) {
 +        this.setFlag(FLAG_OPEN_MOUTH, open);
 +    }
 +    public boolean isMouthOpen() {
 +        return this.getFlag(FLAG_OPEN_MOUTH);
-     }
++    }
 +    // Paper end - Horse API
- 
++
      @Override
      public InteractionResult mobInteract(Player player, InteractionHand hand) {
-@@ -764,6 +830,11 @@
-         this.setFlag(16, eatingGrass);
+         if (this.isVehicle() || this.isBaby()) {
+@@ -727,6 +_,11 @@
+         this.setFlag(16, eating);
      }
  
 +    // Paper start - Horse API
@@ -159,40 +157,39 @@
 +        this.setFlag(FLAG_STANDING, standing);
 +    }
 +    // Paper end - Horse API
-     public void setStanding(boolean angry) {
-         if (angry) {
+     public void setStanding(boolean standing) {
+         if (standing) {
              this.setEating(false);
-@@ -883,6 +954,7 @@
+@@ -838,6 +_,7 @@
          if (this.getOwnerUUID() != null) {
-             nbt.putUUID("Owner", this.getOwnerUUID());
+             compound.putUUID("Owner", this.getOwnerUUID());
          }
-+        nbt.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
++        compound.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
  
          if (!this.inventory.getItem(0).isEmpty()) {
-             nbt.put("SaddleItem", this.inventory.getItem(0).save(this.registryAccess()));
-@@ -909,7 +981,12 @@
- 
+             compound.put("SaddleItem", this.inventory.getItem(0).save(this.registryAccess()));
+@@ -862,6 +_,11 @@
          if (uuid != null) {
              this.setOwnerUUID(uuid);
-+        }
-+        // CraftBukkit start
-+        if (nbt.contains("Bukkit.MaxDomestication")) {
-+            this.maxDomestication = nbt.getInt("Bukkit.MaxDomestication");
          }
++        // CraftBukkit start
++        if (compound.contains("Bukkit.MaxDomestication")) {
++            this.maxDomestication = compound.getInt("Bukkit.MaxDomestication");
++        }
 +        // CraftBukkit end
  
-         if (nbt.contains("SaddleItem", 10)) {
-             ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("SaddleItem")).orElse(ItemStack.EMPTY);
-@@ -1012,6 +1089,17 @@
+         if (compound.contains("SaddleItem", 10)) {
+             ItemStack itemStack = ItemStack.parse(this.registryAccess(), compound.getCompound("SaddleItem")).orElse(ItemStack.EMPTY);
+@@ -959,6 +_,17 @@
  
      @Override
-     public void handleStartJump(int height) {
+     public void handleStartJump(int jumpPower) {
 +        // CraftBukkit start
 +        float power;
-+        if (height >= 90) {
++        if (jumpPower >= 90) {
 +            power = 1.0F;
 +        } else {
-+            power = 0.4F + 0.4F * (float) height / 90.0F;
++            power = 0.4F + 0.4F * (float) jumpPower / 90.0F;
 +        }
 +        if (!CraftEventFactory.callHorseJumpEvent(this, power)) {
 +            return;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/Llama.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
similarity index 80%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/Llama.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
index df44c55d01..7de40d1411 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/Llama.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/entity/animal/horse/Llama.java
 +++ b/net/minecraft/world/entity/animal/horse/Llama.java
-@@ -71,17 +71,23 @@
+@@ -71,17 +_,23 @@
      @Nullable
      private Llama caravanHead;
      @Nullable
 -    private Llama caravanTail;
 +    public Llama caravanTail; // Paper
  
-     public Llama(EntityType<? extends Llama> type, Level world) {
-         super(type, world);
+     public Llama(EntityType<? extends Llama> entityType, Level level) {
+         super(entityType, level);
          this.getNavigation().setRequiredPathLength(40.0F);
 +        this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value
      }
@@ -18,14 +18,14 @@
      }
  
 +    // CraftBukkit start
-+    public void setStrengthPublic(int i) {
-+        this.setStrength(i);
++    public void setStrengthPublic(int strength) {
++        this.setStrength(strength);
 +    }
 +    // CraftBukkit end
      private void setStrength(int strength) {
-         this.entityData.set(Llama.DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength)));
+         this.entityData.set(DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength)));
      }
-@@ -171,12 +177,12 @@
+@@ -168,12 +_,12 @@
              f = 10.0F;
              if (this.isTamed() && this.getAge() == 0 && this.canFallInLove()) {
                  flag = true;
@@ -40,7 +40,7 @@
              flag = true;
          }
  
-@@ -289,7 +295,7 @@
+@@ -295,7 +_,7 @@
  
      @Override
      public int getMaxTemper() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
new file mode 100644
index 0000000000..cdbce96d99
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
++++ b/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
+@@ -122,7 +_,7 @@
+     public void aiStep() {
+         super.aiStep();
+         if (this.isTrap() && this.trapTime++ >= 18000) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
new file mode 100644
index 0000000000..6d15d96e13
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
++++ b/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
+@@ -18,6 +_,7 @@
+ 
+ public class SkeletonTrapGoal extends Goal {
+     private final SkeletonHorse horse;
++    private java.util.List<org.bukkit.entity.HumanEntity> eligiblePlayers; // Paper
+ 
+     public SkeletonTrapGoal(SkeletonHorse horse) {
+         this.horse = horse;
+@@ -25,12 +_,13 @@
+ 
+     @Override
+     public boolean canUse() {
+-        return this.horse.level().hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0);
++        return !(this.eligiblePlayers = this.horse.level().findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0, net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING)).isEmpty(); // Paper - Affects Spawning API & SkeletonHorseTrapEvent
+     }
+ 
+     @Override
+     public void tick() {
+         ServerLevel serverLevel = (ServerLevel)this.horse.level();
++        if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), this.eligiblePlayers).callEvent()) return; // Paper
+         DifficultyInstance currentDifficultyAt = serverLevel.getCurrentDifficultyAt(this.horse.blockPosition());
+         this.horse.setTrap(false);
+         this.horse.setTamed(true);
+@@ -39,11 +_,11 @@
+         if (lightningBolt != null) {
+             lightningBolt.moveTo(this.horse.getX(), this.horse.getY(), this.horse.getZ());
+             lightningBolt.setVisualOnly(true);
+-            serverLevel.addFreshEntity(lightningBolt);
++            serverLevel.strikeLightning(lightningBolt, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRAP); // CraftBukkit
+             Skeleton skeleton = this.createSkeleton(currentDifficultyAt, this.horse);
+             if (skeleton != null) {
+                 skeleton.startRiding(this.horse);
+-                serverLevel.addFreshEntityWithPassengers(skeleton);
++                serverLevel.addFreshEntityWithPassengers(skeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRAP); // CraftBukkit
+ 
+                 for (int i = 0; i < 3; i++) {
+                     AbstractHorse abstractHorse = this.createHorse(currentDifficultyAt);
+@@ -52,7 +_,7 @@
+                         if (skeleton1 != null) {
+                             skeleton1.startRiding(abstractHorse);
+                             abstractHorse.push(this.horse.getRandom().triangle(0.0, 1.1485), 0.0, this.horse.getRandom().triangle(0.0, 1.1485));
+-                            serverLevel.addFreshEntityWithPassengers(abstractHorse);
++                            serverLevel.addFreshEntityWithPassengers(abstractHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); // CraftBukkit
+                         }
+                     }
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
new file mode 100644
index 0000000000..83a979d52e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/animal/horse/TraderLlama.java
++++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java
+@@ -89,7 +_,7 @@
+             this.despawnDelay = this.isLeashedToWanderingTrader() ? ((WanderingTrader)this.getLeashHolder()).getDespawnDelay() - 1 : this.despawnDelay - 1;
+             if (this.despawnDelay <= 0) {
+                 this.removeLeash();
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+@@ -148,7 +_,7 @@
+ 
+         @Override
+         public void start() {
+-            this.mob.setTarget(this.ownerLastHurtBy);
++            this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit
+             Entity leashHolder = this.llama.getLeashHolder();
+             if (leashHolder instanceof WanderingTrader) {
+                 this.timestamp = ((WanderingTrader)leashHolder).getLastHurtByMobTimestamp();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
index 4ba6bdac5e..504d509667 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
@@ -1,21 +1,5 @@
 --- a/net/minecraft/world/entity/monster/piglin/Piglin.java
 +++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
-@@ -4,15 +_,6 @@
- import com.mojang.serialization.Dynamic;
- import java.util.List;
- import javax.annotation.Nullable;
--import net.minecraft.core.BlockPos;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.network.syncher.EntityDataAccessor;
--import net.minecraft.network.syncher.EntityDataSerializers;
--import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.resources.ResourceLocation;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundEvents;
- import net.minecraft.tags.ItemTags;
- import net.minecraft.tags.TagKey;
- import net.minecraft.util.RandomSource;
 @@ -59,6 +_,25 @@
  import net.minecraft.world.level.ServerLevelAccessor;
  import net.minecraft.world.level.block.Blocks;
@@ -79,7 +63,7 @@
      }
  
      @VisibleForDebug
-@@ -325,7 +_,9 @@
+@@ -325,11 +_,16 @@
      @Override
      protected void finishConversion(ServerLevel serverLevel) {
          PiglinAi.cancelAdmiring(serverLevel, this);
@@ -89,6 +73,13 @@
          super.finishConversion(serverLevel);
      }
  
++
+     private ItemStack createSpawnWeapon() {
++        // Food time for 10 minutes WOOP WOOP
++        // Please send help cat started singing
+         return this.random.nextFloat() < 0.5 ? new ItemStack(Items.CROSSBOW) : new ItemStack(Items.GOLDEN_SWORD);
+     }
+ 
 @@ -400,7 +_,7 @@
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
deleted file mode 100644
index a0ae868b36..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonHorse.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
-+++ b/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
-@@ -26,6 +26,9 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelAccessor;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class SkeletonHorse extends AbstractHorse {
- 
-@@ -122,7 +125,7 @@
-     public void aiStep() {
-         super.aiStep();
-         if (this.isTrap() && this.trapTime++ >= 18000) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
deleted file mode 100644
index 8f22bd7fd2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
-+++ b/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
-@@ -20,6 +20,7 @@
- public class SkeletonTrapGoal extends Goal {
- 
-     private final SkeletonHorse horse;
-+    private java.util.List<org.bukkit.entity.HumanEntity> eligiblePlayers; // Paper
- 
-     public SkeletonTrapGoal(SkeletonHorse skeletonHorse) {
-         this.horse = skeletonHorse;
-@@ -27,12 +28,13 @@
- 
-     @Override
-     public boolean canUse() {
--        return this.horse.level().hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D);
-+        return !(eligiblePlayers = this.horse.level().findNearbyBukkitPlayers(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D, net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING)).isEmpty(); // Paper - Affects Spawning API & SkeletonHorseTrapEvent
-     }
- 
-     @Override
-     public void tick() {
-         ServerLevel worldserver = (ServerLevel) this.horse.level();
-+        if (!new com.destroystokyo.paper.event.entity.SkeletonHorseTrapEvent((org.bukkit.entity.SkeletonHorse) this.horse.getBukkitEntity(), eligiblePlayers).callEvent()) return; // Paper
-         DifficultyInstance difficultydamagescaler = worldserver.getCurrentDifficultyAt(this.horse.blockPosition());
- 
-         this.horse.setTrap(false);
-@@ -43,12 +45,12 @@
-         if (entitylightning != null) {
-             entitylightning.moveTo(this.horse.getX(), this.horse.getY(), this.horse.getZ());
-             entitylightning.setVisualOnly(true);
--            worldserver.addFreshEntity(entitylightning);
-+            worldserver.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRAP); // CraftBukkit
-             Skeleton entityskeleton = this.createSkeleton(difficultydamagescaler, this.horse);
- 
-             if (entityskeleton != null) {
-                 entityskeleton.startRiding(this.horse);
--                worldserver.addFreshEntityWithPassengers(entityskeleton);
-+                worldserver.addFreshEntityWithPassengers(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRAP); // CraftBukkit
- 
-                 for (int i = 0; i < 3; ++i) {
-                     AbstractHorse entityhorseabstract = this.createHorse(difficultydamagescaler);
-@@ -59,7 +61,7 @@
-                         if (entityskeleton1 != null) {
-                             entityskeleton1.startRiding(entityhorseabstract);
-                             entityhorseabstract.push(this.horse.getRandom().triangle(0.0D, 1.1485D), 0.0D, this.horse.getRandom().triangle(0.0D, 1.1485D));
--                            worldserver.addFreshEntityWithPassengers(entityhorseabstract);
-+                            worldserver.addFreshEntityWithPassengers(entityhorseabstract, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); // CraftBukkit
-                         }
-                     }
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
deleted file mode 100644
index 1edf3cd1bd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/entity/animal/horse/TraderLlama.java
-+++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java
-@@ -21,6 +21,9 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.ServerLevelAccessor;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class TraderLlama extends Llama {
- 
-@@ -94,7 +97,7 @@
-             this.despawnDelay = this.isLeashedToWanderingTrader() ? ((WanderingTrader) this.getLeashHolder()).getDespawnDelay() - 1 : this.despawnDelay - 1;
-             if (this.despawnDelay <= 0) {
-                 this.removeLeash();
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             }
- 
-         }
-@@ -160,7 +163,7 @@
- 
-         @Override
-         public void start() {
--            this.mob.setTarget(this.ownerLastHurtBy);
-+            this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit
-             Entity entity = this.llama.getLeashHolder();
- 
-             if (entity instanceof WanderingTrader) {

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 019/285] 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);

From b95e27be6ca637b2c42e4f14ca53d42dd99e5355 Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Fri, 13 Dec 2024 20:16:59 +0100
Subject: [PATCH 020/285] skip setupMacheSource on patch changes to save some
 time

---
 paper-server/build.gradle.kts | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 7a8c6d0fbf..22cdfb4fcc 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -1,4 +1,5 @@
 import io.papermc.paperweight.attribute.DevBundleOutput
+import io.papermc.paperweight.core.ext
 import io.papermc.paperweight.util.*
 import java.time.Instant
 
@@ -49,6 +50,12 @@ tasks.generateDevelopmentBundle {
     )
 }
 
+// TODO remove me again, this is just to not run setupMacheSource when patches change
+tasks.setupMacheSources {
+    paperPatches.setFrom(project.ext.paper.sourcePatchDir.dir("com"), project.ext.paper.featurePatchDir)
+    // paperPatches.from(project.ext.paper.sourcePatchDir, project.ext.paper.featurePatchDir)
+}
+
 abstract class Services {
     @get:Inject
     abstract val softwareComponentFactory: SoftwareComponentFactory

From 4091c6ac4da4eb8a8aaa1be86fb575478fc09eb0 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Fri, 13 Dec 2024 20:17:05 +0100
Subject: [PATCH 021/285] Follow up on block entities

---
 .../world/level/block/entity/BellBlockEntity.java.patch       | 2 +-
 .../block/entity/ChiseledBookShelfBlockEntity.java.patch      | 2 +-
 .../world/level/block/entity/HopperBlockEntity.java.patch     | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
index 1cc3771a2f..0f09f0acaf 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
@@ -35,7 +35,7 @@
      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();
++        final List<org.bukkit.entity.LivingEntity> inRangeRaiders = raiders.stream().filter(raider -> isRaiderWithinRange(pos, raider)).map(e -> (org.bukkit.entity.LivingEntity) 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
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index bb3e767def..746ad85ba8 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -24,7 +24,7 @@
 +    }
 +
 +    @Override
-+    public List<org.bukkit.entity.HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
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
index 9f296844d6..20e268ca9c 100644
--- 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
@@ -76,7 +76,7 @@
 +
 +                        org.bukkit.inventory.Inventory destinationInventory;
 +                        // Have to special case large chests as they work oddly
-+                        if (attachedContainer instanceof final CompoundContainer compoundContainer) {
++                        if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) {
 +                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
 +                        } else if (attachedContainer.getOwner() != null) {
 +                            destinationInventory = attachedContainer.getOwner().getInventory();
@@ -140,7 +140,7 @@
 +
 +            org.bukkit.inventory.Inventory sourceInventory;
 +            // Have to special case large chests as they work oddly
-+            if (container instanceof final CompoundContainer compoundContainer) {
++            if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) {
 +                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
 +            } else if (container.getOwner() != null) {
 +                sourceInventory = container.getOwner().getInventory();

From c3d5f253fedf91cb9d1bd9413618ec7730a99a61 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 20:30:07 +0100
Subject: [PATCH 022/285] Moar

---
 .../util/SimpleBitStorage.java.patch          | 37 ++++------
 .../net/minecraft/util/SpawnUtil.java.patch   | 70 +++++++++++++++++++
 .../net/minecraft/util/StringUtil.java.patch  |  8 +--
 .../minecraft/util/TickThrottler.java.patch   | 26 +++----
 .../minecraft/util/ZeroBitStorage.java.patch  |  2 +-
 .../DynamicGameEventListener.java.patch       | 11 +++
 .../level/gameevent/GameEvent.java.patch      | 10 +--
 .../gameevent/GameEventDispatcher.java.patch  | 40 +++++++++++
 .../minecraft/util/SortedArraySet.java.patch  | 11 ---
 .../net/minecraft/util/SpawnUtil.java.patch   | 60 ----------------
 .../DynamicGameEventListener.java.patch       | 11 ---
 .../gameevent/GameEventDispatcher.java.patch  | 39 -----------
 12 files changed, 155 insertions(+), 170 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/util/SimpleBitStorage.java.patch (58%)
 create mode 100644 paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/util/StringUtil.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/util/TickThrottler.java.patch (73%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/util/ZeroBitStorage.java.patch (98%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/gameevent/GameEvent.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/SortedArraySet.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/SpawnUtil.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/util/SimpleBitStorage.java.patch b/paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/util/SimpleBitStorage.java.patch
rename to paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch
index d3a669c38d..29822af20e 100644
--- a/paper-server/patches/unapplied/net/minecraft/util/SimpleBitStorage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/SimpleBitStorage.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/util/SimpleBitStorage.java
 +++ b/net/minecraft/util/SimpleBitStorage.java
-@@ -204,8 +204,8 @@
+@@ -204,8 +_,8 @@
      private final long mask;
      private final int size;
      private final int valuesPerLong;
@@ -10,28 +10,26 @@
 +    private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage
      private final int divideShift;
  
-     public SimpleBitStorage(int elementBits, int size, int[] data) {
-@@ -248,8 +248,8 @@
-         this.mask = (1L << elementBits) - 1L;
-         this.valuesPerLong = (char)(64 / elementBits);
+     public SimpleBitStorage(int bits, int size, int[] data) {
+@@ -248,8 +_,8 @@
+         this.mask = (1L << bits) - 1L;
+         this.valuesPerLong = (char)(64 / bits);
          int i = 3 * (this.valuesPerLong - 1);
 -        this.divideMul = MAGIC[i + 0];
 -        this.divideAdd = MAGIC[i + 1];
 +        this.divideMul = MAGIC[i + 0]; this.divideMulUnsigned = Integer.toUnsignedLong(this.divideMul); // Paper - Perf: Optimize SimpleBitStorage
 +        this.divideAdd = MAGIC[i + 1]; this.divideAddUnsigned = Integer.toUnsignedLong(this.divideAdd); // Paper - Perf: Optimize SimpleBitStorage
          this.divideShift = MAGIC[i + 2];
-         int j = (size + this.valuesPerLong - 1) / this.valuesPerLong;
+         int i1 = (size + this.valuesPerLong - 1) / this.valuesPerLong;
          if (data != null) {
-@@ -264,15 +264,15 @@
+@@ -264,15 +_,11 @@
      }
  
      private int cellIndex(int index) {
 -        long l = Integer.toUnsignedLong(this.divideMul);
--        long m = Integer.toUnsignedLong(this.divideAdd);
--        return (int)((long)index * l + m >> 32 >> this.divideShift);
-+        //long l = Integer.toUnsignedLong(this.divideMul); // Paper - Perf: Optimize SimpleBitStorage
-+        //long m = Integer.toUnsignedLong(this.divideAdd); // Paper - Perf: Optimize SimpleBitStorage
-+        return (int) (index * this.divideMulUnsigned + this.divideAddUnsigned >> 32 >> this.divideShift); // Paper - Perf: Optimize SimpleBitStorage
+-        long l1 = Integer.toUnsignedLong(this.divideAdd);
+-        return (int)(index * l + l1 >> 32 >> this.divideShift);
++        return (int)(index * this.divideMulUnsigned + this.divideAddUnsigned >> 32 >> this.divideShift); // Paper - Perf: Optimize SimpleBitStorage
      }
  
      @Override
@@ -39,12 +37,10 @@
 -        Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index);
 -        Validate.inclusiveBetween(0L, this.mask, (long)value);
 +    public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
-+        //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
-+        //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage
          int i = this.cellIndex(index);
          long l = this.data[i];
-         int j = (index - i * this.valuesPerLong) * this.bits;
-@@ -282,9 +282,9 @@
+         int i1 = (index - i * this.valuesPerLong) * this.bits;
+@@ -282,9 +_,7 @@
      }
  
      @Override
@@ -52,19 +48,16 @@
 -        Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index);
 -        Validate.inclusiveBetween(0L, this.mask, (long)value);
 +    public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
-+        //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
-+        //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage
          int i = this.cellIndex(index);
          long l = this.data[i];
-         int j = (index - i * this.valuesPerLong) * this.bits;
-@@ -292,8 +292,8 @@
+         int i1 = (index - i * this.valuesPerLong) * this.bits;
+@@ -292,8 +_,7 @@
      }
  
      @Override
 -    public int get(int index) {
 -        Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index);
 +    public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage
-+        //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
          int i = this.cellIndex(index);
          long l = this.data[i];
-         int j = (index - i * this.valuesPerLong) * this.bits;
+         int i1 = (index - i * this.valuesPerLong) * this.bits;
diff --git a/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch
new file mode 100644
index 0000000000..89419f5c21
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/util/SpawnUtil.java
++++ b/net/minecraft/util/SpawnUtil.java
+@@ -16,6 +_,7 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ 
+ public class SpawnUtil {
++
+     public static <T extends Mob> Optional<T> trySpawnMob(
+         EntityType<T> entityType,
+         EntitySpawnReason spawnReason,
+@@ -27,6 +_,24 @@
+         SpawnUtil.Strategy strategy,
+         boolean checkCollision
+     ) {
++        // CraftBukkit start
++        return trySpawnMob(entityType, spawnReason, level, pos, attempts, range, yOffset, strategy, checkCollision, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper - pre creature spawn event
++    }
++
++    public static <T extends Mob> Optional<T> trySpawnMob(
++        EntityType<T> entityType,
++        EntitySpawnReason spawnReason,
++        ServerLevel level,
++        BlockPos pos,
++        int attempts,
++        int range,
++        int yOffset,
++        SpawnUtil.Strategy strategy,
++        boolean checkCollision,
++        org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason,
++        @javax.annotation.Nullable Runnable onAbort // Paper - pre creature spawn event
++    ) {
++        // CraftBukkit end
+         BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
+ 
+         for (int i = 0; i < attempts; i++) {
+@@ -39,15 +_,32 @@
+                     !checkCollision
+                         || level.noCollision(entityType.getSpawnAABB(mutableBlockPos.getX() + 0.5, mutableBlockPos.getY(), mutableBlockPos.getZ() + 0.5))
+                 )) {
++                // Paper start - PreCreatureSpawnEvent
++                final com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
++                    io.papermc.paper.util.MCUtil.toLocation(level, pos),
++                    org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType),
++                    reason
++                );
++                if (!event.callEvent()) {
++                    if (event.shouldAbortSpawn()) {
++                        if (onAbort != null) {
++                            onAbort.run();
++                        }
++                        return Optional.empty();
++                    }
++                    break;
++                }
++                // Paper end - PreCreatureSpawnEvent
+                 T mob = (T)entityType.create(level, null, mutableBlockPos, spawnReason, false, false);
+                 if (mob != null) {
+                     if (mob.checkSpawnRules(level, spawnReason) && mob.checkSpawnObstruction(level)) {
+-                        level.addFreshEntityWithPassengers(mob);
++                        level.addFreshEntityWithPassengers(mob, reason); // CraftBukkit
++                        if (mob.isRemoved()) return Optional.empty(); // CraftBukkit
+                         mob.playAmbientSound();
+                         return Optional.of(mob);
+                     }
+ 
+-                    mob.discard();
++                    mob.discard(null); // CraftBukkit - add Bukkit remove cause
+                 }
+             }
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/util/StringUtil.java.patch b/paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/util/StringUtil.java.patch
rename to paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch
index d0f9b93e86..99b6032596 100644
--- a/paper-server/patches/unapplied/net/minecraft/util/StringUtil.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/StringUtil.java.patch
@@ -1,7 +1,7 @@
 --- a/net/minecraft/util/StringUtil.java
 +++ b/net/minecraft/util/StringUtil.java
-@@ -67,6 +67,25 @@
-         return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty();
+@@ -85,6 +_,25 @@
+         return stringBuilder.toString();
      }
  
 +    // Paper start - Username validation
@@ -23,6 +23,6 @@
 +    }
 +    // Paper end - Username validation
 +
-     public static String filterText(String string) {
-         return filterText(string, false);
+     public static boolean isWhitespace(int character) {
+         return Character.isWhitespace(character) || Character.isSpaceChar(character);
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/util/TickThrottler.java.patch b/paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/util/TickThrottler.java.patch
rename to paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch
index b829a1224e..b50d74a2be 100644
--- a/paper-server/patches/unapplied/net/minecraft/util/TickThrottler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/TickThrottler.java.patch
@@ -1,26 +1,19 @@
 --- a/net/minecraft/util/TickThrottler.java
 +++ b/net/minecraft/util/TickThrottler.java
-@@ -1,10 +1,14 @@
- package net.minecraft.util;
- 
-+// CraftBukkit start
-+import java.util.concurrent.atomic.AtomicInteger;
-+// CraftBukkit end
-+
+@@ -3,7 +_,7 @@
  public class TickThrottler {
- 
      private final int incrementStep;
      private final int threshold;
 -    private int count;
-+    private final AtomicInteger count = new AtomicInteger(); // CraftBukkit - multithreaded field
++    private final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(); // CraftBukkit - multithreaded field
  
-     public TickThrottler(int increment, int threshold) {
-         this.incrementStep = increment;
-@@ -12,17 +16,32 @@
+     public TickThrottler(int incrementStep, int threshold) {
+         this.incrementStep = incrementStep;
+@@ -11,16 +_,31 @@
      }
  
      public void increment() {
--        this.count += this.incrementStep;
+-        this.count = this.count + this.incrementStep;
 +        this.count.addAndGet(this.incrementStep); // CraftBukkit - use thread-safe field access instead
      }
  
@@ -29,18 +22,17 @@
 +        for (int val; (val = this.count.get()) > 0 && !this.count.compareAndSet(val, val - 1); ) ;
 +        /* Use thread-safe field access instead
          if (this.count > 0) {
-             --this.count;
+             this.count--;
          }
 +        */
 +        // CraftBukkit end
- 
      }
  
      public boolean isUnderThreshold() {
 -        return this.count < this.threshold;
 +        // CraftBukkit start - use thread-safe field access instead
 +        return this.count.get() < this.threshold;
-     }
++    }
 +
 +    public boolean isIncrementAndUnderThreshold() {
 +        return this.isIncrementAndUnderThreshold(this.incrementStep, this.threshold);
@@ -49,5 +41,5 @@
 +    public boolean isIncrementAndUnderThreshold(int incrementStep, int threshold) {
 +        return this.count.addAndGet(incrementStep) < threshold;
 +        // CraftBukkit end
-+    }
+     }
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/util/ZeroBitStorage.java.patch b/paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch
similarity index 98%
rename from paper-server/patches/unapplied/net/minecraft/util/ZeroBitStorage.java.patch
rename to paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch
index a9e7cdc67b..93069aec37 100644
--- a/paper-server/patches/unapplied/net/minecraft/util/ZeroBitStorage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/ZeroBitStorage.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/util/ZeroBitStorage.java
 +++ b/net/minecraft/util/ZeroBitStorage.java
-@@ -13,21 +13,21 @@
+@@ -13,21 +_,21 @@
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
new file mode 100644
index 0000000000..c0246df6bb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/gameevent/DynamicGameEventListener.java
++++ b/net/minecraft/world/level/gameevent/DynamicGameEventListener.java
+@@ -41,7 +_,7 @@
+ 
+     private static void ifChunkExists(LevelReader level, @Nullable SectionPos sectionPos, Consumer<GameEventListenerRegistry> dispatcherConsumer) {
+         if (sectionPos != null) {
+-            ChunkAccess chunk = level.getChunk(sectionPos.x(), sectionPos.z(), ChunkStatus.FULL, false);
++            ChunkAccess chunk = level.getChunkIfLoadedImmediately(sectionPos.getX(), sectionPos.getZ()); // Paper - Perf: can cause sync loads while completing a chunk, resulting in deadlock
+             if (chunk != null) {
+                 dispatcherConsumer.accept(chunk.getListenerRegistry(sectionPos.y()));
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEvent.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEvent.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch
index 508ac18f4c..be2290d7c9 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEvent.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEvent.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/gameevent/GameEvent.java
 +++ b/net/minecraft/world/level/gameevent/GameEvent.java
-@@ -85,7 +85,7 @@
+@@ -85,7 +_,7 @@
      }
  
-     private static Holder.Reference<GameEvent> register(String id, int range) {
--        return Registry.registerForHolder(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range));
-+        return io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerForHolderWithListeners(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(id), new GameEvent(range)); // Paper - run with listeners
+     private static Holder.Reference<GameEvent> register(String name, int notificationRadius) {
+-        return Registry.registerForHolder(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(name), new GameEvent(notificationRadius));
++        return io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerForHolderWithListeners(BuiltInRegistries.GAME_EVENT, ResourceLocation.withDefaultNamespace(name), new GameEvent(notificationRadius)); // Paper - run with listeners
      }
  
-     public static record Context(@Nullable Entity sourceEntity, @Nullable BlockState affectedState) {
+     public record Context(@Nullable Entity sourceEntity, @Nullable BlockState affectedState) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
new file mode 100644
index 0000000000..bcefb8669e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java
++++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java
+@@ -11,6 +_,13 @@
+ import net.minecraft.world.level.chunk.ChunkAccess;
+ import net.minecraft.world.phys.Vec3;
+ 
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.world.GenericGameEvent;
++// CraftBukkit end
++
+ public class GameEventDispatcher {
+     private final ServerLevel level;
+ 
+@@ -21,6 +_,14 @@
+     public void post(Holder<GameEvent> gameEvent, Vec3 pos, GameEvent.Context context) {
+         int notificationRadius = gameEvent.value().notificationRadius();
+         BlockPos blockPos = BlockPos.containing(pos);
++        // CraftBukkit start
++        GenericGameEvent apiEvent = new GenericGameEvent(CraftGameEvent.minecraftToBukkit(gameEvent.value()), CraftLocation.toBukkit(blockPos, this.level.getWorld()), (context.sourceEntity() == null) ? null : context.sourceEntity().getBukkitEntity(), notificationRadius, !Bukkit.isPrimaryThread());
++        this.level.getCraftServer().getPluginManager().callEvent(apiEvent);
++        if (apiEvent.isCancelled()) {
++            return;
++        }
++        notificationRadius = apiEvent.getRadius();
++        // CraftBukkit end
+         int sectionPosCoord = SectionPos.blockToSectionCoord(blockPos.getX() - notificationRadius);
+         int sectionPosCoord1 = SectionPos.blockToSectionCoord(blockPos.getY() - notificationRadius);
+         int sectionPosCoord2 = SectionPos.blockToSectionCoord(blockPos.getZ() - notificationRadius);
+@@ -39,7 +_,7 @@
+ 
+         for (int i = sectionPosCoord; i <= sectionPosCoord3; i++) {
+             for (int i1 = sectionPosCoord2; i1 <= sectionPosCoord5; i1++) {
+-                ChunkAccess chunkNow = this.level.getChunkSource().getChunkNow(i, i1);
++                ChunkAccess chunkNow = this.level.getChunkIfLoadedImmediately(i, i1); // Paper - Use getChunkIfLoadedImmediately
+                 if (chunkNow != null) {
+                     for (int i2 = sectionPosCoord1; i2 <= sectionPosCoord4; i2++) {
+                         flag |= chunkNow.getListenerRegistry(i2).visitInRangeListeners(gameEvent, pos, context, listenerVisitor);
diff --git a/paper-server/patches/unapplied/net/minecraft/util/SortedArraySet.java.patch b/paper-server/patches/unapplied/net/minecraft/util/SortedArraySet.java.patch
deleted file mode 100644
index 00af73f775..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/SortedArraySet.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/util/SortedArraySet.java
-+++ b/net/minecraft/util/SortedArraySet.java
-@@ -28,7 +28,7 @@
-     }
- 
-     public static <T extends Comparable<T>> SortedArraySet<T> create(int initialCapacity) {
--        return new SortedArraySet<>(initialCapacity, Comparator.naturalOrder());
-+        return new SortedArraySet<>(initialCapacity, Comparator.<T>naturalOrder()); // Paper - decompile fix
-     }
- 
-     public static <T> SortedArraySet<T> create(Comparator<T> comparator) {
diff --git a/paper-server/patches/unapplied/net/minecraft/util/SpawnUtil.java.patch b/paper-server/patches/unapplied/net/minecraft/util/SpawnUtil.java.patch
deleted file mode 100644
index 803fd149f1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/SpawnUtil.java.patch
+++ /dev/null
@@ -1,60 +0,0 @@
---- a/net/minecraft/util/SpawnUtil.java
-+++ b/net/minecraft/util/SpawnUtil.java
-@@ -21,24 +21,47 @@
-     public SpawnUtil() {}
- 
-     public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entityType, EntitySpawnReason reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements, boolean requireEmptySpace) {
--        BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
-+        // CraftBukkit start
-+        return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, requireEmptySpace, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper - pre creature spawn event
-+    }
- 
--        for (int l = 0; l < tries; ++l) {
--            int i1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange);
--            int j1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange);
-+    public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) {  // Paper - pre creature spawn event
-+        // CraftBukkit end
-+        BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
- 
--            blockposition_mutableblockposition.setWithOffset(pos, i1, verticalRange, j1);
--            if (world.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(world, verticalRange, blockposition_mutableblockposition, requirements) && (!requireEmptySpace || world.noCollision(entityType.getSpawnAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D)))) {
--                T t0 = (Mob) entityType.create(world, (Consumer) null, blockposition_mutableblockposition, reason, false, false);
-+        for (int l = 0; l < i; ++l) {
-+            int i1 = Mth.randomBetweenInclusive(worldserver.random, -j, j);
-+            int j1 = Mth.randomBetweenInclusive(worldserver.random, -j, j);
- 
-+            blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1);
-+            if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a) && (!flag || worldserver.noCollision(entitytypes.getSpawnAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D)))) {
-+                // Paper start - PreCreatureSpawnEvent
-+                final com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
-+                    io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition),
-+                    org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes),
-+                    reason
-+                );
-+                if (!event.callEvent()) {
-+                    if (event.shouldAbortSpawn()) {
-+                        if (onAbort != null) {
-+                            onAbort.run();
-+                        }
-+                        return Optional.empty();
-+                    }
-+                    break;
-+                }
-+                // Paper end - PreCreatureSpawnEvent
-+                T t0 = entitytypes.create(worldserver, (Consumer<T>) null, blockposition_mutableblockposition, entityspawnreason, false, false); // CraftBukkit - decompile error
-+
-                 if (t0 != null) {
--                    if (t0.checkSpawnRules(world, reason) && t0.checkSpawnObstruction(world)) {
--                        world.addFreshEntityWithPassengers(t0);
-+                    if (t0.checkSpawnRules(worldserver, entityspawnreason) && t0.checkSpawnObstruction(worldserver)) {
-+                        worldserver.addFreshEntityWithPassengers(t0, reason); // CraftBukkit
-+                        if (t0.isRemoved()) return Optional.empty(); // CraftBukkit
-                         t0.playAmbientSound();
-                         return Optional.of(t0);
-                     }
- 
--                    t0.discard();
-+                    t0.discard(null); // CraftBukkit - add Bukkit remove cause
-                 }
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
deleted file mode 100644
index bcaf4b1a37..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/DynamicGameEventListener.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/gameevent/DynamicGameEventListener.java
-+++ b/net/minecraft/world/level/gameevent/DynamicGameEventListener.java
-@@ -41,7 +41,7 @@
- 
-     private static void ifChunkExists(LevelReader world, @Nullable SectionPos sectionPos, Consumer<GameEventListenerRegistry> dispatcherConsumer) {
-         if (sectionPos != null) {
--            ChunkAccess chunkAccess = world.getChunk(sectionPos.x(), sectionPos.z(), ChunkStatus.FULL, false);
-+            ChunkAccess chunkAccess = world.getChunkIfLoadedImmediately(sectionPos.getX(), sectionPos.getZ()); // Paper - Perf: can cause sync loads while completing a chunk, resulting in deadlock
-             if (chunkAccess != null) {
-                 dispatcherConsumer.accept(chunkAccess.getListenerRegistry(sectionPos.y()));
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
deleted file mode 100644
index 25f012f615..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java
-+++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java
-@@ -11,6 +11,12 @@
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.level.chunk.LevelChunk;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.CraftGameEvent;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.world.GenericGameEvent;
-+// CraftBukkit end
- 
- public class GameEventDispatcher {
- 
-@@ -23,6 +29,14 @@
-     public void post(Holder<GameEvent> event, Vec3 emitterPos, GameEvent.Context emitter) {
-         int i = ((GameEvent) event.value()).notificationRadius();
-         BlockPos blockposition = BlockPos.containing(emitterPos);
-+        // CraftBukkit start
-+        GenericGameEvent event1 = new GenericGameEvent(CraftGameEvent.minecraftToBukkit(event.value()), CraftLocation.toBukkit(blockposition, this.level.getWorld()), (emitter.sourceEntity() == null) ? null : emitter.sourceEntity().getBukkitEntity(), i, !Bukkit.isPrimaryThread());
-+        this.level.getCraftServer().getPluginManager().callEvent(event1);
-+        if (event1.isCancelled()) {
-+            return;
-+        }
-+        i = event1.getRadius();
-+        // CraftBukkit end
-         int j = SectionPos.blockToSectionCoord(blockposition.getX() - i);
-         int k = SectionPos.blockToSectionCoord(blockposition.getY() - i);
-         int l = SectionPos.blockToSectionCoord(blockposition.getZ() - i);
-@@ -42,7 +56,7 @@
- 
-         for (int l1 = j; l1 <= i1; ++l1) {
-             for (int i2 = l; i2 <= k1; ++i2) {
--                LevelChunk chunk = this.level.getChunkSource().getChunkNow(l1, i2);
-+                LevelChunk chunk = (LevelChunk) this.level.getChunkIfLoadedImmediately(l1, i2); // Paper - Use getChunkIfLoadedImmediately
- 
-                 if (chunk != null) {
-                     for (int j2 = k; j2 <= j1; ++j2) {

From d9e895a6d4b02155b07f944fe3c1d0fa7097de55 Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Fri, 13 Dec 2024 20:44:19 +0100
Subject: [PATCH 023/285] copy over access transformers from the old repo

---
 build-data/paper.at | 712 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 712 insertions(+)

diff --git a/build-data/paper.at b/build-data/paper.at
index da400007ff..4b3b886b7f 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -1,7 +1,719 @@
 # This file is auto generated, any changes may be overridden!
 # See CONTRIBUTING.md on how to add access transformers.
+private-f net.minecraft.network.CompressionDecoder inflater
+private-f net.minecraft.server.MinecraftServer connection
+private-f net.minecraft.server.MinecraftServer levels
+private-f net.minecraft.world.item.ItemStack components
+private-f net.minecraft.world.item.ItemStack item
+private-f net.minecraft.world.level.block.entity.BeehiveBlockEntity stored
+protected net.minecraft.world.entity.LivingEntity skipDropExperience
+protected-f net.minecraft.server.MinecraftServer worldData
+public net.minecraft.ChatFormatting code
+public net.minecraft.Util onThreadException(Ljava/lang/Thread;Ljava/lang/Throwable;)V
+public net.minecraft.advancements.Advancement decorateName(Lnet/minecraft/advancements/DisplayInfo;)Lnet/minecraft/network/chat/Component;
+public net.minecraft.commands.CommandSourceStack source
+public net.minecraft.commands.arguments.DimensionArgument ERROR_INVALID_VALUE
+public net.minecraft.commands.arguments.blocks.BlockInput tag
+public net.minecraft.core.MappedRegistry validateWrite(Lnet/minecraft/resources/ResourceKey;)V
+public net.minecraft.nbt.ListTag <init>(Ljava/util/List;B)V
 public net.minecraft.nbt.TagParser readArrayTag()Lnet/minecraft/nbt/Tag;
+public net.minecraft.nbt.TagParser type(Ljava/lang/String;)Lnet/minecraft/nbt/Tag;
+public net.minecraft.network.Connection address
+public net.minecraft.network.Connection channel
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo components
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo count
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo item
+public net.minecraft.network.chat.TextColor name
+public net.minecraft.network.chat.contents.TranslatableContents filterAllowedArguments(Ljava/lang/Object;)Lcom/mojang/serialization/DataResult;
+public net.minecraft.network.chat.numbers.FixedFormat value
+public net.minecraft.network.chat.numbers.StyledFormat style
+public net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket <init>(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V
+public net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket blockState
+public net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket pos
+public net.minecraft.network.protocol.game.ClientboundTabListPacket footer
+public net.minecraft.network.protocol.game.ClientboundTabListPacket header
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket hasPos
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket hasRot
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket x
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket xRot
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket y
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket yRot
+public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket z
+public net.minecraft.network.protocol.handshake.ClientIntentionPacket port
+public net.minecraft.resources.RegistryOps lookupProvider
+public net.minecraft.resources.RegistryOps$HolderLookupAdapter
+public net.minecraft.server.Main forceUpgrade(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lcom/mojang/datafixers/DataFixer;ZLjava/util/function/BooleanSupplier;Lnet/minecraft/core/RegistryAccess;Z)V
+public net.minecraft.server.MinecraftServer LOGGER
 public net.minecraft.server.MinecraftServer doRunTask(Lnet/minecraft/server/TickTask;)V
+public net.minecraft.server.MinecraftServer executor
+public net.minecraft.server.MinecraftServer fixerUpper
+public net.minecraft.server.MinecraftServer playerDataStorage
+public net.minecraft.server.MinecraftServer prepareLevels(Lnet/minecraft/server/level/progress/ChunkProgressListener;)V
+public net.minecraft.server.MinecraftServer progressListenerFactory
+public net.minecraft.server.MinecraftServer resources
+public net.minecraft.server.MinecraftServer serverThread
+public net.minecraft.server.MinecraftServer$ReloadableResources
+public net.minecraft.server.RegistryLayer STATIC_ACCESS
+public net.minecraft.server.ReloadableServerResources
+public net.minecraft.server.ServerAdvancementManager advancements
+public net.minecraft.server.dedicated.DedicatedServerProperties$WorldDimensionData
+public net.minecraft.server.dedicated.Settings getStringRaw(Ljava/lang/String;)Ljava/lang/String;
+public net.minecraft.server.dedicated.Settings properties
+public net.minecraft.server.level.ChunkHolder oldTicketLevel
+public net.minecraft.server.level.ChunkHolder playerProvider
+public net.minecraft.server.level.ChunkLevel ENTITY_TICKING_LEVEL
+public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V
+public net.minecraft.server.level.ChunkMap anyPlayerCloseEnoughForSpawning(Lnet/minecraft/world/level/ChunkPos;)Z
+public net.minecraft.server.level.ChunkMap distanceManager
+public net.minecraft.server.level.ChunkMap entityMap
+public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder;
+public net.minecraft.server.level.ChunkMap level
+public net.minecraft.server.level.ChunkMap progressListener
+public net.minecraft.server.level.ChunkMap save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z
+public net.minecraft.server.level.ChunkMap serverViewDistance
+public net.minecraft.server.level.ChunkMap toDrop
+public net.minecraft.server.level.ChunkMap updatingChunkMap
+public net.minecraft.server.level.ChunkMap visibleChunkMap
+public net.minecraft.server.level.ChunkMap$TrackedEntity
+public net.minecraft.server.level.ChunkMap$TrackedEntity seenBy
+public net.minecraft.server.level.ChunkMap$TrackedEntity serverEntity
+public net.minecraft.server.level.DistanceManager simulationDistance
+public net.minecraft.server.level.DistanceManager tickets
+public net.minecraft.server.level.ServerBossEvent broadcast(Ljava/util/function/Function;)V
+public net.minecraft.server.level.ServerBossEvent visible
+public net.minecraft.server.level.ServerChunkCache mainThread
+public net.minecraft.server.level.ServerChunkCache mainThreadProcessor
+public net.minecraft.server.level.ServerChunkCache spawnEnemies
+public net.minecraft.server.level.ServerChunkCache spawnFriendlies
+public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor
+public net.minecraft.server.level.ServerLevel chunkSource
+public net.minecraft.server.level.ServerLevel entityManager
+public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;
+public net.minecraft.server.level.ServerLevel getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter;
+public net.minecraft.server.level.ServerLevel serverLevelData
+public net.minecraft.server.level.ServerPlayer completeUsingItem()V
+public net.minecraft.server.level.ServerPlayer containerSynchronizer
+public net.minecraft.server.level.ServerPlayer findRespawnAndUseSpawnBlock(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;FZZ)Ljava/util/Optional;
+public net.minecraft.server.level.ServerPlayer initMenu(Lnet/minecraft/world/inventory/AbstractContainerMenu;)V
+public net.minecraft.server.level.ServerPlayer isChangingDimension
+public net.minecraft.server.level.ServerPlayer language
+public net.minecraft.server.level.ServerPlayer lastSentExp
+public net.minecraft.server.level.ServerPlayer nextContainerCounter()V
+public net.minecraft.server.level.ServerPlayer particleStatus
+public net.minecraft.server.level.ServerPlayer seenCredits
+public net.minecraft.server.level.ServerPlayer triggerDimensionChangeTriggers(Lnet/minecraft/server/level/ServerLevel;)V
+public net.minecraft.server.level.ServerPlayer wardenSpawnTracker
+public net.minecraft.server.level.ServerPlayer$RespawnPosAngle
+public net.minecraft.server.level.Ticket key
+public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z
+public net.minecraft.server.network.ServerLoginPacketListenerImpl connection
+public net.minecraft.server.network.ServerLoginPacketListenerImpl state
+public net.minecraft.server.network.ServerLoginPacketListenerImpl$State
+public net.minecraft.server.packs.VanillaPackResourcesBuilder safeGetPath(Ljava/net/URI;)Ljava/nio/file/Path;
+public net.minecraft.server.packs.repository.Pack resources
+public net.minecraft.server.players.PlayerList playerIo
+public net.minecraft.server.players.PlayerList players
+public net.minecraft.server.players.PlayerList updateEntireScoreboard(Lnet/minecraft/server/ServerScoreboard;Lnet/minecraft/server/level/ServerPlayer;)V
+public net.minecraft.server.players.StoredUserEntry getUser()Ljava/lang/Object;
+public net.minecraft.stats.ServerRecipeBook known
+public net.minecraft.tags.TagEntry id
+public net.minecraft.tags.TagEntry required
+public net.minecraft.tags.TagEntry tag
+public net.minecraft.util.datafix.fixes.BlockStateData register(ILjava/lang/String;[Ljava/lang/String;)V
+public net.minecraft.util.datafix.fixes.ItemIdFix ITEM_NAMES
+public net.minecraft.util.datafix.fixes.ItemSpawnEggFix ID_TO_ENTITY
+public net.minecraft.world.BossEvent color
+public net.minecraft.world.BossEvent name
+public net.minecraft.world.BossEvent overlay
+public net.minecraft.world.CompoundContainer container1
+public net.minecraft.world.CompoundContainer container2
+public net.minecraft.world.SimpleContainer items
+public net.minecraft.world.damagesource.DamageSource <init>(Lnet/minecraft/core/Holder;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;)V
+public net.minecraft.world.effect.MobEffect attributeModifiers
+public net.minecraft.world.effect.MobEffect$AttributeTemplate
+public net.minecraft.world.effect.MobEffectInstance hiddenEffect
+public net.minecraft.world.entity.AreaEffectCloud durationOnUse
+public net.minecraft.world.entity.AreaEffectCloud ownerUUID
+public net.minecraft.world.entity.AreaEffectCloud potionContents
+public net.minecraft.world.entity.AreaEffectCloud radiusOnUse
+public net.minecraft.world.entity.AreaEffectCloud radiusPerTick
+public net.minecraft.world.entity.AreaEffectCloud reapplicationDelay
+public net.minecraft.world.entity.AreaEffectCloud updateColor()V
+public net.minecraft.world.entity.AreaEffectCloud waitTime
+public net.minecraft.world.entity.Display DATA_POS_ROT_INTERPOLATION_DURATION_ID
+public net.minecraft.world.entity.Display createTransformation(Lnet/minecraft/network/syncher/SynchedEntityData;)Lcom/mojang/math/Transformation;
+public net.minecraft.world.entity.Display getBillboardConstraints()Lnet/minecraft/world/entity/Display$BillboardConstraints;
+public net.minecraft.world.entity.Display getBrightnessOverride()Lnet/minecraft/util/Brightness;
+public net.minecraft.world.entity.Display getGlowColorOverride()I
+public net.minecraft.world.entity.Display getHeight()F
+public net.minecraft.world.entity.Display getShadowRadius()F
+public net.minecraft.world.entity.Display getShadowStrength()F
+public net.minecraft.world.entity.Display getTransformationInterpolationDelay()I
+public net.minecraft.world.entity.Display getTransformationInterpolationDuration()I
+public net.minecraft.world.entity.Display getViewRange()F
+public net.minecraft.world.entity.Display getWidth()F
+public net.minecraft.world.entity.Display setBillboardConstraints(Lnet/minecraft/world/entity/Display$BillboardConstraints;)V
+public net.minecraft.world.entity.Display setBrightnessOverride(Lnet/minecraft/util/Brightness;)V
+public net.minecraft.world.entity.Display setGlowColorOverride(I)V
+public net.minecraft.world.entity.Display setHeight(F)V
+public net.minecraft.world.entity.Display setShadowRadius(F)V
+public net.minecraft.world.entity.Display setShadowStrength(F)V
+public net.minecraft.world.entity.Display setTransformation(Lcom/mojang/math/Transformation;)V
+public net.minecraft.world.entity.Display setTransformationInterpolationDelay(I)V
+public net.minecraft.world.entity.Display setTransformationInterpolationDuration(I)V
+public net.minecraft.world.entity.Display setViewRange(F)V
+public net.minecraft.world.entity.Display setWidth(F)V
 public net.minecraft.world.entity.Display$BlockDisplay getBlockState()Lnet/minecraft/world/level/block/state/BlockState;
+public net.minecraft.world.entity.Display$BlockDisplay setBlockState(Lnet/minecraft/world/level/block/state/BlockState;)V
+public net.minecraft.world.entity.Display$ItemDisplay getItemStack()Lnet/minecraft/world/item/ItemStack;
+public net.minecraft.world.entity.Display$ItemDisplay getItemTransform()Lnet/minecraft/world/item/ItemDisplayContext;
+public net.minecraft.world.entity.Display$ItemDisplay setItemStack(Lnet/minecraft/world/item/ItemStack;)V
+public net.minecraft.world.entity.Display$ItemDisplay setItemTransform(Lnet/minecraft/world/item/ItemDisplayContext;)V
+public net.minecraft.world.entity.Display$TextDisplay DATA_BACKGROUND_COLOR_ID
+public net.minecraft.world.entity.Display$TextDisplay DATA_LINE_WIDTH_ID
+public net.minecraft.world.entity.Display$TextDisplay getBackgroundColor()I
+public net.minecraft.world.entity.Display$TextDisplay getFlags()B
+public net.minecraft.world.entity.Display$TextDisplay getLineWidth()I
 public net.minecraft.world.entity.Display$TextDisplay getText()Lnet/minecraft/network/chat/Component;
+public net.minecraft.world.entity.Display$TextDisplay getTextOpacity()B
+public net.minecraft.world.entity.Display$TextDisplay setFlags(B)V
+public net.minecraft.world.entity.Display$TextDisplay setText(Lnet/minecraft/network/chat/Component;)V
+public net.minecraft.world.entity.Display$TextDisplay setTextOpacity(B)V
+public net.minecraft.world.entity.ExperienceOrb count
+public net.minecraft.world.entity.ExperienceOrb value
+public net.minecraft.world.entity.GlowSquid setDarkTicks(I)V
+public net.minecraft.world.entity.Interaction attack
+public net.minecraft.world.entity.Interaction getHeight()F
+public net.minecraft.world.entity.Interaction getResponse()Z
+public net.minecraft.world.entity.Interaction getWidth()F
+public net.minecraft.world.entity.Interaction interaction
+public net.minecraft.world.entity.Interaction setHeight(F)V
+public net.minecraft.world.entity.Interaction setResponse(Z)V
+public net.minecraft.world.entity.Interaction setWidth(F)V
+public net.minecraft.world.entity.Interaction$PlayerAction
+public net.minecraft.world.entity.ItemBasedSteering boostTime
+public net.minecraft.world.entity.ItemBasedSteering boostTimeTotal()I
+public net.minecraft.world.entity.ItemBasedSteering boosting
+public net.minecraft.world.entity.LightningBolt flashes
+public net.minecraft.world.entity.LightningBolt life
+public net.minecraft.world.entity.LightningBolt visualOnly
+public net.minecraft.world.entity.LivingEntity DATA_ARROW_COUNT_ID
+public net.minecraft.world.entity.LivingEntity DATA_HEALTH_ID
+public net.minecraft.world.entity.LivingEntity LIVING_ENTITY_FLAG_SPIN_ATTACK
+public net.minecraft.world.entity.LivingEntity activeEffects
+public net.minecraft.world.entity.LivingEntity completeUsingItem()V
+public net.minecraft.world.entity.LivingEntity detectEquipmentUpdates()V
+public net.minecraft.world.entity.LivingEntity effectsDirty
+public net.minecraft.world.entity.LivingEntity entityEventForEquipmentBreak(Lnet/minecraft/world/entity/EquipmentSlot;)B
+public net.minecraft.world.entity.LivingEntity getDeathSound()Lnet/minecraft/sounds/SoundEvent;
+public net.minecraft.world.entity.LivingEntity getSoundVolume()F
+public net.minecraft.world.entity.LivingEntity jumping
+public net.minecraft.world.entity.LivingEntity lastHurt
+public net.minecraft.world.entity.LivingEntity lastHurtByMob
+public net.minecraft.world.entity.LivingEntity lastHurtByMobTimestamp
+public net.minecraft.world.entity.LivingEntity lastHurtByPlayer
+public net.minecraft.world.entity.LivingEntity lastHurtByPlayerTime
+public net.minecraft.world.entity.LivingEntity setLivingEntityFlag(IZ)V
+public net.minecraft.world.entity.LivingEntity useItemRemaining
+public net.minecraft.world.entity.Mob armorDropChances
+public net.minecraft.world.entity.Mob getEquipmentDropChance(Lnet/minecraft/world/entity/EquipmentSlot;)F
+public net.minecraft.world.entity.Mob handDropChances
+public net.minecraft.world.entity.Mob isSunBurnTick()Z
+public net.minecraft.world.entity.Mob lootTable
+public net.minecraft.world.entity.Mob lootTableSeed
+public net.minecraft.world.entity.OminousItemSpawner setItem(Lnet/minecraft/world/item/ItemStack;)V
+public net.minecraft.world.entity.OminousItemSpawner spawnItemAfterTicks
+public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance;
+public net.minecraft.world.entity.ai.control.MoveControl$Operation
+public net.minecraft.world.entity.ai.gossip.GossipContainer gossips
+public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips
+public net.minecraft.world.entity.ai.navigation.PathNavigation pathFinder
+public net.minecraft.world.entity.ambient.Bat targetPosition
+public net.minecraft.world.entity.animal.AbstractSchoolingFish leader
+public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize
+public net.minecraft.world.entity.animal.Animal inLove
+public net.minecraft.world.entity.animal.Animal loveCause
+public net.minecraft.world.entity.animal.Bee hivePos
+public net.minecraft.world.entity.animal.Bee isRolling()Z
+public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination
+public net.minecraft.world.entity.animal.Bee setHasNectar(Z)V
+public net.minecraft.world.entity.animal.Bee setHasStung(Z)V
+public net.minecraft.world.entity.animal.Bee setRolling(Z)V
+public net.minecraft.world.entity.animal.Bee stayOutOfHiveCountdown
+public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive
+public net.minecraft.world.entity.animal.Cat isRelaxStateOne()Z
+public net.minecraft.world.entity.animal.Cat setCollarColor(Lnet/minecraft/world/item/DyeColor;)V
+public net.minecraft.world.entity.animal.Cat setRelaxStateOne(Z)V
+public net.minecraft.world.entity.animal.Fox DATA_TRUSTED_ID_0
+public net.minecraft.world.entity.animal.Fox DATA_TRUSTED_ID_1
+public net.minecraft.world.entity.animal.Fox isDefending()Z
+public net.minecraft.world.entity.animal.Fox setDefending(Z)V
+public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V
+public net.minecraft.world.entity.animal.Fox setSleeping(Z)V
+public net.minecraft.world.entity.animal.MushroomCow stewEffects
+public net.minecraft.world.entity.animal.Ocelot isTrusting()Z
+public net.minecraft.world.entity.animal.Ocelot setTrusting(Z)V
+public net.minecraft.world.entity.animal.Panda getEatCounter()I
+public net.minecraft.world.entity.animal.Panda setEatCounter(I)V
+public net.minecraft.world.entity.animal.Pig steering
+public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks
+public net.minecraft.world.entity.animal.Rabbit registerGoals()V
+public net.minecraft.world.entity.animal.TropicalFish getPackedVariant()I
+public net.minecraft.world.entity.animal.TropicalFish setPackedVariant(I)V
+public net.minecraft.world.entity.animal.Turtle getHomePos()Lnet/minecraft/core/BlockPos;
+public net.minecraft.world.entity.animal.Turtle isGoingHome()Z
+public net.minecraft.world.entity.animal.Turtle isTravelling()Z
+public net.minecraft.world.entity.animal.Turtle setGoingHome(Z)V
+public net.minecraft.world.entity.animal.Turtle setHasEgg(Z)V
+public net.minecraft.world.entity.animal.Turtle setTravelling(Z)V
+public net.minecraft.world.entity.animal.Wolf isWet
+public net.minecraft.world.entity.animal.Wolf setCollarColor(Lnet/minecraft/world/item/DyeColor;)V
+public net.minecraft.world.entity.animal.allay.Allay canDuplicate()Z
+public net.minecraft.world.entity.animal.allay.Allay duplicateAllay()V
+public net.minecraft.world.entity.animal.allay.Allay duplicationCooldown
+public net.minecraft.world.entity.animal.allay.Allay jukeboxPos
+public net.minecraft.world.entity.animal.allay.Allay resetDuplicationCooldown()V
+public net.minecraft.world.entity.animal.frog.Tadpole age
+public net.minecraft.world.entity.animal.goat.Goat DATA_HAS_LEFT_HORN
+public net.minecraft.world.entity.animal.goat.Goat DATA_HAS_RIGHT_HORN
+public net.minecraft.world.entity.animal.horse.AbstractHorse createInventory()V
+public net.minecraft.world.entity.animal.horse.AbstractHorse inventory
+public net.minecraft.world.entity.animal.horse.Horse setVariantAndMarkings(Lnet/minecraft/world/entity/animal/horse/Variant;Lnet/minecraft/world/entity/animal/horse/Markings;)V
+public net.minecraft.world.entity.animal.horse.SkeletonHorse trapTime
+public net.minecraft.world.entity.animal.sniffer.Sniffer calculateDigPosition()Ljava/util/Optional;
+public net.minecraft.world.entity.animal.sniffer.Sniffer canDig()Z
+public net.minecraft.world.entity.animal.sniffer.Sniffer getExploredPositions()Ljava/util/stream/Stream;
+public net.minecraft.world.entity.animal.sniffer.Sniffer getState()Lnet/minecraft/world/entity/animal/sniffer/Sniffer$State;
+public net.minecraft.world.entity.animal.sniffer.Sniffer storeExploredPosition(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/entity/animal/sniffer/Sniffer;
+public net.minecraft.world.entity.boss.enderdragon.EnderDragon subEntities
+public net.minecraft.world.entity.boss.wither.WitherBoss bossEvent
+public net.minecraft.world.entity.decoration.ArmorStand bodyPose
+public net.minecraft.world.entity.decoration.ArmorStand disabledSlots
+public net.minecraft.world.entity.decoration.ArmorStand headPose
+public net.minecraft.world.entity.decoration.ArmorStand isDisabled(Lnet/minecraft/world/entity/EquipmentSlot;)Z
+public net.minecraft.world.entity.decoration.ArmorStand leftArmPose
+public net.minecraft.world.entity.decoration.ArmorStand leftLegPose
+public net.minecraft.world.entity.decoration.ArmorStand rightArmPose
+public net.minecraft.world.entity.decoration.ArmorStand rightLegPose
+public net.minecraft.world.entity.decoration.ArmorStand setMarker(Z)V
+public net.minecraft.world.entity.decoration.ArmorStand setSmall(Z)V
+public net.minecraft.world.entity.decoration.HangingEntity setDirection(Lnet/minecraft/core/Direction;)V
+public net.minecraft.world.entity.decoration.ItemFrame DATA_ITEM
+public net.minecraft.world.entity.decoration.ItemFrame DATA_ROTATION
+public net.minecraft.world.entity.decoration.ItemFrame dropChance
+public net.minecraft.world.entity.decoration.ItemFrame fixed
+public net.minecraft.world.entity.decoration.ItemFrame setDirection(Lnet/minecraft/core/Direction;)V
+public net.minecraft.world.entity.item.FallingBlockEntity <init>(Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V
+public net.minecraft.world.entity.item.FallingBlockEntity blockState
+public net.minecraft.world.entity.item.FallingBlockEntity cancelDrop
+public net.minecraft.world.entity.item.FallingBlockEntity fallDamageMax
+public net.minecraft.world.entity.item.FallingBlockEntity fallDamagePerDistance
+public net.minecraft.world.entity.item.FallingBlockEntity hurtEntities
+public net.minecraft.world.entity.item.ItemEntity age
+public net.minecraft.world.entity.item.ItemEntity health
+public net.minecraft.world.entity.item.ItemEntity pickupDelay
+public net.minecraft.world.entity.item.ItemEntity target
+public net.minecraft.world.entity.item.ItemEntity thrower
+public net.minecraft.world.entity.item.PrimedTnt explosionPower
+public net.minecraft.world.entity.item.PrimedTnt owner
+public net.minecraft.world.entity.monster.Creeper explodeCreeper()V
+public net.minecraft.world.entity.monster.Creeper explosionRadius
+public net.minecraft.world.entity.monster.Creeper maxSwell
+public net.minecraft.world.entity.monster.Creeper swell
+public net.minecraft.world.entity.monster.Drowned groundNavigation
+public net.minecraft.world.entity.monster.Drowned waterNavigation
+public net.minecraft.world.entity.monster.EnderMan teleport()Z
+public net.minecraft.world.entity.monster.EnderMan teleportTowards(Lnet/minecraft/world/entity/Entity;)Z
+public net.minecraft.world.entity.monster.Endermite life
+public net.minecraft.world.entity.monster.Evoker getWololoTarget()Lnet/minecraft/world/entity/animal/Sheep;
+public net.minecraft.world.entity.monster.Evoker setWololoTarget(Lnet/minecraft/world/entity/animal/Sheep;)V
+public net.minecraft.world.entity.monster.Guardian randomStrollGoal
+public net.minecraft.world.entity.monster.Guardian setActiveAttackTarget(I)V
+public net.minecraft.world.entity.monster.Guardian$GuardianAttackGoal
+public net.minecraft.world.entity.monster.Guardian$GuardianAttackGoal attackTime
+public net.minecraft.world.entity.monster.Phantom anchorPoint
+public net.minecraft.world.entity.monster.Pillager inventory
+public net.minecraft.world.entity.monster.Ravager attackTick
+public net.minecraft.world.entity.monster.Ravager roarTick
+public net.minecraft.world.entity.monster.Ravager stunnedTick
+public net.minecraft.world.entity.monster.Shulker DATA_COLOR_ID
+public net.minecraft.world.entity.monster.Shulker getRawPeekAmount()I
+public net.minecraft.world.entity.monster.Shulker setAttachFace(Lnet/minecraft/core/Direction;)V
+public net.minecraft.world.entity.monster.Shulker setRawPeekAmount(I)V
+public net.minecraft.world.entity.monster.Skeleton DATA_STRAY_CONVERSION_ID
+public net.minecraft.world.entity.monster.Skeleton conversionTime
+public net.minecraft.world.entity.monster.Skeleton inPowderSnowTime
+public net.minecraft.world.entity.monster.SpellcasterIllager$IllagerSpell
+public net.minecraft.world.entity.monster.Strider steering
+public net.minecraft.world.entity.monster.Vex hasLimitedLife
+public net.minecraft.world.entity.monster.Vex limitedLifeTicks
+public net.minecraft.world.entity.monster.Vindicator DOOR_BREAKING_PREDICATE
+public net.minecraft.world.entity.monster.Vindicator isJohnny
+public net.minecraft.world.entity.monster.Witch usingTime
+public net.minecraft.world.entity.monster.Zombie DATA_DROWNED_CONVERSION_ID
+public net.minecraft.world.entity.monster.Zombie DOOR_BREAKING_PREDICATE
+public net.minecraft.world.entity.monster.Zombie conversionTime
+public net.minecraft.world.entity.monster.Zombie isSunSensitive()Z
+public net.minecraft.world.entity.monster.Zombie startUnderWaterConversion(I)V
+public net.minecraft.world.entity.monster.ZombieVillager DATA_CONVERTING_ID
+public net.minecraft.world.entity.monster.ZombieVillager conversionStarter
+public net.minecraft.world.entity.monster.ZombieVillager startConverting(Ljava/util/UUID;I)V
+public net.minecraft.world.entity.monster.ZombieVillager villagerConversionTime
+public net.minecraft.world.entity.monster.hoglin.Hoglin cannotBeHunted
+public net.minecraft.world.entity.monster.hoglin.Hoglin isImmuneToZombification()Z
+public net.minecraft.world.entity.monster.hoglin.Hoglin timeInOverworld
+public net.minecraft.world.entity.monster.piglin.AbstractPiglin isImmuneToZombification()Z
+public net.minecraft.world.entity.monster.piglin.AbstractPiglin timeInOverworld
+public net.minecraft.world.entity.monster.piglin.Piglin cannotHunt
+public net.minecraft.world.entity.monster.piglin.Piglin inventory
+public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z
+public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTicks
+public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V
+public net.minecraft.world.entity.monster.warden.WardenSpawnTracker ticksSinceLastWarning
+public net.minecraft.world.entity.npc.Villager increaseMerchantCareer()V
+public net.minecraft.world.entity.npc.Villager numberOfRestocksToday
+public net.minecraft.world.entity.npc.Villager releaseAllPois()V
+public net.minecraft.world.entity.npc.Villager setUnhappy()V
+public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos;
+public net.minecraft.world.entity.player.Abilities flyingSpeed
+public net.minecraft.world.entity.player.Abilities walkingSpeed
+public net.minecraft.world.entity.player.Inventory compartments
+public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION
+public net.minecraft.world.entity.player.Player closeContainer()V
+public net.minecraft.world.entity.player.Player enchantmentSeed
+public net.minecraft.world.entity.player.Player getFireImmuneTicks()I
+public net.minecraft.world.entity.player.Player removeEntitiesOnShoulder()V
+public net.minecraft.world.entity.player.Player setShoulderEntityLeft(Lnet/minecraft/nbt/CompoundTag;)V
+public net.minecraft.world.entity.player.Player setShoulderEntityRight(Lnet/minecraft/nbt/CompoundTag;)V
+public net.minecraft.world.entity.player.Player sleepCounter
+public net.minecraft.world.entity.projectile.AbstractHurtingProjectile assignDirectionalMovement(Lnet/minecraft/world/phys/Vec3;D)V
+public net.minecraft.world.entity.projectile.Arrow NO_EFFECT_COLOR
+public net.minecraft.world.entity.projectile.Arrow getPotionContents()Lnet/minecraft/world/item/alchemy/PotionContents;
+public net.minecraft.world.entity.projectile.Arrow setPotionContents(Lnet/minecraft/world/item/alchemy/PotionContents;)V
+public net.minecraft.world.entity.projectile.Arrow updateColor()V
+public net.minecraft.world.entity.projectile.EvokerFangs warmupDelayTicks
+public net.minecraft.world.entity.projectile.EyeOfEnder life
+public net.minecraft.world.entity.projectile.EyeOfEnder surviveAfterDeath
+public net.minecraft.world.entity.projectile.EyeOfEnder tx
+public net.minecraft.world.entity.projectile.EyeOfEnder ty
+public net.minecraft.world.entity.projectile.EyeOfEnder tz
+public net.minecraft.world.entity.projectile.FireworkRocketEntity DATA_ATTACHED_TO_TARGET
+public net.minecraft.world.entity.projectile.FireworkRocketEntity DATA_ID_FIREWORKS_ITEM
+public net.minecraft.world.entity.projectile.FireworkRocketEntity DATA_SHOT_AT_ANGLE
+public net.minecraft.world.entity.projectile.FireworkRocketEntity attachedToEntity
+public net.minecraft.world.entity.projectile.FireworkRocketEntity getDefaultItem()Lnet/minecraft/world/item/ItemStack;
+public net.minecraft.world.entity.projectile.FireworkRocketEntity life
+public net.minecraft.world.entity.projectile.FireworkRocketEntity lifetime
+public net.minecraft.world.entity.projectile.FishingHook DATA_HOOKED_ENTITY
+public net.minecraft.world.entity.projectile.FishingHook calculateOpenWater(Lnet/minecraft/core/BlockPos;)Z
+public net.minecraft.world.entity.projectile.FishingHook currentState
+public net.minecraft.world.entity.projectile.FishingHook fishAngle
+public net.minecraft.world.entity.projectile.FishingHook hookedIn
+public net.minecraft.world.entity.projectile.FishingHook outOfWaterTime
+public net.minecraft.world.entity.projectile.FishingHook pullEntity(Lnet/minecraft/world/entity/Entity;)V
+public net.minecraft.world.entity.projectile.FishingHook setHookedEntity(Lnet/minecraft/world/entity/Entity;)V
+public net.minecraft.world.entity.projectile.FishingHook timeUntilHooked
+public net.minecraft.world.entity.projectile.FishingHook timeUntilLured
+public net.minecraft.world.entity.projectile.FishingHook$FishHookState
+public net.minecraft.world.entity.projectile.LargeFireball explosionPower
+public net.minecraft.world.entity.projectile.Projectile cachedOwner
+public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z
+public net.minecraft.world.entity.projectile.Projectile hasBeenShot
+public net.minecraft.world.entity.projectile.Projectile leftOwner
+public net.minecraft.world.entity.projectile.Projectile ownerUUID
+public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection
+public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ
+public net.minecraft.world.entity.projectile.SpectralArrow duration
+public net.minecraft.world.entity.projectile.ThrownPotion isLingering()Z
+public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage
+public net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge explode(Lnet/minecraft/world/phys/Vec3;)V
+public net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge explode(Lnet/minecraft/world/phys/Vec3;)V
+public net.minecraft.world.entity.projectile.windcharge.WindCharge explode(Lnet/minecraft/world/phys/Vec3;)V
+public net.minecraft.world.entity.raid.Raid heroesOfTheVillage
+public net.minecraft.world.entity.raid.Raid numGroups
+public net.minecraft.world.entity.raid.Raid raidEvent
+public net.minecraft.world.entity.raid.Raid raidOmenLevel
+public net.minecraft.world.entity.raid.Raid ticksActive
+public net.minecraft.world.entity.raid.Raid totalHealth
+public net.minecraft.world.entity.raid.Raider$HoldGroundAttackGoal
+public net.minecraft.world.entity.raid.Raids raidMap
+public net.minecraft.world.entity.vehicle.AbstractBoat getDropItem()Lnet/minecraft/world/item/Item;
+public net.minecraft.world.entity.vehicle.AbstractBoat getStatus()Lnet/minecraft/world/entity/vehicle/AbstractBoat$Status;
+public net.minecraft.world.entity.vehicle.AbstractBoat status
+public net.minecraft.world.entity.vehicle.AbstractMinecartContainer lootTable
+public net.minecraft.world.entity.vehicle.AbstractMinecartContainer lootTableSeed
+public net.minecraft.world.entity.vehicle.MinecartCommandBlock DATA_ID_COMMAND_NAME
+public net.minecraft.world.entity.vehicle.MinecartFurnace fuel
+public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V
+public net.minecraft.world.entity.vehicle.MinecartTNT explosionPowerBase
+public net.minecraft.world.entity.vehicle.MinecartTNT explosionSpeedFactor
+public net.minecraft.world.entity.vehicle.MinecartTNT fuse
+public net.minecraft.world.flag.FeatureFlag mask
+public net.minecraft.world.flag.FeatureFlag universe
+public net.minecraft.world.flag.FeatureFlagRegistry names
+public net.minecraft.world.food.FoodData exhaustionLevel
+public net.minecraft.world.food.FoodData foodLevel
+public net.minecraft.world.food.FoodData saturationLevel
+public net.minecraft.world.inventory.AbstractContainerMenu quickcraftSlots
+public net.minecraft.world.inventory.AbstractContainerMenu quickcraftStatus
+public net.minecraft.world.inventory.AbstractContainerMenu quickcraftType
+public net.minecraft.world.inventory.AbstractContainerMenu resetQuickCraft()V
+public net.minecraft.world.inventory.AbstractCraftingMenu craftSlots
+public net.minecraft.world.inventory.AbstractCraftingMenu resultSlots
+public net.minecraft.world.inventory.AnvilMenu cost
+public net.minecraft.world.inventory.AnvilMenu itemName
+public net.minecraft.world.inventory.AnvilMenu repairItemCountCost
+public net.minecraft.world.inventory.BrewingStandMenu brewingStandData
+public net.minecraft.world.inventory.CraftingMenu access
+public net.minecraft.world.inventory.DispenserMenu dispenser
+public net.minecraft.world.inventory.HorseInventoryMenu SLOT_BODY_ARMOR
+public net.minecraft.world.inventory.MerchantContainer selectionHint
+public net.minecraft.world.inventory.Slot slot
+public net.minecraft.world.item.AdventureModePredicate predicates
+public net.minecraft.world.item.BucketItem content
+public net.minecraft.world.item.CrossbowItem FIREWORK_POWER
+public net.minecraft.world.item.DebugStickItem handleInteraction(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;ZLnet/minecraft/world/item/ItemStack;)Z
+public net.minecraft.world.item.ItemCooldowns cooldowns
+public net.minecraft.world.item.ItemCooldowns tickCount
+public net.minecraft.world.item.ItemCooldowns$CooldownInstance
+public net.minecraft.world.item.ItemCooldowns$CooldownInstance endTime
+public net.minecraft.world.item.ItemStackLinkedSet TYPE_AND_TAG
+public net.minecraft.world.item.JukeboxSongPlayer song
+public net.minecraft.world.item.MapItem createNewSavedData(Lnet/minecraft/world/level/Level;IIIZZLnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/level/saveddata/maps/MapId;
+public net.minecraft.world.item.StandingAndWallBlockItem wallBlock
+public net.minecraft.world.item.component.ItemContainerContents MAX_SIZE
+public net.minecraft.world.item.component.ItemContainerContents items
+public net.minecraft.world.item.context.UseOnContext <init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/phys/BlockHitResult;)V
+public net.minecraft.world.item.crafting.RecipeManager recipes
+public net.minecraft.world.item.crafting.RecipeMap byKey
+public net.minecraft.world.item.crafting.RecipeMap byType
+public net.minecraft.world.item.enchantment.Enchantment definition
+public net.minecraft.world.item.enchantment.ItemEnchantments showInTooltip
+public net.minecraft.world.item.trading.MerchantOffer demand
+public net.minecraft.world.item.trading.MerchantOffer result
+public net.minecraft.world.item.trading.MerchantOffer specialPriceDiff
+public net.minecraft.world.item.trading.MerchantOffer uses
+public net.minecraft.world.level.BaseSpawner delay(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V
+public net.minecraft.world.level.BaseSpawner isNearPlayer(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Z
+public net.minecraft.world.level.BaseSpawner maxNearbyEntities
+public net.minecraft.world.level.BaseSpawner maxSpawnDelay
+public net.minecraft.world.level.BaseSpawner minSpawnDelay
+public net.minecraft.world.level.BaseSpawner nextSpawnData
+public net.minecraft.world.level.BaseSpawner requiredPlayerRange
+public net.minecraft.world.level.BaseSpawner setNextSpawnData(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/SpawnData;)V
+public net.minecraft.world.level.BaseSpawner spawnCount
+public net.minecraft.world.level.BaseSpawner spawnDelay
+public net.minecraft.world.level.BaseSpawner spawnPotentials
+public net.minecraft.world.level.BaseSpawner spawnRange
+public net.minecraft.world.level.GameRules$Value onChanged(Lnet/minecraft/server/MinecraftServer;)V
+public net.minecraft.world.level.Level getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter;
+public net.minecraft.world.level.Level levelData
+public net.minecraft.world.level.Level rainLevel
+public net.minecraft.world.level.Level thread
+public net.minecraft.world.level.Level thunderLevel
+public net.minecraft.world.level.NaturalSpawner SPAWNING_CATEGORIES
+public net.minecraft.world.level.StructureManager level
+public net.minecraft.world.level.biome.Biome climateSettings
+public net.minecraft.world.level.biome.Biome getTemperature(Lnet/minecraft/core/BlockPos;I)F
+public net.minecraft.world.level.biome.Biome$ClimateSettings
+public net.minecraft.world.level.block.Block popExperience(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;I)V
+public net.minecraft.world.level.block.ChestBlock isBlockedChestByBlock(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z
+public net.minecraft.world.level.block.ChiseledBookShelfBlock getHitSlot(Lnet/minecraft/world/phys/BlockHitResult;Lnet/minecraft/world/level/block/state/BlockState;)Ljava/util/OptionalInt;
+public net.minecraft.world.level.block.ChiseledBookShelfBlock getSection(F)I
+public net.minecraft.world.level.block.ComposterBlock$EmptyContainer
+public net.minecraft.world.level.block.ComposterBlock$InputContainer
+public net.minecraft.world.level.block.ComposterBlock$OutputContainer
+public net.minecraft.world.level.block.DispenserBlock dispenseFrom(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;)V
+public net.minecraft.world.level.block.DropperBlock dispenseFrom(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;)V
+public net.minecraft.world.level.block.FireBlock igniteOdds
+public net.minecraft.world.level.block.RedStoneWireBlock canSurvive(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z
+public net.minecraft.world.level.block.RedStoneWireBlock shouldSignal
+public net.minecraft.world.level.block.ShulkerBoxBlock color
+public net.minecraft.world.level.block.SoundType breakSound
+public net.minecraft.world.level.block.SoundType hitSound
+public net.minecraft.world.level.block.TurtleEggBlock decreaseEggs(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V
+public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity cookingTimer
+public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity cookingTotalTime
+public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity getTotalCookTime(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity;)I
+public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity litTimeRemaining
+public net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity recipesUsed
+public net.minecraft.world.level.block.entity.BarrelBlockEntity openersCounter
+public net.minecraft.world.level.block.entity.BarrelBlockEntity playSound(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/sounds/SoundEvent;)V
+public net.minecraft.world.level.block.entity.BarrelBlockEntity updateBlockState(Lnet/minecraft/world/level/block/state/BlockState;Z)V
+public net.minecraft.world.level.block.entity.BaseContainerBlockEntity lockKey
+public net.minecraft.world.level.block.entity.BaseContainerBlockEntity name
+public net.minecraft.world.level.block.entity.BeaconBlockEntity levels
+public net.minecraft.world.level.block.entity.BeaconBlockEntity lockKey
+public net.minecraft.world.level.block.entity.BeaconBlockEntity name
+public net.minecraft.world.level.block.entity.BeaconBlockEntity primaryPower
+public net.minecraft.world.level.block.entity.BeaconBlockEntity secondaryPower
+public net.minecraft.world.level.block.entity.BedBlockEntity color
+public net.minecraft.world.level.block.entity.BeehiveBlockEntity savedFlowerPos
+public net.minecraft.world.level.block.entity.BellBlockEntity resonating
+public net.minecraft.world.level.block.entity.BellBlockEntity resonationTicks
+public net.minecraft.world.level.block.entity.BlockEntity saveId(Lnet/minecraft/nbt/CompoundTag;)V
+public net.minecraft.world.level.block.entity.BlockEntityType validBlocks
+public net.minecraft.world.level.block.entity.BrewingStandBlockEntity brewTime
+public net.minecraft.world.level.block.entity.BrewingStandBlockEntity fuel
+public net.minecraft.world.level.block.entity.BrushableBlockEntity item
+public net.minecraft.world.level.block.entity.BrushableBlockEntity lootTable
+public net.minecraft.world.level.block.entity.BrushableBlockEntity lootTableSeed
+public net.minecraft.world.level.block.entity.CampfireBlockEntity cookingProgress
+public net.minecraft.world.level.block.entity.CampfireBlockEntity cookingTime
+public net.minecraft.world.level.block.entity.ChestBlockEntity openersCounter
+public net.minecraft.world.level.block.entity.ChestBlockEntity playSound(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/sounds/SoundEvent;)V
+public net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity lastInteractedSlot
+public net.minecraft.world.level.block.entity.ConduitBlockEntity destroyTarget
+public net.minecraft.world.level.block.entity.ConduitBlockEntity destroyTargetUUID
+public net.minecraft.world.level.block.entity.ConduitBlockEntity effectBlocks
+public net.minecraft.world.level.block.entity.ConduitBlockEntity getDestroyRangeAABB(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/AABB;
+public net.minecraft.world.level.block.entity.CrafterBlockEntity craftingTicksRemaining
+public net.minecraft.world.level.block.entity.DecoratedPotBlockEntity decorations
+public net.minecraft.world.level.block.entity.EnderChestBlockEntity openersCounter
+public net.minecraft.world.level.block.entity.HopperBlockEntity cooldownTime
+public net.minecraft.world.level.block.entity.HopperBlockEntity setCooldown(I)V
+public net.minecraft.world.level.block.entity.LecternBlockEntity bookAccess
+public net.minecraft.world.level.block.entity.LecternBlockEntity setPage(I)V
+public net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity lootTable
+public net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity lootTableSeed
+public net.minecraft.world.level.block.entity.SculkCatalystBlockEntity$CatalystListener bloom(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/util/RandomSource;)V
+public net.minecraft.world.level.block.entity.SculkSensorBlockEntity lastVibrationFrequency
+public net.minecraft.world.level.block.entity.SculkShriekerBlockEntity warningLevel
+public net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity openCount
+public net.minecraft.world.level.block.entity.SignBlockEntity playerWhoMayEdit
+public net.minecraft.world.level.block.entity.SkullBlockEntity noteBlockSound
+public net.minecraft.world.level.block.entity.SkullBlockEntity owner
+public net.minecraft.world.level.block.entity.StructureBlockEntity author
+public net.minecraft.world.level.block.entity.StructureBlockEntity ignoreEntities
+public net.minecraft.world.level.block.entity.StructureBlockEntity integrity
+public net.minecraft.world.level.block.entity.StructureBlockEntity metaData
+public net.minecraft.world.level.block.entity.StructureBlockEntity mirror
+public net.minecraft.world.level.block.entity.StructureBlockEntity mode
+public net.minecraft.world.level.block.entity.StructureBlockEntity rotation
+public net.minecraft.world.level.block.entity.StructureBlockEntity seed
+public net.minecraft.world.level.block.entity.StructureBlockEntity showAir
+public net.minecraft.world.level.block.entity.StructureBlockEntity showBoundingBox
+public net.minecraft.world.level.block.entity.StructureBlockEntity structurePos
+public net.minecraft.world.level.block.entity.StructureBlockEntity structureSize
+public net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity age
+public net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity exactTeleport
+public net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity exitPortal
+public net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity trialSpawner
+public net.minecraft.world.level.block.entity.trialspawner.TrialSpawner isOminous
+public net.minecraft.world.level.block.entity.trialspawner.TrialSpawner stateAccessor
+public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData currentMobs
+public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData detectedPlayers
+public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData nextSpawnData
+public net.minecraft.world.level.block.state.BlockBehaviour getMenuProvider(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/MenuProvider;
+public net.minecraft.world.level.block.state.BlockBehaviour hasCollision
+public net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase destroySpeed
+public net.minecraft.world.level.block.state.StateHolder PROPERTY_ENTRY_TO_STRING_FUNCTION
+public net.minecraft.world.level.block.state.properties.IntegerProperty max
+public net.minecraft.world.level.block.state.properties.IntegerProperty min
+public net.minecraft.world.level.chunk.ChunkAccess blockEntities
+public net.minecraft.world.level.chunk.ChunkAccess heightmaps
+public net.minecraft.world.level.chunk.ChunkGenerator generationSettingsGetter
+public net.minecraft.world.level.chunk.LevelChunk level
+public net.minecraft.world.level.chunk.LevelChunk loaded
+public net.minecraft.world.level.chunk.LevelChunkSection states
+public net.minecraft.world.level.chunk.PalettedContainer registry
+public net.minecraft.world.level.chunk.storage.EntityStorage entityDeserializerQueue
+public net.minecraft.world.level.chunk.storage.EntityStorage level
+public net.minecraft.world.level.chunk.storage.RegionFileStorage regionCache
+public net.minecraft.world.level.chunk.storage.SerializableChunkData BLOCK_STATE_CODEC
+public net.minecraft.world.level.dimension.end.EndDragonFight GATEWAY_COUNT
+public net.minecraft.world.level.dimension.end.EndDragonFight dragonEvent
+public net.minecraft.world.level.dimension.end.EndDragonFight dragonUUID
 public net.minecraft.world.level.dimension.end.EndDragonFight findExitPortal()Lnet/minecraft/world/level/block/state/pattern/BlockPattern$BlockPatternMatch;
+public net.minecraft.world.level.dimension.end.EndDragonFight gateways
+public net.minecraft.world.level.dimension.end.EndDragonFight level
+public net.minecraft.world.level.dimension.end.EndDragonFight portalLocation
+public net.minecraft.world.level.dimension.end.EndDragonFight previouslyKilled
+public net.minecraft.world.level.dimension.end.EndDragonFight respawnCrystals
+public net.minecraft.world.level.dimension.end.EndDragonFight respawnDragon(Ljava/util/List;)V
+public net.minecraft.world.level.dimension.end.EndDragonFight respawnStage
+public net.minecraft.world.level.dimension.end.EndDragonFight setRespawnStage(Lnet/minecraft/world/level/dimension/end/DragonRespawnAnimation;)V
+public net.minecraft.world.level.dimension.end.EndDragonFight spawnExitPortal(Z)V
+public net.minecraft.world.level.dimension.end.EndDragonFight spawnNewGateway(Lnet/minecraft/core/BlockPos;)V
+public net.minecraft.world.level.entity.PersistentEntitySectionManager ensureChunkQueuedForLoad(J)V
+public net.minecraft.world.level.entity.PersistentEntitySectionManager permanentStorage
+public net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator settings
+public net.minecraft.world.level.levelgen.SurfaceRules$Condition
+public net.minecraft.world.level.levelgen.SurfaceRules$Context
+public net.minecraft.world.level.levelgen.SurfaceRules$Context blockX
+public net.minecraft.world.level.levelgen.SurfaceRules$Context blockY
+public net.minecraft.world.level.levelgen.SurfaceRules$Context blockZ
+public net.minecraft.world.level.levelgen.SurfaceRules$Context context
+public net.minecraft.world.level.levelgen.SurfaceRules$Context randomState
+public net.minecraft.world.level.levelgen.SurfaceRules$LazyCondition
+public net.minecraft.world.level.levelgen.SurfaceRules$LazyYCondition
+public net.minecraft.world.level.levelgen.SurfaceRules$SurfaceRule
+public net.minecraft.world.level.levelgen.SurfaceRules$VerticalGradientConditionSource
+public net.minecraft.world.level.levelgen.structure.placement.StructurePlacement exclusionZone
+public net.minecraft.world.level.levelgen.structure.placement.StructurePlacement frequency
+public net.minecraft.world.level.levelgen.structure.placement.StructurePlacement frequencyReductionMethod
+public net.minecraft.world.level.levelgen.structure.placement.StructurePlacement locateOffset
+public net.minecraft.world.level.levelgen.structure.placement.StructurePlacement salt
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate entityInfoList
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate palettes
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager loadFromGenerated(Lnet/minecraft/resources/ResourceLocation;)Ljava/util/Optional;
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager loadFromResource(Lnet/minecraft/resources/ResourceLocation;)Ljava/util/Optional;
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager readStructure(Ljava/io/InputStream;)Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;
+public net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager structureRepository
+public net.minecraft.world.level.material.MapColor MATERIAL_COLORS
+public net.minecraft.world.level.pathfinder.Path nodes
+public net.minecraft.world.level.pathfinder.PathFinder nodeEvaluator
+public net.minecraft.world.level.saveddata.maps.MapItemSavedData carriedBy
+public net.minecraft.world.level.saveddata.maps.MapItemSavedData carriedByPlayers
+public net.minecraft.world.level.saveddata.maps.MapItemSavedData decorations
+public net.minecraft.world.level.saveddata.maps.MapItemSavedData setColorsDirty(II)V
+public net.minecraft.world.level.saveddata.maps.MapItemSavedData setDecorationsDirty()V
+public net.minecraft.world.level.storage.DimensionDataStorage cache
+public net.minecraft.world.level.storage.LevelStorageSource baseDir
+public net.minecraft.world.level.storage.LevelStorageSource$LevelStorageAccess levelDirectory
+public net.minecraft.world.level.storage.PrimaryLevelData settings
+public net.minecraft.world.scores.Objective displayName
+public net.minecraft.world.scores.criteria.ObjectiveCriteria CRITERIA_CACHE
+public-f net.minecraft.network.protocol.handshake.ClientIntentionPacket hostName
+public-f net.minecraft.server.MinecraftServer potionBrewing
+public-f net.minecraft.server.MinecraftServer storageSource
+public-f net.minecraft.server.ReloadableServerResources commands
+public-f net.minecraft.server.dedicated.DedicatedServer serverLinks
+public-f net.minecraft.server.dedicated.DedicatedServer settings
+public-f net.minecraft.server.level.TicketType timeout
+public-f net.minecraft.server.players.PlayerList maxPlayers
+public-f net.minecraft.world.entity.LivingEntity combatTracker
+public-f net.minecraft.world.entity.LivingEntity invulnerableDuration
+public-f net.minecraft.world.entity.Mob goalSelector
+public-f net.minecraft.world.entity.Mob targetSelector
+public-f net.minecraft.world.entity.ai.attributes.RangedAttribute maxValue
+public-f net.minecraft.world.entity.player.Player gameProfile
+public-f net.minecraft.world.inventory.AbstractContainerMenu dataSlots
+public-f net.minecraft.world.inventory.AbstractContainerMenu lastSlots
+public-f net.minecraft.world.inventory.AbstractContainerMenu remoteDataSlots
+public-f net.minecraft.world.inventory.AbstractContainerMenu remoteSlots
+public-f net.minecraft.world.inventory.AbstractContainerMenu slots
+public-f net.minecraft.world.item.enchantment.ItemEnchantments$Mutable showInTooltip
+public-f net.minecraft.world.item.trading.MerchantOffer baseCostA
+public-f net.minecraft.world.item.trading.MerchantOffer costB
+public-f net.minecraft.world.item.trading.MerchantOffer maxUses
+public-f net.minecraft.world.item.trading.MerchantOffer priceMultiplier
+public-f net.minecraft.world.item.trading.MerchantOffer rewardExp
+public-f net.minecraft.world.item.trading.MerchantOffer xp
+public-f net.minecraft.world.level.LevelSettings hardcore
+public-f net.minecraft.world.level.LevelSettings levelName
+public-f net.minecraft.world.level.block.entity.BannerBlockEntity baseColor
+public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner normalConfig
+public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner ominousConfig
+public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner requiredPlayerRange
+public-f net.minecraft.world.level.block.entity.trialspawner.TrialSpawner targetCooldownLength
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData centerX
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData centerZ
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData dimension
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData locked
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData scale
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData trackingPosition
+public-f net.minecraft.world.level.saveddata.maps.MapItemSavedData unlimitedTracking

From 2509faa08ecd38f3c84f59a54bddf0cc4bb0f17e Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Fri, 13 Dec 2024 21:03:19 +0100
Subject: [PATCH 024/285] mob spawn settings

---
 .../minecraft/world/level/biome/MobSpawnSettings.java.patch | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/biome/MobSpawnSettings.java.patch (88%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/biome/MobSpawnSettings.java.patch b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
index d1fb7a4640..eaf222a4f8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/biome/MobSpawnSettings.java
 +++ b/net/minecraft/world/level/biome/MobSpawnSettings.java
-@@ -75,8 +75,40 @@
+@@ -75,8 +_,40 @@
      }
  
      public static class Builder {
@@ -36,9 +36,9 @@
 +        }
 +        // use toImmutableEnumMap collector
          private final Map<MobCategory, List<MobSpawnSettings.SpawnerData>> spawners = Stream.of(MobCategory.values())
--            .collect(ImmutableMap.toImmutableMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> Lists.newArrayList()));
+-            .collect(ImmutableMap.toImmutableMap(key -> (MobCategory)key, value -> Lists.newArrayList()));
 +            .collect(Maps.toImmutableEnumMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> new MobList())); // Use MobList instead of ArrayList
-+        // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls
++            // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls
          private final Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap();
          private float creatureGenerationProbability = 0.1F;
  

From f73e864f1846a6b1f796481bfdf583154e11b9cc Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 21:21:48 +0100
Subject: [PATCH 025/285] Commands

---
 .../commands/CommandSource.java.patch         |   8 +-
 .../commands/CommandSourceStack.java.patch    |  47 ++-
 .../minecraft/commands/Commands.java.patch    | 312 +++++++++++++++
 .../world/CompoundContainer.java.patch        |   4 +-
 .../FlyingPathNavigation.java.patch           |   8 +-
 .../GroundPathNavigation.java.patch           |  46 +++
 .../ai/navigation/PathNavigation.java.patch   |  78 ++--
 .../WallClimberNavigation.java.patch          |  14 +
 .../animal/horse/AbstractHorse.java.patch     |   2 +-
 .../block/entity/BarrelBlockEntity.java.patch |   2 +-
 .../ChiseledBookShelfBlockEntity.java.patch   |   2 +-
 .../dimension/end/EndDragonFight.java.patch   |   6 +-
 .../minecraft/commands/Commands.java.patch    | 372 ------------------
 .../GroundPathNavigation.java.patch           |  46 ---
 .../WallClimberNavigation.java.patch          |  14 -
 15 files changed, 450 insertions(+), 511 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/CommandSource.java.patch (84%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/CommandSourceStack.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch (57%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/commands/Commands.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/commands/CommandSource.java.patch b/paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch
similarity index 84%
rename from paper-server/patches/unapplied/net/minecraft/commands/CommandSource.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch
index 4d74948c36..c541c1a047 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/CommandSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/CommandSource.java.patch
@@ -1,20 +1,20 @@
 --- a/net/minecraft/commands/CommandSource.java
 +++ b/net/minecraft/commands/CommandSource.java
-@@ -22,6 +22,13 @@
+@@ -22,6 +_,13 @@
          public boolean shouldInformAdmins() {
              return false;
          }
 +
 +        // CraftBukkit start
 +        @Override
-+        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack stack) {
 +            return io.papermc.paper.brigadier.NullCommandSender.INSTANCE; // Paper - expose a no-op CommandSender
 +        }
 +        // CraftBukkit end
      };
  
-     void sendSystemMessage(Component message);
-@@ -35,4 +42,6 @@
+     void sendSystemMessage(Component component);
+@@ -35,4 +_,6 @@
      default boolean alwaysAccepts() {
          return false;
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/CommandSourceStack.java.patch b/paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/commands/CommandSourceStack.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch
index 94ee38720d..b8593758f7 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/CommandSourceStack.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/CommandSourceStack.java.patch
@@ -1,33 +1,30 @@
 --- a/net/minecraft/commands/CommandSourceStack.java
 +++ b/net/minecraft/commands/CommandSourceStack.java
-@@ -45,9 +45,9 @@
- import net.minecraft.world.level.dimension.DimensionType;
+@@ -45,7 +_,7 @@
  import net.minecraft.world.phys.Vec2;
  import net.minecraft.world.phys.Vec3;
-+import com.mojang.brigadier.tree.CommandNode; // CraftBukkit
  
 -public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider {
--
 +public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider, io.papermc.paper.command.brigadier.PaperCommandSourceStack { // Paper - Brigadier API
      public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player"));
      public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity"));
      public final CommandSource source;
-@@ -65,6 +65,8 @@
+@@ -63,6 +_,8 @@
      private final Vec2 rotation;
      private final CommandSigningContext signingContext;
      private final TaskChainer chatMessageChainer;
-+    public java.util.Map<Thread, CommandNode> currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - Thread Safe Vanilla Command permission checking
++    public java.util.Map<Thread, com.mojang.brigadier.tree.CommandNode> currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - Thread Safe Vanilla Command permission checking
 +    public boolean bypassSelectorPermissions = false; // Paper - add bypass for selector permissions
  
-     public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) {
-         this(output, pos, rot, world, level, name, displayName, server, entity, false, CommandResultCallback.EMPTY, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(server));
-@@ -171,8 +173,43 @@
+     public CommandSourceStack(
+         CommandSource source,
+@@ -391,9 +_,44 @@
  
      @Override
      public boolean hasPermission(int level) {
 +        // CraftBukkit start
 +        // Paper start - Thread Safe Vanilla Command permission checking
-+        CommandNode currentCommand = this.currentCommand.get(Thread.currentThread());
++        com.mojang.brigadier.tree.CommandNode currentCommand = this.currentCommand.get(Thread.currentThread());
 +        if (currentCommand != null) {
 +            return this.hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand));
 +            // Paper end - Thread Safe Vanilla Command permission checking
@@ -35,8 +32,8 @@
 +        // CraftBukkit end
 +
          return this.permissionLevel >= level;
-+    }
-+
+     }
+ 
 +    // Paper start - Fix permission levels for command blocks
 +    private boolean forceRespectPermissionLevel() {
 +        return this.source == CommandSource.NULL || (this.source instanceof final net.minecraft.world.level.BaseCommandBlock commandBlock && commandBlock.getLevel().paperConfig().commandBlocks.forceFollowPermLevel);
@@ -60,27 +57,27 @@
 +        }
 +        return hasBukkitPerm.getAsBoolean();
 +        // Paper end - Fix permission levels for command blocks
-     }
++    }
 +    // CraftBukkit end
- 
++
      public Vec3 getPosition() {
          return this.worldPosition;
-@@ -302,21 +339,26 @@
-             while (iterator.hasNext()) {
-                 ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
--                if (entityplayer.commandSource() != this.source && this.server.getPlayerList().isOp(entityplayer.getGameProfile())) {
-+                if (entityplayer.commandSource() != this.source && entityplayer.getBukkitEntity().hasPermission("minecraft.admin.command_feedback")) { // CraftBukkit
-                     entityplayer.sendSystemMessage(ichatmutablecomponent);
+     }
+@@ -499,20 +_,25 @@
+         Component component = Component.translatable("chat.type.admin", this.getDisplayName(), message).withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC);
+         if (this.server.getGameRules().getBoolean(GameRules.RULE_SENDCOMMANDFEEDBACK)) {
+             for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+-                if (serverPlayer.commandSource() != this.source && this.server.getPlayerList().isOp(serverPlayer.getGameProfile())) {
++                if (serverPlayer.commandSource() != this.source && serverPlayer.getBukkitEntity().hasPermission("minecraft.admin.command_feedback")) { // CraftBukkit
+                     serverPlayer.sendSystemMessage(component);
                  }
              }
          }
  
 -        if (this.source != this.server && this.server.getGameRules().getBoolean(GameRules.RULE_LOGADMINCOMMANDS)) {
 +        if (this.source != this.server && this.server.getGameRules().getBoolean(GameRules.RULE_LOGADMINCOMMANDS) && !org.spigotmc.SpigotConfig.silentCommandBlocks) { // Spigot
-             this.server.sendSystemMessage(ichatmutablecomponent);
+             this.server.sendSystemMessage(component);
          }
- 
      }
  
      public void sendFailure(Component message) {
@@ -93,9 +90,9 @@
 -            this.source.sendSystemMessage(Component.empty().append(message).withStyle(ChatFormatting.RED));
 +            this.source.sendSystemMessage(withStyle ? Component.empty().append(message).withStyle(ChatFormatting.RED) : message); // Paper - Add UnknownCommandEvent
          }
- 
      }
-@@ -400,4 +442,16 @@
+ 
+@@ -598,4 +_,16 @@
      public boolean isSilent() {
          return this.silent;
      }
diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
new file mode 100644
index 0000000000..843b1af707
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
@@ -0,0 +1,312 @@
+--- a/net/minecraft/commands/Commands.java
++++ b/net/minecraft/commands/Commands.java
+@@ -139,6 +_,14 @@
+ import net.minecraft.world.level.GameRules;
+ import org.slf4j.Logger;
+ 
++// CraftBukkit start
++import com.google.common.base.Joiner;
++import java.util.Collection;
++import java.util.LinkedHashSet;
++import org.bukkit.event.player.PlayerCommandSendEvent;
++import org.bukkit.event.server.ServerCommandEvent;
++// CraftBukkit end
++
+ public class Commands {
+     private static final ThreadLocal<ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal<>();
+     private static final Logger LOGGER = LogUtils.getLogger();
+@@ -251,6 +_,30 @@
+             PublishCommand.register(this.dispatcher);
+         }
+ 
++        // Paper start - Vanilla command permission fixes
++        for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
++            if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
++                node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node));
++            }
++        }
++        // Paper end - Vanilla command permission fixes
++        // Paper start - Brigadier Command API
++        // Create legacy minecraft namespace commands
++        for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
++            // The brigadier dispatcher is not able to resolve nested redirects.
++            // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
++            // Instead, target the first none redirecting node.
++            CommandNode<CommandSourceStack> flattenedAliasTarget = node;
++            while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
++
++            this.dispatcher.register(
++                com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
++                    .executes(flattenedAliasTarget.getCommand())
++                    .requires(flattenedAliasTarget.getRequirement())
++                    .redirect(flattenedAliasTarget)
++            );
++        }
++        // Paper end - Brigadier Command API
+         this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
+     }
+ 
+@@ -260,15 +_,58 @@
+         return new ParseResults<>(commandContextBuilder, parseResults.getReader(), parseResults.getExceptions());
+     }
+ 
++    // CraftBukkit start
++    public void dispatchServerCommand(CommandSourceStack sender, String command) {
++        Joiner joiner = Joiner.on(" ");
++        if (command.startsWith("/")) {
++            command = command.substring(1);
++        }
++
++        ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command);
++        org.bukkit.Bukkit.getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return;
++        }
++        command = event.getCommand();
++
++        String[] args = command.split(" ");
++        if (args.length == 0) return; // Paper - empty commands shall not be dispatched
++
++        // Paper - Fix permission levels for command blocks
++
++        // Handle vanilla commands; // Paper - handled in CommandNode/CommandDispatcher
++
++        String newCommand = joiner.join(args);
++        this.performPrefixedCommand(sender, newCommand, newCommand);
++    }
++    // CraftBukkit end
++
+     public void performPrefixedCommand(CommandSourceStack source, String command) {
++        // CraftBukkit start
++        this.performPrefixedCommand(source, command, command);
++    }
++
++    public void performPrefixedCommand(CommandSourceStack source, String command, String label) {
+         command = command.startsWith("/") ? command.substring(1) : command;
+-        this.performCommand(this.dispatcher.parse(command, source), command);
++        this.performCommand(this.dispatcher.parse(command, source), command, label);
++        // CraftBukkit end
+     }
+ 
+     public void performCommand(ParseResults<CommandSourceStack> parseResults, String command) {
++        // CraftBukkit start
++        this.performCommand(parseResults, command, command);
++    }
++
++    public void performCommand(ParseResults<CommandSourceStack> parseResults, String command, String label) {
++        // CraftBukkit end
++        // Paper start
++        this.performCommand(parseResults, command, label, false);
++    }
++    public void performCommand(ParseResults<CommandSourceStack> parseResults, String command, String label, boolean throwCommandError) {
++        // Paper end
+         CommandSourceStack commandSourceStack = parseResults.getContext().getSource();
+         Profiler.get().push(() -> "/" + command);
+-        ContextChain<CommandSourceStack> contextChain = finishParsing(parseResults, command, commandSourceStack);
++        ContextChain contextChain = this.finishParsing(parseResults, command, commandSourceStack, label); // CraftBukkit // Paper - Add UnknownCommandEvent
+ 
+         try {
+             if (contextChain != null) {
+@@ -280,9 +_,10 @@
+                 );
+             }
+         } catch (Exception var12) {
++            if (throwCommandError) throw var12; // Paper
+             MutableComponent mutableComponent = Component.literal(var12.getMessage() == null ? var12.getClass().getName() : var12.getMessage());
+-            if (LOGGER.isDebugEnabled()) {
+-                LOGGER.error("Command exception: /{}", command, var12);
++            Commands.LOGGER.error("Command exception: /{}", command, var12); // Paper - always show execution exception in console log
++            if (commandSourceStack.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
+                 StackTraceElement[] stackTrace = var12.getStackTrace();
+ 
+                 for (int i = 0; i < Math.min(stackTrace.length, 3); i++) {
+@@ -309,18 +_,22 @@
+     }
+ 
+     @Nullable
+-    private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseResults, String command, CommandSourceStack source) {
++    private ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseResults, String command, CommandSourceStack source, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent
+         try {
+             validateParseResults(parseResults);
+             return ContextChain.tryFlatten(parseResults.getContext().build(command))
+                 .orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseResults.getReader()));
+         } catch (CommandSyntaxException var7) {
+-            source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage()));
++            // Paper start - Add UnknownCommandEvent
++            final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
++            // source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage()));
++            builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(var7.getRawMessage()));
++            // Paper end - Add UnknownCommandEvent
+             if (var7.getInput() != null && var7.getCursor() >= 0) {
+                 int min = Math.min(var7.getInput().length(), var7.getCursor());
+                 MutableComponent mutableComponent = Component.empty()
+                     .withStyle(ChatFormatting.GRAY)
+-                    .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command)));
++                    .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label))); // CraftBukkit // Paper
+                 if (min > 10) {
+                     mutableComponent.append(CommonComponents.ELLIPSIS);
+                 }
+@@ -332,7 +_,17 @@
+                 }
+ 
+                 mutableComponent.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
+-                source.sendFailure(mutableComponent);
++                // Paper start - Add UnknownCommandEvent
++                // source.sendFailure(mutableComponent);
++                builder
++                    .append(net.kyori.adventure.text.Component.newline())
++                    .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent));
++            }
++            org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build());
++            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event);
++            if (event.message() != null) {
++                source.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
++                // Paper end - Add UnknownCommandEvent
+             }
+ 
+             return null;
+@@ -360,25 +_,130 @@
+     }
+ 
+     public void sendCommands(ServerPlayer player) {
+-        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
++        // Paper start - Send empty commands if tab completion is disabled
++        if (org.spigotmc.SpigotConfig.tabComplete < 0) {
++            player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>()));
++            return;
++        }
++        // Paper end - Send empty commands if tab completion is disabled
++        // CraftBukkit start
++        // Register Vanilla commands into builtRoot as before
++        // Paper start - Perf: Async command map building
++        // Copy root children to avoid concurrent modification during building
++        final Collection<CommandNode<CommandSourceStack>> commandNodes = new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren());
++        COMMAND_SENDING_POOL.execute(() -> this.sendAsync(player, commandNodes));
++    }
++
++    // Fixed pool, but with discard policy
++    public static final java.util.concurrent.ExecutorService COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor(
++        2, 2, 0, java.util.concurrent.TimeUnit.MILLISECONDS,
++        new java.util.concurrent.LinkedBlockingQueue<>(),
++        new com.google.common.util.concurrent.ThreadFactoryBuilder()
++            .setNameFormat("Paper Async Command Builder Thread Pool - %1$d")
++            .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
++            .build(),
++        new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy()
++    );
++
++    private void sendAsync(ServerPlayer player, Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
++        // Paper end - Perf: Async command map building
++        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
++        // Paper - brigadier API removes the need to fill the map twice
+         RootCommandNode<SharedSuggestionProvider> rootCommandNode = new RootCommandNode<>();
+         map.put(this.dispatcher.getRoot(), rootCommandNode);
+-        this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
++        this.fillUsableCommands(dispatcherRootChildren, rootCommandNode, player.createCommandSourceStack(), map); // Paper - Perf: Async command map building; pass copy of children
++
++        Collection<String> bukkit = new LinkedHashSet<>();
++        for (CommandNode node : rootCommandNode.getChildren()) {
++            bukkit.add(node.getName());
++        }
++        // Paper start - Perf: Async command map building
++        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootCommandNode, false).callEvent(); // Paper - Brigadier API
++        net.minecraft.server.MinecraftServer.getServer().execute(() -> {
++           runSync(player, bukkit, rootCommandNode);
++        });
++    }
++
++    private void runSync(ServerPlayer player, Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootCommandNode) {
++        // Paper end - Perf: Async command map building
++        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootCommandNode, true).callEvent(); // Paper - Brigadier API
++        PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
++        event.getPlayer().getServer().getPluginManager().callEvent(event);
++
++        // Remove labels that were removed during the event
++        for (String orig : bukkit) {
++            if (!event.getCommands().contains(orig)) {
++                rootCommandNode.removeCommand(orig);
++            }
++        }
++        // CraftBukkit end
++
+         player.connection.send(new ClientboundCommandsPacket(rootCommandNode));
+     }
+ 
+     private void fillUsableCommands(
+-        CommandNode<CommandSourceStack> rootCommandSource,
++        Collection<CommandNode<CommandSourceStack>> children, // Paper - Perf: Async command map building; pass copy of children
+         CommandNode<SharedSuggestionProvider> rootSuggestion,
+         CommandSourceStack source,
+         Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> commandNodeToSuggestionNode
+     ) {
+-        for (CommandNode<CommandSourceStack> commandNode : rootCommandSource.getChildren()) {
++        commandNodeToSuggestionNode.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains(":")); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
++        for (CommandNode<CommandSourceStack> commandNode : children) { // Paper - Perf: Async command map building; pass copy of children
++            // Paper start - Brigadier API
++            if (commandNode.clientNode != null) {
++                commandNode = commandNode.clientNode;
++            }
++            // Paper end - Brigadier API
++            if (!org.spigotmc.SpigotConfig.sendNamespaced && commandNode.getName().contains(":")) continue; // Spigot
+             if (commandNode.canUse(source)) {
+                 ArgumentBuilder<SharedSuggestionProvider, ?> argumentBuilder = (ArgumentBuilder) commandNode.createBuilder();
++                // Paper start
++                /*
++                Because of how commands can be yeeted right left and center due to bad bukkit practices
++                we need to be able to ensure that ALL commands are registered (even redirects).
++
++                What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
++                all the children from the dead redirect to the node.
++
++                So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
++
++                The only way to fix this is to either:
++                - Send EVERYTHING flattened, don't use redirects
++                - Don't allow command nodes to be deleted
++                - Do this :)
++                 */
++
++                // Is there an invalid command redirect?
++                if (argumentBuilder.getRedirect() != null && commandNodeToSuggestionNode.get(argumentBuilder.getRedirect()) == null) {
++                    // Create the argument builder with the same values as the specified node, but with a different literal and populated children
++
++                    CommandNode<SharedSuggestionProvider> redirect = argumentBuilder.getRedirect();
++                    // Diff copied from LiteralCommand#createBuilder
++                    final com.mojang.brigadier.builder.LiteralArgumentBuilder<SharedSuggestionProvider> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandNode.getName());
++                    builder.requires(redirect.getRequirement());
++                    // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
++                    if (redirect.getCommand() != null) {
++                        builder.executes(redirect.getCommand());
++                    }
++                    // Diff copied from LiteralCommand#createBuilder
++                    for (CommandNode<SharedSuggestionProvider> child : redirect.getChildren()) {
++                        builder.then(child);
++                    }
++
++                    argumentBuilder = builder;
++                }
++                // Paper end
+                 argumentBuilder.requires(suggestions -> true);
+                 if (argumentBuilder.getCommand() != null) {
+-                    argumentBuilder.executes(commandContext -> 0);
++                    // Paper start - fix suggestions due to falsely equal nodes
++                    // Always create a new instance
++                    argumentBuilder.executes(new com.mojang.brigadier.Command<>() {
++                        @Override
++                        public int run(com.mojang.brigadier.context.CommandContext<SharedSuggestionProvider> commandContext) {
++                            return 0;
++                        }
++                    });
++                    // Paper end - fix suggestions due to falsely equal nodes
+                 }
+ 
+                 if (argumentBuilder instanceof RequiredArgumentBuilder) {
+@@ -396,7 +_,7 @@
+                 commandNodeToSuggestionNode.put(commandNode, commandNode1);
+                 rootSuggestion.addChild(commandNode1);
+                 if (!commandNode.getChildren().isEmpty()) {
+-                    this.fillUsableCommands(commandNode, commandNode1, source, commandNodeToSuggestionNode);
++                    this.fillUsableCommands(commandNode.getChildren(), commandNode1, source, commandNodeToSuggestionNode); // Paper - Perf: Async command map building; pass copy of children
+                 }
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
index ffcea4eeab..54adbbf786 100644
--- a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/world/CompoundContainer.java
 +++ b/net/minecraft/world/CompoundContainer.java
 @@ -7,6 +_,48 @@
-     private final Container container1;
-     private final Container container2;
+     public final Container container1;
+     public final Container container2;
  
 +    // Paper start - add fields and methods
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch
index ac1686a6c3..92e7c74467 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
 +++ b/net/minecraft/world/entity/ai/navigation/FlyingPathNavigation.java
-@@ -39,7 +39,7 @@
+@@ -39,7 +_,7 @@
  
      @Override
-     public Path createPath(Entity entity, int distance) {
--        return this.createPath(entity.blockPosition(), distance);
-+        return this.createPath(entity.blockPosition(), entity, distance); // Paper - EntityPathfindEvent
+     public Path createPath(Entity entity, int i) {
+-        return this.createPath(entity.blockPosition(), i);
++        return this.createPath(entity.blockPosition(), entity, i); // Paper - EntityPathfindEvent
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
new file mode 100644
index 0000000000..1880055015
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
++++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
+@@ -41,7 +_,7 @@
+     }
+ 
+     @Override
+-    public Path createPath(BlockPos pos, int accuracy) {
++    public Path createPath(BlockPos pos, @javax.annotation.Nullable Entity entity, int accuracy) { // Paper - EntityPathfindEvent
+         LevelChunk chunkNow = this.level.getChunkSource().getChunkNow(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
+         if (chunkNow == null) {
+             return null;
+@@ -54,7 +_,7 @@
+                 }
+ 
+                 if (mutableBlockPos.getY() > this.level.getMinY()) {
+-                    return super.createPath(mutableBlockPos.above(), accuracy);
++                    return super.createPath(mutableBlockPos.above(), entity, accuracy); // Paper - EntityPathfindEvent
+                 }
+ 
+                 mutableBlockPos.setY(pos.getY() + 1);
+@@ -67,7 +_,7 @@
+             }
+ 
+             if (!chunkNow.getBlockState(pos).isSolid()) {
+-                return super.createPath(pos, accuracy);
++                return super.createPath(pos, entity, accuracy); // Paper - EntityPathfindEvent
+             } else {
+                 BlockPos.MutableBlockPos mutableBlockPos = pos.mutable().move(Direction.UP);
+ 
+@@ -75,14 +_,14 @@
+                     mutableBlockPos.move(Direction.UP);
+                 }
+ 
+-                return super.createPath(mutableBlockPos.immutable(), accuracy);
++                return super.createPath(mutableBlockPos.immutable(), entity, accuracy); // Paper - EntityPathfindEvent
+             }
+         }
+     }
+ 
+     @Override
+     public Path createPath(Entity entity, int i) {
+-        return this.createPath(entity.blockPosition(), i);
++        return this.createPath(entity.blockPosition(), entity, i); // Paper - EntityPathfindEvent
+     }
+ 
+     private int getSurfaceY() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
index 124ed48519..bd9b6ee1ee 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
@@ -1,64 +1,64 @@
 --- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java
 +++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
-@@ -125,8 +125,14 @@
+@@ -125,7 +_,13 @@
  
      @Nullable
-     public Path createPath(BlockPos target, int distance) {
--        return this.createPath(ImmutableSet.of(target), 8, false, distance);
+     public Path createPath(BlockPos pos, int accuracy) {
+-        return this.createPath(ImmutableSet.of(pos), 8, false, accuracy);
 +        // Paper start - EntityPathfindEvent
-+        return this.createPath(target, null, distance);
-     }
-+    @Nullable
-+    public Path createPath(BlockPos target, @Nullable Entity entity, int distance) {
-+        return this.createPath(ImmutableSet.of(target), entity, 8, false, distance);
-+        // Paper end - EntityPathfindEvent
++        return this.createPath(pos, null, accuracy);
 +    }
- 
-     @Nullable
-     public Path createPath(BlockPos target, int minDistance, int maxDistance) {
-@@ -135,7 +141,7 @@
- 
-     @Nullable
-     public Path createPath(Entity entity, int distance) {
--        return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, distance);
-+        return this.createPath(ImmutableSet.of(entity.blockPosition()), entity, 16, true, distance); // Paper - EntityPathfindEvent
++    @Nullable
++    public Path createPath(BlockPos target, @Nullable Entity entity, int accuracy) {
++        return this.createPath(ImmutableSet.of(target), entity, 8, false, accuracy);
++        // Paper end - EntityPathfindEvent
      }
  
      @Nullable
-@@ -145,6 +151,17 @@
+@@ -135,7 +_,7 @@
  
      @Nullable
-     protected Path createPath(Set<BlockPos> positions, int range, boolean useHeadPos, int distance, float followRange) {
+     public Path createPath(Entity entity, int accuracy) {
+-        return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, accuracy);
++        return this.createPath(ImmutableSet.of(entity.blockPosition()), entity, 16, true, accuracy); // Paper - EntityPathfindEvent
+     }
+ 
+     @Nullable
+@@ -145,6 +_,18 @@
+ 
+     @Nullable
+     protected Path createPath(Set<BlockPos> targets, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
 +        // Paper start - EntityPathfindEvent
-+        return this.createPath(positions, null, range, useHeadPos, distance, followRange);
++        return this.createPath(targets, null, regionOffset, offsetUpward, accuracy, followRange);
 +    }
 +
 +    @Nullable
-+    protected Path createPath(Set<BlockPos> positions, @Nullable Entity target, int range, boolean useHeadPos, int distance) {
-+        return this.createPath(positions, target, range, useHeadPos, distance, (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE));
++    protected Path createPath(Set<BlockPos> targets, @Nullable Entity target, int regionOffset, boolean offsetUpward, int accuracy) {
++        return this.createPath(targets, target, regionOffset, offsetUpward, accuracy, (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE));
 +    }
 +
-+    @Nullable protected Path createPath(Set<BlockPos> positions, @Nullable Entity target, int range, boolean useHeadPos, int distance, float followRange) {
++    @Nullable
++    protected Path createPath(Set<BlockPos> targets, @Nullable Entity target, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
 +        // Paper end - EntityPathfindEvent
-         if (positions.isEmpty()) {
+         if (targets.isEmpty()) {
              return null;
-         } else if (this.mob.getY() < (double)this.level.getMinY()) {
-@@ -154,6 +171,23 @@
-         } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) {
+         } else if (this.mob.getY() < this.level.getMinY()) {
+@@ -154,6 +_,23 @@
+         } else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) {
              return this.path;
          } else {
 +            // Paper start - EntityPathfindEvent
 +            boolean copiedSet = false;
-+            for (BlockPos possibleTarget : positions) {
++            for (BlockPos possibleTarget : targets) {
 +                if (!this.mob.getCommandSenderWorld().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border
 +                    io.papermc.paper.util.MCUtil.toLocation(this.mob.level(), possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) {
 +                    if (!copiedSet) {
 +                        copiedSet = true;
-+                        positions = new java.util.HashSet<>(positions);
++                        targets = new java.util.HashSet<>(targets);
 +                    }
 +                    // note: since we copy the set this remove call is safe, since we're iterating over the old copy
-+                    positions.remove(possibleTarget);
-+                    if (positions.isEmpty()) {
++                    targets.remove(possibleTarget);
++                    if (targets.isEmpty()) {
 +                        return null;
 +                    }
 +                }
@@ -66,9 +66,9 @@
 +            // Paper end - EntityPathfindEvent
              ProfilerFiller profilerFiller = Profiler.get();
              profilerFiller.push("pathfind");
-             BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition();
-@@ -175,13 +209,33 @@
-         return this.moveTo(this.createPath(x, y, z, 1), speed);
+             BlockPos blockPos = offsetUpward ? this.mob.blockPosition().above() : this.mob.blockPosition();
+@@ -171,6 +_,11 @@
+         }
      }
  
 +    // Paper start - Perf: Optimise pathfinding
@@ -76,8 +76,10 @@
 +    private int pathfindFailures = 0;
 +    // Paper end - Perf: Optimise pathfinding
 +
-     public boolean moveTo(double x, double y, double z, int distance, double speed) {
-         return this.moveTo(this.createPath(x, y, z, distance), speed);
+     public boolean moveTo(double x, double y, double z, double speed) {
+         return this.moveTo(this.createPath(x, y, z, 1), speed);
+     }
+@@ -180,8 +_,23 @@
      }
  
      public boolean moveTo(Entity entity, double speed) {
@@ -101,4 +103,4 @@
 +        // Paper end - Perf: Optimise pathfinding
      }
  
-     public boolean moveTo(@Nullable Path path, double speed) {
+     public boolean moveTo(@Nullable Path pathentity, double speed) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch
new file mode 100644
index 0000000000..ca88f3f478
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java
++++ b/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java
+@@ -16,9 +_,9 @@
+     }
+ 
+     @Override
+-    public Path createPath(BlockPos pos, int accuracy) {
++    public Path createPath(BlockPos pos, @Nullable Entity entity, int accuracy) {
+         this.pathToPosition = pos;
+-        return super.createPath(pos, accuracy);
++        return super.createPath(pos, entity, accuracy); // Paper - EntityPathfindEvent
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
index 180a6dcee4..1b2858a398 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -83,7 +83,7 @@
  
 @@ -340,7 +_,7 @@
  
-     protected void createInventory() {
+     public void createInventory() {
          SimpleContainer simpleContainer = this.inventory;
 -        this.inventory = new SimpleContainer(this.getInventorySize());
 +        this.inventory = new SimpleContainer(this.getInventorySize(), (org.bukkit.entity.AbstractHorse) this.getBukkitEntity()); // CraftBukkit
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
index 11854ff324..24372abac2 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -39,5 +39,5 @@
 +    }
 +    // CraftBukkit end
      private NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
-     private final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
+     public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
          @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index 746ad85ba8..6248b82627 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -2,7 +2,7 @@
 +++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
 @@ -27,6 +_,42 @@
      private final NonNullList<ItemStack> items = NonNullList.withSize(6, ItemStack.EMPTY);
-     private int lastInteractedSlot = -1;
+     public int lastInteractedSlot = -1;
  
 +    // CraftBukkit start - add fields and methods
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
index c28a5c1792..e6ba6d525f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -2,10 +2,10 @@
 +++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
 @@ -92,7 +_,7 @@
      @Nullable
-     private BlockPos portalLocation;
+     public BlockPos portalLocation;
      @Nullable
--    private DragonRespawnAnimation respawnStage;
+-    public DragonRespawnAnimation respawnStage;
 +    public DragonRespawnAnimation respawnStage;// Paper-At: public net.minecraft.world.level.dimension.end.EndDragonFight respawnStage
      private int respawnTime;
      @Nullable
-     private List<EndCrystal> respawnCrystals;
+     public List<EndCrystal> respawnCrystals;
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/Commands.java.patch b/paper-server/patches/unapplied/net/minecraft/commands/Commands.java.patch
deleted file mode 100644
index 1fc19d5217..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/commands/Commands.java.patch
+++ /dev/null
@@ -1,372 +0,0 @@
---- a/net/minecraft/commands/Commands.java
-+++ b/net/minecraft/commands/Commands.java
-@@ -138,6 +138,14 @@
- import net.minecraft.world.flag.FeatureFlags;
- import net.minecraft.world.level.GameRules;
- import org.slf4j.Logger;
-+
-+// CraftBukkit start
-+import com.google.common.base.Joiner;
-+import java.util.Collection;
-+import java.util.LinkedHashSet;
-+import org.bukkit.event.player.PlayerCommandSendEvent;
-+import org.bukkit.event.server.ServerCommandEvent;
-+// CraftBukkit end
- 
- public class Commands {
- 
-@@ -151,6 +159,7 @@
-     private final com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = new com.mojang.brigadier.CommandDispatcher();
- 
-     public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) {
-+        // Paper
-         AdvancementCommands.register(this.dispatcher);
-         AttributeCommand.register(this.dispatcher, commandRegistryAccess);
-         ExecuteCommand.register(this.dispatcher, commandRegistryAccess);
-@@ -250,8 +259,33 @@
- 
-         if (environment.includeIntegrated) {
-             PublishCommand.register(this.dispatcher);
-+        }
-+
-+        // Paper start - Vanilla command permission fixes
-+        for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
-+            if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
-+                node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node));
-+            }
-         }
-+        // Paper end - Vanilla command permission fixes
-+        // Paper start - Brigadier Command API
-+        // Create legacy minecraft namespace commands
-+        for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
-+            // The brigadier dispatcher is not able to resolve nested redirects.
-+            // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
-+            // Instead, target the first none redirecting node.
-+            CommandNode<CommandSourceStack> flattenedAliasTarget = node;
-+            while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
- 
-+            this.dispatcher.register(
-+                com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
-+                    .executes(flattenedAliasTarget.getCommand())
-+                    .requires(flattenedAliasTarget.getRequirement())
-+                    .redirect(flattenedAliasTarget)
-+            );
-+        }
-+        // Paper end - Brigadier Command API
-+        // Paper - remove public constructor, no longer needed
-         this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
-     }
- 
-@@ -262,30 +296,72 @@
-         return new ParseResults(commandcontextbuilder1, parseResults.getReader(), parseResults.getExceptions());
-     }
- 
-+    // CraftBukkit start
-+    public void dispatchServerCommand(CommandSourceStack sender, String command) {
-+        Joiner joiner = Joiner.on(" ");
-+        if (command.startsWith("/")) {
-+            command = command.substring(1);
-+        }
-+
-+        ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command);
-+        org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return;
-+        }
-+        command = event.getCommand();
-+
-+        String[] args = command.split(" ");
-+        if (args.length == 0) return; // Paper - empty commands shall not be dispatched
-+
-+        // Paper - Fix permission levels for command blocks
-+
-+        // Handle vanilla commands; // Paper - handled in CommandNode/CommandDispatcher
-+
-+        String newCommand = joiner.join(args);
-+        this.performPrefixedCommand(sender, newCommand, newCommand);
-+    }
-+    // CraftBukkit end
-+
-     public void performPrefixedCommand(CommandSourceStack source, String command) {
--        command = command.startsWith("/") ? command.substring(1) : command;
--        this.performCommand(this.dispatcher.parse(command, source), command);
-+        // CraftBukkit start
-+        this.performPrefixedCommand(source, command, command);
-+    }
-+
-+    public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s, String label) {
-+        s = s.startsWith("/") ? s.substring(1) : s;
-+        this.performCommand(this.dispatcher.parse(s, commandlistenerwrapper), s, label);
-+        // CraftBukkit end
-     }
- 
-     public void performCommand(ParseResults<CommandSourceStack> parseResults, String command) {
--        CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseResults.getContext().getSource();
-+        this.performCommand(parseResults, command, command);
-+    }
- 
-+    public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
-+        // Paper start
-+        this.performCommand(parseresults, s, label, false);
-+    }
-+    public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label, boolean throwCommandError) {
-+        // Paper end
-+        CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
-+
-         Profiler.get().push(() -> {
--            return "/" + command;
-+            return "/" + s;
-         });
--        ContextChain<CommandSourceStack> contextchain = Commands.finishParsing(parseResults, command, commandlistenerwrapper);
-+        ContextChain contextchain = this.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit // Paper - Add UnknownCommandEvent
- 
-         try {
-             if (contextchain != null) {
-                 Commands.executeCommandInContext(commandlistenerwrapper, (executioncontext) -> {
--                    ExecutionContext.queueInitialCommandExecution(executioncontext, command, contextchain, commandlistenerwrapper, CommandResultCallback.EMPTY);
-+                    ExecutionContext.queueInitialCommandExecution(executioncontext, s, contextchain, commandlistenerwrapper, CommandResultCallback.EMPTY);
-                 });
-             }
-         } catch (Exception exception) {
-+            if (throwCommandError) throw exception;
-             MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage());
- 
--            if (Commands.LOGGER.isDebugEnabled()) {
--                Commands.LOGGER.error("Command exception: /{}", command, exception);
-+            Commands.LOGGER.error("Command exception: /{}", s, exception); // Paper - always show execution exception in console log
-+            if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
-                 StackTraceElement[] astacktraceelement = exception.getStackTrace();
- 
-                 for (int i = 0; i < Math.min(astacktraceelement.length, 3); ++i) {
-@@ -298,7 +374,7 @@
-             }));
-             if (SharedConstants.IS_RUNNING_IN_IDE) {
-                 commandlistenerwrapper.sendFailure(Component.literal(Util.describeError(exception)));
--                Commands.LOGGER.error("'/{}' threw an exception", command, exception);
-+                Commands.LOGGER.error("'/{}' threw an exception", s, exception);
-             }
-         } finally {
-             Profiler.get().pop();
-@@ -307,18 +383,22 @@
-     }
- 
-     @Nullable
--    private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseResults, String command, CommandSourceStack source) {
-+    private ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent
-         try {
--            Commands.validateParseResults(parseResults);
--            return (ContextChain) ContextChain.tryFlatten(parseResults.getContext().build(command)).orElseThrow(() -> {
--                return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseResults.getReader());
-+            Commands.validateParseResults(parseresults);
-+            return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> {
-+                return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader());
-             });
-         } catch (CommandSyntaxException commandsyntaxexception) {
--            source.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage()));
-+            // Paper start - Add UnknownCommandEvent
-+            final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
-+            // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage()));
-+            builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage()));
-+            // Paper end - Add UnknownCommandEvent
-             if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) {
-                 int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor());
-                 MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> {
--                    return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command));
-+                    return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper
-                 });
- 
-                 if (i > 10) {
-@@ -333,8 +413,18 @@
-                 }
- 
-                 ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
--                source.sendFailure(ichatmutablecomponent);
-+                // Paper start - Add UnknownCommandEvent
-+                // commandlistenerwrapper.sendFailure(ichatmutablecomponent);
-+                builder
-+                    .append(net.kyori.adventure.text.Component.newline())
-+                    .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent));
-             }
-+            org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build());
-+            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event);
-+            if (event.message() != null) {
-+                commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
-+                // Paper end - Add UnknownCommandEvent
-+            }
- 
-             return null;
-         }
-@@ -368,7 +458,7 @@
- 
-                 executioncontext1.close();
-             } finally {
--                Commands.CURRENT_EXECUTION_CONTEXT.set((Object) null);
-+                Commands.CURRENT_EXECUTION_CONTEXT.set(null); // CraftBukkit - decompile error
-             }
-         } else {
-             callback.accept(executioncontext);
-@@ -377,30 +467,133 @@
-     }
- 
-     public void sendCommands(ServerPlayer player) {
--        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
-+        // Paper start - Send empty commands if tab completion is disabled
-+        if (org.spigotmc.SpigotConfig.tabComplete < 0) {
-+            player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>()));
-+            return;
-+        }
-+        // Paper end - Send empty commands if tab completion is disabled
-+        // CraftBukkit start
-+        // Register Vanilla commands into builtRoot as before
-+        // Paper start - Perf: Async command map building
-+        // Copy root children to avoid concurrent modification during building
-+        final Collection<CommandNode<CommandSourceStack>> commandNodes = new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren());
-+        COMMAND_SENDING_POOL.execute(() -> this.sendAsync(player, commandNodes));
-+    }
-+
-+    // Fixed pool, but with discard policy
-+    public static final java.util.concurrent.ExecutorService COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor(
-+        2, 2, 0, java.util.concurrent.TimeUnit.MILLISECONDS,
-+        new java.util.concurrent.LinkedBlockingQueue<>(),
-+        new com.google.common.util.concurrent.ThreadFactoryBuilder()
-+            .setNameFormat("Paper Async Command Builder Thread Pool - %1$d")
-+            .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
-+            .build(),
-+        new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy()
-+    );
-+
-+    private void sendAsync(ServerPlayer player, Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
-+        // Paper end - Perf: Async command map building
-+        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
-+        // Paper - brigadier API removes the need to fill the map twice
-         RootCommandNode<SharedSuggestionProvider> rootcommandnode = new RootCommandNode();
- 
-         map.put(this.dispatcher.getRoot(), rootcommandnode);
--        this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, player.createCommandSourceStack(), map);
-+        this.fillUsableCommands(dispatcherRootChildren, rootcommandnode, player.createCommandSourceStack(), map); // Paper - Perf: Async command map building; pass copy of children
-+
-+        Collection<String> bukkit = new LinkedHashSet<>();
-+        for (CommandNode node : rootcommandnode.getChildren()) {
-+            bukkit.add(node.getName());
-+        }
-+        // Paper start - Perf: Async command map building
-+        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API
-+        net.minecraft.server.MinecraftServer.getServer().execute(() -> {
-+           runSync(player, bukkit, rootcommandnode);
-+        });
-+    }
-+
-+    private void runSync(ServerPlayer player, Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootcommandnode) {
-+        // Paper end - Perf: Async command map building
-+        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API
-+        PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
-+        event.getPlayer().getServer().getPluginManager().callEvent(event);
-+
-+        // Remove labels that were removed during the event
-+        for (String orig : bukkit) {
-+            if (!event.getCommands().contains(orig)) {
-+                rootcommandnode.removeCommand(orig);
-+            }
-+        }
-+        // CraftBukkit end
-         player.connection.send(new ClientboundCommandsPacket(rootcommandnode));
-     }
- 
--    private void fillUsableCommands(CommandNode<CommandSourceStack> tree, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
--        Iterator iterator = tree.getChildren().iterator();
-+    // Paper start - Perf: Async command map building; pass copy of children
-+    private void fillUsableCommands(Collection<CommandNode<CommandSourceStack>> children, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
-+        resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
-+        Iterator iterator = children.iterator();
-+        // Paper end - Perf: Async command map building
- 
-         while (iterator.hasNext()) {
-             CommandNode<CommandSourceStack> commandnode2 = (CommandNode) iterator.next();
-+            // Paper start - Brigadier API
-+            if (commandnode2.clientNode != null) {
-+                commandnode2 = commandnode2.clientNode;
-+            }
-+            // Paper end - Brigadier API
-+            if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot
- 
-             if (commandnode2.canUse(source)) {
--                ArgumentBuilder<SharedSuggestionProvider, ?> argumentbuilder = commandnode2.createBuilder();
-+                ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); // CraftBukkit - decompile error
-+                // Paper start
-+                /*
-+                Because of how commands can be yeeted right left and center due to bad bukkit practices
-+                we need to be able to ensure that ALL commands are registered (even redirects).
- 
-+                What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
-+                all the children from the dead redirect to the node.
-+
-+                So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
-+
-+                The only way to fix this is to either:
-+                - Send EVERYTHING flattened, don't use redirects
-+                - Don't allow command nodes to be deleted
-+                - Do this :)
-+                 */
-+
-+                // Is there an invalid command redirect?
-+                if (argumentbuilder.getRedirect() != null && (CommandNode) resultNodes.get(argumentbuilder.getRedirect()) == null) {
-+                    // Create the argument builder with the same values as the specified node, but with a different literal and populated children
-+
-+                    CommandNode<CommandSourceStack> redirect = argumentbuilder.getRedirect();
-+                    // Diff copied from LiteralCommand#createBuilder
-+                    final com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandnode2.getName());
-+                    builder.requires(redirect.getRequirement());
-+                    // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
-+                    if (redirect.getCommand() != null) {
-+                        builder.executes(redirect.getCommand());
-+                    }
-+                    // Diff copied from LiteralCommand#createBuilder
-+                    for (CommandNode<CommandSourceStack> child : redirect.getChildren()) {
-+                        builder.then(child);
-+                    }
-+
-+                    argumentbuilder = builder;
-+                }
-+                // Paper end
-+
-                 argumentbuilder.requires((icompletionprovider) -> {
-                     return true;
-                 });
-                 if (argumentbuilder.getCommand() != null) {
--                    argumentbuilder.executes((commandcontext) -> {
--                        return 0;
-+                    // Paper start - fix suggestions due to falsely equal nodes
-+                    argumentbuilder.executes(new com.mojang.brigadier.Command<io.papermc.paper.command.brigadier.CommandSourceStack>() {
-+                        @Override
-+                        public int run(com.mojang.brigadier.context.CommandContext<io.papermc.paper.command.brigadier.CommandSourceStack> commandContext) throws CommandSyntaxException {
-+                            return 0;
-+                        }
-                     });
-+                    // Paper end
-                 }
- 
-                 if (argumentbuilder instanceof RequiredArgumentBuilder) {
-@@ -415,12 +608,12 @@
-                     argumentbuilder.redirect((CommandNode) resultNodes.get(argumentbuilder.getRedirect()));
-                 }
- 
--                CommandNode<SharedSuggestionProvider> commandnode3 = argumentbuilder.build();
-+                CommandNode commandnode3 = argumentbuilder.build(); // CraftBukkit - decompile error
- 
-                 resultNodes.put(commandnode2, commandnode3);
-                 result.addChild(commandnode3);
-                 if (!commandnode2.getChildren().isEmpty()) {
--                    this.fillUsableCommands(commandnode2, commandnode3, source, resultNodes);
-+                    this.fillUsableCommands(commandnode2.getChildren(), commandnode3, source, resultNodes); // Paper - Perf: Async command map building; pass children directly
-                 }
-             }
-         }
-@@ -481,7 +674,7 @@
-             }
- 
-             private <T> HolderLookup.RegistryLookup.Delegate<T> createLookup(final HolderLookup.RegistryLookup<T> original) {
--                return new HolderLookup.RegistryLookup.Delegate<T>(this) {
-+                return new HolderLookup.RegistryLookup.Delegate<T>() { // CraftBukkit - decompile error
-                     @Override
-                     public HolderLookup.RegistryLookup<T> parent() {
-                         return original;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
deleted file mode 100644
index 36cc381dc3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
-+++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java
-@@ -41,7 +41,7 @@
-     }
- 
-     @Override
--    public Path createPath(BlockPos target, int distance) {
-+    public Path createPath(BlockPos target, @javax.annotation.Nullable Entity entity, int distance) { // Paper - EntityPathfindEvent
-         LevelChunk levelChunk = this.level
-             .getChunkSource()
-             .getChunkNow(SectionPos.blockToSectionCoord(target.getX()), SectionPos.blockToSectionCoord(target.getZ()));
-@@ -56,7 +56,7 @@
-                 }
- 
-                 if (mutableBlockPos.getY() > this.level.getMinY()) {
--                    return super.createPath(mutableBlockPos.above(), distance);
-+                    return super.createPath(mutableBlockPos.above(), entity, distance); // Paper - EntityPathfindEvent
-                 }
- 
-                 mutableBlockPos.setY(target.getY() + 1);
-@@ -69,7 +69,7 @@
-             }
- 
-             if (!levelChunk.getBlockState(target).isSolid()) {
--                return super.createPath(target, distance);
-+                return super.createPath(target, entity, distance); // Paper - EntityPathfindEvent
-             } else {
-                 BlockPos.MutableBlockPos mutableBlockPos2 = target.mutable().move(Direction.UP);
- 
-@@ -77,14 +77,14 @@
-                     mutableBlockPos2.move(Direction.UP);
-                 }
- 
--                return super.createPath(mutableBlockPos2.immutable(), distance);
-+                return super.createPath(mutableBlockPos2.immutable(), entity, distance);  // Paper - EntityPathfindEvent
-             }
-         }
-     }
- 
-     @Override
-     public Path createPath(Entity entity, int distance) {
--        return this.createPath(entity.blockPosition(), distance);
-+        return this.createPath(entity.blockPosition(), entity, distance); // Paper - EntityPathfindEvent
-     }
- 
-     private int getSurfaceY() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch
deleted file mode 100644
index b2662d7401..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java
-+++ b/net/minecraft/world/entity/ai/navigation/WallClimberNavigation.java
-@@ -16,9 +16,9 @@
-     }
- 
-     @Override
--    public Path createPath(BlockPos target, int distance) {
-+    public Path createPath(BlockPos target, @Nullable Entity entity, int distance) { // Paper - EntityPathfindEvent
-         this.pathToPosition = target;
--        return super.createPath(target, distance);
-+        return super.createPath(target, entity, distance); // Paper - EntityPathfindEvent
-     }
- 
-     @Override

From c6a426beda3b91adce42c9624f56b1d13d37fdec Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Fri, 13 Dec 2024 21:36:11 +0100
Subject: [PATCH 026/285] warden AI

---
 .../ai/behavior/warden/Digging.java.patch     | 11 ++++++++++
 .../ai/behavior/warden/SonicBoom.java.patch   | 11 ++++++++++
 .../ai/behavior/warden/Digging.java.patch     | 22 -------------------
 .../ai/behavior/warden/SonicBoom.java.patch   | 11 ----------
 4 files changed, 22 insertions(+), 33 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
new file mode 100644
index 0000000000..c571e45b5e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ai/behavior/warden/Digging.java
++++ b/net/minecraft/world/entity/ai/behavior/warden/Digging.java
+@@ -39,7 +_,7 @@
+     @Override
+     protected void stop(ServerLevel level, E entity, long gameTime) {
+         if (entity.getRemovalReason() == null) {
+-            entity.remove(Entity.RemovalReason.DISCARDED);
++            entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch
new file mode 100644
index 0000000000..4289534ef3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java
++++ b/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java
+@@ -83,7 +_,7 @@
+                     if (livingEntity.hurtServer(level, level.damageSources().sonicBoom(owner), 10.0F)) {
+                         double d = 0.5 * (1.0 - livingEntity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
+                         double d1 = 2.5 * (1.0 - livingEntity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
+-                        livingEntity.push(vec32.x() * d1, vec32.y() * d, vec32.z() * d1);
++                        livingEntity.push(vec32.x() * d1, vec32.y() * d, vec32.z() * d1, owner); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+                     }
+                 });
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
deleted file mode 100644
index 6f3b4a357c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/Digging.java.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/warden/Digging.java
-+++ b/net/minecraft/world/entity/ai/behavior/warden/Digging.java
-@@ -10,6 +10,10 @@
- import net.minecraft.world.entity.ai.memory.MemoryStatus;
- import net.minecraft.world.entity.monster.warden.Warden;
- 
-+// CraftBukkit start - imports
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
-+
- public class Digging<E extends Warden> extends Behavior<E> {
- 
-     public Digging(int duration) {
-@@ -37,7 +41,7 @@
- 
-     protected void stop(ServerLevel worldserver, E e0, long i) {
-         if (e0.getRemovalReason() == null) {
--            e0.remove(Entity.RemovalReason.DISCARDED);
-+            e0.remove(Entity.RemovalReason.DISCARDED, EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch
deleted file mode 100644
index 4fbd06f256..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java
-+++ b/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java
-@@ -83,7 +83,7 @@
-                     if (target.hurtServer(world, world.damageSources().sonicBoom(entity), 10.0F)) {
-                         double d = 0.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
-                         double e = 2.5 * (1.0 - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
--                        target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e);
-+                        target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-                     }
-                 });
-         }

From 83c42080d65fb28e39cf9789635d69e32617e3c9 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Fri, 13 Dec 2024 21:49:47 +0100
Subject: [PATCH 027/285] chat/contents

---
 .../chat/contents/NbtContents.java.patch      | 20 +++++++++++++
 .../chat/contents/SelectorContents.java.patch | 10 +++----
 .../contents/TranslatableContents.java.patch  | 30 ++++++++++---------
 .../chat/contents/NbtContents.java.patch      | 20 -------------
 4 files changed, 41 insertions(+), 39 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/contents/SelectorContents.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/contents/TranslatableContents.java.patch (69%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/chat/contents/NbtContents.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch
new file mode 100644
index 0000000000..ee118dfd02
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/NbtContents.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/network/chat/contents/NbtContents.java
++++ b/net/minecraft/network/chat/contents/NbtContents.java
+@@ -115,7 +_,7 @@
+             }).map(Tag::getAsString);
+             if (this.interpreting) {
+                 Component component = DataFixUtils.orElse(
+-                    ComponentUtils.updateForEntity(nbtPathPattern, this.separator, entity, recursionDepth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR
++                    ComponentUtils.updateSeparatorForEntity(nbtPathPattern, this.separator, entity, recursionDepth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator
+                 );
+                 return stream.flatMap(text -> {
+                     try {
+@@ -127,7 +_,7 @@
+                     }
+                 }).reduce((mutableComponent, component1) -> mutableComponent.append(component).append(component1)).orElseGet(Component::empty);
+             } else {
+-                return ComponentUtils.updateForEntity(nbtPathPattern, this.separator, entity, recursionDepth)
++                return ComponentUtils.updateSeparatorForEntity(nbtPathPattern, this.separator, entity, recursionDepth) // Paper - validate separator
+                     .map(
+                         mutableComponent -> stream.map(Component::literal)
+                             .reduce((mutableComponent1, otherMutableComponent) -> mutableComponent1.append(mutableComponent).append(otherMutableComponent))
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/SelectorContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/contents/SelectorContents.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch
index aff540bbdc..16c49dcbd5 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/SelectorContents.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/SelectorContents.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/network/chat/contents/SelectorContents.java
 +++ b/net/minecraft/network/chat/contents/SelectorContents.java
-@@ -36,7 +36,7 @@
-         if (source == null) {
+@@ -36,7 +_,7 @@
+         if (nbtPathPattern == null) {
              return Component.empty();
          } else {
--            Optional<? extends Component> optional = ComponentUtils.updateForEntity(source, this.separator, sender, depth);
-+            Optional<? extends Component> optional = ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth); // Paper - validate separator
-             return ComponentUtils.formatList(this.selector.resolved().findEntities(source), optional, Entity::getDisplayName);
+-            Optional<? extends Component> optional = ComponentUtils.updateForEntity(nbtPathPattern, this.separator, entity, recursionDepth);
++            Optional<? extends Component> optional = ComponentUtils.updateSeparatorForEntity(nbtPathPattern, this.separator, entity, recursionDepth); // Paper - validate separator
+             return ComponentUtils.formatList(this.selector.resolved().findEntities(nbtPathPattern), optional, Entity::getDisplayName);
          }
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/TranslatableContents.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch
similarity index 69%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/contents/TranslatableContents.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch
index 88b91fae6d..6240ecc49b 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/TranslatableContents.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/contents/TranslatableContents.java.patch
@@ -1,45 +1,47 @@
 --- a/net/minecraft/network/chat/contents/TranslatableContents.java
 +++ b/net/minecraft/network/chat/contents/TranslatableContents.java
-@@ -181,6 +181,15 @@
+@@ -181,6 +_,16 @@
  
      @Override
-     public <T> Optional<T> visit(FormattedText.ContentConsumer<T> visitor) {
+     public <T> Optional<T> visit(FormattedText.ContentConsumer<T> contentConsumer) {
 +        // Paper start - Count visited parts
 +        try {
-+            return this.visit(new TranslatableContentConsumer<>(visitor));
++            return this.visit(new TranslatableContentConsumer<>(contentConsumer));
 +        } catch (IllegalArgumentException ignored) {
-+            return visitor.accept("...");
++            return contentConsumer.accept("...");
 +        }
 +    }
-+    private <T> Optional<T> visit(TranslatableContentConsumer<T> visitor) {
++
++    private <T> Optional<T> visit(TranslatableContentConsumer<T> contentConsumer) {
 +        // Paper end - Count visited parts
          this.decompose();
  
          for (FormattedText formattedText : this.decomposedParts) {
-@@ -191,7 +200,26 @@
-         }
+@@ -192,6 +_,27 @@
  
          return Optional.empty();
-+    }
+     }
++
 +    // Paper start - Count visited parts
 +    private static final class TranslatableContentConsumer<T> implements FormattedText.ContentConsumer<T> {
-+        private static final IllegalArgumentException EX = new IllegalArgumentException("Too long");
++        private static final IllegalArgumentException NESTED_TOO_LONG = new IllegalArgumentException("Too long");
++
 +        private final FormattedText.ContentConsumer<T> visitor;
 +        private int visited;
 +
-+        private TranslatableContentConsumer(FormattedText.ContentConsumer<T> visitor) {
++        private TranslatableContentConsumer(final FormattedText.ContentConsumer<T> visitor) {
 +            this.visitor = visitor;
 +        }
 +
 +        @Override
 +        public Optional<T> accept(final String asString) {
-+            if (visited++ > 32) {
-+                throw EX;
++            if (this.visited++ > 32) {
++                throw NESTED_TOO_LONG;
 +            }
 +            return this.visitor.accept(asString);
 +        }
-     }
++    }
 +    // Paper end - Count visited parts
  
      @Override
-     public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException {
+     public MutableComponent resolve(@Nullable CommandSourceStack nbtPathPattern, @Nullable Entity entity, int recursionDepth) throws CommandSyntaxException {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/NbtContents.java.patch b/paper-server/patches/unapplied/net/minecraft/network/chat/contents/NbtContents.java.patch
deleted file mode 100644
index f30203349e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/contents/NbtContents.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/network/chat/contents/NbtContents.java
-+++ b/net/minecraft/network/chat/contents/NbtContents.java
-@@ -120,7 +120,7 @@
-             }).map(Tag::getAsString);
-             if (this.interpreting) {
-                 Component component = DataFixUtils.orElse(
--                    ComponentUtils.updateForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR
-+                    ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator
-                 );
-                 return stream.flatMap(text -> {
-                     try {
-@@ -132,7 +132,7 @@
-                     }
-                 }).reduce((accumulator, current) -> accumulator.append(component).append(current)).orElseGet(Component::empty);
-             } else {
--                return ComponentUtils.updateForEntity(source, this.separator, sender, depth)
-+                return ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth) // Paper - validate separator
-                     .map(
-                         text -> stream.map(Component::literal)
-                                 .reduce((accumulator, current) -> accumulator.append(text).append(current))

From 732cf86b9911bce3853dc51d6d80762c7ca1dbfe Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 13 Dec 2024 22:18:58 +0100
Subject: [PATCH 028/285] Update paperweight to 2.0.0-beta.1

---
 build.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index f57497cb0b..dd83603682 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -11,7 +11,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-SNAPSHOT" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.1" apply false
 }
 
 subprojects {

From f252b67a97812bc5500f383572dc8e8669189a3c Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 13:30:06 -0800
Subject: [PATCH 029/285] net.minecraft.core.dispenser

---
 .../BoatDispenseItemBehavior.java.patch       |  45 ++
 .../DefaultDispenseItemBehavior.java.patch    | 100 +++
 .../dispenser/DispenseItemBehavior.java.patch | 606 ++++++++++++++++
 .../EquipmentDispenseItemBehavior.java.patch  |  61 ++
 .../MinecartDispenseItemBehavior.java.patch   |  46 ++
 .../ProjectileDispenseBehavior.java.patch     |  52 ++
 .../ShearsDispenseItemBehavior.java.patch     |  57 ++
 .../ShulkerBoxDispenseBehavior.java.patch     |  42 ++
 .../BoatDispenseItemBehavior.java.patch       |  58 --
 .../DefaultDispenseItemBehavior.java.patch    | 126 ----
 .../dispenser/DispenseItemBehavior.java.patch | 651 ------------------
 .../EquipmentDispenseItemBehavior.java.patch  |  82 ---
 .../MinecartDispenseItemBehavior.java.patch   |  58 --
 .../ProjectileDispenseBehavior.java.patch     |  58 --
 .../ShearsDispenseItemBehavior.java.patch     |  81 ---
 .../ShulkerBoxDispenseBehavior.java.patch     |  54 --
 16 files changed, 1009 insertions(+), 1168 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..13ffca4845
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+@@ -40,13 +_,39 @@
+             d4 = 0.0;
+         }
+ 
++        // CraftBukkit start
++        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink at end and single item in event
++        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
++        if (!DispenserBlock.eventFired) {
++            serverLevel.getCraftServer().getPluginManager().callEvent(event);
++        }
++
++        if (event.isCancelled()) {
++            // stack.grow(1); // Paper - shrink below
++            return item;
++        }
++
++        boolean shrink = true; // Paper
++        if (!event.getItem().equals(craftItem)) {
++            shrink = false; // Paper - shrink below
++            // Chain to handler for new item
++            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++                idispensebehavior.dispense(blockSource, eventStack);
++                return item;
++            }
++        }
++        // CraftBukkit end
+         AbstractBoat abstractBoat = this.type.create(serverLevel, EntitySpawnReason.DISPENSER);
+         if (abstractBoat != null) {
+-            abstractBoat.setInitialPos(d1, d2 + d4, d3);
++            abstractBoat.setInitialPos(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); // CraftBukkit
+             EntityType.<AbstractBoat>createDefaultStackConfig(serverLevel, item, null).accept(abstractBoat);
+             abstractBoat.setYRot(direction.toYRot());
+-            serverLevel.addFreshEntity(abstractBoat);
+-            item.shrink(1);
++            if (serverLevel.addFreshEntity(abstractBoat) && shrink) item.shrink(1); // Paper - if entity add was successful and supposed to shrink
+         }
+ 
+         return item;
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..fcdecd3dc5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+@@ -8,25 +_,48 @@
+ import net.minecraft.world.level.block.DispenserBlock;
+ 
+ public class DefaultDispenseItemBehavior implements DispenseItemBehavior {
++    private Direction direction; // Paper - cache facing direction
+     private static final int DEFAULT_ACCURACY = 6;
+ 
++    // CraftBukkit start
++    private boolean dropper;
++
++    public DefaultDispenseItemBehavior(boolean dropper) {
++        this.dropper = dropper;
++    }
++
++    public DefaultDispenseItemBehavior() {}
++    // CraftBukkit end
++
+     @Override
+     public final ItemStack dispense(BlockSource blockSource, ItemStack item) {
++        this.direction = blockSource.state().getValue(DispenserBlock.FACING); // Paper - cache facing direction
+         ItemStack itemStack = this.execute(blockSource, item);
+         this.playSound(blockSource);
+-        this.playAnimation(blockSource, blockSource.state().getValue(DispenserBlock.FACING));
++        this.playAnimation(blockSource, this.direction); // Paper - cache facing direction
+         return itemStack;
+     }
+ 
+     protected ItemStack execute(BlockSource blockSource, ItemStack item) {
+-        Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
++        // Paper - cached enum direction
+         Position dispensePosition = DispenserBlock.getDispensePosition(blockSource);
+         ItemStack itemStack = item.split(1);
+-        spawnItem(blockSource.level(), itemStack, 6, direction, dispensePosition);
++        // CraftBukkit start
++        if (!DefaultDispenseItemBehavior.spawnItem(blockSource.level(), itemStack, 6, this.direction, dispensePosition, blockSource, this.dropper)) {
++            item.grow(1);
++        }
++        // CraftBukkit end
+         return item;
+     }
+ 
+     public static void spawnItem(Level level, ItemStack stack, int speed, Direction facing, Position position) {
++        // CraftBukkit start
++        ItemEntity itemEntity = prepareItem(level, stack, speed, facing, position);
++        level.addFreshEntity(itemEntity);
++    }
++
++    private static ItemEntity prepareItem(Level level, ItemStack stack, int speed, Direction facing, Position position) {
++        // CraftBukkit end
+         double d = position.x();
+         double d1 = position.y();
+         double d2 = position.z();
+@@ -44,6 +_,45 @@
+             level.random.triangle(facing.getStepZ() * d3, 0.0172275 * speed)
+         );
+         level.addFreshEntity(itemEntity);
++        return itemEntity; // CraftBukkit
++    }
++
++    // CraftBukkit - void -> boolean return, IPosition -> ISourceBlock last argument, dropper
++    public static boolean spawnItem(Level level, ItemStack stack, int speed, Direction facing, Position dispensePosition, BlockSource blockSource, boolean dropper) {
++        if (stack.isEmpty()) return true;
++        ItemEntity itemEntity = DefaultDispenseItemBehavior.prepareItem(level, stack, speed, facing, dispensePosition);
++
++        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack);
++
++        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement()));
++        if (!DispenserBlock.eventFired) {
++            level.getCraftServer().getPluginManager().callEvent(event);
++        }
++
++        if (event.isCancelled()) {
++            return false;
++        }
++
++        itemEntity.setItem(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
++        itemEntity.setDeltaMovement(org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getVelocity()));
++
++        if (!dropper && !event.getItem().getType().equals(craftItem.getType())) {
++            // Chain to handler for new item
++            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior.getClass() != DefaultDispenseItemBehavior.class) {
++                dispenseBehavior.dispense(blockSource, eventStack);
++            } else {
++                level.addFreshEntity(itemEntity);
++            }
++            return false;
++        }
++
++        level.addFreshEntity(itemEntity);
++
++        return true;
++        // CraftBukkit end
+     }
+ 
+     protected void playSound(BlockSource blockSource) {
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..687221251f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
@@ -0,0 +1,606 @@
+--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
+@@ -43,7 +_,9 @@
+ import net.minecraft.world.level.block.CandleCakeBlock;
+ import net.minecraft.world.level.block.CarvedPumpkinBlock;
+ import net.minecraft.world.level.block.DispenserBlock;
++import net.minecraft.world.level.block.LiquidBlockContainer;
+ import net.minecraft.world.level.block.RespawnAnchorBlock;
++import net.minecraft.world.level.block.SaplingBlock;
+ import net.minecraft.world.level.block.ShulkerBoxBlock;
+ import net.minecraft.world.level.block.SkullBlock;
+ import net.minecraft.world.level.block.TntBlock;
+@@ -82,16 +_,48 @@
+                 Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+                 EntityType<?> type = ((SpawnEggItem)item.getItem()).getType(blockSource.level().registryAccess(), item);
+ 
++                // CraftBukkit start
++                ServerLevel serverLevel = blockSource.level();
++                ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++                if (!DispenserBlock.eventFired) {
++                    serverLevel.getCraftServer().getPluginManager().callEvent(event);
++                }
++
++                if (event.isCancelled()) {
++                    // item.grow(1); // Paper - shrink below
++                    return item;
++                }
++
++                boolean shrink = true; // Paper
++                if (!event.getItem().equals(craftItem)) {
++                    shrink = false; // Paper - shrink below
++                    // Chain to handler for new item
++                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++                        idispensebehavior.dispense(blockSource, eventStack);
++                        return item;
++                    }
++                    // Paper start - track changed items in the dispense event
++                    itemstack1 = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified
++                    type = ((SpawnEggItem) itemstack1.getItem()).getType(serverLevel.registryAccess(), itemstack1);
++                    // Paper end - track changed item from dispense event
++                }
+                 try {
+                     type.spawn(
+-                        blockSource.level(), item, null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false
++                        blockSource.level(), itemstack1, null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false // Paper - track changed item in dispense event
+                     );
+                 } catch (Exception var6) {
+                     LOGGER.error("Error while dispensing spawn egg from dispenser at {}", blockSource.pos(), var6);
+                     return ItemStack.EMPTY;
+                 }
+ 
+-                item.shrink(1);
++                if (shrink) item.shrink(1); // Paper - actually handle here
++                // CraftBukkit end
+                 blockSource.level().gameEvent(null, GameEvent.ENTITY_PLACE, blockSource.pos());
+                 return item;
+             }
+@@ -109,12 +_,40 @@
+                     Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+                     BlockPos blockPos = blockSource.pos().relative(direction);
+                     ServerLevel serverLevel = blockSource.level();
++                    // CraftBukkit start
++                    ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                    org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++                    if (!DispenserBlock.eventFired) {
++                        serverLevel.getCraftServer().getPluginManager().callEvent(event);
++                    }
++
++                    if (event.isCancelled()) {
++                        // item.grow(1); // Paper - shrink below
++                        return item;
++                    }
++
++                    boolean shrink = true; // Paper
++                    if (!event.getItem().equals(craftItem)) {
++                        shrink = false; // Paper - shrink below
++                        // Chain to handler for new item
++                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                        DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                            dispenseBehavior.dispense(blockSource, eventStack);
++                            return item;
++                        }
++                    }
++                    // CraftBukkit end
++                    final ItemStack newStack = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()); // Paper - use event itemstack (unwrap is fine here because the stack won't be modified)
+                     Consumer<ArmorStand> consumer = EntityType.appendDefaultStackConfig(
+-                        armorStand1 -> armorStand1.setYRot(direction.toYRot()), serverLevel, item, null
++                        armorStand1 -> armorStand1.setYRot(direction.toYRot()), serverLevel, newStack, null // Paper - track changed items in the dispense event
+                     );
+                     ArmorStand armorStand = EntityType.ARMOR_STAND.spawn(serverLevel, consumer, blockPos, EntitySpawnReason.DISPENSER, false, false);
+                     if (armorStand != null) {
+-                        item.shrink(1);
++                        if (shrink) item.shrink(1); // Paper - actually handle here
+                     }
+ 
+                     return item;
+@@ -134,7 +_,36 @@
+                             livingEntity -> livingEntity instanceof Saddleable saddleable && !saddleable.isSaddled() && saddleable.isSaddleable()
+                         );
+                     if (!entitiesOfClass.isEmpty()) {
+-                        ((Saddleable)entitiesOfClass.get(0)).equipSaddle(item.split(1), SoundSource.BLOCKS);
++                        // CraftBukkit start
++                        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                        ServerLevel world = blockSource.level();
++                        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos());
++                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++                        org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity());
++                        if (!DispenserBlock.eventFired) {
++                            world.getCraftServer().getPluginManager().callEvent(event);
++                        }
++
++                        if (event.isCancelled()) {
++                            // item.grow(1); // Paper - shrink below
++                            return item;
++                        }
++
++                        boolean shrink = true; // Paper
++                        if (!event.getItem().equals(craftItem)) {
++                            shrink = false; // Paper - shrink below
++                            // Chain to handler for new item
++                            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
++                                idispensebehavior.dispense(blockSource, eventStack);
++                                return item;
++                            }
++                        }
++                        ((Saddleable) entitiesOfClass.get(0)).equipSaddle(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), SoundSource.BLOCKS); // Paper - track changed items in dispense event
++                        // CraftBukkit end
++                        if (shrink) item.shrink(1); // Paper - actually handle here
+                         this.setSuccess(true);
+                         return item;
+                     } else {
+@@ -156,8 +_,36 @@
+                             new AABB(blockPos),
+                             abstractChestedHorse1 -> abstractChestedHorse1.isAlive() && !abstractChestedHorse1.hasChest()
+                         )) {
+-                        if (abstractChestedHorse.isTamed() && abstractChestedHorse.getSlot(499).set(item)) {
+-                            item.shrink(1);
++                        if (abstractChestedHorse.isTamed()/* && abstractChestedHorse.getSlot(499).set(item)*/) {
++                            ItemStack singleCopy = item.copyWithCount(1); // Paper - shrink below
++                            ServerLevel world = blockSource.level();
++                            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos());
++                            org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy);
++                            org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity());
++                            if (!DispenserBlock.eventFired) {
++                                world.getCraftServer().getPluginManager().callEvent(event);
++                            }
++
++                            if (event.isCancelled()) {
++                                // stack.grow(1); // Paper - shrink below (this was actually missing and should be here, added it commented out to be consistent)
++                                return item;
++                            }
++
++                            boolean shrink = true; // Paper
++                            if (!event.getItem().equals(craftItem)) {
++                                shrink = false; // Paper - shrink below
++                                // Chain to handler for new item
++                                ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                                DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                                if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) { // Paper - fix possible StackOverflowError
++                                    dispenseBehavior.dispense(blockSource, eventStack);
++                                    return item;
++                                }
++                            }
++                            abstractChestedHorse.getSlot(499).set(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
++                            // CraftBukkit end
++
++                            if (shrink) item.shrink(1); // Paper - actually handle here
+                             this.setSuccess(true);
+                             return item;
+                         }
+@@ -195,8 +_,50 @@
+                 DispensibleContainerItem dispensibleContainerItem = (DispensibleContainerItem)item.getItem();
+                 BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+                 Level level = blockSource.level();
++                // CraftBukkit start
++                int x = blockPos.getX();
++                int y = blockPos.getY();
++                int z = blockPos.getZ();
++                BlockState iblockdata = level.getBlockState(blockPos);
++                ItemStack dispensedItem = item; // Paper - track changed item from the dispense event
++                // Paper start - correctly check if the bucket place will succeed
++                /* Taken from SolidBucketItem#emptyContents */
++                boolean willEmptyContentsSolidBucketItem = dispensibleContainerItem instanceof net.minecraft.world.item.SolidBucketItem && level.isInWorldBounds(blockPos) && iblockdata.isAir();
++                /* Taken from BucketItem#emptyContents */
++                boolean willEmptyBucketItem = dispensibleContainerItem instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, blockPos, iblockdata, bucketItem.content)));
++                if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
++                // Paper end - correctly check if the bucket place will succeed
++                    org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
++                    if (!DispenserBlock.eventFired) {
++                        level.getCraftServer().getPluginManager().callEvent(event);
++                    }
++
++                    if (event.isCancelled()) {
++                        return item;
++                    }
++
++                    if (!event.getItem().equals(craftItem)) {
++                        // Chain to handler for new item
++                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                        DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                            dispenseBehavior.dispense(blockSource, eventStack);
++                            return item;
++                        }
++                    }
++
++                    // Paper start - track changed item from dispense event
++                    dispensedItem = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()); // unwrap is safe here as the stack isn't mutated
++                    dispensibleContainerItem = (DispensibleContainerItem) dispensedItem.getItem();
++                    // Paper end - track changed item from dispense event
++                }
++                // CraftBukkit end
++
+                 if (dispensibleContainerItem.emptyContents(null, level, blockPos, null)) {
+-                    dispensibleContainerItem.checkExtraContent(null, level, item, blockPos);
++                    dispensibleContainerItem.checkExtraContent(null, level, dispensedItem, blockPos); // Paper - track changed item from dispense event
+                     return this.consumeWithRemainder(blockSource, item, new ItemStack(Items.BUCKET));
+                 } else {
+                     return this.defaultDispenseItemBehavior.dispense(blockSource, item);
+@@ -219,12 +_,37 @@
+                 BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+                 BlockState blockState = levelAccessor.getBlockState(blockPos);
+                 if (blockState.getBlock() instanceof BucketPickup bucketPickup) {
+-                    ItemStack itemStack = bucketPickup.pickupBlock(null, levelAccessor, blockPos, blockState);
++                    ItemStack itemStack = bucketPickup.pickupBlock(null, org.bukkit.craftbukkit.util.DummyGeneratorAccess.INSTANCE, blockPos, blockState); // CraftBukkit
+                     if (itemStack.isEmpty()) {
+                         return super.execute(blockSource, item);
+                     } else {
+                         levelAccessor.gameEvent(null, GameEvent.FLUID_PICKUP, blockPos);
+                         Item item1 = itemStack.getItem();
++                        // CraftBukkit start
++                        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockSource.pos());
++                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++                        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
++                        if (!DispenserBlock.eventFired) {
++                            levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event);
++                        }
++
++                        if (event.isCancelled()) {
++                            return item;
++                        }
++
++                        if (!event.getItem().equals(craftItem)) {
++                            // Chain to handler for new item
++                            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                                dispenseBehavior.dispense(blockSource, eventStack);
++                                return item;
++                            }
++                        }
++
++                        itemStack = bucketPickup.pickupBlock(null, levelAccessor, blockPos, blockState); // From above
++                        // CraftBukkit end
+                         return this.consumeWithRemainder(blockSource, item, new ItemStack(item1));
+                     }
+                 } else {
+@@ -236,17 +_,44 @@
+             @Override
+             protected ItemStack execute(BlockSource blockSource, ItemStack item) {
+                 ServerLevel serverLevel = blockSource.level();
++                // CraftBukkit start
++                org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
++
++                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++                if (!DispenserBlock.eventFired) {
++                    serverLevel.getCraftServer().getPluginManager().callEvent(event);
++                }
++
++                if (event.isCancelled()) {
++                    return item;
++                }
++
++                if (!event.getItem().equals(craftItem)) {
++                    // Chain to handler for new item
++                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                    DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                        dispenseBehavior.dispense(blockSource, eventStack);
++                        return item;
++                    }
++                }
++                // CraftBukkit end
+                 this.setSuccess(true);
+                 Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+                 BlockPos blockPos = blockSource.pos().relative(direction);
+                 BlockState blockState = serverLevel.getBlockState(blockPos);
+                 if (BaseFireBlock.canBePlacedAt(serverLevel, blockPos, direction)) {
+-                    serverLevel.setBlockAndUpdate(blockPos, BaseFireBlock.getState(serverLevel, blockPos));
+-                    serverLevel.gameEvent(null, GameEvent.BLOCK_PLACE, blockPos);
++                    // CraftBukkit start - Ignition by dispensing flint and steel
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(serverLevel, blockPos, blockSource.pos()).isCancelled()) {
++                        serverLevel.setBlockAndUpdate(blockPos, BaseFireBlock.getState(serverLevel, blockPos));
++                        serverLevel.gameEvent(null, GameEvent.BLOCK_PLACE, blockPos);
++                    }
++                    // CraftBukkit end
+                 } else if (CampfireBlock.canLight(blockState) || CandleBlock.canLight(blockState) || CandleCakeBlock.canLight(blockState)) {
+                     serverLevel.setBlockAndUpdate(blockPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)));
+                     serverLevel.gameEvent(null, GameEvent.BLOCK_CHANGE, blockPos);
+-                } else if (blockState.getBlock() instanceof TntBlock) {
++                } else if (blockState.getBlock() instanceof TntBlock && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(serverLevel, blockPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, blockSource.pos())) { // CraftBukkit - TNTPrimeEvent
+                     TntBlock.explode(serverLevel, blockPos);
+                     serverLevel.removeBlock(blockPos, false);
+                 } else {
+@@ -266,11 +_,62 @@
+                 this.setSuccess(true);
+                 Level level = blockSource.level();
+                 BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
++                // CraftBukkit start
++                org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++                if (!DispenserBlock.eventFired) {
++                    level.getCraftServer().getPluginManager().callEvent(event);
++                }
++
++                if (event.isCancelled()) {
++                    return item;
++                }
++
++                if (!event.getItem().equals(craftItem)) {
++                    // Chain to handler for new item
++                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                    DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                        dispenseBehavior.dispense(blockSource, eventStack);
++                        return item;
++                    }
++                }
++
++                level.captureTreeGeneration = true;
++                // CraftBukkit end
+                 if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) {
+                     this.setSuccess(false);
+                 } else if (!level.isClientSide) {
+                     level.levelEvent(1505, blockPos, 15);
+                 }
++                // CraftBukkit start
++                level.captureTreeGeneration = false;
++                if (level.capturedBlockStates.size() > 0) {
++                    org.bukkit.TreeType treeType = SaplingBlock.treeType;
++                    SaplingBlock.treeType = null;
++                    org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld());
++                    List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
++                    level.capturedBlockStates.clear();
++                    org.bukkit.event.world.StructureGrowEvent structureEvent = null;
++                    if (treeType != null) {
++                        structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks);
++                        org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
++                    }
++
++                    org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(location.getBlock(), null, blocks);
++                    fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
++                    org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
++
++                    if (!fertilizeEvent.isCancelled()) {
++                        for (org.bukkit.block.BlockState blockstate : blocks) {
++                            blockstate.update(true);
++                            blockSource.level().checkCapturedTreeStateForObserverNotify(blockPos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed
++                        }
++                    }
++                }
++                // CraftBukkit end
+ 
+                 return item;
+             }
+@@ -280,11 +_,39 @@
+             protected ItemStack execute(BlockSource blockSource, ItemStack item) {
+                 Level level = blockSource.level();
+                 BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+-                PrimedTnt primedTnt = new PrimedTnt(level, blockPos.getX() + 0.5, blockPos.getY(), blockPos.getZ() + 0.5, null);
++                // CraftBukkit start
++                ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink at end and single item in event
++                org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D));
++                if (!DispenserBlock.eventFired) {
++                    level.getCraftServer().getPluginManager().callEvent(event);
++                }
++
++                if (event.isCancelled()) {
++                    // item.grow(1); // Paper - shrink below
++                    return item;
++                }
++
++                boolean shrink = true; // Paper
++                if (!event.getItem().equals(craftItem)) {
++                    shrink = false; // Paper - shrink below
++                    // Chain to handler for new item
++                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                    DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                        dispenseBehavior.dispense(blockSource, eventStack);
++                        return item;
++                    }
++                }
++
++                PrimedTnt primedTnt = new PrimedTnt(level, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), null);
++                // CraftBukkit end
+                 level.addFreshEntity(primedTnt);
+                 level.playSound(null, primedTnt.getX(), primedTnt.getY(), primedTnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F);
+                 level.gameEvent(null, GameEvent.ENTITY_PLACE, blockPos);
+-                item.shrink(1);
++                if (shrink) item.shrink(1); // Paper - actually handle here
+                 return item;
+             }
+         });
+@@ -296,6 +_,29 @@
+                     Level level = blockSource.level();
+                     Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+                     BlockPos blockPos = blockSource.pos().relative(direction);
++                    // CraftBukkit start
++                    org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
++                    if (!DispenserBlock.eventFired) {
++                        level.getCraftServer().getPluginManager().callEvent(event);
++                    }
++
++                    if (event.isCancelled()) {
++                        return item;
++                    }
++
++                    if (!event.getItem().equals(craftItem)) {
++                        // Chain to handler for new item
++                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                        DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                            dispenseBehavior.dispense(blockSource, eventStack);
++                            return item;
++                        }
++                    }
++                    // CraftBukkit end
+                     if (level.isEmptyBlock(blockPos) && WitherSkullBlock.canSpawnMob(level, blockPos, item)) {
+                         level.setBlock(
+                             blockPos,
+@@ -313,7 +_,7 @@
+                         item.shrink(1);
+                         this.setSuccess(true);
+                     } else {
+-                        this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(blockSource, item));
++                        this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(blockSource, item, this)); // Paper - fix possible StackOverflowError
+                     }
+ 
+                     return item;
+@@ -326,6 +_,29 @@
+                 Level level = blockSource.level();
+                 BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+                 CarvedPumpkinBlock carvedPumpkinBlock = (CarvedPumpkinBlock)Blocks.CARVED_PUMPKIN;
++                // CraftBukkit start
++                org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
++                if (!DispenserBlock.eventFired) {
++                    level.getCraftServer().getPluginManager().callEvent(event);
++                }
++
++                if (event.isCancelled()) {
++                    return item;
++                }
++
++                if (!event.getItem().equals(craftItem)) {
++                    // Chain to handler for new item
++                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                    DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                        dispenseBehavior.dispense(blockSource, eventStack);
++                        return item;
++                    }
++                }
++                // CraftBukkit end
+                 if (level.isEmptyBlock(blockPos) && carvedPumpkinBlock.canSpawnGolem(level, blockPos)) {
+                     if (!level.isClientSide) {
+                         level.setBlock(blockPos, carvedPumpkinBlock.defaultBlockState(), 3);
+@@ -335,7 +_,7 @@
+                     item.shrink(1);
+                     this.setSuccess(true);
+                 } else {
+-                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(blockSource, item));
++                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(blockSource, item, this)); // Paper - fix possible StackOverflowError
+                 }
+ 
+                 return item;
+@@ -361,6 +_,29 @@
+                     ServerLevel serverLevel = blockSource.level();
+                     BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+                     BlockState blockState = serverLevel.getBlockState(blockPos);
++                    // CraftBukkit start
++                    org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - only single item in event
++
++                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos));
++                    if (!DispenserBlock.eventFired) {
++                        serverLevel.getCraftServer().getPluginManager().callEvent(event);
++                    }
++
++                    if (event.isCancelled()) {
++                        return item;
++                    }
++
++                    if (!event.getItem().equals(craftItem)) {
++                        // Chain to handler for new item
++                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                        DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                            dispenseBehavior.dispense(blockSource, eventStack);
++                            return item;
++                        }
++                    }
++                    // CraftBukkit end
+                     if (blockState.is(
+                             BlockTags.BEEHIVES,
+                             blockStateBase -> blockStateBase.hasProperty(BeehiveBlock.HONEY_LEVEL) && blockStateBase.getBlock() instanceof BeehiveBlock
+@@ -389,6 +_,13 @@
+                 this.setSuccess(true);
+                 if (blockState.is(Blocks.RESPAWN_ANCHOR)) {
+                     if (blockState.getValue(RespawnAnchorBlock.CHARGE) != 4) {
++                        // Paper start - Call missing BlockDispenseEvent
++                        ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this);
++                        if (result != null) {
++                            this.setSuccess(false);
++                            return result;
++                        }
++                        // Paper end - Call missing BlockDispenseEvent
+                         RespawnAnchorBlock.charge(null, level, blockPos, blockState);
+                         item.shrink(1);
+                     } else {
+@@ -412,6 +_,29 @@
+                     this.setSuccess(false);
+                     return item;
+                 } else {
++                    // CraftBukkit start
++                    org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
++
++                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity());
++                    if (!DispenserBlock.eventFired) {
++                        serverLevel.getCraftServer().getPluginManager().callEvent(event);
++                    }
++
++                    if (event.isCancelled()) {
++                        return item;
++                    }
++
++                    if (!event.getItem().equals(craftItem)) {
++                        // Chain to handler for new item
++                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                        DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
++                            idispensebehavior.dispense(blockSource, eventStack);
++                            return item;
++                        }
++                    }
++                    // CraftBukkit end
+                     for (Armadillo armadillo : entitiesOfClass) {
+                         if (armadillo.brushOffScute()) {
+                             item.hurtAndBreak(16, serverLevel, null, item1 -> {});
+@@ -432,6 +_,13 @@
+                 BlockState blockState = level.getBlockState(blockPos);
+                 Optional<BlockState> waxed = HoneycombItem.getWaxed(blockState);
+                 if (waxed.isPresent()) {
++                    // Paper start - Call missing BlockDispenseEvent
++                    ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this);
++                    if (result != null) {
++                        this.setSuccess(false);
++                        return result;
++                    }
++                    // Paper end - Call missing BlockDispenseEvent
+                     level.setBlockAndUpdate(blockPos, waxed.get());
+                     level.levelEvent(3003, blockPos, 0);
+                     item.shrink(1);
+@@ -459,6 +_,12 @@
+                         if (!serverLevel.getBlockState(blockPos1).is(BlockTags.CONVERTABLE_TO_MUD)) {
+                             return this.defaultDispenseItemBehavior.dispense(blockSource, item);
+                         } else {
++                            // Paper start - Call missing BlockDispenseEvent
++                            ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos1, item, this);
++                            if (result != null) {
++                                return result;
++                            }
++                            // Paper end - Call missing BlockDispenseEvent
+                             if (!serverLevel.isClientSide) {
+                                 for (int i = 0; i < 5; i++) {
+                                     serverLevel.sendParticles(
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..b3e53ec5b6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
@@ -0,0 +1,61 @@
+--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
+@@ -17,7 +_,13 @@
+         return dispenseEquipment(blockSource, item) ? item : super.execute(blockSource, item);
+     }
+ 
++    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper
+     public static boolean dispenseEquipment(BlockSource blockSource, ItemStack item) {
++        // Paper start
++        return dispenseEquipment(blockSource, item, null);
++    }
++    public static boolean dispenseEquipment(BlockSource blockSource, ItemStack item, @javax.annotation.Nullable DispenseItemBehavior currentBehavior) {
++        // Paper end
+         BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+         List<LivingEntity> entitiesOfClass = blockSource.level()
+             .getEntitiesOfClass(LivingEntity.class, new AABB(blockPos), entity -> entity.canEquipWithDispenser(item));
+@@ -26,13 +_,42 @@
+         } else {
+             LivingEntity livingEntity = entitiesOfClass.getFirst();
+             EquipmentSlot equipmentSlotForItem = livingEntity.getEquipmentSlotForItem(item);
+-            ItemStack itemStack = item.split(1);
+-            livingEntity.setItemSlot(equipmentSlotForItem, itemStack);
++            ItemStack itemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
++            // CraftBukkit start
++            net.minecraft.world.level.Level world = blockSource.level();
++            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos());
++            org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++
++            org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity());
++            if (!DispenserBlock.eventFired) {
++                world.getCraftServer().getPluginManager().callEvent(event);
++            }
++
++            if (event.isCancelled()) {
++                // stack.grow(1); // Paper - shrink below
++                return false;
++            }
++
++            boolean shrink = true; // Paper
++            if (!event.getItem().equals(craftItem)) {
++                shrink = false; // Paper - shrink below
++                // Chain to handler for new item
++                ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                DispenseItemBehavior dispenseItemBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                if (dispenseItemBehavior != DispenseItemBehavior.NOOP && (currentBehavior == null || dispenseItemBehavior != currentBehavior)) { // Paper - fix possible StackOverflowError
++                    dispenseItemBehavior.dispense(blockSource, eventStack);
++                    return true;
++                }
++            }
++
++            livingEntity.setItemSlot(equipmentSlotForItem, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()));
++            // CraftBukkit end
+             if (livingEntity instanceof Mob mob) {
+                 mob.setDropChance(equipmentSlotForItem, 2.0F);
+                 mob.setPersistenceRequired();
+             }
+ 
++            if (shrink) item.shrink(1); // Paper - shrink here
+             return true;
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..1f891175a1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
+@@ -57,12 +_,38 @@
+         }
+ 
+         Vec3 vec31 = new Vec3(d, d1 + d3, d2);
+-        AbstractMinecart abstractMinecart = AbstractMinecart.createMinecart(
+-            serverLevel, vec31.x, vec31.y, vec31.z, this.entityType, EntitySpawnReason.DISPENSER, item, null
+-        );
++        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++        org.bukkit.block.Block block2 = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z));
++        if (!DispenserBlock.eventFired) {
++            serverLevel.getCraftServer().getPluginManager().callEvent(event);
++        }
++
++        if (event.isCancelled()) {
++            // stack.grow(1); // Paper - shrink below
++            return item;
++        }
++
++        boolean shrink = true; // Paper
++        if (!event.getItem().equals(craftItem)) {
++            shrink = false; // Paper - shrink below
++            // Chain to handler for new item
++            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++            DispenseItemBehavior dispenseItemBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (dispenseItemBehavior != DispenseItemBehavior.NOOP && dispenseItemBehavior != this) {
++                dispenseItemBehavior.dispense(blockSource, eventStack);
++                return item;
++            }
++        }
++
++        itemstack1 = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++        AbstractMinecart abstractMinecart = AbstractMinecart.createMinecart(serverLevel, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.entityType, EntitySpawnReason.DISPENSER, itemstack1, null);
++
+         if (abstractMinecart != null) {
+-            serverLevel.addFreshEntity(abstractMinecart);
+-            item.shrink(1);
++            if (serverLevel.addFreshEntity(abstractMinecart) && shrink) item.shrink(1); // Paper - if entity add was successful and supposed to shrink
++            // CraftBukkit end
+         }
+ 
+         return item;
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
new file mode 100644
index 0000000000..8f98c3092d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
@@ -0,0 +1,52 @@
+--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
+@@ -27,16 +_,39 @@
+         ServerLevel serverLevel = blockSource.level();
+         Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+         Position dispensePosition = this.dispenseConfig.positionFunction().getDispensePosition(blockSource, direction);
+-        Projectile.spawnProjectileUsingShoot(
+-            this.projectileItem.asProjectile(serverLevel, dispensePosition, item, direction),
+-            serverLevel,
+-            item,
+-            direction.getStepX(),
+-            direction.getStepY(),
+-            direction.getStepZ(),
+-            this.dispenseConfig.power(),
+-            this.dispenseConfig.uncertainty()
+-        );
++        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++
++        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ()));
++        if (!DispenserBlock.eventFired) {
++            serverLevel.getCraftServer().getPluginManager().callEvent(event);
++        }
++
++        if (event.isCancelled()) {
++            // item.grow(1); // Paper - shrink below
++            return item;
++        }
++
++        boolean shrink = true; // Paper
++        if (!event.getItem().equals(craftItem)) {
++            shrink = false; // Paper - shrink below
++            // Chain to handler for new item
++            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++                idispensebehavior.dispense(blockSource, eventStack);
++                return item;
++            }
++        }
++
++        // SPIGOT-7923: Avoid create projectiles with empty item
++        if (!itemstack1.isEmpty()) {
++            Projectile iprojectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(serverLevel, dispensePosition, org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()), direction), serverLevel, itemstack1, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies
++            iprojectile.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(blockSource.blockEntity());
++        }
++        if (shrink) item.shrink(1); // Paper - actually handle here
++        // CraftBukkit end
+         item.shrink(1);
+         return item;
+     }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..fb3b9ff276
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+@@ -20,9 +_,32 @@
+     @Override
+     protected ItemStack execute(BlockSource blockSource, ItemStack item) {
+         ServerLevel serverLevel = blockSource.level();
++
++        // CraftBukkit start
++        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
++        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++        if (!DispenserBlock.eventFired) {
++            serverLevel.getCraftServer().getPluginManager().callEvent(event);
++        }
++
++        if (event.isCancelled()) {
++            return item;
++        }
++
++        if (!event.getItem().equals(craftItem)) {
++            // Chain to handler for new item
++            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                dispenseBehavior.dispense(blockSource, eventStack);
++                return item;
++            }
++        }
++        // CraftBukkit end
+         if (!serverLevel.isClientSide()) {
+             BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+-            this.setSuccess(tryShearBeehive(serverLevel, blockPos) || tryShearLivingEntity(serverLevel, blockPos, item));
++            this.setSuccess(tryShearBeehive(serverLevel, blockPos) || tryShearLivingEntity(serverLevel, blockPos, item, bukkitBlock, craftItem)); // CraftBukkit
+             if (this.isSuccess()) {
+                 item.hurtAndBreak(1, serverLevel, null, item1 -> {});
+             }
+@@ -50,10 +_,18 @@
+         return false;
+     }
+ 
+-    private static boolean tryShearLivingEntity(ServerLevel level, BlockPos pos, ItemStack stack) {
++    private static boolean tryShearLivingEntity(ServerLevel level, BlockPos pos, ItemStack stack, org.bukkit.block.Block bukkitBlock, org.bukkit.craftbukkit.inventory.CraftItemStack craftItem) { // CraftBukkit - add args
+         for (LivingEntity livingEntity : level.getEntitiesOfClass(LivingEntity.class, new AABB(pos), EntitySelector.NO_SPECTATORS)) {
+             if (livingEntity instanceof Shearable shearable && shearable.readyForShearing()) {
+-                shearable.shear(level, SoundSource.BLOCKS, stack);
++                // CraftBukkit start
++                // Paper start - Add drops to shear events
++                org.bukkit.event.block.BlockShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockShearEntityEvent(livingEntity, bukkitBlock, craftItem, shearable.generateDefaultDrops(level, stack));
++                if (event.isCancelled()) {
++                    // Paper end - Add drops to shear events
++                    continue;
++                }
++                // CraftBukkit end
++                shearable.shear(level, SoundSource.BLOCKS, stack, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events
+                 level.gameEvent(null, GameEvent.SHEAR, pos);
+                 return true;
+             }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
new file mode 100644
index 0000000000..56088e9418
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
+@@ -22,10 +_,38 @@
+             BlockPos blockPos = blockSource.pos().relative(direction);
+             Direction direction1 = blockSource.level().isEmptyBlock(blockPos.below()) ? direction : Direction.UP;
+ 
++            // CraftBukkit start
++            org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(blockSource.level(), blockSource.pos());
++            org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event
++
++            org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ()));
++            if (!DispenserBlock.eventFired) {
++                blockSource.level().getCraftServer().getPluginManager().callEvent(event);
++            }
++
++            if (event.isCancelled()) {
++                return item;
++            }
++
++            if (!event.getItem().equals(craftItem)) {
++                // Chain to handler for new item
++                ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
++                DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                    dispenseBehavior.dispense(blockSource, eventStack);
++                    return item;
++                }
++            }
++            // CraftBukkit end
+             try {
++                // Paper start - track changed items in the dispense event
+                 this.setSuccess(
+-                    ((BlockItem)item1).place(new DirectionalPlaceContext(blockSource.level(), blockPos, direction, item, direction1)).consumesAction()
++                    ((BlockItem) item1).place(new DirectionalPlaceContext(blockSource.level(), blockPos, direction, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), direction1)).consumesAction()
+                 );
++                if (this.isSuccess()) {
++                    item.shrink(1); // vanilla shrink is in the place function above, manually handle it here
++                }
++                // Paper end - track changed items in the dispense event
+             } catch (Exception var8) {
+                 LOGGER.error("Error trying to place shulker box at {}", blockPos, var8);
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
deleted file mode 100644
index a97acf2799..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,58 +0,0 @@
---- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
-@@ -11,6 +11,11 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.block.DispenserBlock;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
- 
- public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior {
- 
-@@ -43,14 +48,40 @@
-             d4 = 0.0D;
-         }
- 
-+        // CraftBukkit start
-+        ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink at end and single item in event
-+        org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+        CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+        BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
-+        if (!DispenserBlock.eventFired) {
-+            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+        }
-+
-+        if (event.isCancelled()) {
-+            // stack.grow(1); // Paper - shrink below
-+            return stack;
-+        }
-+
-+        boolean shrink = true; // Paper
-+        if (!event.getItem().equals(craftItem)) {
-+            shrink = false; // Paper - shrink below
-+            // Chain to handler for new item
-+            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                idispensebehavior.dispense(pointer, eventStack);
-+                return stack;
-+            }
-+        }
-+        // CraftBukkit end
-         AbstractBoat abstractboat = (AbstractBoat) this.type.create(worldserver, EntitySpawnReason.DISPENSER);
- 
-         if (abstractboat != null) {
--            abstractboat.setInitialPos(d1, d2 + d4, d3);
-+            abstractboat.setInitialPos(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()); // CraftBukkit
-             EntityType.createDefaultStackConfig(worldserver, stack, (Player) null).accept(abstractboat);
-             abstractboat.setYRot(enumdirection.toYRot());
--            worldserver.addFreshEntity(abstractboat);
--            stack.shrink(1);
-+            if (worldserver.addFreshEntity(abstractboat) && shrink) stack.shrink(1); // Paper - if entity add was successful and supposed to shrink
-         }
- 
-         return stack;
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
deleted file mode 100644
index 9a3359fc85..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,126 +0,0 @@
---- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
-@@ -6,47 +6,114 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.DispenserBlock;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.util.CraftVector;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
- 
- public class DefaultDispenseItemBehavior implements DispenseItemBehavior {
-+    private Direction enumdirection; // Paper - cache facing direction
- 
-     private static final int DEFAULT_ACCURACY = 6;
- 
-+    // CraftBukkit start
-+    private boolean dropper;
-+
-+    public DefaultDispenseItemBehavior(boolean dropper) {
-+        this.dropper = dropper;
-+    }
-+    // CraftBukkit end
-+
-     public DefaultDispenseItemBehavior() {}
- 
-     @Override
-     public final ItemStack dispense(BlockSource pointer, ItemStack stack) {
-+        enumdirection = pointer.state().getValue(DispenserBlock.FACING); // Paper - cache facing direction
-         ItemStack itemstack1 = this.execute(pointer, stack);
- 
-         this.playSound(pointer);
--        this.playAnimation(pointer, (Direction) pointer.state().getValue(DispenserBlock.FACING));
-+        this.playAnimation(pointer, enumdirection); // Paper - cache facing direction
-         return itemstack1;
-     }
- 
-     protected ItemStack execute(BlockSource pointer, ItemStack stack) {
--        Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-+        // Paper - cached enum direction
-         Position iposition = DispenserBlock.getDispensePosition(pointer);
-         ItemStack itemstack1 = stack.split(1);
- 
--        DefaultDispenseItemBehavior.spawnItem(pointer.level(), itemstack1, 6, enumdirection, iposition);
-+        // CraftBukkit start
-+        if (!DefaultDispenseItemBehavior.spawnItem(pointer.level(), itemstack1, 6, enumdirection, pointer, this.dropper)) {
-+            stack.grow(1);
-+        }
-+        // CraftBukkit end
-         return stack;
-     }
- 
-     public static void spawnItem(Level world, ItemStack stack, int speed, Direction side, Position pos) {
--        double d0 = pos.x();
--        double d1 = pos.y();
--        double d2 = pos.z();
-+        // CraftBukkit start
-+        ItemEntity entityitem = DefaultDispenseItemBehavior.prepareItem(world, stack, speed, side, pos);
-+        world.addFreshEntity(entityitem);
-+    }
- 
--        if (side.getAxis() == Direction.Axis.Y) {
-+    private static ItemEntity prepareItem(Level world, ItemStack itemstack, int i, Direction enumdirection, Position iposition) {
-+        // CraftBukkit end
-+        double d0 = iposition.x();
-+        double d1 = iposition.y();
-+        double d2 = iposition.z();
-+
-+        if (enumdirection.getAxis() == Direction.Axis.Y) {
-             d1 -= 0.125D;
-         } else {
-             d1 -= 0.15625D;
-         }
- 
--        ItemEntity entityitem = new ItemEntity(world, d0, d1, d2, stack);
-+        ItemEntity entityitem = new ItemEntity(world, d0, d1, d2, itemstack);
-         double d3 = world.random.nextDouble() * 0.1D + 0.2D;
- 
--        entityitem.setDeltaMovement(world.random.triangle((double) side.getStepX() * d3, 0.0172275D * (double) speed), world.random.triangle(0.2D, 0.0172275D * (double) speed), world.random.triangle((double) side.getStepZ() * d3, 0.0172275D * (double) speed));
-+        entityitem.setDeltaMovement(world.random.triangle((double) enumdirection.getStepX() * d3, 0.0172275D * (double) i), world.random.triangle(0.2D, 0.0172275D * (double) i), world.random.triangle((double) enumdirection.getStepZ() * d3, 0.0172275D * (double) i));
-+        // CraftBukkit start
-+        return entityitem;
-+    }
-+
-+    // CraftBukkit - void -> boolean return, IPosition -> ISourceBlock last argument, dropper
-+    public static boolean spawnItem(Level world, ItemStack itemstack, int i, Direction enumdirection, BlockSource sourceblock, boolean dropper) {
-+        if (itemstack.isEmpty()) return true;
-+        Position iposition = DispenserBlock.getDispensePosition(sourceblock);
-+        ItemEntity entityitem = DefaultDispenseItemBehavior.prepareItem(world, itemstack, i, enumdirection, iposition);
-+
-+        org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
-+        CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
-+
-+        BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), CraftVector.toBukkit(entityitem.getDeltaMovement()));
-+        if (!DispenserBlock.eventFired) {
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+        }
-+
-+        if (event.isCancelled()) {
-+            return false;
-+        }
-+
-+        entityitem.setItem(CraftItemStack.asNMSCopy(event.getItem()));
-+        entityitem.setDeltaMovement(CraftVector.toNMS(event.getVelocity()));
-+
-+        if (!dropper && !event.getItem().getType().equals(craftItem.getType())) {
-+            // Chain to handler for new item
-+            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(sourceblock, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior.getClass() != DefaultDispenseItemBehavior.class) {
-+                idispensebehavior.dispense(sourceblock, eventStack);
-+            } else {
-+                world.addFreshEntity(entityitem);
-+            }
-+            return false;
-+        }
-+
-         world.addFreshEntity(entityitem);
-+
-+        return true;
-+        // CraftBukkit end
-     }
- 
-     protected void playSound(BlockSource pointer) {
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
deleted file mode 100644
index 824b11e0d1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,651 +0,0 @@
---- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
-@@ -28,6 +28,7 @@
- import net.minecraft.world.entity.item.PrimedTnt;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.BoneMealItem;
-+import net.minecraft.world.item.BucketItem;
- import net.minecraft.world.item.DispensibleContainerItem;
- import net.minecraft.world.item.DyeColor;
- import net.minecraft.world.item.HoneycombItem;
-@@ -47,7 +48,9 @@
- import net.minecraft.world.level.block.CandleCakeBlock;
- import net.minecraft.world.level.block.CarvedPumpkinBlock;
- import net.minecraft.world.level.block.DispenserBlock;
-+import net.minecraft.world.level.block.LiquidBlockContainer;
- import net.minecraft.world.level.block.RespawnAnchorBlock;
-+import net.minecraft.world.level.block.SaplingBlock;
- import net.minecraft.world.level.block.ShulkerBoxBlock;
- import net.minecraft.world.level.block.SkullBlock;
- import net.minecraft.world.level.block.TntBlock;
-@@ -62,6 +65,17 @@
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
- import org.slf4j.Logger;
-+import org.bukkit.Location;
-+import org.bukkit.TreeType;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
-+import org.bukkit.event.block.BlockDispenseArmorEvent;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+import org.bukkit.event.block.BlockFertilizeEvent;
-+import org.bukkit.event.world.StructureGrowEvent;
-+// CraftBukkit end
- 
- public interface DispenseItemBehavior {
- 
-@@ -90,14 +104,47 @@
-                 Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-                 EntityType<?> entitytypes = ((SpawnEggItem) stack.getItem()).getType(pointer.level().registryAccess(), stack);
- 
-+                // CraftBukkit start
-+                ServerLevel worldserver = pointer.level();
-+                ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
-+                org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    // stack.grow(1); // Paper - shrink below
-+                    return stack;
-+                }
-+
-+                boolean shrink = true; // Paper
-+                if (!event.getItem().equals(craftItem)) {
-+                    shrink = false; // Paper - shrink below
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                    // Paper start - track changed items in the dispense event
-+                    itemstack1 = CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified
-+                    entitytypes = ((SpawnEggItem) itemstack1.getItem()).getType(worldserver.registryAccess(), itemstack1);
-+                    // Paper end - track changed item from dispense event
-+                }
-+
-                 try {
--                    entitytypes.spawn(pointer.level(), stack, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false);
-+                    entitytypes.spawn(pointer.level(), itemstack1, (Player) null, pointer.pos().relative(enumdirection), EntitySpawnReason.DISPENSER, enumdirection != Direction.UP, false); // Paper - track changed item in dispense event
-                 } catch (Exception exception) {
--                    null.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception);
-+                    DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", pointer.pos(), exception); // CraftBukkit - decompile error
-                     return ItemStack.EMPTY;
-                 }
- 
--                stack.shrink(1);
-+                if (shrink) stack.shrink(1); // Paper - actually handle here
-+                // CraftBukkit end
-                 pointer.level().gameEvent((Entity) null, (Holder) GameEvent.ENTITY_PLACE, pointer.pos());
-                 return stack;
-             }
-@@ -116,13 +163,43 @@
-                 Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-                 BlockPos blockposition = pointer.pos().relative(enumdirection);
-                 ServerLevel worldserver = pointer.level();
-+
-+                // CraftBukkit start
-+                ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
-+                org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    // stack.grow(1); // Paper - shrink below
-+                    return stack;
-+                }
-+
-+                boolean shrink = true; // Paper
-+                if (!event.getItem().equals(craftItem)) {
-+                    shrink = false; // Paper - shrink below
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                // CraftBukkit end
-+
-+                final ItemStack newStack = CraftItemStack.unwrap(event.getItem()); // Paper - use event itemstack (unwrap is fine here because the stack won't be modified)
-                 Consumer<ArmorStand> consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> {
-                     entityarmorstand.setYRot(enumdirection.toYRot());
--                }, worldserver, stack, (Player) null);
-+                }, worldserver, newStack, (Player) null); // Paper - track changed items in the dispense event
-                 ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, consumer, blockposition, EntitySpawnReason.DISPENSER, false, false);
- 
-                 if (entityarmorstand != null) {
--                    stack.shrink(1);
-+                    if (shrink) stack.shrink(1); // Paper - actually handle here
-                 }
- 
-                 return stack;
-@@ -141,7 +218,36 @@
-                 });
- 
-                 if (!list.isEmpty()) {
--                    ((Saddleable) list.get(0)).equipSaddle(stack.split(1), SoundSource.BLOCKS);
-+                    // CraftBukkit start
-+                    ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
-+                    ServerLevel world = pointer.level();
-+                    org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos());
-+                    CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+                    BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity());
-+                    if (!DispenserBlock.eventFired) {
-+                        world.getCraftServer().getPluginManager().callEvent(event);
-+                    }
-+
-+                    if (event.isCancelled()) {
-+                        // stack.grow(1); // Paper - shrink below
-+                        return stack;
-+                    }
-+
-+                    boolean shrink = true; // Paper
-+                    if (!event.getItem().equals(craftItem)) {
-+                        shrink = false; // Paper - shrink below
-+                        // Chain to handler for new item
-+                        ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                        DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                        if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
-+                            idispensebehavior.dispense(pointer, eventStack);
-+                            return stack;
-+                        }
-+                    }
-+                    ((Saddleable) list.get(0)).equipSaddle(CraftItemStack.asNMSCopy(event.getItem()), SoundSource.BLOCKS); // Paper - track changed items in dispense event
-+                    // CraftBukkit end
-+                    if (shrink) stack.shrink(1); // Paper - actually handle here
-                     this.setSuccess(true);
-                     return stack;
-                 } else {
-@@ -166,9 +272,38 @@
-                     }
- 
-                     entityhorsechestedabstract = (AbstractChestedHorse) iterator1.next();
--                } while (!entityhorsechestedabstract.isTamed() || !entityhorsechestedabstract.getSlot(499).set(stack));
-+                    // CraftBukkit start
-+                } while (!entityhorsechestedabstract.isTamed());
-+                ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below
-+                ServerLevel world = pointer.level();
-+                org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
- 
--                stack.shrink(1);
-+                BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity());
-+                if (!DispenserBlock.eventFired) {
-+                    world.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    // stack.grow(1); // Paper - shrink below (this was actually missing and should be here, added it commented out to be consistent)
-+                    return stack;
-+                }
-+
-+                boolean shrink = true; // Paper
-+                if (!event.getItem().equals(craftItem)) {
-+                    shrink = false; // Paper - shrink below
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                entityhorsechestedabstract.getSlot(499).set(CraftItemStack.asNMSCopy(event.getItem()));
-+                // CraftBukkit end
-+
-+                if (shrink) stack.shrink(1); // Paper - actually handle here
-                 this.setSuccess(true);
-                 return stack;
-             }
-@@ -202,8 +337,50 @@
-                 BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
-                 ServerLevel worldserver = pointer.level();
- 
-+                // CraftBukkit start
-+                int x = blockposition.getX();
-+                int y = blockposition.getY();
-+                int z = blockposition.getZ();
-+                BlockState iblockdata = worldserver.getBlockState(blockposition);
-+                ItemStack dispensedItem = stack; // Paper - track changed item from the dispense event
-+                // Paper start - correctly check if the bucket place will succeed
-+                /* Taken from SolidBucketItem#emptyContents */
-+                boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir();
-+                /* Taken from BucketItem#emptyContents */
-+                boolean willEmptyBucketItem = dispensiblecontaineritem instanceof final BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, worldserver, blockposition, iblockdata, bucketItem.content)));
-+                if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
-+                // Paper end - correctly check if the bucket place will succeed
-+                    org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+                    CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
-+
-+                    BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
-+                    if (!DispenserBlock.eventFired) {
-+                        worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                    }
-+
-+                    if (event.isCancelled()) {
-+                        return stack;
-+                    }
-+
-+                    if (!event.getItem().equals(craftItem)) {
-+                        // Chain to handler for new item
-+                        ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                        DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                        if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                            idispensebehavior.dispense(pointer, eventStack);
-+                            return stack;
-+                        }
-+                    }
-+
-+                    // Paper start - track changed item from dispense event
-+                    dispensedItem = CraftItemStack.unwrap(event.getItem()); // unwrap is safe here as the stack isn't mutated
-+                    dispensiblecontaineritem = (DispensibleContainerItem) dispensedItem.getItem();
-+                    // Paper end - track changed item from dispense event
-+                }
-+                // CraftBukkit end
-+
-                 if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) {
--                    dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, stack, blockposition);
-+                    dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, dispensedItem, blockposition); // Paper - track changed item from dispense event
-                     return this.consumeWithRemainder(pointer, stack, new ItemStack(Items.BUCKET));
-                 } else {
-                     return this.defaultDispenseItemBehavior.dispense(pointer, stack);
-@@ -229,7 +406,7 @@
-                 Block block = iblockdata.getBlock();
- 
-                 if (block instanceof BucketPickup ifluidsource) {
--                    ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, worldserver, blockposition, iblockdata);
-+                    ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); // CraftBukkit
- 
-                     if (itemstack1.isEmpty()) {
-                         return super.execute(pointer, stack);
-@@ -237,6 +414,32 @@
-                         worldserver.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, blockposition);
-                         Item item = itemstack1.getItem();
- 
-+                        // CraftBukkit start
-+                        org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+                        CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
-+
-+                        BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
-+                        if (!DispenserBlock.eventFired) {
-+                            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                        }
-+
-+                        if (event.isCancelled()) {
-+                            return stack;
-+                        }
-+
-+                        if (!event.getItem().equals(craftItem)) {
-+                            // Chain to handler for new item
-+                            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                                idispensebehavior.dispense(pointer, eventStack);
-+                                return stack;
-+                            }
-+                        }
-+
-+                        itemstack1 = ifluidsource.pickupBlock((Player) null, worldserver, blockposition, iblockdata); // From above
-+                        // CraftBukkit end
-+
-                         return this.consumeWithRemainder(pointer, stack, new ItemStack(item));
-                     }
-                 } else {
-@@ -249,16 +452,44 @@
-             protected ItemStack execute(BlockSource pointer, ItemStack stack) {
-                 ServerLevel worldserver = pointer.level();
- 
-+                // CraftBukkit start
-+                org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    return stack;
-+                }
-+
-+                if (!event.getItem().equals(craftItem)) {
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 this.setSuccess(true);
-                 Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-                 BlockPos blockposition = pointer.pos().relative(enumdirection);
-                 BlockState iblockdata = worldserver.getBlockState(blockposition);
- 
-                 if (BaseFireBlock.canBePlacedAt(worldserver, blockposition, enumdirection)) {
--                    worldserver.setBlockAndUpdate(blockposition, BaseFireBlock.getState(worldserver, blockposition));
--                    worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition);
-+                    // CraftBukkit start - Ignition by dispensing flint and steel
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(worldserver, blockposition, pointer.pos()).isCancelled()) {
-+                        worldserver.setBlockAndUpdate(blockposition, BaseFireBlock.getState(worldserver, blockposition));
-+                        worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition);
-+                    }
-+                    // CraftBukkit end
-                 } else if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) {
--                    if (iblockdata.getBlock() instanceof TntBlock) {
-+                    if (iblockdata.getBlock() instanceof TntBlock && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(worldserver, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, pointer.pos())) { // CraftBukkit - TNTPrimeEvent
-                         TntBlock.explode(worldserver, blockposition);
-                         worldserver.removeBlock(blockposition, false);
-                     } else {
-@@ -283,13 +514,64 @@
-                 this.setSuccess(true);
-                 ServerLevel worldserver = pointer.level();
-                 BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
-+                // CraftBukkit start
-+                org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
- 
-+                BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    return stack;
-+                }
-+
-+                if (!event.getItem().equals(craftItem)) {
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+
-+                worldserver.captureTreeGeneration = true;
-+                // CraftBukkit end
-+
-                 if (!BoneMealItem.growCrop(stack, worldserver, blockposition) && !BoneMealItem.growWaterPlant(stack, worldserver, blockposition, (Direction) null)) {
-                     this.setSuccess(false);
-                 } else if (!worldserver.isClientSide) {
-                     worldserver.levelEvent(1505, blockposition, 15);
-                 }
-+                // CraftBukkit start
-+                worldserver.captureTreeGeneration = false;
-+                if (worldserver.capturedBlockStates.size() > 0) {
-+                    TreeType treeType = SaplingBlock.treeType;
-+                    SaplingBlock.treeType = null;
-+                    Location location = CraftLocation.toBukkit(blockposition, worldserver.getWorld());
-+                    List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(worldserver.capturedBlockStates.values());
-+                    worldserver.capturedBlockStates.clear();
-+                    StructureGrowEvent structureEvent = null;
-+                    if (treeType != null) {
-+                        structureEvent = new StructureGrowEvent(location, treeType, false, null, blocks);
-+                        org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
-+                    }
- 
-+                    BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(location.getBlock(), null, blocks);
-+                    fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
-+                    org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
-+
-+                    if (!fertilizeEvent.isCancelled()) {
-+                        for (org.bukkit.block.BlockState blockstate : blocks) {
-+                            blockstate.update(true);
-+                            worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed
-+                        }
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 return stack;
-             }
-         });
-@@ -298,12 +580,42 @@
-             protected ItemStack execute(BlockSource pointer, ItemStack stack) {
-                 ServerLevel worldserver = pointer.level();
-                 BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
--                PrimedTnt entitytntprimed = new PrimedTnt(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (LivingEntity) null);
-+                // CraftBukkit start
-+                // EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null);
- 
-+                ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink at end and single item in event
-+                org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D));
-+                if (!DispenserBlock.eventFired) {
-+                   worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    // stack.grow(1); // Paper - shrink below
-+                    return stack;
-+                }
-+
-+                boolean shrink = true; // Paper
-+                if (!event.getItem().equals(craftItem)) {
-+                    shrink = false; // Paper - shrink below
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+
-+                PrimedTnt entitytntprimed = new PrimedTnt(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (LivingEntity) null);
-+                // CraftBukkit end
-+
-                 worldserver.addFreshEntity(entitytntprimed);
-                 worldserver.playSound((Player) null, entitytntprimed.getX(), entitytntprimed.getY(), entitytntprimed.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F);
-                 worldserver.gameEvent((Entity) null, (Holder) GameEvent.ENTITY_PLACE, blockposition);
--                stack.shrink(1);
-+                if (shrink) stack.shrink(1); // Paper - actually handle here
-                 return stack;
-             }
-         });
-@@ -313,7 +625,31 @@
-                 ServerLevel worldserver = pointer.level();
-                 Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-                 BlockPos blockposition = pointer.pos().relative(enumdirection);
-+
-+                // CraftBukkit start
-+                org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
- 
-+                if (event.isCancelled()) {
-+                    return stack;
-+                }
-+
-+                if (!event.getItem().equals(craftItem)) {
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 if (worldserver.isEmptyBlock(blockposition) && WitherSkullBlock.canSpawnMob(worldserver, blockposition, stack)) {
-                     worldserver.setBlock(blockposition, (BlockState) Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(enumdirection)), 3);
-                     worldserver.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_PLACE, blockposition);
-@@ -326,7 +662,7 @@
-                     stack.shrink(1);
-                     this.setSuccess(true);
-                 } else {
--                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack));
-+                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError
-                 }
- 
-                 return stack;
-@@ -339,6 +675,30 @@
-                 BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
-                 CarvedPumpkinBlock blockpumpkincarved = (CarvedPumpkinBlock) Blocks.CARVED_PUMPKIN;
- 
-+                // CraftBukkit start
-+                org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    return stack;
-+                }
-+
-+                if (!event.getItem().equals(craftItem)) {
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 if (worldserver.isEmptyBlock(blockposition) && blockpumpkincarved.canSpawnGolem(worldserver, blockposition)) {
-                     if (!worldserver.isClientSide) {
-                         worldserver.setBlock(blockposition, blockpumpkincarved.defaultBlockState(), 3);
-@@ -348,7 +708,7 @@
-                     stack.shrink(1);
-                     this.setSuccess(true);
-                 } else {
--                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack));
-+                    this.setSuccess(EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this)); // Paper - fix possible StackOverflowError
-                 }
- 
-                 return stack;
-@@ -377,6 +737,30 @@
-                 BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
-                 BlockState iblockdata = worldserver.getBlockState(blockposition);
- 
-+                // CraftBukkit start
-+                org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+                CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - only single item in event
-+
-+                BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
-+                if (!DispenserBlock.eventFired) {
-+                    worldserver.getCraftServer().getPluginManager().callEvent(event);
-+                }
-+
-+                if (event.isCancelled()) {
-+                    return stack;
-+                }
-+
-+                if (!event.getItem().equals(craftItem)) {
-+                    // Chain to handler for new item
-+                    ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(pointer, eventStack);
-+                        return stack;
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 if (iblockdata.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> {
-                     return blockbase_blockdata.hasProperty(BeehiveBlock.HONEY_LEVEL) && blockbase_blockdata.getBlock() instanceof BeehiveBlock;
-                 }) && (Integer) iblockdata.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
-@@ -402,6 +786,13 @@
-                 this.setSuccess(true);
-                 if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) {
-                     if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) {
-+                        // Paper start - Call missing BlockDispenseEvent
-+                        ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this);
-+                        if (result != null) {
-+                            this.setSuccess(false);
-+                            return result;
-+                        }
-+                        // Paper end - Call missing BlockDispenseEvent
-                         RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata);
-                         stack.shrink(1);
-                     } else {
-@@ -426,6 +817,31 @@
-                     this.setSuccess(false);
-                     return stack;
-                 } else {
-+                    // CraftBukkit start
-+                    ItemStack itemstack1 = stack;
-+                    ServerLevel world = pointer.level();
-+                    org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos());
-+                    CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); // Paper - ignore stack size on damageable items
-+
-+                    BlockDispenseEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity());
-+                    if (!DispenserBlock.eventFired) {
-+                        world.getCraftServer().getPluginManager().callEvent(event);
-+                    }
-+
-+                    if (event.isCancelled()) {
-+                        return stack;
-+                    }
-+
-+                    if (!event.getItem().equals(craftItem)) {
-+                        // Chain to handler for new item
-+                        ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                        DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                        if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
-+                            idispensebehavior.dispense(pointer, eventStack);
-+                            return stack;
-+                        }
-+                    }
-+                    // CraftBukkit end
-                     Iterator iterator1 = list.iterator();
- 
-                     Armadillo armadillo;
-@@ -454,6 +870,13 @@
-                 Optional<BlockState> optional = HoneycombItem.getWaxed(iblockdata);
- 
-                 if (optional.isPresent()) {
-+                    // Paper start - Call missing BlockDispenseEvent
-+                    ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this);
-+                    if (result != null) {
-+                        this.setSuccess(false);
-+                        return result;
-+                    }
-+                    // Paper end - Call missing BlockDispenseEvent
-                     worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get());
-                     worldserver.levelEvent(3003, blockposition, 0);
-                     stack.shrink(1);
-@@ -481,6 +904,12 @@
-                     if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) {
-                         return this.defaultDispenseItemBehavior.dispense(pointer, stack);
-                     } else {
-+                        // Paper start - Call missing BlockDispenseEvent
-+                        ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this);
-+                        if (result != null) {
-+                            return result;
-+                        }
-+                        // Paper end - Call missing BlockDispenseEvent
-                         if (!worldserver.isClientSide) {
-                             for (int k = 0; k < 5; ++k) {
-                                 worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D);
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
deleted file mode 100644
index 18eb499fba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,82 +0,0 @@
---- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
-@@ -7,8 +7,13 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.Mob;
- import net.minecraft.world.item.ItemStack;
-+import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.DispenserBlock;
- import net.minecraft.world.phys.AABB;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseArmorEvent;
-+// CraftBukkit end
- 
- public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior {
- 
-@@ -18,10 +23,15 @@
- 
-     @Override
-     protected ItemStack execute(BlockSource pointer, ItemStack stack) {
--        return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack) ? stack : super.execute(pointer, stack);
-+        return EquipmentDispenseItemBehavior.dispenseEquipment(pointer, stack, this) ? stack : super.execute(pointer, stack); // Paper - fix possible StackOverflowError
-     }
- 
--    public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack) {
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper
-+    public static boolean dispenseEquipment(BlockSource pointer, ItemStack armor) {
-+        // Paper start
-+        return dispenseEquipment(pointer, armor, null);
-+    }
-+    public static boolean dispenseEquipment(BlockSource pointer, ItemStack stack, @javax.annotation.Nullable DispenseItemBehavior currentBehavior) {
-         BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
-         List<LivingEntity> list = pointer.level().getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), (entityliving) -> {
-             return entityliving.canEquipWithDispenser(stack);
-@@ -32,9 +42,37 @@
-         } else {
-             LivingEntity entityliving = (LivingEntity) list.getFirst();
-             EquipmentSlot enumitemslot = entityliving.getEquipmentSlotForItem(stack);
--            ItemStack itemstack1 = stack.split(1);
-+            ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
- 
--            entityliving.setItemSlot(enumitemslot, itemstack1);
-+            // CraftBukkit start
-+            Level world = pointer.level();
-+            org.bukkit.block.Block block = CraftBlock.at(world, pointer.pos());
-+            CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+            BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityliving.getBukkitEntity());
-+            if (!DispenserBlock.eventFired) {
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+            }
-+
-+            if (event.isCancelled()) {
-+                // stack.grow(1); // Paper - shrink below
-+                return false;
-+            }
-+
-+            boolean shrink = true; // Paper
-+            if (!event.getItem().equals(craftItem)) {
-+                shrink = false; // Paper - shrink below
-+                // Chain to handler for new item
-+                ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                if (idispensebehavior != DispenseItemBehavior.NOOP && (currentBehavior == null || idispensebehavior != currentBehavior)) { // Paper - fix possible StackOverflowError
-+                    idispensebehavior.dispense(pointer, eventStack);
-+                    return true;
-+                }
-+            }
-+
-+            entityliving.setItemSlot(enumitemslot, CraftItemStack.asNMSCopy(event.getItem()));
-+            // CraftBukkit end
-             if (entityliving instanceof Mob) {
-                 Mob entityinsentient = (Mob) entityliving;
- 
-@@ -42,6 +80,7 @@
-                 entityinsentient.setPersistenceRequired();
-             }
- 
-+            if (shrink) stack.shrink(1); // Paper - shrink here
-             return true;
-         }
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
deleted file mode 100644
index ea22a6dd42..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,58 +0,0 @@
---- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
-@@ -15,6 +15,11 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
- 
- public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior {
- 
-@@ -62,11 +67,40 @@
-         }
- 
-         Vec3 vec3d1 = new Vec3(d0, d1 + d3, d2);
--        AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, vec3d1.x, vec3d1.y, vec3d1.z, this.entityType, EntitySpawnReason.DISPENSER, stack, (Player) null);
-+        // CraftBukkit start
-+        // EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.createMinecart(worldserver, vec3d1.x, vec3d1.y, vec3d1.z, this.entityType, EntitySpawnReason.DISPENSER, itemstack, (EntityHuman) null);
-+        ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
-+        org.bukkit.block.Block block2 = CraftBlock.at(worldserver, pointer.pos());
-+        CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
- 
-+        BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec3d1.x, vec3d1.y, vec3d1.z));
-+        if (!DispenserBlock.eventFired) {
-+            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+        }
-+
-+        if (event.isCancelled()) {
-+            // stack.grow(1); // Paper - shrink below
-+            return stack;
-+        }
-+
-+        boolean shrink = true; // Paper
-+        if (!event.getItem().equals(craftItem)) {
-+            shrink = false; // Paper - shrink below
-+            // Chain to handler for new item
-+            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                idispensebehavior.dispense(pointer, eventStack);
-+                return stack;
-+            }
-+        }
-+
-+        itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
-+        AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.entityType, EntitySpawnReason.DISPENSER, itemstack1, (Player) null);
-+
-         if (entityminecartabstract != null) {
--            worldserver.addFreshEntity(entityminecartabstract);
--            stack.shrink(1);
-+            if (worldserver.addFreshEntity(entityminecartabstract) && shrink) stack.shrink(1); // Paper - if entity add was successful and supposed to shrink
-+            // CraftBukkit end
-         }
- 
-         return stack;
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
deleted file mode 100644
index 2061a38275..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
+++ /dev/null
@@ -1,58 +0,0 @@
---- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
-+++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
-@@ -8,6 +8,11 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.ProjectileItem;
- import net.minecraft.world.level.block.DispenserBlock;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
- 
- public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior {
- 
-@@ -31,8 +36,41 @@
-         Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
-         Position iposition = this.dispenseConfig.positionFunction().getDispensePosition(pointer, enumdirection);
- 
--        Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, stack, enumdirection), worldserver, stack, (double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty());
--        stack.shrink(1);
-+        // CraftBukkit start
-+        // IProjectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, itemstack, enumdirection), worldserver, itemstack, (double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // CraftBukkit - call when finish the BlockDispenseEvent
-+        ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event
-+        org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
-+        CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
-+
-+        BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ()));
-+        if (!DispenserBlock.eventFired) {
-+            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+        }
-+
-+        if (event.isCancelled()) {
-+            // stack.grow(1); // Paper - shrink below
-+            return stack;
-+        }
-+
-+        boolean shrink = true; // Paper
-+        if (!event.getItem().equals(craftItem)) {
-+            shrink = false; // Paper - shrink below
-+            // Chain to handler for new item
-+            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                idispensebehavior.dispense(pointer, eventStack);
-+                return stack;
-+            }
-+        }
-+
-+        // SPIGOT-7923: Avoid create projectiles with empty item
-+        if (!itemstack1.isEmpty()) {
-+            Projectile iprojectile = Projectile.spawnProjectileUsingShoot(this.projectileItem.asProjectile(worldserver, iposition, CraftItemStack.unwrap(event.getItem()), enumdirection), worldserver, itemstack1, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.dispenseConfig.power(), this.dispenseConfig.uncertainty()); // Paper - track changed items in the dispense event; unwrap is safe here because all uses of the stack make their own copies
-+            iprojectile.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(pointer.blockEntity());
-+        }
-+        if (shrink) stack.shrink(1); // Paper - actually handle here
-+        // CraftBukkit end
-         return stack;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
deleted file mode 100644
index bc579690dd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
+++ /dev/null
@@ -1,81 +0,0 @@
---- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
-+++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
-@@ -22,6 +22,12 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.AABB;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
- 
- public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
- 
-@@ -30,11 +36,34 @@
-     @Override
-     protected ItemStack execute(BlockSource pointer, ItemStack stack) {
-         ServerLevel worldserver = pointer.level();
-+        // CraftBukkit start
-+        org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, pointer.pos());
-+        CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items
- 
-+        BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
-+        if (!DispenserBlock.eventFired) {
-+            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+        }
-+
-+        if (event.isCancelled()) {
-+            return stack;
-+        }
-+
-+        if (!event.getItem().equals(craftItem)) {
-+            // Chain to handler for new item
-+            ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                idispensebehavior.dispense(pointer, eventStack);
-+                return stack;
-+            }
-+        }
-+        // CraftBukkit end
-+
-         if (!worldserver.isClientSide()) {
-             BlockPos blockposition = pointer.pos().relative((Direction) pointer.state().getValue(DispenserBlock.FACING));
- 
--            this.setSuccess(ShearsDispenseItemBehavior.tryShearBeehive(worldserver, blockposition) || ShearsDispenseItemBehavior.tryShearLivingEntity(worldserver, blockposition, stack));
-+            this.setSuccess(ShearsDispenseItemBehavior.tryShearBeehive(worldserver, blockposition) || ShearsDispenseItemBehavior.tryShearLivingEntity(worldserver, blockposition, stack, bukkitBlock, craftItem)); // CraftBukkit
-             if (this.isSuccess()) {
-                 stack.hurtAndBreak(1, worldserver, (ServerPlayer) null, (item) -> {
-                 });
-@@ -64,8 +93,8 @@
-         return false;
-     }
- 
--    private static boolean tryShearLivingEntity(ServerLevel world, BlockPos pos, ItemStack shears) {
--        List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, new AABB(pos), EntitySelector.NO_SPECTATORS);
-+    private static boolean tryShearLivingEntity(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, org.bukkit.block.Block bukkitBlock, CraftItemStack craftItem) { // CraftBukkit - add args
-+        List<LivingEntity> list = worldserver.getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), EntitySelector.NO_SPECTATORS);
-         Iterator iterator = list.iterator();
- 
-         while (iterator.hasNext()) {
-@@ -73,8 +102,16 @@
- 
-             if (entityliving instanceof Shearable ishearable) {
-                 if (ishearable.readyForShearing()) {
--                    ishearable.shear(world, SoundSource.BLOCKS, shears);
--                    world.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, pos);
-+                    // CraftBukkit start
-+                    // Paper start - Add drops to shear events
-+                    org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops(worldserver, itemstack));
-+                    if (event.isCancelled()) {
-+                        // Paper end - Add drops to shear events
-+                        continue;
-+                    }
-+                    // CraftBukkit end
-+                    ishearable.shear(worldserver, SoundSource.BLOCKS, itemstack, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events
-+                    worldserver.gameEvent((Entity) null, (Holder) GameEvent.SHEAR, blockposition);
-                     return true;
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
deleted file mode 100644
index bfa73fef42..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
+++ /dev/null
@@ -1,54 +0,0 @@
---- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
-+++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
-@@ -10,6 +10,12 @@
- import net.minecraft.world.level.block.DispenserBlock;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseEvent;
-+// CraftBukkit end
-+
- public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -26,8 +32,37 @@
-             BlockPos blockposition = pointer.pos().relative(enumdirection);
-             Direction enumdirection1 = pointer.level().isEmptyBlock(blockposition.below()) ? enumdirection : Direction.UP;
- 
-+            // CraftBukkit start
-+            org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos());
-+            CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
-+
-+            BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
-+            if (!DispenserBlock.eventFired) {
-+                pointer.level().getCraftServer().getPluginManager().callEvent(event);
-+            }
-+
-+            if (event.isCancelled()) {
-+                return stack;
-+            }
-+
-+            if (!event.getItem().equals(craftItem)) {
-+                // Chain to handler for new item
-+                ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
-+                DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(pointer, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                    idispensebehavior.dispense(pointer, eventStack);
-+                    return stack;
-+                }
-+            }
-+            // CraftBukkit end
-+
-             try {
--                this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, stack, enumdirection1)).consumesAction());
-+                // Paper start - track changed items in the dispense event
-+                this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, CraftItemStack.asNMSCopy(event.getItem()), enumdirection1)).consumesAction());
-+                if (this.isSuccess()) {
-+                    stack.shrink(1); // vanilla shrink is in the place function above, manually handle it here
-+                }
-+                // Paper end - track changed items in the dispense event
-             } catch (Exception exception) {
-                 ShulkerBoxDispenseBehavior.LOGGER.error("Error trying to place shulker box at {}", blockposition, exception);
-             }

From f0e7d7e5f754ff7736777510460060b2e035fba0 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 15:50:44 -0800
Subject: [PATCH 030/285] net.minecraft.world.level.block.state

---
 .../block/state/BlockBehaviour.java.patch     | 127 ++++++++----------
 .../level/block/state/BlockState.java.patch   |   9 +-
 2 files changed, 63 insertions(+), 73 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/state/BlockBehaviour.java.patch (53%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/state/BlockState.java.patch (71%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
index 56a6b42a76..f0c398822e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java
 +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
-@@ -46,6 +46,7 @@
+@@ -46,6 +_,7 @@
  import net.minecraft.world.item.Item;
  import net.minecraft.world.item.ItemStack;
  import net.minecraft.world.item.context.BlockPlaceContext;
@@ -8,111 +8,100 @@
  import net.minecraft.world.level.BlockGetter;
  import net.minecraft.world.level.EmptyBlockGetter;
  import net.minecraft.world.level.Explosion;
-@@ -83,6 +84,8 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import net.minecraft.world.level.ServerExplosion;
-+// CraftBukkit end
+@@ -167,16 +_,24 @@
+     }
  
- public abstract class BlockBehaviour implements FeatureElement {
- 
-@@ -156,9 +159,18 @@
- 
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {}
- 
--    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {}
-+    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
+-    }
 +        org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot
 +    }
- 
++
 +    // CraftBukkit start
 +    protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, @Nullable UseOnContext context) {
 +        this.onPlace(iblockdata, world, blockposition, iblockdata1, flag);
 +    }
 +    // CraftBukkit end
-+
-     protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) {
+ 
+     protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
 +        org.spigotmc.AsyncCatcher.catchOp("block remove"); // Spigot
          if (state.hasBlockEntity() && !state.is(newState.getBlock())) {
-             world.removeBlockEntity(pos);
+             level.removeBlockEntity(pos);
          }
-@@ -166,7 +178,7 @@
      }
  
-     protected void onExplosionHit(BlockState state, ServerLevel world, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> stackMerger) {
+     protected void onExplosionHit(BlockState state, ServerLevel level, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> dropConsumer) {
 -        if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) {
 +        if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed
              Block block = state.getBlock();
              boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
- 
-@@ -174,8 +186,10 @@
-                 BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
-                 LootParams.Builder lootparams_a = (new LootParams.Builder(world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
- 
+             if (block.dropFromExplosion(explosion)) {
+@@ -186,8 +_,10 @@
+                     .withParameter(LootContextParams.TOOL, ItemStack.EMPTY)
+                     .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity)
+                     .withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
 -                if (explosion.getBlockInteraction() == Explosion.BlockInteraction.DESTROY_WITH_DECAY) {
--                    lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
+-                    builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
 +                // CraftBukkit start - add yield
-+                if (explosion instanceof ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) {
-+                    lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield);
++                if (explosion instanceof net.minecraft.world.level.ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) {
++                    builder.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield);
 +                    // CraftBukkit end
                  }
  
-                 state.spawnAfterBreak(world, pos, ItemStack.EMPTY, flag);
-@@ -243,7 +257,7 @@
+                 state.spawnAfterBreak(level, pos, ItemStack.EMPTY, flag);
+@@ -255,7 +_,7 @@
      }
  
-     protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
--        return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem()));
-+        return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
+     protected boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) {
+-        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem()));
++        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem()))&& (state.isDestroyable() || (useContext.getPlayer() != null && useContext.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
      }
  
      protected boolean canBeReplaced(BlockState state, Fluid fluid) {
-@@ -851,7 +865,15 @@
-             this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles;
-             this.instrument = blockbase_info.instrument;
-             this.replaceable = blockbase_info.replaceable;
-+        }
+@@ -468,6 +_,16 @@
+             this.instrument = properties.instrument;
+             this.replaceable = properties.replaceable;
+         }
 +        // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time
++        @Nullable
 +        private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData;
 +
 +        public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() {
-+            if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState());
-+            return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone();
-         }
++            if (this.cachedCraftBlockData == null) this.cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(this.asState());
++            return (org.bukkit.craftbukkit.block.data.CraftBlockData) this.cachedCraftBlockData.clone();
++        }
 +        // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time
++
  
          private boolean calculateSolid() {
-             if (((Block) this.owner).properties.forceSolidOn) {
-@@ -873,12 +895,14 @@
+             if (this.owner.properties.forceSolidOn) {
+@@ -487,12 +_,14 @@
              }
          }
  
 +        protected boolean shapeExceedsCube = true; // Paper - moved from actual method to here
          public void initCache() {
-             this.fluidState = ((Block) this.owner).getFluidState(this.asState());
-             this.isRandomlyTicking = ((Block) this.owner).isRandomlyTicking(this.asState());
+             this.fluidState = this.owner.getFluidState(this.asState());
+             this.isRandomlyTicking = this.owner.isRandomlyTicking(this.asState());
              if (!this.getBlock().hasDynamicShape()) {
                  this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
              }
 +            this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
  
              this.legacySolid = this.calculateSolid();
-             this.occlusionShape = this.canOcclude ? ((Block) this.owner).getOcclusionShape(this.asState()) : Shapes.empty();
-@@ -925,6 +949,12 @@
+             this.occlusionShape = this.canOcclude ? this.owner.getOcclusionShape(this.asState()) : Shapes.empty();
+@@ -531,6 +_,11 @@
+         public boolean isSolid() {
              return this.legacySolid;
          }
- 
 +        // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
 +        public final boolean isDestroyable() {
 +            return getBlock().isDestroyable();
 +        }
 +        // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
-+
-         public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType<?> type) {
-             return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type);
-         }
-@@ -945,19 +975,19 @@
+ 
+         public boolean isValidSpawn(BlockGetter level, BlockPos pos, EntityType<?> entityType) {
+             return this.getBlock().properties.isValidSpawn.test(this.asState(), level, pos, entityType);
+@@ -552,19 +_,19 @@
              return this.occlusionShape;
          }
  
@@ -137,7 +126,7 @@
              return this.isAir;
          }
  
-@@ -1028,14 +1058,14 @@
+@@ -634,14 +_,14 @@
          }
  
          public PushReaction getPistonPushReaction() {
@@ -154,31 +143,31 @@
              return this.canOcclude;
          }
  
-@@ -1125,7 +1155,13 @@
+@@ -725,7 +_,13 @@
          }
  
-         public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify) {
--            this.getBlock().onPlace(this.asState(), world, pos, state, notify);
+         public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
+-            this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston);
 +            // CraftBukkit start
-+            this.onPlace(world, pos, state, notify, null);
++            this.onPlace(level, pos, oldState, movedByPiston, null);
 +        }
 +
-+        public void onPlace(Level world, BlockPos blockposition, BlockState iblockdata, boolean flag, @Nullable UseOnContext context) {
-+            this.getBlock().onPlace(this.asState(), world, blockposition, iblockdata, flag, context);
++        public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, @Nullable UseOnContext context) {
++            this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston, context);
 +            // CraftBukkit end
          }
  
-         public void onRemove(Level world, BlockPos pos, BlockState state, boolean moved) {
-@@ -1154,6 +1190,7 @@
+         public void onRemove(Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
+@@ -754,6 +_,7 @@
  
-         public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-             this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience);
-+            if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping
+         public void spawnAfterBreak(ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+             this.getBlock().spawnAfterBreak(this.asState(), level, pos, stack, dropExperience);
++            if (dropExperience) {this.getBlock().popExperience(level, pos, this.getBlock().getExpDrop(this.asState(), level, pos, stack, true));} // Paper - Properly handle xp dropping
          }
  
-         public List<ItemStack> getDrops(LootParams.Builder builder) {
-@@ -1250,11 +1287,11 @@
-             return this.getBlock().builtInRegistryHolder().is(key);
+         public List<ItemStack> getDrops(LootParams.Builder lootParams) {
+@@ -858,11 +_,11 @@
+             return this.getBlock().builtInRegistryHolder().is(block);
          }
  
 -        public FluidState getFluidState() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch
similarity index 71%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockState.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch
index 77d0d6d1c5..01e51688b1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/BlockState.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockState.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/state/BlockState.java
 +++ b/net/minecraft/world/level/block/state/BlockState.java
-@@ -10,6 +10,16 @@
+@@ -10,6 +_,17 @@
  public class BlockState extends BlockBehaviour.BlockStateBase {
      public static final Codec<BlockState> CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable();
  
 +    // Paper start - optimise getType calls
-+    org.bukkit.Material cachedMaterial;
++    org.bukkit.@org.jspecify.annotations.Nullable Material cachedMaterial;
 +
 +    public final org.bukkit.Material getBukkitMaterial() {
 +        if (this.cachedMaterial == null) {
@@ -14,6 +14,7 @@
 +        return this.cachedMaterial;
 +    }
 +    // Paper end - optimise getType calls
-     public BlockState(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
-         super(block, propertyMap, codec);
++
+     public BlockState(Block owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<BlockState> propertiesCodec) {
+         super(owner, values, propertiesCodec);
      }

From f60983ac062ef8007bdcffdd3ed207011d8ef2d1 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 15:55:52 -0800
Subject: [PATCH 031/285] net.minecraft.world.entity.animal.allay

---
 .../entity/animal/allay/Allay.java.patch      |  83 ++++++++++++++
 .../entity/animal/allay/Allay.java.patch      | 102 ------------------
 2 files changed, 83 insertions(+), 102 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/allay/Allay.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
new file mode 100644
index 0000000000..c30af2f229
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/entity/animal/allay/Allay.java
++++ b/net/minecraft/world/entity/animal/allay/Allay.java
+@@ -118,6 +_,7 @@
+     private float dancingAnimationTicks;
+     private float spinningAnimationTicks;
+     private float spinningAnimationTicks0;
++    public boolean forceDancing = false; // CraftBukkit
+ 
+     public Allay(EntityType<? extends Allay> entityType, Level level) {
+         super(entityType, level);
+@@ -131,6 +_,12 @@
+         );
+     }
+ 
++    // CraftBukkit start
++    public void setCanDuplicate(boolean canDuplicate) {
++        this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate);
++    }
++    // CraftBukkit end
++
+     @Override
+     protected Brain.Provider<Allay> brainProvider() {
+         return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
+@@ -252,7 +_,7 @@
+     public void aiStep() {
+         super.aiStep();
+         if (!this.level().isClientSide && this.isAlive() && this.tickCount % 10 == 0) {
+-            this.heal(1.0F);
++            this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+         }
+ 
+         if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) {
+@@ -320,7 +_,12 @@
+         ItemStack itemInHand = player.getItemInHand(hand);
+         ItemStack itemInHand1 = this.getItemInHand(InteractionHand.MAIN_HAND);
+         if (this.isDancing() && itemInHand.is(ItemTags.DUPLICATES_ALLAYS) && this.canDuplicate()) {
+-            this.duplicateAllay();
++            // CraftBukkit start - handle cancel duplication
++            Allay allay = this.duplicateAllay();
++            if (allay == null) {
++                return InteractionResult.SUCCESS;
++            }
++            // CraftBukkit end
+             this.level().broadcastEntityEvent(this, (byte)18);
+             this.level().playSound(player, this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F);
+             this.removeInteractionItem(player, itemInHand);
+@@ -425,6 +_,7 @@
+     }
+ 
+     private boolean shouldStopDancing() {
++        if (this.forceDancing) {return false;} // CraftBukkit
+         return this.jukeboxPos == null
+             || !this.jukeboxPos.closerToCenterThan(this.position(), GameEvent.JUKEBOX_PLAY.value().notificationRadius())
+             || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX);
+@@ -489,7 +_,7 @@
+                 .ifPresent(data -> this.vibrationData = data);
+         }
+ 
+-        this.duplicationCooldown = tag.getInt("DuplicationCooldown");
++        this.duplicationCooldown = tag.getLong("DuplicationCooldown"); // Paper - Load as long
+         this.entityData.set(DATA_CAN_DUPLICATE, tag.getBoolean("CanDuplicate"));
+     }
+ 
+@@ -508,15 +_,17 @@
+         }
+     }
+ 
+-    public void duplicateAllay() {
++    @Nullable public Allay duplicateAllay() { // CraftBukkit - return allay
+         Allay allay = EntityType.ALLAY.create(this.level(), EntitySpawnReason.BREEDING);
+         if (allay != null) {
+             allay.moveTo(this.position());
+             allay.setPersistenceRequired();
+             allay.resetDuplicationCooldown();
+             this.resetDuplicationCooldown();
+-            this.level().addFreshEntity(allay);
++            this.level().addFreshEntity(allay, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DUPLICATION); // CraftBukkit - reason for duplicated allay
+         }
++
++        return allay; // CraftBukkit
+     }
+ 
+     public void resetDuplicationCooldown() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/allay/Allay.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/allay/Allay.java.patch
deleted file mode 100644
index 8c853dc764..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/allay/Allay.java.patch
+++ /dev/null
@@ -1,102 +0,0 @@
---- a/net/minecraft/world/entity/animal/allay/Allay.java
-+++ b/net/minecraft/world/entity/animal/allay/Allay.java
-@@ -103,6 +103,7 @@
-     private float dancingAnimationTicks;
-     private float spinningAnimationTicks;
-     private float spinningAnimationTicks0;
-+    public boolean forceDancing = false; // CraftBukkit
- 
-     public Allay(EntityType<? extends Allay> type, Level world) {
-         super(type, world);
-@@ -114,6 +115,12 @@
-         this.dynamicJukeboxListener = new DynamicGameEventListener<>(new Allay.JukeboxListener(this.vibrationUser.getPositionSource(), ((GameEvent) GameEvent.JUKEBOX_PLAY.value()).notificationRadius()));
-     }
- 
-+    // CraftBukkit start
-+    public void setCanDuplicate(boolean canDuplicate) {
-+        this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate);
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     protected Brain.Provider<Allay> brainProvider() {
-         return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES);
-@@ -126,7 +133,7 @@
- 
-     @Override
-     public Brain<Allay> getBrain() {
--        return super.getBrain();
-+        return (Brain<Allay>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     public static AttributeSupplier.Builder createAttributes() {
-@@ -233,7 +240,7 @@
-     public void aiStep() {
-         super.aiStep();
-         if (!this.level().isClientSide && this.isAlive() && this.tickCount % 10 == 0) {
--            this.heal(1.0F);
-+            this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
-         }
- 
-         if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) {
-@@ -303,7 +310,12 @@
-         ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND);
- 
-         if (this.isDancing() && itemstack.is(ItemTags.DUPLICATES_ALLAYS) && this.canDuplicate()) {
--            this.duplicateAllay();
-+            // CraftBukkit start - handle cancel duplication
-+            Allay allay = this.duplicateAllay();
-+            if (allay == null) {
-+                return InteractionResult.SUCCESS;
-+            }
-+            // CraftBukkit end
-             this.level().broadcastEntityEvent(this, (byte) 18);
-             this.level().playSound(player, (Entity) this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F);
-             this.removeInteractionItem(player, itemstack);
-@@ -314,7 +326,7 @@
-             this.setItemInHand(InteractionHand.MAIN_HAND, itemstack2);
-             this.removeInteractionItem(player, itemstack);
-             this.level().playSound(player, (Entity) this, SoundEvents.ALLAY_ITEM_GIVEN, SoundSource.NEUTRAL, 2.0F, 1.0F);
--            this.getBrain().setMemory(MemoryModuleType.LIKED_PLAYER, (Object) player.getUUID());
-+            this.getBrain().setMemory(MemoryModuleType.LIKED_PLAYER, player.getUUID()); // CraftBukkit - decompile error
-             return InteractionResult.SUCCESS;
-         } else if (!itemstack1.isEmpty() && hand == InteractionHand.MAIN_HAND && itemstack.isEmpty()) {
-             this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
-@@ -415,6 +427,7 @@
-     }
- 
-     private boolean shouldStopDancing() {
-+        if (this.forceDancing) {return false;} // CraftBukkit
-         return this.jukeboxPos == null || !this.jukeboxPos.closerToCenterThan(this.position(), (double) ((GameEvent) GameEvent.JUKEBOX_PLAY.value()).notificationRadius()) || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX);
-     }
- 
-@@ -486,7 +499,7 @@
-             });
-         }
- 
--        this.duplicationCooldown = (long) nbt.getInt("DuplicationCooldown");
-+        this.duplicationCooldown = nbt.getLong("DuplicationCooldown"); // Paper - Load as long
-         this.entityData.set(Allay.DATA_CAN_DUPLICATE, nbt.getBoolean("CanDuplicate"));
-     }
- 
-@@ -506,7 +519,7 @@
- 
-     }
- 
--    public void duplicateAllay() {
-+    public Allay duplicateAllay() { // CraftBukkit - return allay
-         Allay allay = (Allay) EntityType.ALLAY.create(this.level(), EntitySpawnReason.BREEDING);
- 
-         if (allay != null) {
-@@ -514,9 +527,9 @@
-             allay.setPersistenceRequired();
-             allay.resetDuplicationCooldown();
-             this.resetDuplicationCooldown();
--            this.level().addFreshEntity(allay);
-+            this.level().addFreshEntity(allay, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DUPLICATION); // CraftBukkit - reason for duplicated allay
-         }
--
-+        return allay; // CraftBukkit
-     }
- 
-     public void resetDuplicationCooldown() {

From fe1744dfd1867de741afa6725c8086dea5eb834f Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 16:02:20 -0800
Subject: [PATCH 032/285] net.minecraft.world.item.alchemy

---
 .../item/alchemy/PotionBrewing.java.patch     | 36 +++++++++----------
 .../item/alchemy/PotionContents.java.patch    | 11 ++++++
 .../item/alchemy/PotionContents.java.patch    | 20 -----------
 3 files changed, 29 insertions(+), 38 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/alchemy/PotionBrewing.java.patch (76%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionContents.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionBrewing.java.patch b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch
similarity index 76%
rename from paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionBrewing.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch
index c9c0b22f57..1ccb0b5432 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionBrewing.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionBrewing.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/item/alchemy/PotionBrewing.java
 +++ b/net/minecraft/world/item/alchemy/PotionBrewing.java
-@@ -19,6 +19,7 @@
+@@ -19,6 +_,7 @@
      private final List<Ingredient> containers;
      private final List<PotionBrewing.Mix<Potion>> potionMixes;
      private final List<PotionBrewing.Mix<Item>> containerMixes;
 +    private final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<org.bukkit.NamespacedKey, io.papermc.paper.potion.PaperPotionMix> customMixes = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes
  
-     PotionBrewing(List<Ingredient> potionTypes, List<PotionBrewing.Mix<Potion>> potionRecipes, List<PotionBrewing.Mix<Item>> itemRecipes) {
-         this.containers = potionTypes;
-@@ -27,7 +28,7 @@
+     PotionBrewing(List<Ingredient> containers, List<PotionBrewing.Mix<Potion>> potionMixes, List<PotionBrewing.Mix<Item>> containerMixes) {
+         this.containers = containers;
+@@ -27,7 +_,7 @@
      }
  
      public boolean isIngredient(ItemStack stack) {
@@ -17,36 +17,37 @@
      }
  
      private boolean isContainer(ItemStack stack) {
-@@ -71,6 +72,11 @@
+@@ -71,6 +_,11 @@
      }
  
-     public boolean hasMix(ItemStack input, ItemStack ingredient) {
+     public boolean hasMix(ItemStack reagent, ItemStack potionItem) {
 +        // Paper start - Custom Potion Mixes
-+        if (this.hasCustomMix(input, ingredient)) {
++        if (this.hasCustomMix(reagent, potionItem)) {
 +            return true;
 +        }
 +        // Paper end - Custom Potion Mixes
-         return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient));
+         return this.isContainer(reagent) && (this.hasContainerMix(reagent, potionItem) || this.hasPotionMix(reagent, potionItem));
      }
  
-@@ -103,6 +109,13 @@
-         if (input.isEmpty()) {
-             return input;
+@@ -103,6 +_,13 @@
+         if (potionItem.isEmpty()) {
+             return potionItem;
          } else {
 +            // Paper start - Custom Potion Mixes
 +            for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
-+                if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
++                if (mix.input().test(potionItem) && mix.ingredient().test(potion)) {
 +                    return mix.result().copy();
 +                }
 +            }
 +            // Paper end - Custom Potion Mixes
-             Optional<Holder<Potion>> optional = input.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion();
+             Optional<Holder<Potion>> optional = potionItem.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion();
              if (optional.isEmpty()) {
-                 return input;
-@@ -190,6 +203,50 @@
+                 return potionItem;
+@@ -189,6 +_,50 @@
+         builder.addMix(Potions.AWKWARD, Items.PHANTOM_MEMBRANE, Potions.SLOW_FALLING);
          builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING);
      }
- 
++
 +    // Paper start - Custom Potion Mixes
 +    public boolean isCustomIngredient(ItemStack stack) {
 +        for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
@@ -90,7 +91,6 @@
 +        return bootstrap(flags);
 +    }
 +    // Paper end - Custom Potion Mixes
-+
+ 
      public static class Builder {
          private final List<Ingredient> containers = new ArrayList<>();
-         private final List<PotionBrewing.Mix<Potion>> potionMixes = new ArrayList<>();
diff --git a/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch
new file mode 100644
index 0000000000..f499dec2ee
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/alchemy/PotionContents.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/alchemy/PotionContents.java
++++ b/net/minecraft/world/item/alchemy/PotionContents.java
+@@ -158,7 +_,7 @@
+                 if (mobEffectInstance.getEffect().value().isInstantenous()) {
+                     mobEffectInstance.getEffect().value().applyInstantenousEffect(serverLevel, player1, player1, entity, mobEffectInstance.getAmplifier(), 1.0);
+                 } else {
+-                    entity.addEffect(mobEffectInstance);
++                    entity.addEffect(mobEffectInstance, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit
+                 }
+             });
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionContents.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionContents.java.patch
deleted file mode 100644
index 163fbb5056..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/alchemy/PotionContents.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/item/alchemy/PotionContents.java
-+++ b/net/minecraft/world/item/alchemy/PotionContents.java
-@@ -93,7 +93,7 @@
-     }
- 
-     public PotionContents withEffectAdded(MobEffectInstance customEffect) {
--        return new PotionContents(this.potion, this.customColor, Util.copyAndAdd(this.customEffects, (Object) customEffect), this.customName);
-+        return new PotionContents(this.potion, this.customColor, Util.copyAndAdd(this.customEffects, customEffect), this.customName); // CraftBukkit - decompile error
-     }
- 
-     public int getColor() {
-@@ -172,7 +172,7 @@
-                 if (((MobEffect) mobeffect.getEffect().value()).isInstantenous()) {
-                     ((MobEffect) mobeffect.getEffect().value()).applyInstantenousEffect(worldserver, entityhuman2, entityhuman2, user, mobeffect.getAmplifier(), 1.0D);
-                 } else {
--                    user.addEffect(mobeffect);
-+                    user.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit
-                 }
- 
-             });

From f98d879f072eef2b6632d0178df6f07d2000913e Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 16:05:23 -0800
Subject: [PATCH 033/285] net.minecraft.world.level.block.state.properties

---
 .../block/state/properties/EnumProperty.java.patch   | 12 ++++++++++++
 .../state/properties/IntegerProperty.java.patch      | 12 ++++++++++++
 .../level/block/state/properties/Property.java.patch | 11 +++++++++++
 .../block/state/properties/EnumProperty.java.patch   | 12 ------------
 .../state/properties/IntegerProperty.java.patch      | 12 ------------
 .../level/block/state/properties/Property.java.patch | 11 -----------
 6 files changed, 35 insertions(+), 35 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/Property.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
new file mode 100644
index 0000000000..94537a6a1d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/state/properties/EnumProperty.java
++++ b/net/minecraft/world/level/block/state/properties/EnumProperty.java
+@@ -59,8 +_,7 @@
+         return this.ordinalToIndex[value.ordinal()];
+     }
+ 
+-    @Override
+-    public boolean equals(Object other) {
++    public boolean equals_unused(Object other) { // Paper - Perf: Optimize hashCode/equals
+         return this == other || other instanceof EnumProperty<?> enumProperty && super.equals(other) && this.values.equals(enumProperty.values);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
new file mode 100644
index 0000000000..a3b33fce20
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/state/properties/IntegerProperty.java
++++ b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
+@@ -28,8 +_,7 @@
+         return this.values;
+     }
+ 
+-    @Override
+-    public boolean equals(Object other) {
++    public boolean equals_unused(Object other) { // Paper - Perf: Optimize hashCode/equals
+         return this == other || other instanceof IntegerProperty integerProperty && super.equals(other) && this.values.equals(integerProperty.values);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch
new file mode 100644
index 0000000000..78b6de8b7e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/properties/Property.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/state/properties/Property.java
++++ b/net/minecraft/world/level/block/state/properties/Property.java
+@@ -72,7 +_,7 @@
+ 
+     @Override
+     public boolean equals(Object other) {
+-        return this == other || other instanceof Property<?> property && this.clazz.equals(property.clazz) && this.name.equals(property.name);
++        return this == other; // Paper - Perf: Optimize hashCode/equals
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
deleted file mode 100644
index 95194b370f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/EnumProperty.java.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- a/net/minecraft/world/level/block/state/properties/EnumProperty.java
-+++ b/net/minecraft/world/level/block/state/properties/EnumProperty.java
-@@ -59,8 +59,7 @@
-         return this.ordinalToIndex[enum_.ordinal()];
-     }
- 
--    @Override
--    public boolean equals(Object object) {
-+    public boolean equals_unused(Object object) { // Paper - Perf: Optimize hashCode/equals
-         if (this == object) {
-             return true;
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
deleted file mode 100644
index 6542f0bc6f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/IntegerProperty.java.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- a/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-+++ b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-@@ -28,8 +28,7 @@
-         return this.values;
-     }
- 
--    @Override
--    public boolean equals(Object object) {
-+    public boolean equals_unused(Object object) { // Paper - Perf: Optimize hashCode/equals
-         if (this == object) {
-             return true;
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/Property.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/Property.java.patch
deleted file mode 100644
index e0a6a659ba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/state/properties/Property.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/state/properties/Property.java
-+++ b/net/minecraft/world/level/block/state/properties/Property.java
-@@ -72,7 +72,7 @@
- 
-     @Override
-     public boolean equals(Object object) {
--        return this == object || object instanceof Property<?> property && this.clazz.equals(property.clazz) && this.name.equals(property.name);
-+        return this == object; // Paper - Perf: Optimize hashCode/equals
-     }
- 
-     @Override

From cb5feced53047c0df74f77c36260b5b0d043eb62 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 16:08:40 -0800
Subject: [PATCH 034/285] net.minecraft.world.entity.monster.warden

---
 .../monster/warden/AngerManagement.java.patch | 11 ++++
 .../entity/monster/warden/Warden.java.patch   | 27 +++++++++
 .../monster/warden/AngerManagement.java.patch | 11 ----
 .../entity/monster/warden/Warden.java.patch   | 59 -------------------
 4 files changed, 38 insertions(+), 70 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/Warden.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
new file mode 100644
index 0000000000..12bd7bec5a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/warden/AngerManagement.java
++++ b/net/minecraft/world/entity/monster/warden/AngerManagement.java
+@@ -146,7 +_,7 @@
+ 
+     public int increaseAnger(Entity entity, int offset) {
+         boolean flag = !this.angerBySuspect.containsKey(entity);
+-        int i = this.angerBySuspect.computeInt(entity, (entity1, integer) -> Math.min(150, (integer == null ? 0 : integer) + offset));
++        int i = this.angerBySuspect.computeInt(entity, (entity1, integer) -> Math.min(150, (integer == null ? 0 : integer) + offset)); // Paper - diff on change (Warden#increaseAngerAt WardenAngerChangeEvent)
+         if (flag) {
+             int i1 = this.angerByUuid.removeInt(entity.getUUID());
+             i += i1;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch
new file mode 100644
index 0000000000..f6da9a7ec3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/warden/Warden.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/monster/warden/Warden.java
++++ b/net/minecraft/world/entity/monster/warden/Warden.java
+@@ -407,7 +_,7 @@
+ 
+     public static void applyDarknessAround(ServerLevel level, Vec3 pos, @Nullable Entity source, int radius) {
+         MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
+-        MobEffectUtil.addEffectToPlayersAround(level, source, pos, radius, mobEffectInstance, 200);
++        MobEffectUtil.addEffectToPlayersAround(level, source, pos, radius, mobEffectInstance, 200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WARDEN); // CraftBukkit - Add EntityPotionEffectEvent.Cause
+     }
+ 
+     @Override
+@@ -469,6 +_,15 @@
+     @VisibleForTesting
+     public void increaseAngerAt(@Nullable Entity entity, int offset, boolean playListeningSound) {
+         if (!this.isNoAi() && this.canTargetEntity(entity)) {
++            // Paper start - Add WardenAngerChangeEvent
++            int activeAnger = this.angerManagement.getActiveAnger(entity);
++            io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + offset));
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++            if (event.isCancelled()) {
++                return;
++            }
++            offset = event.getNewAnger() - activeAnger;
++            // Paper end - Add WardenAngerChangeEvent
+             WardenAi.setDigCooldown(this);
+             boolean flag = !(this.getTarget() instanceof Player);
+             int i = this.angerManagement.increaseAnger(entity, offset);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
deleted file mode 100644
index f65a699a17..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/AngerManagement.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/warden/AngerManagement.java
-+++ b/net/minecraft/world/entity/monster/warden/AngerManagement.java
-@@ -146,7 +146,7 @@
- 
-     public int increaseAnger(Entity entity, int amount) {
-         boolean bl = !this.angerBySuspect.containsKey(entity);
--        int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount));
-+        int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount)); // Paper - diff on change (Warden#increaseAngerAt WardenAngerChangeEvent)
-         if (bl) {
-             int j = this.angerByUuid.removeInt(entity.getUUID());
-             i += j;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/Warden.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/Warden.java.patch
deleted file mode 100644
index c6378460b2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/warden/Warden.java.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- a/net/minecraft/world/entity/monster/warden/Warden.java
-+++ b/net/minecraft/world/entity/monster/warden/Warden.java
-@@ -375,7 +375,7 @@
- 
-     @Override
-     public Brain<Warden> getBrain() {
--        return super.getBrain();
-+        return (Brain<Warden>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -412,7 +412,7 @@
-     public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) {
-         MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
- 
--        MobEffectUtil.addEffectToPlayersAround(world, entity, pos, (double) range, mobeffect, 200);
-+        MobEffectUtil.addEffectToPlayersAround(world, entity, pos, range, mobeffect, 200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WARDEN); // CraftBukkit - Add EntityPotionEffectEvent.Cause
-     }
- 
-     @Override
-@@ -482,6 +482,15 @@
-     @VisibleForTesting
-     public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) {
-         if (!this.isNoAi() && this.canTargetEntity(entity)) {
-+            // Paper start - Add WardenAngerChangeEvent
-+            int activeAnger = this.angerManagement.getActiveAnger(entity);
-+            io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount));
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+            amount = event.getNewAnger() - activeAnger;
-+            // Paper end - Add WardenAngerChangeEvent
-             WardenAi.setDigCooldown(this);
-             boolean flag1 = !(this.getTarget() instanceof Player);
-             int j = this.angerManagement.increaseAnger(entity, amount);
-@@ -547,7 +556,7 @@
- 
-     public void setAttackTarget(LivingEntity target) {
-         this.getBrain().eraseMemory(MemoryModuleType.ROAR_TARGET);
--        this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, (Object) target);
-+        this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, target); // CraftBukkit - decompile error
-         this.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
-         SonicBoom.setCooldown(this, 200);
-     }
-@@ -582,11 +591,11 @@
- 
-     @Override
-     protected PathNavigation createNavigation(Level world) {
--        return new GroundPathNavigation(this, this, world) {
-+        return new GroundPathNavigation(this, world) { // CraftBukkit - decompile error
-             @Override
-             protected PathFinder createPathFinder(int range) {
-                 this.nodeEvaluator = new WalkNodeEvaluator();
--                return new PathFinder(this, this.nodeEvaluator, range) {
-+                return new PathFinder(this.nodeEvaluator, range) { // CraftBukkit - decompile error
-                     @Override
-                     protected float distance(Node a, Node b) {
-                         return a.distanceToXZ(b);

From 9ef230aa311f3603a44238e728a12bde5eafd1f5 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 17:45:53 -0800
Subject: [PATCH 035/285] net.minecraft.world.entity.projectile

---
 .../projectile/AbstractArrow.java.patch       | 224 +++++++++++
 .../AbstractHurtingProjectile.java.patch      |  26 ++
 .../world/entity/projectile/Arrow.java.patch  |  17 +
 .../projectile/DragonFireball.java.patch      |  16 +
 .../entity/projectile/EvokerFangs.java.patch  |  20 +
 .../entity/projectile/EyeOfEnder.java.patch   |  40 +-
 .../entity/projectile/Fireball.java.patch     |  16 +
 .../FireworkRocketEntity.java.patch           | 117 ++++++
 .../entity/projectile/FishingHook.java.patch  | 281 ++++++++++++++
 .../projectile/LargeFireball.java.patch       |  45 +++
 .../entity/projectile/LlamaSpit.java.patch    |  31 ++
 .../entity/projectile/Projectile.java.patch   | 230 ++++++++++++
 .../projectile/ShulkerBullet.java.patch       |  91 +++++
 .../projectile/SmallFireball.java.patch       |  51 +++
 .../entity/projectile/Snowball.java.patch     |  11 +
 .../projectile/SpectralArrow.java.patch       |  11 +
 .../ThrowableItemProjectile.java.patch        |   4 +-
 .../projectile/ThrowableProjectile.java.patch |  11 +
 .../entity/projectile/ThrownEgg.java.patch    |  63 ++++
 .../projectile/ThrownEnderpearl.java.patch    |  83 +++++
 .../ThrownExperienceBottle.java.patch         |  23 ++
 .../entity/projectile/ThrownPotion.java.patch | 237 ++++++++++++
 .../projectile/ThrownTrident.java.patch       |  65 ++++
 .../entity/projectile/WitherSkull.java.patch  |  43 +++
 .../projectile/AbstractArrow.java.patch       | 320 ----------------
 .../AbstractHurtingProjectile.java.patch      |  38 --
 .../world/entity/projectile/Arrow.java.patch  |  20 -
 .../projectile/DragonFireball.java.patch      |  26 --
 .../entity/projectile/EvokerFangs.java.patch  |  30 --
 .../entity/projectile/Fireball.java.patch     |  16 -
 .../FireworkRocketEntity.java.patch           | 132 -------
 .../entity/projectile/FishingHook.java.patch  | 352 ------------------
 .../projectile/LargeFireball.java.patch       |  56 ---
 .../entity/projectile/LlamaSpit.java.patch    |  42 ---
 .../entity/projectile/Projectile.java.patch   | 231 ------------
 .../projectile/ShulkerBullet.java.patch       | 100 -----
 .../projectile/SmallFireball.java.patch       |  63 ----
 .../entity/projectile/Snowball.java.patch     |  21 --
 .../projectile/SpectralArrow.java.patch       |  11 -
 .../projectile/ThrowableProjectile.java.patch |  11 -
 .../entity/projectile/ThrownEgg.java.patch    | 121 ------
 .../projectile/ThrownEnderpearl.java.patch    |  95 -----
 .../ThrownExperienceBottle.java.patch         |  36 --
 .../entity/projectile/ThrownPotion.java.patch | 322 ----------------
 .../projectile/ThrownTrident.java.patch       |  86 -----
 .../entity/projectile/WitherSkull.java.patch  |  55 ---
 46 files changed, 1729 insertions(+), 2211 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Arrow.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/DragonFireball.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Fireball.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FishingHook.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LargeFireball.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Projectile.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SmallFireball.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Snowball.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/WitherSkull.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
new file mode 100644
index 0000000000..e6732700cf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -0,0 +1,224 @@
+--- a/net/minecraft/world/entity/projectile/AbstractArrow.java
++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java
+@@ -33,6 +_,7 @@
+ import net.minecraft.world.entity.OminousItemSpawner;
+ import net.minecraft.world.entity.SlotAccess;
+ import net.minecraft.world.entity.ai.attributes.Attributes;
++import net.minecraft.world.entity.item.ItemEntity;
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
+@@ -74,6 +_,16 @@
+     @Nullable
+     private ItemStack firedFromWeapon = null;
+ 
++    // Spigot Start
++    @Override
++    public void inactiveTick() {
++        if (this.isInGround()) {
++            this.life += 1;
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
+     protected AbstractArrow(EntityType<? extends AbstractArrow> entityType, Level level) {
+         super(entityType, level);
+     }
+@@ -87,7 +_,13 @@
+         ItemStack pickupItemStack,
+         @Nullable ItemStack firedFromWeapon
+     ) {
++        // CraftBukkit start - handle the owner before the rest of things
++        this(entityType, x, y, z, level, pickupItemStack, firedFromWeapon, null);
++    }
++    protected AbstractArrow(EntityType<? extends AbstractArrow> entityType, double x, double y, double z, Level level, ItemStack pickupItemStack, @Nullable ItemStack firedFromWeapon, @Nullable LivingEntity ownerEntity) {
+         this(entityType, level);
++        this.setOwner(ownerEntity);
++        // CraftBukkit end
+         this.pickupItemStack = pickupItemStack.copy();
+         this.setCustomName(pickupItemStack.get(DataComponents.CUSTOM_NAME));
+         Unit unit = pickupItemStack.remove(DataComponents.INTANGIBLE_PROJECTILE);
+@@ -112,8 +_,8 @@
+     protected AbstractArrow(
+         EntityType<? extends AbstractArrow> entityType, LivingEntity owner, Level level, ItemStack pickupItemStack, @Nullable ItemStack firedFromWeapon
+     ) {
+-        this(entityType, owner.getX(), owner.getEyeY() - 0.1F, owner.getZ(), level, pickupItemStack, firedFromWeapon);
+-        this.setOwner(owner);
++        this(entityType, owner.getX(), owner.getEyeY() - 0.1F, owner.getZ(), level, pickupItemStack, firedFromWeapon, owner); // CraftBukkit
++        // this.setOwner(owner); // SPIGOT-7744 - Moved to the above constructor
+     }
+ 
+     public void setSoundEvent(SoundEvent soundEvent) {
+@@ -209,6 +_,7 @@
+                 this.applyEffectsFromBlocks();
+             }
+         } else {
++            if (this.tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds
+             this.inGroundTime = 0;
+             Vec3 vec31 = this.position();
+             if (this.isInWater()) {
+@@ -275,12 +_,12 @@
+ 
+             if (entityHitResult == null) {
+                 if (this.isAlive() && hitResult.getType() != HitResult.Type.MISS) {
+-                    this.hitTargetOrDeflectSelf(hitResult);
++                    this.preHitTargetOrDeflectSelf(hitResult); // CraftBukkit - projectile hit event
+                     this.hasImpulse = true;
+                 }
+                 break;
+             } else if (this.isAlive() && !this.noPhysics) {
+-                ProjectileDeflection projectileDeflection = this.hitTargetOrDeflectSelf(entityHitResult);
++                ProjectileDeflection projectileDeflection = this.preHitTargetOrDeflectSelf(entityHitResult); // CraftBukkit - projectile hit event
+                 this.hasImpulse = true;
+                 if (this.getPierceLevel() > 0 && projectileDeflection == ProjectileDeflection.NONE) {
+                     continue;
+@@ -313,6 +_,19 @@
+         }
+     }
+ 
++    // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows
++    @Override
++    public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) {
++        if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) {
++            if (this.piercingIgnoreEntityIds == null) {
++                this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
++            }
++            this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId());
++        }
++        return super.preHitTargetOrDeflectSelf(hitResult);
++    }
++    // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows
++
+     @Override
+     protected double getDefaultGravity() {
+         return 0.05;
+@@ -347,8 +_,8 @@
+ 
+     protected void tickDespawn() {
+         this.life++;
+-        if (this.life >= 1200) {
+-            this.discard();
++        if (this.life >= (this.pickup == Pickup.CREATIVE_ONLY ? this.level().paperConfig().entities.spawning.creativeArrowDespawnRate.value() : (this.pickup == Pickup.DISALLOWED ? this.level().paperConfig().entities.spawning.nonPlayerArrowDespawnRate.value() : ((this instanceof ThrownTrident) ? this.level().spigotConfig.tridentDespawnRate : this.level().spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - Configurable non-player arrow despawn rate; TODO: Extract this to init?
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -375,9 +_,9 @@
+     }
+ 
+     @Override
+-    public void push(double x, double y, double z) {
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - add push source entity param
+         if (!this.isInGround()) {
+-            super.push(x, y, z);
++            super.push(x, y, z, pushingEntity); // Paper - add push source entity param
+         }
+     }
+ 
+@@ -404,7 +_,7 @@
+             }
+ 
+             if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+                 return;
+             }
+ 
+@@ -420,10 +_,17 @@
+             livingEntity.setLastHurtMob(entity);
+         }
+ 
++        if (this.isCritArrow()) damageSource = damageSource.critical(); // Paper - add critical damage API
+         boolean flag = entity.getType() == EntityType.ENDERMAN;
+         int remainingFireTicks = entity.getRemainingFireTicks();
+         if (this.isOnFire() && !flag) {
+-            entity.igniteForSeconds(5.0F);
++            // CraftBukkit start
++            org.bukkit.event.entity.EntityCombustByEntityEvent combustEvent = new org.bukkit.event.entity.EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5.0F);
++            org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++            if (!combustEvent.isCancelled()) {
++                entity.igniteForSeconds(combustEvent.getDuration(), false);
++            }
++            // CraftBukkit end
+         }
+ 
+         if (entity.hurtOrSimulate(damageSource, ceil)) {
+@@ -461,7 +_,7 @@
+ 
+             this.playSound(this.soundEvent, 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
+             if (this.getPierceLevel() <= 0) {
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+             }
+         } else {
+             entity.setRemainingFireTicks(remainingFireTicks);
+@@ -472,7 +_,7 @@
+                     this.spawnAtLocation(serverLevel2, this.getPickupItem(), 0.1F);
+                 }
+ 
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+@@ -485,7 +_,7 @@
+             double max = Math.max(0.0, 1.0 - entity.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
+             Vec3 vec3 = this.getDeltaMovement().multiply(1.0, 0.0, 1.0).normalize().scale(d * 0.6 * max);
+             if (vec3.lengthSqr() > 0.0) {
+-                entity.push(vec3.x, 0.1, vec3.z);
++                entity.push(vec3.x, 0.1, vec3.z, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+             }
+         }
+     }
+@@ -597,7 +_,7 @@
+         this.setPierceLevel(compound.getByte("PierceLevel"));
+         if (compound.contains("SoundEvent", 8)) {
+             this.soundEvent = BuiltInRegistries.SOUND_EVENT
+-                .getOptional(ResourceLocation.parse(compound.getString("SoundEvent")))
++                .getOptional(ResourceLocation.tryParse(compound.getString("SoundEvent"))) // Paper - Validate resource location
+                 .orElse(this.getDefaultHitGroundSoundEvent());
+         }
+ 
+@@ -616,7 +_,14 @@
+ 
+     @Override
+     public void setOwner(@Nullable Entity entity) {
++        // Paper start - Fix PickupStatus getting reset
++        this.setOwner(entity, true);
++    }
++
++    public void setOwner(@Nullable Entity entity, boolean resetPickup) {
++        // Paper end - Fix PickupStatus getting reset
+         super.setOwner(entity);
++        if (!resetPickup) return; // Paper - Fix PickupStatus getting reset
+ 
+         this.pickup = switch (entity) {
+             case Player player when this.pickup == AbstractArrow.Pickup.DISALLOWED -> AbstractArrow.Pickup.ALLOWED;
+@@ -628,9 +_,24 @@
+     @Override
+     public void playerTouch(Player entity) {
+         if (!this.level().isClientSide && (this.isInGround() || this.isNoPhysics()) && this.shakeTime <= 0) {
+-            if (this.tryPickup(entity)) {
++            // CraftBukkit start
++            ItemStack itemstack = this.getPickupItem();
++            if (this.pickup == Pickup.ALLOWED && !itemstack.isEmpty() && entity.getInventory().canHold(itemstack) > 0) {
++                ItemEntity item = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack);
++                org.bukkit.event.player.PlayerPickupArrowEvent event = new org.bukkit.event.player.PlayerPickupArrowEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.level().getCraftServer(), item), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity());
++                // event.setCancelled(!entityhuman.canPickUpLoot); TODO
++                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    return;
++                }
++                itemstack = item.getItem();
++            }
++
++            if ((this.pickup == AbstractArrow.Pickup.ALLOWED && entity.getInventory().add(itemstack)) || (this.pickup == AbstractArrow.Pickup.CREATIVE_ONLY && entity.getAbilities().instabuild)) {
++                // CraftBukkit end
+                 entity.take(this, 1);
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
new file mode 100644
index 0000000000..75c739c6ba
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
++++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
+@@ -19,6 +_,8 @@
+     public static final double INITAL_ACCELERATION_POWER = 0.1;
+     public static final double DEFLECTION_SCALE = 0.5;
+     public double accelerationPower = 0.1;
++    public float bukkitYield = 1; // CraftBukkit
++    public boolean isIncendiary = true; // CraftBukkit
+ 
+     protected AbstractHurtingProjectile(EntityType<? extends AbstractHurtingProjectile> entityType, Level level) {
+         super(entityType, level);
+@@ -83,12 +_,12 @@
+             }
+ 
+             if (hitResultOnMoveVector.getType() != HitResult.Type.MISS && this.isAlive()) {
+-                this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
++                this.preHitTargetOrDeflectSelf(hitResultOnMoveVector);
+             }
+ 
+             this.createParticleTrail();
+         } else {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch
new file mode 100644
index 0000000000..f23f9c6a7e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Arrow.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/entity/projectile/Arrow.java
++++ b/net/minecraft/world/entity/projectile/Arrow.java
+@@ -121,12 +_,13 @@
+                         mobEffectInstance.isVisible()
+                     ),
+                     effectSource
++                    , org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW // CraftBukkit
+                 );
+             }
+         }
+ 
+         for (MobEffectInstance mobEffectInstance : potionContents.customEffects()) {
+-            living.addEffect(mobEffectInstance, effectSource);
++            living.addEffect(mobEffectInstance, effectSource, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch
new file mode 100644
index 0000000000..b04695ec1e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/DragonFireball.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/projectile/DragonFireball.java
++++ b/net/minecraft/world/entity/projectile/DragonFireball.java
+@@ -52,9 +_,11 @@
+                     }
+                 }
+ 
++                if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), entitiesOfClass.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) areaEffectCloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events
+                 this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1);
+-                this.level().addFreshEntity(areaEffectCloud);
+-                this.discard();
++                this.level().addFreshEntity(areaEffectCloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason
++                } else areaEffectCloud.discard(null); // Paper - EnderDragon Events
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
new file mode 100644
index 0000000000..e980aa3858
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/projectile/EvokerFangs.java
++++ b/net/minecraft/world/entity/projectile/EvokerFangs.java
+@@ -109,7 +_,7 @@
+             }
+ 
+             if (--this.lifeTicks < 0) {
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+@@ -118,7 +_,7 @@
+         LivingEntity owner = this.getOwner();
+         if (target.isAlive() && !target.isInvulnerable() && target != owner) {
+             if (owner == null) {
+-                target.hurt(this.damageSources().magic(), 6.0F);
++                target.hurt(this.damageSources().magic().customEventDamager(this), 6.0F); // CraftBukkit // Paper - fix DamageSource API
+             } else {
+                 if (owner.isAlliedTo(target)) {
+                     return;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
index 67aa21fe00..450c900de3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
@@ -1,16 +1,6 @@
 --- a/net/minecraft/world/entity/projectile/EyeOfEnder.java
 +++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java
-@@ -17,6 +17,9 @@
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class EyeOfEnder extends Entity implements ItemSupplier {
- 
-@@ -73,6 +76,11 @@
+@@ -70,6 +_,11 @@
      }
  
      public void signalTo(BlockPos pos) {
@@ -19,10 +9,10 @@
 +    }
 +    public void signalTo(BlockPos pos, boolean update) {
 +        // Paper end - Change EnderEye target without changing other things
-         double d0 = (double) pos.getX();
-         int i = pos.getY();
-         double d1 = (double) pos.getZ();
-@@ -90,8 +98,10 @@
+         double d = pos.getX();
+         int y = pos.getY();
+         double d1 = pos.getZ();
+@@ -86,8 +_,10 @@
              this.tz = d1;
          }
  
@@ -33,24 +23,24 @@
      }
  
      @Override
-@@ -153,7 +163,7 @@
-             ++this.life;
+@@ -161,7 +_,7 @@
+             this.life++;
              if (this.life > 80 && !this.level().isClientSide) {
                  this.playSound(SoundEvents.ENDER_EYE_DEATH, 1.0F, 1.0F);
 -                this.discard();
-+                this.discard(this.surviveAfterDeath ? EntityRemoveEvent.Cause.DROP : EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++                this.discard(this.surviveAfterDeath ? org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP : org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
                  if (this.surviveAfterDeath) {
                      this.level().addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), this.getItem()));
                  } else {
-@@ -174,7 +184,12 @@
+@@ -181,7 +_,12 @@
      @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         if (nbt.contains("Item", 10)) {
--            this.setItem((ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem()));
+     public void readAdditionalSaveData(CompoundTag compound) {
+         if (compound.contains("Item", 10)) {
+-            this.setItem(ItemStack.parse(this.registryAccess(), compound.getCompound("Item")).orElse(this.getDefaultItem()));
 +            // CraftBukkit start - SPIGOT-6103 summon, see also SPIGOT-5474
-+            ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem());
-+            if (!itemstack.isEmpty()) {
-+                this.setItem(itemstack);
++            ItemStack itemStack = ItemStack.parse(this.registryAccess(), compound.getCompound("Item")).orElse(this.getDefaultItem());
++            if (!itemStack.isEmpty()) {
++                this.setItem(itemStack);
 +            }
 +            // CraftBukkit end
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch
new file mode 100644
index 0000000000..96bac378a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Fireball.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/projectile/Fireball.java
++++ b/net/minecraft/world/entity/projectile/Fireball.java
+@@ -60,7 +_,12 @@
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+         if (compound.contains("Item", 10)) {
+-            this.setItem(ItemStack.parse(this.registryAccess(), compound.getCompound("Item")).orElse(this.getDefaultItem()));
++            // CraftBukkit start - SPIGOT-5474 probably came from bugged earlier versions
++            final ItemStack itemStack = ItemStack.parse(this.registryAccess(), compound.getCompound("Item")).orElse(this.getDefaultItem());
++            if (!itemStack.isEmpty()) {
++                this.setItem(itemStack);
++            }
++            // CraftBukkit end
+         } else {
+             this.setItem(this.getDefaultItem());
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
new file mode 100644
index 0000000000..fb50fc5183
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
@@ -0,0 +1,117 @@
+--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+@@ -43,6 +_,7 @@
+     public int lifetime;
+     @Nullable
+     public LivingEntity attachedToEntity;
++    @Nullable public java.util.UUID spawningEntity; // Paper
+ 
+     public FireworkRocketEntity(EntityType<? extends FireworkRocketEntity> entityType, Level level) {
+         super(entityType, level);
+@@ -84,6 +_,26 @@
+         this.setOwner(shooter);
+     }
+ 
++    // Spigot Start - copied from tick
++    @Override
++    public void inactiveTick() {
++        this.life += 1;
++
++        if (this.life > this.lifetime) {
++            Level world = this.level();
++
++            if (world instanceof ServerLevel serverLevel) {
++                // CraftBukkit start
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                    this.explode(serverLevel);
++                }
++                // CraftBukkit end
++            }
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
+     @Override
+     protected void defineSynchedData(SynchedEntityData.Builder builder) {
+         builder.define(DATA_ID_FIREWORKS_ITEM, getDefaultItem());
+@@ -158,7 +_,7 @@
+         }
+ 
+         if (!this.noPhysics && this.isAlive() && hitResultOnMoveVector.getType() != HitResult.Type.MISS) {
+-            this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
++            this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event
+             this.hasImpulse = true;
+         }
+ 
+@@ -182,7 +_,11 @@
+         }
+ 
+         if (this.life > this.lifetime && this.level() instanceof ServerLevel serverLevel) {
+-            this.explode(serverLevel);
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                this.explode(serverLevel);
++            }
++            // CraftBukkit end
+         }
+     }
+ 
+@@ -190,14 +_,18 @@
+         level.broadcastEntityEvent(this, (byte)17);
+         this.gameEvent(GameEvent.EXPLODE, this.getOwner());
+         this.dealExplosionDamage(level);
+-        this.discard();
++        this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
+     protected void onHitEntity(EntityHitResult result) {
+         super.onHitEntity(result);
+         if (this.level() instanceof ServerLevel serverLevel) {
+-            this.explode(serverLevel);
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                this.explode(serverLevel);
++            }
++            // CraftBukkit end
+         }
+     }
+ 
+@@ -206,7 +_,11 @@
+         BlockPos blockPos = new BlockPos(result.getBlockPos());
+         this.level().getBlockState(blockPos).entityInside(this.level(), blockPos, this);
+         if (this.level() instanceof ServerLevel serverLevel && this.hasExplosion()) {
+-            this.explode(serverLevel);
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                this.explode(serverLevel);
++            }
++            // CraftBukkit end
+         }
+ 
+         super.onHitBlock(result);
+@@ -278,6 +_,11 @@
+         compound.putInt("LifeTime", this.lifetime);
+         compound.put("FireworksItem", this.getItem().save(this.registryAccess()));
+         compound.putBoolean("ShotAtAngle", this.entityData.get(DATA_SHOT_AT_ANGLE));
++        // Paper start
++        if (this.spawningEntity != null) {
++            compound.putUUID("SpawningEntity", this.spawningEntity);
++        }
++        // Paper end
+     }
+ 
+     @Override
+@@ -298,6 +_,11 @@
+         if (compound.contains("ShotAtAngle")) {
+             this.entityData.set(DATA_SHOT_AT_ANGLE, compound.getBoolean("ShotAtAngle"));
+         }
++        // Paper start
++        if (compound.hasUUID("SpawningEntity")) {
++            this.spawningEntity = compound.getUUID("SpawningEntity");
++        }
++        // Paper end
+     }
+ 
+     private List<FireworkExplosion> getExplosions() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
new file mode 100644
index 0000000000..68d1d7f9e5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
@@ -0,0 +1,281 @@
+--- a/net/minecraft/world/entity/projectile/FishingHook.java
++++ b/net/minecraft/world/entity/projectile/FishingHook.java
+@@ -66,10 +_,26 @@
+     private final int luck;
+     private final int lureSpeed;
+ 
++    // CraftBukkit start - Extra variables to enable modification of fishing wait time, values are minecraft defaults
++    public int minWaitTime = 100;
++    public int maxWaitTime = 600;
++    public int minLureTime = 20;
++    public int maxLureTime = 80;
++    public float minLureAngle = 0.0F;
++    public float maxLureAngle = 360.0F;
++    public boolean applyLure = true;
++    public boolean rainInfluenced = true;
++    public boolean skyInfluenced = true;
++    // CraftBukkit end
++
+     private FishingHook(EntityType<? extends FishingHook> entityType, Level level, int luck, int lureSpeed) {
+         super(entityType, level);
+         this.luck = Math.max(0, luck);
+         this.lureSpeed = Math.max(0, lureSpeed);
++        // Paper start - Configurable fishing time ranges
++        this.minWaitTime = level.paperConfig().fishingTimeRange.minimum;
++        this.maxWaitTime = level.paperConfig().fishingTimeRange.maximum;
++        // Paper end - Configurable fishing time ranges
+     }
+ 
+     public FishingHook(EntityType<? extends FishingHook> entityType, Level level) {
+@@ -147,12 +_,12 @@
+         super.tick();
+         Player playerOwner = this.getPlayerOwner();
+         if (playerOwner == null) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else if (this.level().isClientSide || !this.shouldStopFishing(playerOwner)) {
+             if (this.onGround()) {
+                 this.life++;
+                 if (this.life >= 1200) {
+-                    this.discard();
++                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                     return;
+                 }
+             } else {
+@@ -251,14 +_,14 @@
+         if (!player.isRemoved() && player.isAlive() && (isFishingRod || isFishingRod1) && !(this.distanceToSqr(player) > 1024.0)) {
+             return false;
+         } else {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             return true;
+         }
+     }
+ 
+     private void checkCollision() {
+         HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+-        this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
++        this.preHitTargetOrDeflectSelf(hitResultOnMoveVector);
+     }
+ 
+     @Override
+@@ -289,11 +_,11 @@
+         ServerLevel serverLevel = (ServerLevel)this.level();
+         int i = 1;
+         BlockPos blockPos = pos.above();
+-        if (this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockPos)) {
++        if (this.rainInfluenced && this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockPos)) { // CraftBukkit
+             i++;
+         }
+ 
+-        if (this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockPos)) {
++        if (this.skyInfluenced && this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockPos)) { // CraftBukkit
+             i--;
+         }
+ 
+@@ -303,6 +_,10 @@
+                 this.timeUntilLured = 0;
+                 this.timeUntilHooked = 0;
+                 this.getEntityData().set(DATA_BITING, false);
++                // CraftBukkit start
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) this.getPlayerOwner().getBukkitEntity(), null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.event.player.PlayerFishEvent.State.FAILED_ATTEMPT);
++                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++                // CraftBukkit end
+             }
+         } else if (this.timeUntilHooked > 0) {
+             this.timeUntilHooked -= i;
+@@ -326,6 +_,13 @@
+                     serverLevel.sendParticles(ParticleTypes.FISHING, d, d1, d2, 0, -f2, 0.01, f1, 1.0);
+                 }
+             } else {
++                // CraftBukkit start
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) this.getPlayerOwner().getBukkitEntity(), null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.event.player.PlayerFishEvent.State.BITE);
++                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++                if (playerFishEvent.isCancelled()) {
++                    return;
++                }
++                // CraftBukkit end
+                 this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
+                 double d3 = this.getY() + 0.5;
+                 serverLevel.sendParticles(
+@@ -377,14 +_,33 @@
+             }
+ 
+             if (this.timeUntilLured <= 0) {
+-                this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F);
+-                this.timeUntilHooked = Mth.nextInt(this.random, 20, 80);
++                // CraftBukkit start - logic to modify fishing wait time, lure time, and lure angle
++                this.fishAngle = Mth.nextFloat(this.random, this.minLureAngle, this.maxLureAngle);
++                this.timeUntilHooked = Mth.nextInt(this.random, this.minLureTime, this.maxLureTime);
++                // CraftBukkit end
++                // Paper start - Add missing fishing event state
++                if (this.getPlayerOwner() != null) {
++                    org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) this.getPlayerOwner().getBukkitEntity(), null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.event.player.PlayerFishEvent.State.LURED);
++                    if (!playerFishEvent.callEvent()) {
++                        this.timeUntilHooked = 0;
++                        return;
++                    }
++                }
++                // Paper end - Add missing fishing event state
+             }
+         } else {
+-            this.timeUntilLured = Mth.nextInt(this.random, 100, 600);
+-            this.timeUntilLured = this.timeUntilLured - this.lureSpeed;
++            // CraftBukkit start - logic to modify fishing wait time
++            this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic
++            // CraftBukkit end
+         }
+     }
++
++    // Paper start - more projectile api - extract time until lured reset logic
++    public void resetTimeUntilLured() {
++        this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
++        this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop
++    }
++    // Paper end - more projectile api - extract time until lured reset logic
+ 
+     public boolean calculateOpenWater(BlockPos pos) {
+         FishingHook.OpenWaterType openWaterType = FishingHook.OpenWaterType.INVALID;
+@@ -443,15 +_,34 @@
+     public void readAdditionalSaveData(CompoundTag compound) {
+     }
+ 
++
++    // Paper start - Add hand parameter to PlayerFishEvent
++    @Deprecated
++    @io.papermc.paper.annotation.DoNotUse
+     public int retrieve(ItemStack stack) {
++        return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, stack);
++    }
++
++    public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack stack) {
++        // Paper end - Add hand parameter to PlayerFishEvent
+         Player playerOwner = this.getPlayerOwner();
+         if (!this.level().isClientSide && playerOwner != null && !this.shouldStopFishing(playerOwner)) {
+             int i = 0;
+             if (this.hookedIn != null) {
++                // CraftBukkit start
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent
++                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++                if (playerFishEvent.isCancelled()) {
++                    return 0;
++                }
++                if (this.hookedIn != null) { // Paper - re-check to see if there is a hooked entity
++                // CraftBukkit end
+                 this.pullEntity(this.hookedIn);
+                 CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)playerOwner, stack, this, Collections.emptyList());
+                 this.level().broadcastEntityEvent(this, (byte)31);
+                 i = this.hookedIn instanceof ItemEntity ? 3 : 5;
++                } // Paper - re-check to see if there is a hooked entity
+             } else if (this.nibble > 0) {
+                 LootParams lootParams = new LootParams.Builder((ServerLevel)this.level())
+                     .withParameter(LootContextParams.ORIGIN, this.position())
+@@ -464,19 +_,43 @@
+                 CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)playerOwner, stack, this, randomItems);
+ 
+                 for (ItemStack itemStack : randomItems) {
+-                    ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
++                    // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemStack turns out to be empty
++                    // if the item stack is empty we instead just have our itemEntity as null
++                    ItemEntity itemEntity = null;
++                    if (!itemStack.isEmpty()) {
++                        itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
++                    }
++                    // Paper end
++                    // CraftBukkit start
++                    org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), itemEntity != null ? itemEntity.getBukkitEntity() : null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.CAUGHT_FISH); // Paper - itemEntity may be null // Paper - Add hand parameter to PlayerFishEvent
++                    playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1);
++                    this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++                    if (playerFishEvent.isCancelled()) {
++                        return 0;
++                    }
++                    // CraftBukkit end
+                     double d = playerOwner.getX() - this.getX();
+                     double d1 = playerOwner.getY() - this.getY();
+                     double d2 = playerOwner.getZ() - this.getZ();
+                     double d3 = 0.1;
+-                    itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
+-                    this.level().addFreshEntity(itemEntity);
+-                    playerOwner.level()
+-                        .addFreshEntity(
+-                            new ExperienceOrb(
+-                                playerOwner.level(), playerOwner.getX(), playerOwner.getY() + 0.5, playerOwner.getZ() + 0.5, this.random.nextInt(6) + 1
+-                            )
+-                        );
++                    // Paper start - entity item can be null, so we need to check against this
++                    if (itemEntity != null) {
++                        itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
++                        this.level().addFreshEntity(itemEntity);
++                    }
++                    // Paper end
++                    // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
++                    if (playerFishEvent.getExpToDrop() > 0) {
++                        playerOwner.level()
++                            .addFreshEntity(
++                                new ExperienceOrb(
++                                    playerOwner.level(), playerOwner.getX(), playerOwner.getY() + 0.5, playerOwner.getZ() + 0.5, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this // Paper
++                                )
++                            );
++                    }
++                    // CraftBukkit end
++
+                     if (itemStack.is(ItemTags.FISHES)) {
+                         playerOwner.awardStat(Stats.FISH_CAUGHT, 1);
+                     }
+@@ -486,10 +_,27 @@
+             }
+ 
+             if (this.onGround()) {
++                // CraftBukkit start
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent
++                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++                if (playerFishEvent.isCancelled()) {
++                    return 0;
++                }
++                // CraftBukkit end
+                 i = 2;
+             }
++            // CraftBukkit start
++            if (i == 0) {
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent
++                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++                if (playerFishEvent.isCancelled()) {
++                    return 0;
++                }
++            }
++            // CraftBukkit end
+ 
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             return i;
+         } else {
+             return 0;
+@@ -520,8 +_,15 @@
+ 
+     @Override
+     public void remove(Entity.RemovalReason reason) {
++        // CraftBukkit start - add Bukkit remove cause
++        this.remove(reason, null);
++    }
++
++    @Override
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        // CraftBukkit end
+         this.updateOwnerInfo(null);
+-        super.remove(reason);
++        super.remove(reason, cause); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
+@@ -570,7 +_,7 @@
+         if (this.getPlayerOwner() == null) {
+             int data = packet.getData();
+             LOGGER.error("Failed to recreate fishing hook on client. {} (id: {}) is not a valid owner.", this.level().getEntity(data), data);
+-            this.discard();
++            this.discard(null); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch
new file mode 100644
index 0000000000..d484e6d4eb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LargeFireball.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/entity/projectile/LargeFireball.java
++++ b/net/minecraft/world/entity/projectile/LargeFireball.java
+@@ -18,11 +_,13 @@
+ 
+     public LargeFireball(EntityType<? extends LargeFireball> entityType, Level level) {
+         super(entityType, level);
++        this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+     }
+ 
+     public LargeFireball(Level level, LivingEntity owner, Vec3 movement, int explosionPower) {
+         super(EntityType.FIREBALL, owner, movement, level);
+         this.explosionPower = explosionPower;
++        this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+     }
+ 
+     @Override
+@@ -30,8 +_,16 @@
+         super.onHit(result);
+         if (this.level() instanceof ServerLevel serverLevel) {
+             boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+-            this.level().explode(this, this.getX(), this.getY(), this.getZ(), this.explosionPower, _boolean, Level.ExplosionInteraction.MOB);
+-            this.discard();
++            // CraftBukkit start - fire ExplosionPrimeEvent
++            org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled()) {
++                // give 'this' instead of (Entity) null so we know what causes the damage
++                this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
++            }
++            // CraftBukkit end
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -57,7 +_,8 @@
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+         if (compound.contains("ExplosionPower", 99)) {
+-            this.explosionPower = compound.getByte("ExplosionPower");
++            // CraftBukkit - set bukkitYield when setting explosionpower
++            this.bukkitYield = this.explosionPower = compound.getByte("ExplosionPower");
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
new file mode 100644
index 0000000000..1e27f667a5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/entity/projectile/LlamaSpit.java
++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java
+@@ -43,16 +_,16 @@
+         super.tick();
+         Vec3 deltaMovement = this.getDeltaMovement();
+         HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+-        this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
++        this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event
+         double d = this.getX() + deltaMovement.x;
+         double d1 = this.getY() + deltaMovement.y;
+         double d2 = this.getZ() + deltaMovement.z;
+         this.updateRotation();
+         float f = 0.99F;
+         if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else if (this.isInWaterOrBubble()) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else {
+             this.setDeltaMovement(deltaMovement.scale(0.99F));
+             this.applyGravity();
+@@ -76,7 +_,7 @@
+     protected void onHitBlock(BlockHitResult result) {
+         super.onHitBlock(result);
+         if (!this.level().isClientSide) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
new file mode 100644
index 0000000000..637335db7b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
@@ -0,0 +1,230 @@
+--- a/net/minecraft/world/entity/projectile/Projectile.java
++++ b/net/minecraft/world/entity/projectile/Projectile.java
+@@ -43,6 +_,7 @@
+     public boolean hasBeenShot;
+     @Nullable
+     private Entity lastDeflectedBy;
++    protected boolean hitCancelled = false; // CraftBukkit
+ 
+     Projectile(EntityType<? extends Projectile> entityType, Level level) {
+         super(entityType, level);
+@@ -53,15 +_,36 @@
+             this.ownerUUID = owner.getUUID();
+             this.cachedOwner = owner;
+         }
+-    }
++        // Paper start - Refresh ProjectileSource for projectiles
++        else {
++            this.ownerUUID = null;
++            this.cachedOwner = null;
++            this.projectileSource = null;
++        }
++        // Paper end - Refresh ProjectileSource for projectiles
++        this.refreshProjectileSource(false); // Paper
++    }
++
++    // Paper start - Refresh ProjectileSource for projectiles
++    public void refreshProjectileSource(boolean fillCache) {
++        if (fillCache) {
++            this.getOwner();
++        }
++        if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof org.bukkit.projectiles.ProjectileSource projSource) {
++            this.projectileSource = projSource;
++        }
++    }
++    // Paper end - Refresh ProjectileSource for projectiles
+ 
+     @Nullable
+     @Override
+     public Entity getOwner() {
+         if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) {
++            this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
+             return this.cachedOwner;
+         } else if (this.ownerUUID != null) {
+             this.cachedOwner = this.findOwner(this.ownerUUID);
++            this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
+             return this.cachedOwner;
+         } else {
+             return null;
+@@ -98,6 +_,7 @@
+     protected void readAdditionalSaveData(CompoundTag compound) {
+         if (compound.hasUUID("Owner")) {
+             this.setOwnerThroughUUID(compound.getUUID("Owner"));
++            if (this instanceof ThrownEnderpearl && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && this.level().paperConfig().misc.legacyEnderPearlBehavior) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit
+         }
+ 
+         this.leftOwner = compound.getBoolean("LeftOwner");
+@@ -175,13 +_,22 @@
+         float f2 = Mth.cos(y * (float) (Math.PI / 180.0)) * Mth.cos(x * (float) (Math.PI / 180.0));
+         this.shoot(f, f1, f2, velocity, inaccuracy);
+         Vec3 knownMovement = shooter.getKnownMovement();
++        // Paper start - allow disabling relative velocity
++        if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) {
+         this.setDeltaMovement(this.getDeltaMovement().add(knownMovement.x, shooter.onGround() ? 0.0 : knownMovement.y, knownMovement.z));
++        }
++        // Paper end - allow disabling relative velocity
+     }
+ 
+     public static <T extends Projectile> T spawnProjectileFromRotation(
+         Projectile.ProjectileFactory<T> factory, ServerLevel level, ItemStack spawnedFrom, LivingEntity owner, float z, float velocity, float innaccuracy
+     ) {
+-        return spawnProjectile(
++        // Paper start - PlayerLaunchProjectileEvent
++        return spawnProjectileFromRotationDelayed(factory, level, spawnedFrom, owner, z, velocity, innaccuracy).spawn();
++    }
++    public static <T extends Projectile> Delayed<T> spawnProjectileFromRotationDelayed(Projectile.ProjectileFactory<T> factory, ServerLevel level, ItemStack spawnedFrom, LivingEntity owner, float z, float velocity, float innaccuracy) {
++        return spawnProjectileDelayed(
++        // Paper end - PlayerLaunchProjectileEvent
+             factory.create(level, owner, spawnedFrom),
+             level,
+             spawnedFrom,
+@@ -200,7 +_,22 @@
+         float velocity,
+         float inaccuracy
+     ) {
+-        return spawnProjectile(factory.create(level, owner, spawnedFrom), level, spawnedFrom, projectile -> projectile.shoot(x, y, z, velocity, inaccuracy));
++        // Paper start - fixes and addition to spawn reason API
++        return Projectile.spawnProjectileUsingShootDelayed(factory, level, spawnedFrom, owner, x, y, z, velocity, inaccuracy).spawn();
++    }
++    public static <T extends Projectile> Delayed<T> spawnProjectileUsingShootDelayed(
++        Projectile.ProjectileFactory<T> factory,
++        ServerLevel level,
++        ItemStack spawnedFrom,
++        LivingEntity owner,
++        double x,
++        double y,
++        double z,
++        float velocity,
++        float inaccuracy
++    ) {
++        return spawnProjectileDelayed(factory.create(level, owner, spawnedFrom), level, spawnedFrom, projectile -> projectile.shoot(x, y, z, velocity, inaccuracy));
++        // Paper end - fixes and addition to spawn reason API
+     }
+ 
+     public static <T extends Projectile> T spawnProjectileUsingShoot(
+@@ -214,11 +_,45 @@
+     }
+ 
+     public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack stack, Consumer<T> adapter) {
++        // Paper start - delayed projectile spawning
++        return spawnProjectileDelayed(projectile, level, stack, adapter).spawn();
++    }
++    public static <T extends Projectile> Delayed<T> spawnProjectileDelayed(T projectile, ServerLevel level, ItemStack stack, Consumer<T> adapter) {
++        // Paper end - delayed projectile spawning
+         adapter.accept(projectile);
+-        level.addFreshEntity(projectile);
+-        projectile.applyOnProjectileSpawned(level, stack);
+-        return projectile;
+-    }
++        return new Delayed<>(projectile, level, stack); // Paper - delayed projectile spawning
++    }
++
++    // Paper start - delayed projectile spawning
++    public record Delayed<T extends Projectile>(
++        T projectile,
++        ServerLevel world,
++        ItemStack projectileStack
++    ) {
++        // Taken from net.minecraft.world.entity.projectile.Projectile.spawnProjectile(T, net.minecraft.server.level.ServerLevel, net.minecraft.world.item.ItemStack, java.util.function.Consumer<T>)
++        public boolean attemptSpawn() {
++            if (!this.world.addFreshEntity(this.projectile)) return false;
++            this.projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
++            return true;
++        }
++
++        public T spawn() {
++            this.attemptSpawn();
++            return this.projectile();
++        }
++
++        public boolean attemptSpawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++            if (!this.world.addFreshEntity(this.projectile, reason)) return false;
++            this.projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
++            return true;
++        }
++
++        public T spawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++            this.attemptSpawn(reason);
++            return this.projectile();
++        }
++    }
++    // Paper end - delayed projectile spawning
+ 
+     public void applyOnProjectileSpawned(ServerLevel level, ItemStack spawnedFrom) {
+         EnchantmentHelper.onProjectileSpawned(level, spawnedFrom, this, item -> {});
+@@ -230,6 +_,17 @@
+         }
+     }
+ 
++    // CraftBukkit start - call projectile hit event
++    public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) { // Paper - protected -> public
++        org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, hitResult);
++        this.hitCancelled = event != null && event.isCancelled();
++        if (hitResult.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
++            return this.hitTargetOrDeflectSelf(hitResult);
++        }
++        return ProjectileDeflection.NONE;
++    }
++    // CraftBukkit end
++
+     protected ProjectileDeflection hitTargetOrDeflectSelf(HitResult hitResult) {
+         if (hitResult.getType() == HitResult.Type.ENTITY) {
+             EntityHitResult entityHitResult = (EntityHitResult)hitResult;
+@@ -261,7 +_,13 @@
+     public boolean deflect(ProjectileDeflection deflection, @Nullable Entity entity, @Nullable Entity owner, boolean deflectedByPlayer) {
+         deflection.deflect(this, entity, this.random);
+         if (!this.level().isClientSide) {
+-            this.setOwner(owner);
++            // Paper start - Fix PickupStatus getting reset
++            if (this instanceof AbstractArrow arrow) {
++                arrow.setOwner(owner, false);
++            } else {
++                this.setOwner(owner);
++            }
++            // Paper end - Fix PickupStatus getting reset
+             this.onDeflection(entity, deflectedByPlayer);
+         }
+ 
+@@ -297,6 +_,11 @@
+     }
+ 
+     protected void onHitBlock(BlockHitResult result) {
++        // CraftBukkit start - cancellable hit event
++        if (this.hitCancelled) {
++            return;
++        }
++        // CraftBukkit end
+         BlockState blockState = this.level().getBlockState(result.getBlockPos());
+         blockState.onProjectileHit(this.level(), blockState, result, this);
+     }
+@@ -306,6 +_,15 @@
+             return false;
+         } else {
+             Entity owner = this.getOwner();
++            // Paper start - Cancel hit for vanished players
++            if (owner instanceof net.minecraft.server.level.ServerPlayer && target instanceof net.minecraft.server.level.ServerPlayer) {
++                org.bukkit.entity.Player collided = (org.bukkit.entity.Player) target.getBukkitEntity();
++                org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) owner.getBukkitEntity();
++                if (!shooter.canSee(collided)) {
++                    return false;
++                }
++            }
++            // Paper end - Cancel hit for vanished players
+             return owner == null || this.leftOwner || !owner.isPassengerOfSameVehicle(target);
+         }
+     }
+@@ -318,13 +_,7 @@
+     }
+ 
+     protected static float lerpRotation(float currentRotation, float targetRotation) {
+-        while (targetRotation - currentRotation < -180.0F) {
+-            currentRotation -= 360.0F;
+-        }
+-
+-        while (targetRotation - currentRotation >= 180.0F) {
+-            currentRotation += 360.0F;
+-        }
++        currentRotation += Math.round((targetRotation - currentRotation) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server
+ 
+         return Mth.lerp(0.2F, currentRotation, targetRotation);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
new file mode 100644
index 0000000000..310b6cb4c4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
++++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
+@@ -57,7 +_,21 @@
+         this.finalTarget = finalTarget;
+         this.currentMoveDirection = Direction.UP;
+         this.selectNextMoveDirection(axis);
+-    }
++        this.projectileSource = shooter.getBukkitLivingEntity(); // CraftBukkit
++    }
++
++    // CraftBukkit start
++    @Nullable
++    public Entity getTarget() {
++        return this.finalTarget;
++    }
++
++    public void setTarget(Entity e) {
++        this.finalTarget = e;
++        this.currentMoveDirection = Direction.UP;
++        this.selectNextMoveDirection(Direction.Axis.X);
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public SoundSource getSoundSource() {
+@@ -187,7 +_,7 @@
+     @Override
+     public void checkDespawn() {
+         if (this.level().getDifficulty() == Difficulty.PEACEFUL) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -233,7 +_,7 @@
+         }
+ 
+         if (hitResult != null && this.isAlive() && hitResult.getType() != HitResult.Type.MISS) {
+-            this.hitTargetOrDeflectSelf(hitResult);
++            this.preHitTargetOrDeflectSelf(hitResult); // CraftBukkit - projectile hit event
+         }
+ 
+         ProjectileUtil.rotateTowardsMovement(this, 0.5F);
+@@ -301,7 +_,7 @@
+             }
+ 
+             if (entity instanceof LivingEntity livingEntity1) {
+-                livingEntity1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), MoreObjects.firstNonNull(owner, this));
++                livingEntity1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), MoreObjects.firstNonNull(owner, this), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             }
+         }
+     }
+@@ -314,14 +_,20 @@
+     }
+ 
+     private void destroy() {
+-        this.discard();
++        // CraftBukkit start - add Bukkit remove cause
++        this.destroy(null);
++    }
++
++    private void destroy(org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        this.discard(cause);
++        // CraftBukkit end
+         this.level().gameEvent(GameEvent.ENTITY_DAMAGE, this.position(), GameEvent.Context.of(this));
+     }
+ 
+     @Override
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+-        this.destroy();
++        this.destroy(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
+@@ -336,9 +_,14 @@
+ 
+     @Override
+     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
++        // CraftBukkit start
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false)) {
++            return false;
++        }
++        // CraftBukkit end
+         this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F);
+         level.sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2, 0.2, 0.2, 0.0);
+-        this.destroy();
++        this.destroy(org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
+         return true;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch
new file mode 100644
index 0000000000..ceefdcdb81
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/entity/projectile/SmallFireball.java
++++ b/net/minecraft/world/entity/projectile/SmallFireball.java
+@@ -23,6 +_,11 @@
+ 
+     public SmallFireball(Level level, LivingEntity owner, Vec3 movement) {
+         super(EntityType.SMALL_FIREBALL, owner, movement, level);
++        // CraftBukkit start
++        if (this.getOwner() != null && this.getOwner() instanceof Mob) {
++            this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
++        }
++        // CraftBukkit end
+     }
+ 
+     public SmallFireball(Level level, double x, double y, double z, Vec3 movement) {
+@@ -36,7 +_,14 @@
+             Entity var7 = result.getEntity();
+             Entity owner = this.getOwner();
+             int remainingFireTicks = var7.getRemainingFireTicks();
+-            var7.igniteForSeconds(5.0F);
++            // CraftBukkit start - Entity damage by entity event + combust event
++            org.bukkit.event.entity.EntityCombustByEntityEvent event = new org.bukkit.event.entity.EntityCombustByEntityEvent(this.getBukkitEntity(), var7.getBukkitEntity(), 5.0F);
++            var7.level().getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled()) {
++                var7.igniteForSeconds(event.getDuration(), false);
++            }
++            // CraftBukkit end
+             DamageSource damageSource = this.damageSources().fireball(this, owner);
+             if (!var7.hurtServer(serverLevel, damageSource, 5.0F)) {
+                 var7.setRemainingFireTicks(remainingFireTicks);
+@@ -51,9 +_,9 @@
+         super.onHitBlock(result);
+         if (this.level() instanceof ServerLevel serverLevel) {
+             Entity owner = this.getOwner();
+-            if (!(owner instanceof Mob) || serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++            if (this.isIncendiary) { // CraftBukkit
+                 BlockPos blockPos = result.getBlockPos().relative(result.getDirection());
+-                if (this.level().isEmptyBlock(blockPos)) {
++                if (this.level().isEmptyBlock(blockPos) && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockPos, this).isCancelled()) { // CraftBukkit
+                     this.level().setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level(), blockPos));
+                 }
+             }
+@@ -64,7 +_,7 @@
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+         if (!this.level().isClientSide) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch
new file mode 100644
index 0000000000..5c87f7788c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Snowball.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/projectile/Snowball.java
++++ b/net/minecraft/world/entity/projectile/Snowball.java
+@@ -61,7 +_,7 @@
+         super.onHit(result);
+         if (!this.level().isClientSide) {
+             this.level().broadcastEntityEvent(this, (byte)3);
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
new file mode 100644
index 0000000000..e7c4ff58ef
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/projectile/SpectralArrow.java
++++ b/net/minecraft/world/entity/projectile/SpectralArrow.java
+@@ -38,7 +_,7 @@
+     protected void doPostHurtEffects(LivingEntity living) {
+         super.doPostHurtEffects(living);
+         MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0);
+-        living.addEffect(mobEffectInstance, this.getEffectSource());
++        living.addEffect(mobEffectInstance, this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
index 6471dbc99e..bd84b5d136 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
 +++ b/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
-@@ -34,6 +34,12 @@
+@@ -35,6 +_,12 @@
  
      protected abstract Item getDefaultItem();
  
@@ -12,4 +12,4 @@
 +
      @Override
      public ItemStack getItem() {
-         return (ItemStack) this.getEntityData().get(ThrowableItemProjectile.DATA_ITEM_STACK);
+         return this.getEntityData().get(DATA_ITEM_STACK);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
new file mode 100644
index 0000000000..3a35555ec9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java
++++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java
+@@ -59,7 +_,7 @@
+         this.applyEffectsFromBlocks();
+         super.tick();
+         if (hitResultOnMoveVector.getType() != HitResult.Type.MISS && this.isAlive()) {
+-            this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
++            this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
new file mode 100644
index 0000000000..4c2c2ef103
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/entity/projectile/ThrownEgg.java
++++ b/net/minecraft/world/entity/projectile/ThrownEgg.java
+@@ -59,22 +_,56 @@
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+         if (!this.level().isClientSide) {
+-            if (this.random.nextInt(8) == 0) {
++            // CraftBukkit start
++            boolean hatching = this.random.nextInt(8) == 0;
++            if (true) {
++            // CraftBukkit end
+                 int i = 1;
+                 if (this.random.nextInt(32) == 0) {
+                     i = 4;
+                 }
++                // CraftBukkit start
++                org.bukkit.entity.EntityType hatchingType = org.bukkit.entity.EntityType.CHICKEN;
++
++                net.minecraft.world.entity.Entity shooter = this.getOwner();
++                if (!hatching) {
++                    i = 0;
++                }
++                if (shooter instanceof net.minecraft.server.level.ServerPlayer) {
++                    org.bukkit.event.player.PlayerEggThrowEvent event = new org.bukkit.event.player.PlayerEggThrowEvent((org.bukkit.entity.Player) shooter.getBukkitEntity(), (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, (byte) i, hatchingType);
++                    this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                    i = event.getNumHatches();
++                    hatching = event.isHatching();
++                    hatchingType = event.getHatchingType();
++                    // If hatching is set to false, ensure child count is 0
++                    if (!hatching) {
++                        i = 0;
++                    }
++                }
++                // CraftBukkit end
++                // Paper start - Add ThrownEggHatchEvent
++                com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, (byte) i, hatchingType);
++                event.callEvent();
++                hatching = event.isHatching();
++                i = hatching ? event.getNumHatches() : 0; // If hatching is set to false, ensure child count is 0
++                hatchingType = event.getHatchingType();
++                // Paper end - Add ThrownEggHatchEvent
+ 
+                 for (int i1 = 0; i1 < i; i1++) {
+-                    Chicken chicken = EntityType.CHICKEN.create(this.level(), EntitySpawnReason.TRIGGERED);
++                    net.minecraft.world.entity.Entity chicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit
+                     if (chicken != null) {
+-                        chicken.setAge(-24000);
++                        // CraftBukkit start
++                        if (chicken.getBukkitEntity() instanceof org.bukkit.entity.Ageable ageable) {
++                            ageable.setBaby();
++                        }
++                        // CraftBukkit end
+                         chicken.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+                         if (!chicken.fudgePositionAfterSizeChange(ZERO_SIZED_DIMENSIONS)) {
+                             break;
+                         }
+ 
+-                        this.level().addFreshEntity(chicken);
++                        this.level().addFreshEntity(chicken, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit
+                     }
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
new file mode 100644
index 0000000000..36963eb6e5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+@@ -126,11 +_,18 @@
+                 Vec3 vec3 = this.oldPosition();
+                 if (owner instanceof ServerPlayer serverPlayer) {
+                     if (serverPlayer.connection.isAcceptingMessages()) {
++                        // CraftBukkit start
++                        ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL));
++                        if (serverPlayer1 == null) {
++                            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT);
++                            return;
++                        }
++                        // CraftBukkit end
+                         if (this.random.nextFloat() < 0.05F && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
+                             Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED);
+                             if (endermite != null) {
+                                 endermite.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
+-                                serverLevel.addFreshEntity(endermite);
++                                serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
+                             }
+                         }
+ 
+@@ -138,15 +_,15 @@
+                             owner.setPortalCooldown();
+                         }
+ 
+-                        ServerPlayer serverPlayer1 = serverPlayer.teleport(
+-                            new TeleportTransition(
+-                                serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
+-                            )
+-                        );
++                        // ServerPlayer serverPlayer1 = serverPlayer.teleport(
++                        //     new TeleportTransition(
++                        //         serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
++                        //     )
++                        // );
+                         if (serverPlayer1 != null) {
+                             serverPlayer1.resetFallDistance();
+                             serverPlayer1.resetCurrentImpulseContext();
+-                            serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl(), 5.0F);
++                            serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API
+                         }
+ 
+                         this.playSound(serverLevel, vec3);
+@@ -162,9 +_,9 @@
+                     this.playSound(serverLevel, vec3);
+                 }
+ 
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+             } else {
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+@@ -185,7 +_,7 @@
+         if (owner instanceof ServerPlayer serverPlayer
+             && !owner.isAlive()
+             && serverPlayer.serverLevel().getGameRules().getBoolean(GameRules.RULE_ENDER_PEARLS_VANISH_ON_DEATH)) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else {
+             super.tick();
+         }
+@@ -212,7 +_,7 @@
+     public Entity teleport(TeleportTransition teleportTransition) {
+         Entity entity = super.teleport(teleportTransition);
+         if (entity != null) {
+-            entity.placePortalTicket(BlockPos.containing(entity.position()));
++            if (!this.level().paperConfig().misc.legacyEnderPearlBehavior) entity.placePortalTicket(BlockPos.containing(entity.position())); // Paper - Allow using old ender pearl behavior
+         }
+ 
+         return entity;
+@@ -220,7 +_,7 @@
+ 
+     @Override
+     public boolean canTeleport(Level fromLevel, Level toLevel) {
+-        return fromLevel.dimension() == Level.END && toLevel.dimension() == Level.OVERWORLD && this.getOwner() instanceof ServerPlayer serverPlayer
++        return fromLevel.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END && toLevel.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD && this.getOwner() instanceof ServerPlayer serverPlayer // CraftBukkit
+             ? super.canTeleport(fromLevel, toLevel) && serverPlayer.seenCredits
+             : super.canTeleport(fromLevel, toLevel);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
new file mode 100644
index 0000000000..e560b1ef1e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
++++ b/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+@@ -37,10 +_,17 @@
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+         if (this.level() instanceof ServerLevel) {
+-            this.level().levelEvent(2002, this.blockPosition(), -13083194);
++            // CraftBukkit - moved to after event
+             int i = 3 + this.level().random.nextInt(5) + this.level().random.nextInt(5);
+-            ExperienceOrb.award((ServerLevel)this.level(), this.position(), i);
+-            this.discard();
++            // CraftBukkit start
++            org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, result, i);
++            i = event.getExperience();
++            if (event.getShowEffect()) {
++                this.level().levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_SPELL_POTION_SPLASH, this.blockPosition(), net.minecraft.world.item.alchemy.PotionContents.BASE_POTION_COLOR);
++            }
++            // CraftBukkit end
++            ExperienceOrb.award((ServerLevel)this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
new file mode 100644
index 0000000000..254faacccd
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
@@ -0,0 +1,237 @@
+--- a/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -9,6 +_,7 @@
+ import net.minecraft.core.Holder;
+ import net.minecraft.core.component.DataComponents;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.tags.BlockTags;
+ import net.minecraft.world.damagesource.DamageSource;
+ import net.minecraft.world.effect.MobEffect;
+@@ -82,51 +_,87 @@
+     @Override
+     protected void onHit(HitResult result) {
+         super.onHit(result);
++        // Paper start - More projectile API
++        this.splash(result);
++    }
++    public void splash(@Nullable HitResult result) {
++        // Paper end - More projectile API
+         if (this.level() instanceof ServerLevel serverLevel) {
+             ItemStack item = this.getItem();
+             PotionContents potionContents = item.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
++            boolean showParticles = true; // Paper - Fix potions splash events
+             if (potionContents.is(Potions.WATER)) {
+-                this.applyWater(serverLevel);
+-            } else if (potionContents.hasEffects()) {
++                showParticles = this.applyWater(serverLevel, result); // Paper - Fix potions splash events
++            } else if (true || potionContents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply
+                 if (this.isLingering()) {
+-                    this.makeAreaOfEffectCloud(potionContents);
++                    showParticles = this.makeAreaOfEffectCloud(potionContents, result); // CraftBukkit - Pass MovingObjectPosition // Paper
+                 } else {
+-                    this.applySplash(
+-                        serverLevel, potionContents.getAllEffects(), result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null
++                    showParticles = this.applySplash(
++                        serverLevel, potionContents.getAllEffects(), result != null && result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null, result  // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
+                     );
+                 }
+             }
+ 
++            if (showParticles) { // Paper - Fix potions splash events
+             int i = potionContents.potion().isPresent() && potionContents.potion().get().value().hasInstantEffects() ? 2007 : 2002;
+             serverLevel.levelEvent(i, this.blockPosition(), potionContents.getColor());
+-            this.discard();
++            } // Paper - Fix potions splash events
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+-    private void applyWater(ServerLevel level) {
++    private static final Predicate<net.minecraft.world.entity.LivingEntity> APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events
++
++    private boolean applyWater(ServerLevel level, @Nullable HitResult result) { // Paper - Fix potions splash events
+         AABB aabb = this.getBoundingBox().inflate(4.0, 2.0, 4.0);
+ 
+-        for (LivingEntity livingEntity : this.level().getEntitiesOfClass(LivingEntity.class, aabb, WATER_SENSITIVE_OR_ON_FIRE)) {
++        // Paper start - Fix potions splash events
++        java.util.Map<org.bukkit.entity.LivingEntity, Double> affected = new java.util.HashMap<>();
++        java.util.Set<org.bukkit.entity.LivingEntity> rehydrate = new java.util.HashSet<>();
++        java.util.Set<org.bukkit.entity.LivingEntity> extinguish = new java.util.HashSet<>();
++        for (LivingEntity livingEntity : this.level().getEntitiesOfClass(LivingEntity.class, aabb, APPLY_WATER_GET_ENTITIES_PREDICATE)) {
++            if (livingEntity instanceof Axolotl axolotl) {
++                rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity()));
++            }
++            // Paper end - Fix potions splash events
+             double d = this.distanceToSqr(livingEntity);
+             if (d < 16.0) {
+                 if (livingEntity.isSensitiveToWater()) {
+-                    livingEntity.hurtServer(level, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
++                    affected.put(livingEntity.getBukkitLivingEntity(), 1.0);
++                    // livingEntity.hurtServer(level, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
+                 }
+ 
+                 if (livingEntity.isOnFire() && livingEntity.isAlive()) {
+-                    livingEntity.extinguishFire();
++                    extinguish.add(livingEntity.getBukkitLivingEntity());
++                    // livingEntity.extinguishFire();
+                 }
+             }
+         }
+ 
+-        for (Axolotl axolotl : this.level().getEntitiesOfClass(Axolotl.class, aabb)) {
+-            axolotl.rehydrate();
++        io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent(
++            this, result, affected, rehydrate, extinguish
++        );
++        if (!event.isCancelled()) {
++            for (org.bukkit.entity.LivingEntity affectedEntity : event.getToDamage()) {
++                ((org.bukkit.craftbukkit.entity.CraftLivingEntity) affectedEntity).getHandle().hurtServer(level, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
++            }
++            for (org.bukkit.entity.LivingEntity toExtinguish : event.getToExtinguish()) {
++                ((org.bukkit.craftbukkit.entity.CraftLivingEntity) toExtinguish).getHandle().extinguishFire();
++            }
++            for (org.bukkit.entity.LivingEntity toRehydrate : event.getToRehydrate()) {
++                if (((org.bukkit.craftbukkit.entity.CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) {
++                    axolotl.rehydrate();
++                }
++            }
++            // Paper end - Fix potions splash events
+         }
++        return !event.isCancelled(); // Paper - Fix potions splash events
+     }
+ 
+-    private void applySplash(ServerLevel level, Iterable<MobEffectInstance> effects, @Nullable Entity entity) {
++    private boolean applySplash(ServerLevel level, Iterable<MobEffectInstance> effects, @Nullable Entity entity, @Nullable HitResult result) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API
+         AABB aabb = this.getBoundingBox().inflate(4.0, 2.0, 4.0);
+         List<LivingEntity> entitiesOfClass = level.getEntitiesOfClass(LivingEntity.class, aabb);
++        java.util.Map<org.bukkit.entity.LivingEntity, Double> affected = new java.util.HashMap<>(); // CraftBukkit
+         if (!entitiesOfClass.isEmpty()) {
+             Entity effectSource = this.getEffectSource();
+ 
+@@ -135,33 +_,57 @@
+                     double d = this.distanceToSqr(livingEntity);
+                     if (d < 16.0) {
+                         double d1;
++                        // Paper - diff on change, used when calling the splash event for water splash potions
+                         if (livingEntity == entity) {
+                             d1 = 1.0;
+                         } else {
+                             d1 = 1.0 - Math.sqrt(d) / 4.0;
+                         }
+ 
+-                        for (MobEffectInstance mobEffectInstance : effects) {
+-                            Holder<MobEffect> effect = mobEffectInstance.getEffect();
+-                            if (effect.value().isInstantenous()) {
+-                                effect.value().applyInstantenousEffect(level, this, this.getOwner(), livingEntity, mobEffectInstance.getAmplifier(), d1);
+-                            } else {
+-                                int i = mobEffectInstance.mapDuration(i1 -> (int)(d1 * i1 + 0.5));
+-                                MobEffectInstance mobEffectInstance1 = new MobEffectInstance(
+-                                    effect, i, mobEffectInstance.getAmplifier(), mobEffectInstance.isAmbient(), mobEffectInstance.isVisible()
+-                                );
+-                                if (!mobEffectInstance1.endsWithin(20)) {
+-                                    livingEntity.addEffect(mobEffectInstance1, effectSource);
+-                                }
+-                            }
+-                        }
+-                    }
+-                }
+-            }
+-        }
++                        affected.put(livingEntity.getBukkitLivingEntity(), d1);
++                    }
++                }
++            }
++        }
++        org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, result, affected);
++        if (!event.isCancelled() && entitiesOfClass != null && !entitiesOfClass.isEmpty()) { // do not process effects if there are no effects to process
++            Entity effectSource = this.getEffectSource();
++            for (org.bukkit.entity.LivingEntity victim : event.getAffectedEntities()) {
++                if (!(victim instanceof org.bukkit.craftbukkit.entity.CraftLivingEntity craftLivingEntity)) {
++                    continue;
++                }
++                net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle();
++                double d1 = event.getIntensity(victim);
++                // CraftBukkit end
++                for (MobEffectInstance mobEffectInstance : effects) {
++                    Holder<MobEffect> effect = mobEffectInstance.getEffect();
++                    // CraftBukkit start - Abide by PVP settings - for players only!
++                    if (!this.level().pvpMode && this.getOwner() instanceof ServerPlayer && livingEntity instanceof ServerPlayer && livingEntity != this.getOwner()) {
++                        MobEffect mobEffect = effect.value();
++                        if (mobEffect == net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN || mobEffect == net.minecraft.world.effect.MobEffects.DIG_SLOWDOWN || mobEffect == net.minecraft.world.effect.MobEffects.HARM || mobEffect == net.minecraft.world.effect.MobEffects.BLINDNESS
++                                || mobEffect == net.minecraft.world.effect.MobEffects.HUNGER || mobEffect == net.minecraft.world.effect.MobEffects.WEAKNESS || mobEffect == net.minecraft.world.effect.MobEffects.POISON) {
++                            continue;
++                        }
++                    }
++                    // CraftBukkit end
++                    if (effect.value().isInstantenous()) {
++                        effect.value().applyInstantenousEffect(level, this, this.getOwner(), livingEntity, mobEffectInstance.getAmplifier(), d1);
++                    } else {
++                        int i = mobEffectInstance.mapDuration(i1 -> (int)(d1 * i1 + 0.5));
++                        MobEffectInstance mobEffectInstance1 = new MobEffectInstance(
++                            effect, i, mobEffectInstance.getAmplifier(), mobEffectInstance.isAmbient(), mobEffectInstance.isVisible()
++                        );
++                        if (!mobEffectInstance1.endsWithin(20)) {
++                            livingEntity.addEffect(mobEffectInstance1, effectSource);
++                        }
++                    }
++                }
++            }
++        }
++        return !event.isCancelled(); // Paper - Fix potions splash events
+     }
+ 
+-    private void makeAreaOfEffectCloud(PotionContents potionContents) {
++    private boolean makeAreaOfEffectCloud(PotionContents potionContents, @Nullable HitResult result) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
+         AreaEffectCloud areaEffectCloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+         if (this.getOwner() instanceof LivingEntity livingEntity) {
+             areaEffectCloud.setOwner(livingEntity);
+@@ -172,7 +_,16 @@
+         areaEffectCloud.setWaitTime(10);
+         areaEffectCloud.setRadiusPerTick(-areaEffectCloud.getRadius() / areaEffectCloud.getDuration());
+         areaEffectCloud.setPotionContents(potionContents);
+-        this.level().addFreshEntity(areaEffectCloud);
++        boolean noEffects = potionContents.hasEffects(); // Paper - Fix potions splash events
++        // CraftBukkit start
++        org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, result, areaEffectCloud);
++        if (!(event.isCancelled() || areaEffectCloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && !areaEffectCloud.potionContents.hasEffects())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling
++            this.level().addFreshEntity(areaEffectCloud);
++        } else {
++            areaEffectCloud.discard(null); // CraftBukkit - add Bukkit remove cause
++        }
++        // CraftBukkit end
++        return !event.isCancelled(); // Paper - Fix potions splash events
+     }
+ 
+     public boolean isLingering() {
+@@ -182,13 +_,25 @@
+     private void dowseFire(BlockPos pos) {
+         BlockState blockState = this.level().getBlockState(pos);
+         if (blockState.is(BlockTags.FIRE)) {
+-            this.level().destroyBlock(pos, false, this);
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                this.level().destroyBlock(pos, false, this);
++            }
++            // CraftBukkit end
+         } else if (AbstractCandleBlock.isLit(blockState)) {
+-            AbstractCandleBlock.extinguish(null, blockState, this.level(), pos);
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(AbstractCandleBlock.LIT, false))) {
++                AbstractCandleBlock.extinguish(null, blockState, this.level(), pos);
++            }
++            // CraftBukkit end
+         } else if (CampfireBlock.isLitCampfire(blockState)) {
+-            this.level().levelEvent(null, 1009, pos, 0);
+-            CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState);
+-            this.level().setBlockAndUpdate(pos, blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)));
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(CampfireBlock.LIT, false))) {
++                this.level().levelEvent(null, 1009, pos, 0);
++                CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState);
++                this.level().setBlockAndUpdate(pos, blockState.setValue(CampfireBlock.LIT, false));
++            }
++            // CraftBukkit end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
new file mode 100644
index 0000000000..dad0da0b92
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/entity/projectile/ThrownTrident.java
++++ b/net/minecraft/world/entity/projectile/ThrownTrident.java
+@@ -32,16 +_,19 @@
+ 
+     public ThrownTrident(EntityType<? extends ThrownTrident> entityType, Level level) {
+         super(entityType, level);
++        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
+     }
+ 
+     public ThrownTrident(Level level, LivingEntity shooter, ItemStack pickupItemStack) {
+         super(EntityType.TRIDENT, shooter, level, pickupItemStack, null);
++        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
+         this.entityData.set(ID_LOYALTY, this.getLoyaltyFromItem(pickupItemStack));
+         this.entityData.set(ID_FOIL, pickupItemStack.hasFoil());
+     }
+ 
+     public ThrownTrident(Level level, double x, double y, double z, ItemStack pickupItemStack) {
+         super(EntityType.TRIDENT, x, y, z, level, pickupItemStack, pickupItemStack);
++        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
+         this.entityData.set(ID_LOYALTY, this.getLoyaltyFromItem(pickupItemStack));
+         this.entityData.set(ID_FOIL, pickupItemStack.hasFoil());
+     }
+@@ -67,10 +_,10 @@
+                     this.spawnAtLocation(serverLevel, this.getPickupItem(), 0.1F);
+                 }
+ 
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+             } else {
+                 if (!(owner instanceof Player) && this.position().distanceTo(owner.getEyePosition()) < owner.getBbWidth() + 1.0) {
+-                    this.discard();
++                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                     return;
+                 }
+ 
+@@ -99,6 +_,20 @@
+         return this.entityData.get(ID_FOIL);
+     }
+ 
++    // Paper start
++    public void setFoil(boolean foil) {
++        this.entityData.set(ThrownTrident.ID_FOIL, foil);
++    }
++
++    public int getLoyalty() {
++        return this.entityData.get(ThrownTrident.ID_LOYALTY);
++    }
++
++    public void setLoyalty(byte loyalty) {
++        this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty);
++    }
++    // Paper end
++
+     @Nullable
+     @Override
+     protected EntityHitResult findHitEntity(Vec3 startVec, Vec3 endVec) {
+@@ -108,7 +_,7 @@
+     @Override
+     protected void onHitEntity(EntityHitResult result) {
+         Entity entity = result.getEntity();
+-        float f = 8.0F;
++        float f = (float) this.getBaseDamage(); // Paper - Allow trident custom damage
+         Entity owner = this.getOwner();
+         DamageSource damageSource = this.damageSources().trident(this, (Entity)(owner == null ? this : owner));
+         if (this.level() instanceof ServerLevel serverLevel) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch
new file mode 100644
index 0000000000..d323b77c2b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/WitherSkull.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/entity/projectile/WitherSkull.java
++++ b/net/minecraft/world/entity/projectile/WitherSkull.java
+@@ -65,11 +_,11 @@
+                     if (var8.isAlive()) {
+                         EnchantmentHelper.doPostAttackEffects(serverLevel, var8, damageSource);
+                     } else {
+-                        livingEntity.heal(5.0F);
++                        livingEntity.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); // CraftBukkit
+                     }
+                 }
+             } else {
+-                flag = var8.hurtServer(serverLevel, this.damageSources().magic(), 5.0F);
++                flag = var8.hurtServer(serverLevel, this.damageSources().magic().customEventDamager(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls // Paper - fix DamageSource API
+             }
+ 
+             if (flag && var8 instanceof LivingEntity livingEntityx) {
+@@ -81,7 +_,7 @@
+                 }
+ 
+                 if (i > 0) {
+-                    livingEntityx.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * i, 1), this.getEffectSource());
++                    livingEntityx.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * i, 1), this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+         }
+@@ -91,8 +_,15 @@
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+         if (!this.level().isClientSide) {
+-            this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, Level.ExplosionInteraction.MOB);
+-            this.discard();
++            // CraftBukkit start
++            org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled()) {
++                this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
++            }
++            // CraftBukkit end
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
deleted file mode 100644
index d2cc2fa1a9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ /dev/null
@@ -1,320 +0,0 @@
---- a/net/minecraft/world/entity/projectile/AbstractArrow.java
-+++ b/net/minecraft/world/entity/projectile/AbstractArrow.java
-@@ -36,6 +36,7 @@
- import net.minecraft.world.entity.OminousItemSpawner;
- import net.minecraft.world.entity.SlotAccess;
- import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
-@@ -50,6 +51,10 @@
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.entity.EntityCombustByEntityEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerPickupArrowEvent;
-+// CraftBukkit end
- 
- public abstract class AbstractArrow extends Projectile {
- 
-@@ -78,6 +83,18 @@
-     @Nullable
-     public ItemStack firedFromWeapon;
- 
-+    // Spigot Start
-+    @Override
-+    public void inactiveTick()
-+    {
-+        if ( this.isInGround() )
-+        {
-+            this.life += 1;
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
-+
-     protected AbstractArrow(EntityType<? extends AbstractArrow> type, Level world) {
-         super(type, world);
-         this.pickup = AbstractArrow.Pickup.DISALLOWED;
-@@ -88,23 +105,30 @@
-     }
- 
-     protected AbstractArrow(EntityType<? extends AbstractArrow> type, double x, double y, double z, Level world, ItemStack stack, @Nullable ItemStack weapon) {
--        this(type, world);
--        this.pickupItemStack = stack.copy();
--        this.setCustomName((Component) stack.get(DataComponents.CUSTOM_NAME));
--        Unit unit = (Unit) stack.remove(DataComponents.INTANGIBLE_PROJECTILE);
-+        // CraftBukkit start - handle the owner before the rest of things
-+        this(type, x, y, z, world, stack, weapon, null);
-+    }
- 
-+    protected AbstractArrow(EntityType<? extends AbstractArrow> entitytypes, double d0, double d1, double d2, Level world, ItemStack itemstack, @Nullable ItemStack itemstack1, @Nullable LivingEntity ownerEntity) {
-+        this(entitytypes, world);
-+        this.setOwner(ownerEntity);
-+        // CraftBukkit end
-+        this.pickupItemStack = itemstack.copy();
-+        this.setCustomName((Component) itemstack.get(DataComponents.CUSTOM_NAME));
-+        Unit unit = (Unit) itemstack.remove(DataComponents.INTANGIBLE_PROJECTILE);
-+
-         if (unit != null) {
-             this.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
-         }
- 
--        this.setPos(x, y, z);
--        if (weapon != null && world instanceof ServerLevel worldserver) {
--            if (weapon.isEmpty()) {
-+        this.setPos(d0, d1, d2);
-+        if (itemstack1 != null && world instanceof ServerLevel worldserver) {
-+            if (itemstack1.isEmpty()) {
-                 throw new IllegalArgumentException("Invalid weapon firing an arrow");
-             }
- 
--            this.firedFromWeapon = weapon.copy();
--            int i = EnchantmentHelper.getPiercingCount(worldserver, weapon, this.pickupItemStack);
-+            this.firedFromWeapon = itemstack1.copy();
-+            int i = EnchantmentHelper.getPiercingCount(worldserver, itemstack1, this.pickupItemStack);
- 
-             if (i > 0) {
-                 this.setPierceLevel((byte) i);
-@@ -114,8 +138,8 @@
-     }
- 
-     protected AbstractArrow(EntityType<? extends AbstractArrow> type, LivingEntity owner, Level world, ItemStack stack, @Nullable ItemStack shotFrom) {
--        this(type, owner.getX(), owner.getEyeY() - 0.10000000149011612D, owner.getZ(), world, stack, shotFrom);
--        this.setOwner(owner);
-+        this(type, owner.getX(), owner.getEyeY() - 0.10000000149011612D, owner.getZ(), world, stack, shotFrom, owner); // CraftBukkit
-+        // this.setOwner(entityliving); // SPIGOT-7744 - Moved to the above constructor
-     }
- 
-     public void setSoundEvent(SoundEvent sound) {
-@@ -220,6 +244,7 @@
-             }
- 
-         } else {
-+            if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds
-             this.inGroundTime = 0;
-             Vec3 vec3d2 = this.position();
- 
-@@ -282,7 +307,7 @@
- 
-                 if (movingobjectpositionentity == null) {
-                     if (this.isAlive() && blockHitResult.getType() != HitResult.Type.MISS) {
--                        this.hitTargetOrDeflectSelf(blockHitResult);
-+                        this.preHitTargetOrDeflectSelf(blockHitResult); // CraftBukkit - projectile hit event
-                         this.hasImpulse = true;
-                     }
-                 } else {
-@@ -290,7 +315,7 @@
-                         continue;
-                     }
- 
--                    ProjectileDeflection projectiledeflection = this.hitTargetOrDeflectSelf(movingobjectpositionentity);
-+                    ProjectileDeflection projectiledeflection = this.preHitTargetOrDeflectSelf(movingobjectpositionentity); // CraftBukkit - projectile hit event
- 
-                     this.hasImpulse = true;
-                     if (this.getPierceLevel() > 0 && projectiledeflection == ProjectileDeflection.NONE) {
-@@ -318,7 +343,20 @@
-             this.level().addParticle(ParticleTypes.BUBBLE, pos.x - vec3d1.x * 0.25D, pos.y - vec3d1.y * 0.25D, pos.z - vec3d1.z * 0.25D, vec3d1.x, vec3d1.y, vec3d1.z);
-         }
- 
-+    }
-+
-+    // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows
-+    @Override
-+    public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) {
-+        if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) {
-+            if (this.piercingIgnoreEntityIds == null) {
-+                this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
-+            }
-+            this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId());
-+        }
-+        return super.preHitTargetOrDeflectSelf(hitResult);
-     }
-+    // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows
- 
-     @Override
-     protected double getDefaultGravity() {
-@@ -356,8 +394,8 @@
- 
-     protected void tickDespawn() {
-         ++this.life;
--        if (this.life >= 1200) {
--            this.discard();
-+        if (this.life >= (pickup == Pickup.CREATIVE_ONLY ? this.level().paperConfig().entities.spawning.creativeArrowDespawnRate.value() : (pickup == Pickup.DISALLOWED ? this.level().paperConfig().entities.spawning.nonPlayerArrowDespawnRate.value() : ((this instanceof ThrownTrident) ? this.level().spigotConfig.tridentDespawnRate : this.level().spigotConfig.arrowDespawnRate)))) { // Spigot // Paper - Configurable non-player arrow despawn rate; TODO: Extract this to init?
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -386,9 +424,9 @@
-     }
- 
-     @Override
--    public void push(double deltaX, double deltaY, double deltaZ) {
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param
-         if (!this.isInGround()) {
--            super.push(deltaX, deltaY, deltaZ);
-+            super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param
-         }
-     }
- 
-@@ -423,7 +461,7 @@
-             }
- 
-             if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-                 return;
-             }
- 
-@@ -440,11 +478,18 @@
-             entityliving.setLastHurtMob(entity);
-         }
- 
-+        if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API
-         boolean flag = entity.getType() == EntityType.ENDERMAN;
-         int k = entity.getRemainingFireTicks();
- 
-         if (this.isOnFire() && !flag) {
--            entity.igniteForSeconds(5.0F);
-+            // CraftBukkit start
-+            EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5.0F);
-+            org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
-+            if (!combustEvent.isCancelled()) {
-+                entity.igniteForSeconds(combustEvent.getDuration(), false);
-+            }
-+            // CraftBukkit end
-         }
- 
-         if (entity.hurtOrSimulate(damagesource, (float) i)) {
-@@ -490,7 +535,7 @@
- 
-             this.playSound(this.soundEvent, 1.0F, 1.2F / (this.random.nextFloat() * 0.2F + 0.9F));
-             if (this.getPierceLevel() <= 0) {
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-             }
-         } else {
-             entity.setRemainingFireTicks(k);
-@@ -506,7 +551,7 @@
-                         this.spawnAtLocation(worldserver2, this.getPickupItem(), 0.1F);
-                     }
- 
--                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-                 }
-             }
-         }
-@@ -538,7 +583,7 @@
-             Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale(d0 * 0.6D * d1);
- 
-             if (vec3d.lengthSqr() > 0.0D) {
--                target.push(vec3d.x, 0.1D, vec3d.z);
-+                target.push(vec3d.x, 0.1D, vec3d.z, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-             }
-         }
- 
-@@ -665,7 +710,7 @@
-         this.setCritArrow(nbt.getBoolean("crit"));
-         this.setPierceLevel(nbt.getByte("PierceLevel"));
-         if (nbt.contains("SoundEvent", 8)) {
--            this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.parse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent());
-+            this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location
-         }
- 
-         if (nbt.contains("item", 10)) {
-@@ -675,7 +720,7 @@
-         }
- 
-         if (nbt.contains("weapon", 10)) {
--            this.firedFromWeapon = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("weapon")).orElse((Object) null);
-+            this.firedFromWeapon = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("weapon")).orElse(null); // CraftBukkit - decompile error
-         } else {
-             this.firedFromWeapon = null;
-         }
-@@ -684,38 +729,42 @@
- 
-     @Override
-     public void setOwner(@Nullable Entity entity) {
-+        // Paper start - Fix PickupStatus getting reset
-+        this.setOwner(entity, true);
-+    }
-+
-+    public void setOwner(@Nullable Entity entity, boolean resetPickup) {
-+        // Paper end - Fix PickupStatus getting reset
-         super.setOwner(entity);
-+        if (!resetPickup) return; // Paper - Fix PickupStatus getting reset
-         Entity entity1 = entity;
-         byte b0 = 0;
- 
--        EntityArrow.PickupStatus entityarrow_pickupstatus;
-+        EntityArrow.PickupStatus entityarrow_pickupstatus = this.pickup; // CraftBukkit - decompile error
- 
-         label16:
--        while(true) {
--            //$FF: b0->value
--            //0->net/minecraft/world/entity/player/EntityHuman
--            //1->net/minecraft/world/entity/OminousItemSpawner
--            switch (entity1.typeSwitch<invokedynamic>(entity1, b0)) {
--                case -1:
--                default:
--                    entityarrow_pickupstatus = this.pickup;
--                    break label16;
--                case 0:
--                    EntityHuman entityhuman = (EntityHuman)entity1;
-+        // CraftBukkit start - decompile error
-+        while (true) {
-+            switch (entity1) {
-+                case EntityHuman entityhuman:
- 
-                     if (this.pickup != EntityArrow.PickupStatus.DISALLOWED) {
-                         b0 = 1;
--                        break;
-+                        break label16;
-                     }
- 
-                     entityarrow_pickupstatus = EntityArrow.PickupStatus.ALLOWED;
-                     break label16;
--                case 1:
--                    OminousItemSpawner ominousitemspawner = (OminousItemSpawner)entity1;
-+                case OminousItemSpawner ominousitemspawner:
- 
-                     entityarrow_pickupstatus = EntityArrow.PickupStatus.DISALLOWED;
-                     break label16;
-+                case null: // SPIGOT-7751: Fix crash caused by null owner
-+                default:
-+                    entityarrow_pickupstatus = this.pickup;
-+                    break label16;
-             }
-+            // CraftBukkit end
-         }
- 
-         this.pickup = entityarrow_pickupstatus;
-@@ -724,9 +773,24 @@
-     @Override
-     public void playerTouch(Player player) {
-         if (!this.level().isClientSide && (this.isInGround() || this.isNoPhysics()) && this.shakeTime <= 0) {
--            if (this.tryPickup(player)) {
-+            // CraftBukkit start
-+            ItemStack itemstack = this.getPickupItem();
-+            if (this.pickup == Pickup.ALLOWED && !itemstack.isEmpty() && player.getInventory().canHold(itemstack) > 0) {
-+                ItemEntity item = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack);
-+                PlayerPickupArrowEvent event = new PlayerPickupArrowEvent((org.bukkit.entity.Player) player.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.level().getCraftServer(), item), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity());
-+                // event.setCancelled(!entityhuman.canPickUpLoot); TODO
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+                itemstack = item.getItem();
-+            }
-+
-+            if ((this.pickup == AbstractArrow.Pickup.ALLOWED && player.getInventory().add(itemstack)) || (this.pickup == AbstractArrow.Pickup.CREATIVE_ONLY && player.getAbilities().instabuild)) {
-+                // CraftBukkit end
-                 player.take(this, 1);
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             }
- 
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
deleted file mode 100644
index b53dd9f069..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
-+++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
-@@ -14,12 +14,17 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public abstract class AbstractHurtingProjectile extends Projectile {
- 
-     public static final double INITAL_ACCELERATION_POWER = 0.1D;
-     public static final double DEFLECTION_SCALE = 0.5D;
-     public double accelerationPower;
-+    public float bukkitYield = 1; // CraftBukkit
-+    public boolean isIncendiary = true; // CraftBukkit
- 
-     protected AbstractHurtingProjectile(EntityType<? extends AbstractHurtingProjectile> type, Level world) {
-         super(type, world);
-@@ -69,7 +74,7 @@
- 
-         this.applyInertia();
-         if (!this.level().isClientSide && (entity != null && entity.isRemoved() || !this.level().hasChunkAt(this.blockPosition()))) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else {
-             HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType());
-             Vec3 vec3d;
-@@ -89,7 +94,7 @@
-             }
- 
-             if (movingobjectposition.getType() != HitResult.Type.MISS && this.isAlive()) {
--                this.hitTargetOrDeflectSelf(movingobjectposition);
-+                this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-             }
- 
-             this.createParticleTrail();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Arrow.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Arrow.java.patch
deleted file mode 100644
index c19bed3b4d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Arrow.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/entity/projectile/Arrow.java
-+++ b/net/minecraft/world/entity/projectile/Arrow.java
-@@ -119,7 +119,7 @@
-                 mobeffect = (MobEffectInstance) iterator.next();
-                 target.addEffect(new MobEffectInstance(mobeffect.getEffect(), Math.max(mobeffect.mapDuration((i) -> {
-                     return i / 8;
--                }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity);
-+                }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
-             }
-         }
- 
-@@ -127,7 +127,7 @@
- 
-         while (iterator.hasNext()) {
-             mobeffect = (MobEffectInstance) iterator.next();
--            target.addEffect(mobeffect, entity);
-+            target.addEffect(mobeffect, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/DragonFireball.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/DragonFireball.java.patch
deleted file mode 100644
index 6278d9b3bb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/DragonFireball.java.patch
+++ /dev/null
@@ -1,26 +0,0 @@
---- a/net/minecraft/world/entity/projectile/DragonFireball.java
-+++ b/net/minecraft/world/entity/projectile/DragonFireball.java
-@@ -14,6 +14,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class DragonFireball extends AbstractHurtingProjectile {
- 
-@@ -59,9 +62,11 @@
-                     }
-                 }
- 
-+                if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) entityareaeffectcloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events
-                 this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1);
--                this.level().addFreshEntity(entityareaeffectcloud);
--                this.discard();
-+                this.level().addFreshEntity(entityareaeffectcloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason
-+                } else entityareaeffectcloud.discard(null); // Paper - EnderDragon Events
-+                this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-             }
- 
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EvokerFangs.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
deleted file mode 100644
index fc93ad07be..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/entity/projectile/EvokerFangs.java
-+++ b/net/minecraft/world/entity/projectile/EvokerFangs.java
-@@ -16,6 +16,9 @@
- import net.minecraft.world.entity.TraceableEntity;
- import net.minecraft.world.item.enchantment.EnchantmentHelper;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class EvokerFangs extends Entity implements TraceableEntity {
- 
-@@ -121,7 +124,7 @@
-             }
- 
-             if (--this.lifeTicks < 0) {
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             }
-         }
- 
-@@ -132,7 +135,7 @@
- 
-         if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) {
-             if (entityliving1 == null) {
--                target.hurt(this.damageSources().magic(), 6.0F);
-+                target.hurt(this.damageSources().magic().customEventDamager(this), 6.0F); // CraftBukkit // Paper - fix DamageSource API
-             } else {
-                 if (entityliving1.isAlliedTo((Entity) target)) {
-                     return;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Fireball.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Fireball.java.patch
deleted file mode 100644
index 6cc358f744..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Fireball.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/entity/projectile/Fireball.java
-+++ b/net/minecraft/world/entity/projectile/Fireball.java
-@@ -61,7 +61,12 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         if (nbt.contains("Item", 10)) {
--            this.setItem((ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem()));
-+            // CraftBukkit start - SPIGOT-5474 probably came from bugged earlier versions
-+            ItemStack itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbt.getCompound("Item")).orElse(this.getDefaultItem());
-+            if (!itemstack.isEmpty()) {
-+                this.setItem(itemstack);
-+            }
-+            // CraftBukkit end
-         } else {
-             this.setItem(this.getDefaultItem());
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
deleted file mode 100644
index 91715aeb75..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
+++ /dev/null
@@ -1,132 +0,0 @@
---- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
-+++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
-@@ -32,6 +32,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class FireworkRocketEntity extends Projectile implements ItemSupplier {
- 
-@@ -42,6 +45,7 @@
-     public int lifetime;
-     @Nullable
-     public LivingEntity attachedToEntity;
-+    @Nullable public java.util.UUID spawningEntity; // Paper
- 
-     public FireworkRocketEntity(EntityType<? extends FireworkRocketEntity> type, Level world) {
-         super(type, world);
-@@ -84,7 +88,29 @@
-         this.setOwner(entity);
-     }
- 
-+    // Spigot Start - copied from tick
-     @Override
-+    public void inactiveTick() {
-+        this.life += 1;
-+
-+        if (this.life > this.lifetime) {
-+            Level world = this.level();
-+
-+            if (world instanceof ServerLevel) {
-+                ServerLevel worldserver = (ServerLevel) world;
-+
-+                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
-+                    this.explode(worldserver);
-+                }
-+                // CraftBukkit end
-+            }
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
-+
-+    @Override
-     protected void defineSynchedData(SynchedEntityData.Builder builder) {
-         builder.define(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, FireworkRocketEntity.getDefaultItem());
-         builder.define(FireworkRocketEntity.DATA_ATTACHED_TO_TARGET, OptionalInt.empty());
-@@ -152,7 +178,7 @@
-         }
- 
-         if (!this.noPhysics && this.isAlive() && movingobjectposition.getType() != HitResult.Type.MISS) {
--            this.hitTargetOrDeflectSelf(movingobjectposition);
-+            this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-             this.hasImpulse = true;
-         }
- 
-@@ -172,7 +198,11 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.explode(worldserver);
-+                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
-+                    this.explode(worldserver);
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -182,7 +212,7 @@
-         world.broadcastEntityEvent(this, (byte) 17);
-         this.gameEvent(GameEvent.EXPLODE, this.getOwner());
-         this.dealExplosionDamage(world);
--        this.discard();
-+        this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
-@@ -191,7 +221,11 @@
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
--            this.explode(worldserver);
-+            // CraftBukkit start
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
-+                this.explode(worldserver);
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
-@@ -205,7 +239,11 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (this.hasExplosion()) {
--                this.explode(worldserver);
-+                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
-+                    this.explode(worldserver);
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -287,6 +325,11 @@
-         nbt.putInt("LifeTime", this.lifetime);
-         nbt.put("FireworksItem", this.getItem().save(this.registryAccess()));
-         nbt.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE));
-+        // Paper start
-+        if (this.spawningEntity != null) {
-+            nbt.putUUID("SpawningEntity", this.spawningEntity);
-+        }
-+        // Paper end
-     }
- 
-     @Override
-@@ -303,7 +346,11 @@
-         if (nbt.contains("ShotAtAngle")) {
-             this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle"));
-         }
--
-+        // Paper start
-+        if (nbt.hasUUID("SpawningEntity")) {
-+            this.spawningEntity = nbt.getUUID("SpawningEntity");
-+        }
-+        // Paper end
-     }
- 
-     private List<FireworkExplosion> getExplosions() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FishingHook.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FishingHook.java.patch
deleted file mode 100644
index 27f0995455..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/FishingHook.java.patch
+++ /dev/null
@@ -1,352 +0,0 @@
---- a/net/minecraft/world/entity/projectile/FishingHook.java
-+++ b/net/minecraft/world/entity/projectile/FishingHook.java
-@@ -29,7 +29,6 @@
- import net.minecraft.world.entity.ExperienceOrb;
- import net.minecraft.world.entity.MoverType;
- import net.minecraft.world.entity.item.ItemEntity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
-@@ -47,6 +46,13 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.entity.Player;
-+import org.bukkit.entity.FishHook;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerFishEvent;
-+// CraftBukkit end
-+
- public class FishingHook extends Projectile {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -68,6 +74,18 @@
-     private final int luck;
-     private final int lureSpeed;
- 
-+    // CraftBukkit start - Extra variables to enable modification of fishing wait time, values are minecraft defaults
-+    public int minWaitTime = 100;
-+    public int maxWaitTime = 600;
-+    public int minLureTime = 20;
-+    public int maxLureTime = 80;
-+    public float minLureAngle = 0.0F;
-+    public float maxLureAngle = 360.0F;
-+    public boolean applyLure = true;
-+    public boolean rainInfluenced = true;
-+    public boolean skyInfluenced = true;
-+    // CraftBukkit end
-+
-     private FishingHook(EntityType<? extends FishingHook> type, Level world, int luckBonus, int waitTimeReductionTicks) {
-         super(type, world);
-         this.syncronizedRandom = RandomSource.create();
-@@ -75,13 +93,17 @@
-         this.currentState = FishingHook.FishHookState.FLYING;
-         this.luck = Math.max(0, luckBonus);
-         this.lureSpeed = Math.max(0, waitTimeReductionTicks);
-+        // Paper start - Configurable fishing time ranges
-+        minWaitTime = world.paperConfig().fishingTimeRange.minimum;
-+        maxWaitTime = world.paperConfig().fishingTimeRange.maximum;
-+        // Paper end - Configurable fishing time ranges
-     }
- 
-     public FishingHook(EntityType<? extends FishingHook> type, Level world) {
-         this(type, world, 0, 0);
-     }
- 
--    public FishingHook(Player thrower, Level world, int luckBonus, int waitTimeReductionTicks) {
-+    public FishingHook(net.minecraft.world.entity.player.Player thrower, Level world, int luckBonus, int waitTimeReductionTicks) {
-         this(EntityType.FISHING_BOBBER, world, luckBonus, waitTimeReductionTicks);
-         this.setOwner(thrower);
-         float f = thrower.getXRot();
-@@ -149,15 +171,15 @@
-     public void tick() {
-         this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level().getGameTime());
-         super.tick();
--        Player entityhuman = this.getPlayerOwner();
-+        net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner();
- 
-         if (entityhuman == null) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else if (this.level().isClientSide || !this.shouldStopFishing(entityhuman)) {
-             if (this.onGround()) {
-                 ++this.life;
-                 if (this.life >= 1200) {
--                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                     return;
-                 }
-             } else {
-@@ -250,7 +272,7 @@
-         }
-     }
- 
--    private boolean shouldStopFishing(Player player) {
-+    private boolean shouldStopFishing(net.minecraft.world.entity.player.Player player) {
-         ItemStack itemstack = player.getMainHandItem();
-         ItemStack itemstack1 = player.getOffhandItem();
-         boolean flag = itemstack.is(Items.FISHING_ROD);
-@@ -259,7 +281,7 @@
-         if (!player.isRemoved() && player.isAlive() && (flag || flag1) && this.distanceToSqr((Entity) player) <= 1024.0D) {
-             return false;
-         } else {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             return true;
-         }
-     }
-@@ -267,7 +289,7 @@
-     private void checkCollision() {
-         HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
- 
--        this.hitTargetOrDeflectSelf(movingobjectposition);
-+        this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-     }
- 
-     @Override
-@@ -300,11 +322,11 @@
-         int i = 1;
-         BlockPos blockposition1 = pos.above();
- 
--        if (this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) {
-+        if (this.rainInfluenced && this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) { // CraftBukkit
-             ++i;
-         }
- 
--        if (this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) {
-+        if (this.skyInfluenced && this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) { // CraftBukkit
-             --i;
-         }
- 
-@@ -314,6 +336,10 @@
-                 this.timeUntilLured = 0;
-                 this.timeUntilHooked = 0;
-                 this.getEntityData().set(FishingHook.DATA_BITING, false);
-+                // CraftBukkit start
-+                PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT);
-+                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+                // CraftBukkit end
-             }
-         } else {
-             float f;
-@@ -347,6 +373,13 @@
-                         worldserver.sendParticles(ParticleTypes.FISHING, d0, d1, d2, 0, (double) (-f4), 0.01D, (double) f3, 1.0D);
-                     }
-                 } else {
-+                    // CraftBukkit start
-+                    PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.BITE);
-+                    this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+                    if (playerFishEvent.isCancelled()) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
-                     double d3 = this.getY() + 0.5D;
- 
-@@ -379,16 +412,34 @@
-                 }
- 
-                 if (this.timeUntilLured <= 0) {
--                    this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F);
--                    this.timeUntilHooked = Mth.nextInt(this.random, 20, 80);
-+                    // CraftBukkit start - logic to modify fishing wait time, lure time, and lure angle
-+                    this.fishAngle = Mth.nextFloat(this.random, this.minLureAngle, this.maxLureAngle);
-+                    this.timeUntilHooked = Mth.nextInt(this.random, this.minLureTime, this.maxLureTime);
-+                    // CraftBukkit end
-+                    // Paper start - Add missing fishing event state
-+                    if (this.getPlayerOwner() != null) {
-+                        PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.LURED);
-+                        if (!playerFishEvent.callEvent()) {
-+                            this.timeUntilHooked = 0;
-+                            return;
-+                        }
-+                    }
-+                    // Paper end - Add missing fishing event state
-                 }
-             } else {
--                this.timeUntilLured = Mth.nextInt(this.random, 100, 600);
--                this.timeUntilLured -= this.lureSpeed;
-+                // CraftBukkit start - logic to modify fishing wait time
-+                this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic
-+                // CraftBukkit end
-             }
-         }
- 
-     }
-+    // Paper start - more projectile api - extract time until lured reset logic
-+    public void resetTimeUntilLured() {
-+        this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
-+        this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop
-+    }
-+    // Paper end - more projectile api - extract time until lured reset logic
- 
-     public boolean calculateOpenWater(BlockPos pos) {
-         FishingHook.OpenWaterType entityfishinghook_waterposition = FishingHook.OpenWaterType.INVALID;
-@@ -445,17 +496,35 @@
-     @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {}
- 
-+    // Paper start - Add hand parameter to PlayerFishEvent
-+    @Deprecated
-+    @io.papermc.paper.annotation.DoNotUse
-     public int retrieve(ItemStack usedItem) {
--        Player entityhuman = this.getPlayerOwner();
-+        return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem);
-+    }
- 
-+    public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) {
-+        // Paper end - Add hand parameter to PlayerFishEvent
-+        net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner();
-+
-         if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) {
-             int i = 0;
- 
-             if (this.hookedIn != null) {
-+                // CraftBukkit start
-+                PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent
-+                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+
-+                if (playerFishEvent.isCancelled()) {
-+                    return 0;
-+                }
-+                if (this.hookedIn != null) { // Paper - re-check to see if there is a hooked entity
-+                // CraftBukkit end
-                 this.pullEntity(this.hookedIn);
-                 CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer) entityhuman, usedItem, this, Collections.emptyList());
-                 this.level().broadcastEntityEvent(this, (byte) 31);
-                 i = this.hookedIn instanceof ItemEntity ? 3 : 5;
-+                } // Paper - re-check to see if there is a hooked entity
-             } else if (this.nibble > 0) {
-                 LootParams lootparams = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.TOOL, usedItem).withParameter(LootContextParams.THIS_ENTITY, this).withLuck((float) this.luck + entityhuman.getLuck()).create(LootContextParamSets.FISHING);
-                 LootTable loottable = this.level().getServer().reloadableRegistries().getLootTable(BuiltInLootTables.FISHING);
-@@ -466,15 +535,38 @@
- 
-                 while (iterator.hasNext()) {
-                     ItemStack itemstack1 = (ItemStack) iterator.next();
--                    ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1);
-+                    // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemstack1 turns out to be empty
-+                    // if the item stack is empty we instead just have our entityitem as null
-+                    ItemEntity entityitem = null;
-+                    if (!itemstack1.isEmpty()) {
-+                        entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1);
-+                    }
-+                    // Paper end
-+                    // CraftBukkit start
-+                    PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent
-+                    playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1);
-+                    this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+
-+                    if (playerFishEvent.isCancelled()) {
-+                        return 0;
-+                    }
-+                    // CraftBukkit end
-                     double d0 = entityhuman.getX() - this.getX();
-                     double d1 = entityhuman.getY() - this.getY();
-                     double d2 = entityhuman.getZ() - this.getZ();
-                     double d3 = 0.1D;
- 
--                    entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D);
--                    this.level().addFreshEntity(entityitem);
--                    entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, this.random.nextInt(6) + 1));
-+                    // Paper start - entity item can be null, so we need to check against this
-+                    if (entityitem != null) {
-+                        entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D);
-+                        this.level().addFreshEntity(entityitem);
-+                    }
-+                    // Paper end
-+                    // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
-+                    if (playerFishEvent.getExpToDrop() > 0) {
-+                        entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper
-+                    }
-+                    // CraftBukkit end
-                     if (itemstack1.is(ItemTags.FISHES)) {
-                         entityhuman.awardStat(Stats.FISH_CAUGHT, 1);
-                     }
-@@ -484,10 +576,27 @@
-             }
- 
-             if (this.onGround()) {
-+                // CraftBukkit start
-+                PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent
-+                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+
-+                if (playerFishEvent.isCancelled()) {
-+                    return 0;
-+                }
-+                // CraftBukkit end
-                 i = 2;
-             }
-+            // CraftBukkit start
-+            if (i == 0) {
-+                PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent
-+                this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+                if (playerFishEvent.isCancelled()) {
-+                    return 0;
-+                }
-+            }
-+            // CraftBukkit end
- 
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             return i;
-         } else {
-             return 0;
-@@ -496,7 +605,7 @@
- 
-     @Override
-     public void handleEntityEvent(byte status) {
--        if (status == 31 && this.level().isClientSide && this.hookedIn instanceof Player && ((Player) this.hookedIn).isLocalPlayer()) {
-+        if (status == 31 && this.level().isClientSide && this.hookedIn instanceof net.minecraft.world.entity.player.Player && ((net.minecraft.world.entity.player.Player) this.hookedIn).isLocalPlayer()) {
-             this.pullEntity(this.hookedIn);
-         }
- 
-@@ -520,8 +629,15 @@
- 
-     @Override
-     public void remove(Entity.RemovalReason reason) {
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.remove(reason, null);
-+    }
-+
-+    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
-+        // CraftBukkit end
-         this.updateOwnerInfo((FishingHook) null);
--        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
-@@ -536,7 +652,7 @@
-     }
- 
-     private void updateOwnerInfo(@Nullable FishingHook fishingBobber) {
--        Player entityhuman = this.getPlayerOwner();
-+        net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner();
- 
-         if (entityhuman != null) {
-             entityhuman.fishing = fishingBobber;
-@@ -545,10 +661,10 @@
-     }
- 
-     @Nullable
--    public Player getPlayerOwner() {
-+    public net.minecraft.world.entity.player.Player getPlayerOwner() {
-         Entity entity = this.getOwner();
- 
--        return entity instanceof Player ? (Player) entity : null;
-+        return entity instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) entity : null;
-     }
- 
-     @Nullable
-@@ -575,7 +691,7 @@
-             int i = packet.getData();
- 
-             FishingHook.LOGGER.error("Failed to recreate fishing hook on client. {} (id: {}) is not a valid owner.", this.level().getEntity(i), i);
--            this.discard();
-+            this.discard(null); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LargeFireball.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LargeFireball.java.patch
deleted file mode 100644
index 494d25292d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LargeFireball.java.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- a/net/minecraft/world/entity/projectile/LargeFireball.java
-+++ b/net/minecraft/world/entity/projectile/LargeFireball.java
-@@ -12,6 +12,10 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
- 
- public class LargeFireball extends Fireball {
- 
-@@ -19,11 +23,13 @@
- 
-     public LargeFireball(EntityType<? extends LargeFireball> type, Level world) {
-         super(type, world);
-+        this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
-     }
- 
-     public LargeFireball(Level world, LivingEntity owner, Vec3 velocity, int explosionPower) {
-         super(EntityType.FIREBALL, owner, velocity, world);
-         this.explosionPower = explosionPower;
-+        this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
-     }
- 
-     @Override
-@@ -34,8 +40,16 @@
-         if (world instanceof ServerLevel worldserver) {
-             boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
- 
--            this.level().explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionPower, flag, Level.ExplosionInteraction.MOB);
--            this.discard();
-+            // CraftBukkit start - fire ExplosionPrimeEvent
-+            ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled()) {
-+                // give 'this' instead of (Entity) null so we know what causes the damage
-+                this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
-+            }
-+            // CraftBukkit end
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -65,7 +79,8 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         if (nbt.contains("ExplosionPower", 99)) {
--            this.explosionPower = nbt.getByte("ExplosionPower");
-+            // CraftBukkit - set bukkitYield when setting explosionpower
-+            this.bukkitYield = this.explosionPower = nbt.getByte("ExplosionPower");
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
deleted file mode 100644
index 370c83f5da..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/entity/projectile/LlamaSpit.java
-+++ b/net/minecraft/world/entity/projectile/LlamaSpit.java
-@@ -17,6 +17,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class LlamaSpit extends Projectile {
- 
-@@ -41,7 +44,7 @@
-         Vec3 vec3d = this.getDeltaMovement();
-         HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
- 
--        this.hitTargetOrDeflectSelf(movingobjectposition);
-+        this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-         double d0 = this.getX() + vec3d.x;
-         double d1 = this.getY() + vec3d.y;
-         double d2 = this.getZ() + vec3d.z;
-@@ -50,9 +53,9 @@
-         float f = 0.99F;
- 
-         if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else if (this.isInWaterOrBubble()) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else {
-             this.setDeltaMovement(vec3d.scale(0.9900000095367432D));
-             this.applyGravity();
-@@ -83,7 +86,7 @@
-     protected void onHitBlock(BlockHitResult blockHitResult) {
-         super.onHitBlock(blockHitResult);
-         if (!this.level().isClientSide) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Projectile.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Projectile.java.patch
deleted file mode 100644
index 4e0aeff12d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Projectile.java.patch
+++ /dev/null
@@ -1,231 +0,0 @@
---- a/net/minecraft/world/entity/projectile/Projectile.java
-+++ b/net/minecraft/world/entity/projectile/Projectile.java
-@@ -35,6 +35,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.projectiles.ProjectileSource;
-+// CraftBukkit end
- 
- public abstract class Projectile extends Entity implements TraceableEntity {
- 
-@@ -47,6 +50,10 @@
-     @Nullable
-     private Entity lastDeflectedBy;
- 
-+    // CraftBukkit start
-+    protected boolean hitCancelled = false;
-+    // CraftBukkit end
-+
-     Projectile(EntityType<? extends Projectile> type, Level world) {
-         super(type, world);
-     }
-@@ -56,16 +63,35 @@
-             this.ownerUUID = entity.getUUID();
-             this.cachedOwner = entity;
-         }
--
-+        // Paper start - Refresh ProjectileSource for projectiles
-+        else {
-+            this.ownerUUID = null;
-+            this.cachedOwner = null;
-+            this.projectileSource = null;
-+        }
-+        // Paper end - Refresh ProjectileSource for projectiles
-+        this.refreshProjectileSource(false); // Paper
-     }
-+    // Paper start - Refresh ProjectileSource for projectiles
-+    public void refreshProjectileSource(boolean fillCache) {
-+        if (fillCache) {
-+            this.getOwner();
-+        }
-+        if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) {
-+            this.projectileSource = projSource;
-+        }
-+    }
-+    // Paper end - Refresh ProjectileSource for projectiles
- 
-     @Nullable
-     @Override
-     public Entity getOwner() {
-         if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) {
-+            this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
-             return this.cachedOwner;
-         } else if (this.ownerUUID != null) {
-             this.cachedOwner = this.findOwner(this.ownerUUID);
-+            this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
-             return this.cachedOwner;
-         } else {
-             return null;
-@@ -108,6 +134,7 @@
-     protected void readAdditionalSaveData(CompoundTag nbt) {
-         if (nbt.hasUUID("Owner")) {
-             this.setOwnerThroughUUID(nbt.getUUID("Owner"));
-+            if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && this.level().paperConfig().misc.legacyEnderPearlBehavior) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit
-         }
- 
-         this.leftOwner = nbt.getBoolean("LeftOwner");
-@@ -184,12 +211,20 @@
- 
-         this.shoot((double) f5, (double) f6, (double) f7, speed, divergence);
-         Vec3 vec3d = shooter.getKnownMovement();
--
-+        // Paper start - allow disabling relative velocity
-+        if (!shooter.level().paperConfig().misc.disableRelativeProjectileVelocity) {
-         this.setDeltaMovement(this.getDeltaMovement().add(vec3d.x, shooter.onGround() ? 0.0D : vec3d.y, vec3d.z));
-+        }
-+        // Paper end - allow disabling relative velocity
-     }
- 
-     public static <T extends Projectile> T spawnProjectileFromRotation(Projectile.ProjectileFactory<T> creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) {
--        return Projectile.spawnProjectile(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> {
-+    // Paper start - PlayerLaunchProjectileEvent
-+        return spawnProjectileFromRotationDelayed(creator, world, projectileStack, shooter, roll, power, divergence).spawn();
-+    }
-+    public static <T extends Projectile> Delayed<T> spawnProjectileFromRotationDelayed(Projectile.ProjectileFactory<T> creator, ServerLevel world, ItemStack projectileStack, LivingEntity shooter, float roll, float power, float divergence) {
-+        return Projectile.spawnProjectileDelayed(creator.create(world, shooter, projectileStack), world, projectileStack, (iprojectile) -> {
-+    // Paper end - PlayerLaunchProjectileEvent
-             iprojectile.shootFromRotation(shooter, shooter.getXRot(), shooter.getYRot(), roll, power, divergence);
-         });
-     }
-@@ -201,7 +236,12 @@
-     }
- 
-     public static <T extends Projectile> T spawnProjectileUsingShoot(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) {
--        return Projectile.spawnProjectile(projectile, world, projectileStack, (iprojectile) -> {
-+    // Paper start - fixes and addition to spawn reason API
-+        return spawnProjectileUsingShootDelayed(projectile, world, projectileStack, velocityX, velocityY, velocityZ, power, divergence).spawn();
-+    }
-+    public static <T extends Projectile> Delayed<T> spawnProjectileUsingShootDelayed(T projectile, ServerLevel world, ItemStack projectileStack, double velocityX, double velocityY, double velocityZ, float power, float divergence) {
-+        return Projectile.spawnProjectileDelayed(projectile, world, projectileStack, (iprojectile) -> {
-+    // Paper end - fixes and addition to spawn reason API
-             projectile.shoot(velocityX, velocityY, velocityZ, power, divergence);
-         });
-     }
-@@ -211,11 +251,45 @@
-         });
-     }
- 
-+    // Paper start - delayed projectile spawning
-+    public record Delayed<T extends Projectile>(
-+        T projectile,
-+        ServerLevel world,
-+        ItemStack projectileStack
-+    ) {
-+        // Taken from net.minecraft.world.entity.projectile.Projectile.spawnProjectile(T, net.minecraft.server.level.ServerLevel, net.minecraft.world.item.ItemStack, java.util.function.Consumer<T>)
-+        public boolean attemptSpawn() {
-+            if (!world.addFreshEntity(projectile)) return false;
-+            projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
-+            return true;
-+        }
-+
-+        public T spawn() {
-+            this.attemptSpawn();
-+            return projectile();
-+        }
-+
-+        public boolean attemptSpawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
-+            if (!world.addFreshEntity(projectile, reason)) return false;
-+            projectile.applyOnProjectileSpawned(this.world, this.projectileStack);
-+            return true;
-+        }
-+
-+        public T spawn(final org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
-+            this.attemptSpawn(reason);
-+            return projectile();
-+        }
-+    }
-+    // Paper end - delayed projectile spawning
-+
-     public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel world, ItemStack projectileStack, Consumer<T> beforeSpawn) {
-+    // Paper start - delayed projectile spawning
-+        return spawnProjectileDelayed(projectile, world, projectileStack, beforeSpawn).spawn();
-+    }
-+    public static <T extends Projectile> Delayed<T> spawnProjectileDelayed(T projectile, ServerLevel world, ItemStack projectileStack, Consumer<T> beforeSpawn) {
-+    // Paper end - delayed projectile spawning
-         beforeSpawn.accept(projectile);
--        world.addFreshEntity(projectile);
--        projectile.applyOnProjectileSpawned(world, projectileStack);
--        return projectile;
-+        return new Delayed<>(projectile, world, projectileStack); // Paper - delayed projectile spawning
-     }
- 
-     public void applyOnProjectileSpawned(ServerLevel world, ItemStack projectileStack) {
-@@ -232,6 +306,17 @@
- 
-     }
- 
-+    // CraftBukkit start - call projectile hit event
-+    public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public
-+        org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
-+        this.hitCancelled = event != null && event.isCancelled();
-+        if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
-+            return this.hitTargetOrDeflectSelf(movingobjectposition);
-+        }
-+        return ProjectileDeflection.NONE;
-+    }
-+    // CraftBukkit end
-+
-     protected ProjectileDeflection hitTargetOrDeflectSelf(HitResult hitResult) {
-         if (hitResult.getType() == HitResult.Type.ENTITY) {
-             EntityHitResult movingobjectpositionentity = (EntityHitResult) hitResult;
-@@ -269,7 +354,13 @@
-     public boolean deflect(ProjectileDeflection deflection, @Nullable Entity deflector, @Nullable Entity owner, boolean fromAttack) {
-         deflection.deflect(this, deflector, this.random);
-         if (!this.level().isClientSide) {
--            this.setOwner(owner);
-+            // Paper start - Fix PickupStatus getting reset
-+            if (this instanceof AbstractArrow arrow) {
-+                arrow.setOwner(owner, false);
-+            } else {
-+                this.setOwner(owner);
-+            }
-+            // Paper end - Fix PickupStatus getting reset
-             this.onDeflection(deflector, fromAttack);
-         }
- 
-@@ -309,6 +400,11 @@
-     protected void onHitEntity(EntityHitResult entityHitResult) {}
- 
-     protected void onHitBlock(BlockHitResult blockHitResult) {
-+        // CraftBukkit start - cancellable hit event
-+        if (this.hitCancelled) {
-+            return;
-+        }
-+        // CraftBukkit end
-         BlockState iblockdata = this.level().getBlockState(blockHitResult.getBlockPos());
- 
-         iblockdata.onProjectileHit(this.level(), iblockdata, blockHitResult, this);
-@@ -320,6 +416,15 @@
-         } else {
-             Entity entity1 = this.getOwner();
- 
-+            // Paper start - Cancel hit for vanished players
-+            if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) {
-+                org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity();
-+                org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity();
-+                if (!shooter.canSee(collided)) {
-+                    return false;
-+                }
-+            }
-+            // Paper end - Cancel hit for vanished players
-             return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity);
-         }
-     }
-@@ -333,14 +438,8 @@
-     }
- 
-     protected static float lerpRotation(float prevRot, float newRot) {
--        while (newRot - prevRot < -180.0F) {
--            prevRot -= 360.0F;
--        }
-+        prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server
- 
--        while (newRot - prevRot >= 180.0F) {
--            prevRot += 360.0F;
--        }
--
-         return Mth.lerp(0.2F, prevRot, newRot);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
deleted file mode 100644
index bac5f4b417..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
+++ /dev/null
@@ -1,100 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
-+++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
-@@ -31,6 +31,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class ShulkerBullet extends Projectile {
- 
-@@ -60,8 +63,21 @@
-         this.finalTarget = target;
-         this.currentMoveDirection = Direction.UP;
-         this.selectNextMoveDirection(axis);
-+        this.projectileSource = (org.bukkit.entity.LivingEntity) owner.getBukkitEntity(); // CraftBukkit
-     }
- 
-+    // CraftBukkit start
-+    public Entity getTarget() {
-+        return this.finalTarget;
-+    }
-+
-+    public void setTarget(Entity e) {
-+        this.finalTarget = e;
-+        this.currentMoveDirection = Direction.UP;
-+        this.selectNextMoveDirection(Direction.Axis.X);
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     public SoundSource getSoundSource() {
-         return SoundSource.HOSTILE;
-@@ -194,7 +210,7 @@
-     @Override
-     public void checkDespawn() {
-         if (this.level().getDifficulty() == Difficulty.PEACEFUL) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -239,7 +255,7 @@
-         }
- 
-         if (movingobjectposition != null && this.isAlive() && movingobjectposition.getType() != HitResult.Type.MISS) {
--            this.hitTargetOrDeflectSelf(movingobjectposition);
-+            this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-         }
- 
-         ProjectileUtil.rotateTowardsMovement(this, 0.5F);
-@@ -312,7 +328,7 @@
-             if (entity instanceof LivingEntity) {
-                 LivingEntity entityliving1 = (LivingEntity) entity;
- 
--                entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this));
-+                entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-             }
-         }
- 
-@@ -326,14 +342,20 @@
-     }
- 
-     private void destroy() {
--        this.discard();
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.destroy(null);
-+    }
-+
-+    private void destroy(EntityRemoveEvent.Cause cause) {
-+        this.discard(cause);
-+        // CraftBukkit end
-         this.level().gameEvent((Holder) GameEvent.ENTITY_DAMAGE, this.position(), GameEvent.Context.of((Entity) this));
-     }
- 
-     @Override
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
--        this.destroy();
-+        this.destroy(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
-@@ -348,9 +370,14 @@
- 
-     @Override
-     public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
-+        // CraftBukkit start
-+        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
-+            return false;
-+        }
-+        // CraftBukkit end
-         this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F);
-         world.sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2D, 0.2D, 0.2D, 0.0D);
--        this.destroy();
-+        this.destroy(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-         return true;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SmallFireball.java.patch
deleted file mode 100644
index 65522ba256..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SmallFireball.java.patch
+++ /dev/null
@@ -1,63 +0,0 @@
---- a/net/minecraft/world/entity/projectile/SmallFireball.java
-+++ b/net/minecraft/world/entity/projectile/SmallFireball.java
-@@ -15,6 +15,10 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityCombustByEntityEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class SmallFireball extends Fireball {
- 
-@@ -24,6 +28,11 @@
- 
-     public SmallFireball(Level world, LivingEntity owner, Vec3 velocity) {
-         super(EntityType.SMALL_FIREBALL, owner, velocity, world);
-+        // CraftBukkit start
-+        if (this.getOwner() != null && this.getOwner() instanceof Mob) {
-+            this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
-+        }
-+        // CraftBukkit end
-     }
- 
-     public SmallFireball(Level world, double x, double y, double z, Vec3 velocity) {
-@@ -40,7 +49,14 @@
-             Entity entity1 = this.getOwner();
-             int i = entity.getRemainingFireTicks();
- 
--            entity.igniteForSeconds(5.0F);
-+            // CraftBukkit start - Entity damage by entity event + combust event
-+            EntityCombustByEntityEvent event = new EntityCombustByEntityEvent((org.bukkit.entity.Projectile) this.getBukkitEntity(), entity.getBukkitEntity(), 5.0F);
-+            entity.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled()) {
-+                entity.igniteForSeconds(event.getDuration(), false);
-+            }
-+            // CraftBukkit end
-             DamageSource damagesource = this.damageSources().fireball(this, entity1);
- 
-             if (!entity.hurtServer(worldserver, damagesource, 5.0F)) {
-@@ -60,10 +76,10 @@
-         if (world instanceof ServerLevel worldserver) {
-             Entity entity = this.getOwner();
- 
--            if (!(entity instanceof Mob) || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+            if (this.isIncendiary) { // CraftBukkit
-                 BlockPos blockposition = blockHitResult.getBlockPos().relative(blockHitResult.getDirection());
- 
--                if (this.level().isEmptyBlock(blockposition)) {
-+                if (this.level().isEmptyBlock(blockposition) && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { // CraftBukkit
-                     this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition));
-                 }
-             }
-@@ -75,7 +91,7 @@
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-         if (!this.level().isClientSide) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Snowball.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Snowball.java.patch
deleted file mode 100644
index 8d2ce7e8d8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/Snowball.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/entity/projectile/Snowball.java
-+++ b/net/minecraft/world/entity/projectile/Snowball.java
-@@ -13,6 +13,9 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Snowball extends ThrowableItemProjectile {
- 
-@@ -65,7 +68,7 @@
-         super.onHit(hitResult);
-         if (!this.level().isClientSide) {
-             this.level().broadcastEntityEvent(this, (byte) 3);
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SpectralArrow.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
deleted file mode 100644
index 629d60da70..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/projectile/SpectralArrow.java
-+++ b/net/minecraft/world/entity/projectile/SpectralArrow.java
-@@ -41,7 +41,7 @@
-         super.doPostHurtEffects(target);
-         MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0);
- 
--        target.addEffect(mobeffect, this.getEffectSource());
-+        target.addEffect(mobeffect, this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
deleted file mode 100644
index db8a43fd04..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java
-+++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java
-@@ -63,7 +63,7 @@
-         this.applyEffectsFromBlocks();
-         super.tick();
-         if (movingobjectposition.getType() != HitResult.Type.MISS && this.isAlive()) {
--            this.hitTargetOrDeflectSelf(movingobjectposition);
-+            this.preHitTargetOrDeflectSelf(movingobjectposition); // CraftBukkit - projectile hit event
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
deleted file mode 100644
index 5df84b160a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
+++ /dev/null
@@ -1,121 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrownEgg.java
-+++ b/net/minecraft/world/entity/projectile/ThrownEgg.java
-@@ -1,33 +1,39 @@
- package net.minecraft.world.entity.projectile;
- 
--import net.minecraft.core.particles.ItemParticleOption;
--import net.minecraft.core.particles.ParticleTypes;
--import net.minecraft.world.entity.EntityDimensions;
- import net.minecraft.world.entity.EntitySpawnReason;
--import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.LivingEntity;
--import net.minecraft.world.entity.animal.Chicken;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
-+import net.minecraft.core.particles.ItemParticleOption;
-+import net.minecraft.core.particles.ParticleTypes;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityDimensions;
-+import org.bukkit.entity.Ageable;
-+import org.bukkit.entity.EntityType;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerEggThrowEvent;
-+// CraftBukkit end
- 
- public class ThrownEgg extends ThrowableItemProjectile {
- 
-     private static final EntityDimensions ZERO_SIZED_DIMENSIONS = EntityDimensions.fixed(0.0F, 0.0F);
- 
--    public ThrownEgg(EntityType<? extends ThrownEgg> type, Level world) {
-+    public ThrownEgg(net.minecraft.world.entity.EntityType<? extends ThrownEgg> type, Level world) {
-         super(type, world);
-     }
- 
-     public ThrownEgg(Level world, LivingEntity owner, ItemStack stack) {
--        super(EntityType.EGG, owner, world, stack);
-+        super(net.minecraft.world.entity.EntityType.EGG, owner, world, stack);
-     }
- 
-     public ThrownEgg(Level world, double x, double y, double z, ItemStack stack) {
--        super(EntityType.EGG, x, y, z, world, stack);
-+        super(net.minecraft.world.entity.EntityType.EGG, x, y, z, world, stack);
-     }
- 
-     @Override
-@@ -52,30 +58,65 @@
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-         if (!this.level().isClientSide) {
--            if (this.random.nextInt(8) == 0) {
-+            // CraftBukkit start
-+            boolean hatching = this.random.nextInt(8) == 0;
-+            if (true) {
-+            // CraftBukkit end
-                 byte b0 = 1;
- 
-                 if (this.random.nextInt(32) == 0) {
-                     b0 = 4;
-                 }
- 
-+                // CraftBukkit start
-+                EntityType hatchingType = EntityType.CHICKEN;
-+
-+                Entity shooter = this.getOwner();
-+                if (!hatching) {
-+                    b0 = 0;
-+                }
-+                if (shooter instanceof ServerPlayer) {
-+                    PlayerEggThrowEvent event = new PlayerEggThrowEvent((Player) shooter.getBukkitEntity(), (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, b0, hatchingType);
-+                    this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                    b0 = event.getNumHatches();
-+                    hatching = event.isHatching();
-+                    hatchingType = event.getHatchingType();
-+                    // If hatching is set to false, ensure child count is 0
-+                    if (!hatching) {
-+                        b0 = 0;
-+                    }
-+                }
-+                // CraftBukkit end
-+                // Paper start - Add ThrownEggHatchEvent
-+                com.destroystokyo.paper.event.entity.ThrownEggHatchEvent event = new com.destroystokyo.paper.event.entity.ThrownEggHatchEvent((org.bukkit.entity.Egg) getBukkitEntity(), hatching, b0, hatchingType);
-+                event.callEvent();
-+                hatching = event.isHatching();
-+                b0 = hatching ? event.getNumHatches() : 0; // If hatching is set to false, ensure child count is 0
-+                hatchingType = event.getHatchingType();
-+                // Paper end - Add ThrownEggHatchEvent
-+
-                 for (int i = 0; i < b0; ++i) {
--                    Chicken entitychicken = (Chicken) EntityType.CHICKEN.create(this.level(), EntitySpawnReason.TRIGGERED);
-+                    Entity entitychicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit
- 
-                     if (entitychicken != null) {
--                        entitychicken.setAge(-24000);
-+                        // CraftBukkit start
-+                        if (entitychicken.getBukkitEntity() instanceof Ageable) {
-+                            ((Ageable) entitychicken.getBukkitEntity()).setBaby();
-+                        }
-+                        // CraftBukkit end
-                         entitychicken.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
-                         if (!entitychicken.fudgePositionAfterSizeChange(ThrownEgg.ZERO_SIZED_DIMENSIONS)) {
-                             break;
-                         }
- 
--                        this.level().addFreshEntity(entitychicken);
-+                        this.level().addFreshEntity(entitychicken, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit
-                     }
-                 }
-             }
- 
-             this.level().broadcastEntityEvent(this, (byte) 3);
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
deleted file mode 100644
index f24cfeb088..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
+++ /dev/null
@@ -1,95 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
-+++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
-@@ -24,10 +24,15 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+// CraftBukkit end
- 
- public class ThrownEnderpearl extends ThrowableItemProjectile {
- 
-@@ -140,12 +145,19 @@
-                         ServerPlayer entityplayer = (ServerPlayer) entity;
- 
-                         if (entityplayer.connection.isAcceptingMessages()) {
-+                            // CraftBukkit start
-+                            ServerPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, PlayerTeleportEvent.TeleportCause.ENDER_PEARL));
-+                            if (entityplayer1 == null) {
-+                                this.discard(EntityRemoveEvent.Cause.HIT);
-+                                return;
-+                            }
-+                            // CraftBukkit end
-                             if (this.random.nextFloat() < 0.05F && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
-                                 Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(worldserver, EntitySpawnReason.TRIGGERED);
- 
-                                 if (entityendermite != null) {
-                                     entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
--                                    worldserver.addFreshEntity(entityendermite);
-+                                    worldserver.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
-                                 }
-                             }
- 
-@@ -153,12 +165,12 @@
-                                 entity.setPortalCooldown();
-                             }
- 
--                            ServerPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING));
-+                            // EntityPlayer entityplayer1 = entityplayer.teleport(new TeleportTransition(worldserver, vec3d, Vec3D.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING)); // CraftBukkit - moved up
- 
-                             if (entityplayer1 != null) {
-                                 entityplayer1.resetFallDistance();
-                                 entityplayer1.resetCurrentImpulseContext();
--                                entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl(), 5.0F);
-+                                entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API
-                             }
- 
-                             this.playSound(worldserver, vec3d);
-@@ -173,11 +185,11 @@
-                         this.playSound(worldserver, vec3d);
-                     }
- 
--                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-                     return;
-                 }
- 
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-                 return;
-             }
-         }
-@@ -210,7 +222,7 @@
-             entity = this.getOwner();
-             if (entity instanceof ServerPlayer entityplayer) {
-                 if (!entity.isAlive() && entityplayer.serverLevel().getGameRules().getBoolean(GameRules.RULE_ENDER_PEARLS_VANISH_ON_DEATH)) {
--                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                     break label30;
-                 }
-             }
-@@ -240,7 +252,7 @@
-         Entity entity = super.teleport(teleportTarget);
- 
-         if (entity != null) {
--            entity.placePortalTicket(BlockPos.containing(entity.position()));
-+            if (!this.level().paperConfig().misc.legacyEnderPearlBehavior) entity.placePortalTicket(BlockPos.containing(entity.position())); // Paper - Allow using old ender pearl behavior
-         }
- 
-         return entity;
-@@ -248,7 +260,7 @@
- 
-     @Override
-     public boolean canTeleport(Level from, Level to) {
--        if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) {
-+        if (from.getTypeKey() == LevelStem.END && to.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit
-             Entity entity = this.getOwner();
- 
-             if (entity instanceof ServerPlayer) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
deleted file mode 100644
index 40cd201036..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
+++ /dev/null
@@ -1,36 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
-+++ b/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
-@@ -9,6 +9,9 @@
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.HitResult;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class ThrownExperienceBottle extends ThrowableItemProjectile {
- 
-@@ -38,11 +41,20 @@
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-         if (this.level() instanceof ServerLevel) {
--            this.level().levelEvent(2002, this.blockPosition(), -13083194);
-+            // CraftBukkit - moved to after event
-+            // this.level().levelEvent(2002, this.blockPosition(), -13083194);
-             int i = 3 + this.level().random.nextInt(5) + this.level().random.nextInt(5);
- 
--            ExperienceOrb.award((ServerLevel) this.level(), this.position(), i);
--            this.discard();
-+            // CraftBukkit start
-+            org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, hitResult, i);
-+            i = event.getExperience();
-+            if (event.getShowEffect()) {
-+                this.level().levelEvent(2002, this.blockPosition(), -13083194);
-+            }
-+            // CraftBukkit end
-+
-+            ExperienceOrb.award((ServerLevel) this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
deleted file mode 100644
index babee054f3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
+++ /dev/null
@@ -1,322 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrownPotion.java
-+++ b/net/minecraft/world/entity/projectile/ThrownPotion.java
-@@ -10,6 +10,7 @@
- import net.minecraft.core.Holder;
- import net.minecraft.core.component.DataComponents;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.tags.BlockTags;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.effect.MobEffect;
-@@ -17,7 +18,6 @@
- import net.minecraft.world.entity.AreaEffectCloud;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityType;
--import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.animal.axolotl.Axolotl;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.Item;
-@@ -28,18 +28,28 @@
- import net.minecraft.world.item.alchemy.Potions;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.AbstractCandleBlock;
-+// CraftBukkit start
-+import java.util.HashMap;
-+import java.util.Map;
-+import net.minecraft.world.effect.MobEffects;
-+import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.CampfireBlock;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.entity.LivingEntity;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class ThrownPotion extends ThrowableItemProjectile {
- 
-     public static final double SPLASH_RANGE = 4.0D;
-     private static final double SPLASH_RANGE_SQ = 16.0D;
--    public static final Predicate<LivingEntity> WATER_SENSITIVE_OR_ON_FIRE = (entityliving) -> {
-+    public static final Predicate<net.minecraft.world.entity.LivingEntity> WATER_SENSITIVE_OR_ON_FIRE = (entityliving) -> {
-         return entityliving.isSensitiveToWater() || entityliving.isOnFire();
-     };
- 
-@@ -47,7 +57,7 @@
-         super(type, world);
-     }
- 
--    public ThrownPotion(Level world, LivingEntity owner, ItemStack stack) {
-+    public ThrownPotion(Level world, net.minecraft.world.entity.LivingEntity owner, ItemStack stack) {
-         super(EntityType.POTION, owner, world, stack);
-     }
- 
-@@ -93,70 +103,96 @@
-     @Override
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-+        // Paper start - More projectile API
-+        this.splash(hitResult);
-+    }
-+    public void splash(@Nullable HitResult hitResult) {
-+        // Paper end - More projectile API
-         Level world = this.level();
--
-         if (world instanceof ServerLevel worldserver) {
-             ItemStack itemstack = this.getItem();
-             PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
- 
-+            boolean showParticles = true; // Paper - Fix potions splash events
-             if (potioncontents.is(Potions.WATER)) {
--                this.applyWater(worldserver);
--            } else if (potioncontents.hasEffects()) {
-+                showParticles = this.applyWater(worldserver, hitResult); // Paper - Fix potions splash events
-+            } else if (true || potioncontents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply
-                 if (this.isLingering()) {
--                    this.makeAreaOfEffectCloud(potioncontents);
-+                    showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper
-                 } else {
--                    this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null);
-+                    showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
-                 }
-             }
- 
-+            if (showParticles) { // Paper - Fix potions splash events
-             int i = potioncontents.potion().isPresent() && ((Potion) ((Holder) potioncontents.potion().get()).value()).hasInstantEffects() ? 2007 : 2002;
- 
-             worldserver.levelEvent(i, this.blockPosition(), potioncontents.getColor());
--            this.discard();
-+            } // Paper - Fix potions splash events
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
-     }
- 
--    private void applyWater(ServerLevel world) {
-+    private static final Predicate<net.minecraft.world.entity.LivingEntity> APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events
-+    private boolean applyWater(ServerLevel world, @Nullable HitResult hitResult) { // Paper - Fix potions splash events
-         AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
--        List<LivingEntity> list = this.level().getEntitiesOfClass(LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE);
-+        // Paper start - Fix potions splash events
-+        List<net.minecraft.world.entity.LivingEntity> list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE);
-+        Map<LivingEntity, Double> affected = new HashMap<>();
-+        java.util.Set<LivingEntity> rehydrate = new java.util.HashSet<>();
-+        java.util.Set<LivingEntity> extinguish = new java.util.HashSet<>();
-         Iterator iterator = list.iterator();
- 
-         while (iterator.hasNext()) {
--            LivingEntity entityliving = (LivingEntity) iterator.next();
-+            net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next();
-+            if (entityliving instanceof Axolotl axolotl) {
-+                rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity()));
-+            }
-             double d0 = this.distanceToSqr((Entity) entityliving);
- 
-             if (d0 < 16.0D) {
-                 if (entityliving.isSensitiveToWater()) {
--                    entityliving.hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
-+                    affected.put(entityliving.getBukkitLivingEntity(), 1.0);
-                 }
- 
-                 if (entityliving.isOnFire() && entityliving.isAlive()) {
--                    entityliving.extinguishFire();
-+                    extinguish.add(entityliving.getBukkitLivingEntity());
-                 }
-             }
-         }
- 
--        List<Axolotl> list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb);
--        Iterator iterator1 = list1.iterator();
--
--        while (iterator1.hasNext()) {
--            Axolotl axolotl = (Axolotl) iterator1.next();
--
--            axolotl.rehydrate();
-+        io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent(
-+            this, hitResult, affected, rehydrate, extinguish
-+        );
-+        if (!event.isCancelled()) {
-+            for (LivingEntity affectedEntity : event.getToDamage()) {
-+                ((CraftLivingEntity) affectedEntity).getHandle().hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
-+            }
-+            for (LivingEntity toExtinguish : event.getToExtinguish()) {
-+                ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire();
-+            }
-+            for (LivingEntity toRehydrate : event.getToRehydrate()) {
-+                if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) {
-+                    axolotl.rehydrate();
-+                }
-+            }
-+            // Paper end - Fix potions splash events
-         }
-+        return !event.isCancelled(); // Paper - Fix potions splash events
- 
-     }
- 
--    private void applySplash(ServerLevel world, Iterable<MobEffectInstance> effects, @Nullable Entity entity) {
-+    private boolean applySplash(ServerLevel worldserver, Iterable<MobEffectInstance> iterable, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API
-         AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
--        List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, axisalignedbb);
-+        List<net.minecraft.world.entity.LivingEntity> list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb);
-+        Map<LivingEntity, Double> affected = new HashMap<LivingEntity, Double>(); // CraftBukkit
- 
-         if (!list.isEmpty()) {
-             Entity entity1 = this.getEffectSource();
-             Iterator iterator = list.iterator();
- 
-             while (iterator.hasNext()) {
--                LivingEntity entityliving = (LivingEntity) iterator.next();
-+                net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next();
- 
-                 if (entityliving.isAffectedByPotions()) {
-                     double d0 = this.distanceToSqr((Entity) entityliving);
-@@ -164,43 +200,71 @@
-                     if (d0 < 16.0D) {
-                         double d1;
- 
-+                        // Paper - diff on change, used when calling the splash event for water splash potions
-                         if (entityliving == entity) {
-                             d1 = 1.0D;
-                         } else {
-                             d1 = 1.0D - Math.sqrt(d0) / 4.0D;
-                         }
- 
--                        Iterator iterator1 = effects.iterator();
-+                        // CraftBukkit start
-+                        affected.put((LivingEntity) entityliving.getBukkitEntity(), d1);
-+                    }
-+                }
-+            }
-+        }
- 
--                        while (iterator1.hasNext()) {
--                            MobEffectInstance mobeffect = (MobEffectInstance) iterator1.next();
--                            Holder<MobEffect> holder = mobeffect.getEffect();
-+        org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, position, affected);
-+        if (!event.isCancelled() && list != null && !list.isEmpty()) { // do not process effects if there are no effects to process
-+            Entity entity1 = this.getEffectSource();
-+            for (LivingEntity victim : event.getAffectedEntities()) {
-+                if (!(victim instanceof CraftLivingEntity)) {
-+                    continue;
-+                }
- 
--                            if (((MobEffect) holder.value()).isInstantenous()) {
--                                ((MobEffect) holder.value()).applyInstantenousEffect(world, this, this.getOwner(), entityliving, mobeffect.getAmplifier(), d1);
--                            } else {
--                                int i = mobeffect.mapDuration((j) -> {
--                                    return (int) (d1 * (double) j + 0.5D);
--                                });
--                                MobEffectInstance mobeffect1 = new MobEffectInstance(holder, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible());
-+                net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) victim).getHandle();
-+                double d1 = event.getIntensity(victim);
-+                // CraftBukkit end
- 
--                                if (!mobeffect1.endsWithin(20)) {
--                                    entityliving.addEffect(mobeffect1, entity1);
--                                }
--                            }
-+                Iterator iterator1 = iterable.iterator();
-+
-+                while (iterator1.hasNext()) {
-+                    MobEffectInstance mobeffect = (MobEffectInstance) iterator1.next();
-+                    Holder<MobEffect> holder = mobeffect.getEffect();
-+                    // CraftBukkit start - Abide by PVP settings - for players only!
-+                    if (!this.level().pvpMode && this.getOwner() instanceof ServerPlayer && entityliving instanceof ServerPlayer && entityliving != this.getOwner()) {
-+                        MobEffect mobeffectlist = (MobEffect) holder.value();
-+                        if (mobeffectlist == MobEffects.MOVEMENT_SLOWDOWN || mobeffectlist == MobEffects.DIG_SLOWDOWN || mobeffectlist == MobEffects.HARM || mobeffectlist == MobEffects.BLINDNESS
-+                                || mobeffectlist == MobEffects.HUNGER || mobeffectlist == MobEffects.WEAKNESS || mobeffectlist == MobEffects.POISON) {
-+                            continue;
-                         }
-                     }
-+                    // CraftBukkit end
-+
-+                    if (((MobEffect) holder.value()).isInstantenous()) {
-+                        ((MobEffect) holder.value()).applyInstantenousEffect(worldserver, this, this.getOwner(), entityliving, mobeffect.getAmplifier(), d1);
-+                    } else {
-+                        int i = mobeffect.mapDuration((j) -> {
-+                            return (int) (d1 * (double) j + 0.5D);
-+                        });
-+                        MobEffectInstance mobeffect1 = new MobEffectInstance(holder, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible());
-+
-+                        if (!mobeffect1.endsWithin(20)) {
-+                            entityliving.addEffect(mobeffect1, entity1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_SPLASH); // CraftBukkit
-+                        }
-+                    }
-                 }
-             }
-         }
-+        return !event.isCancelled(); // Paper - Fix potions splash events
- 
-     }
- 
--    private void makeAreaOfEffectCloud(PotionContents potion) {
-+    private boolean makeAreaOfEffectCloud(PotionContents potioncontents, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
-         AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
-         Entity entity = this.getOwner();
- 
--        if (entity instanceof LivingEntity entityliving) {
-+        if (entity instanceof net.minecraft.world.entity.LivingEntity entityliving) {
-             entityareaeffectcloud.setOwner(entityliving);
-         }
- 
-@@ -208,8 +272,17 @@
-         entityareaeffectcloud.setRadiusOnUse(-0.5F);
-         entityareaeffectcloud.setWaitTime(10);
-         entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration());
--        entityareaeffectcloud.setPotionContents(potion);
--        this.level().addFreshEntity(entityareaeffectcloud);
-+        entityareaeffectcloud.setPotionContents(potioncontents);
-+        boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events
-+        // CraftBukkit start
-+        org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud);
-+        if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && !entityareaeffectcloud.potionContents.hasEffects())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling
-+            this.level().addFreshEntity(entityareaeffectcloud);
-+        } else {
-+            entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause
-+        }
-+        // CraftBukkit end
-+        return !event.isCancelled(); // Paper - Fix potions splash events
-     }
- 
-     public boolean isLingering() {
-@@ -220,19 +293,31 @@
-         BlockState iblockdata = this.level().getBlockState(pos);
- 
-         if (iblockdata.is(BlockTags.FIRE)) {
--            this.level().destroyBlock(pos, false, this);
-+            // CraftBukkit start
-+            if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                this.level().destroyBlock(pos, false, this);
-+            }
-+            // CraftBukkit end
-         } else if (AbstractCandleBlock.isLit(iblockdata)) {
--            AbstractCandleBlock.extinguish((Player) null, iblockdata, this.level(), pos);
-+            // CraftBukkit start
-+            if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(AbstractCandleBlock.LIT, false))) {
-+                AbstractCandleBlock.extinguish((Player) null, iblockdata, this.level(), pos);
-+            }
-+            // CraftBukkit end
-         } else if (CampfireBlock.isLitCampfire(iblockdata)) {
--            this.level().levelEvent((Player) null, 1009, pos, 0);
--            CampfireBlock.dowse(this.getOwner(), this.level(), pos, iblockdata);
--            this.level().setBlockAndUpdate(pos, (BlockState) iblockdata.setValue(CampfireBlock.LIT, false));
-+            // CraftBukkit start
-+            if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(CampfireBlock.LIT, false))) {
-+                this.level().levelEvent((Player) null, 1009, pos, 0);
-+                CampfireBlock.dowse(this.getOwner(), this.level(), pos, iblockdata);
-+                this.level().setBlockAndUpdate(pos, (BlockState) iblockdata.setValue(CampfireBlock.LIT, false));
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
- 
-     @Override
--    public DoubleDoubleImmutablePair calculateHorizontalHurtKnockbackDirection(LivingEntity target, DamageSource source) {
-+    public DoubleDoubleImmutablePair calculateHorizontalHurtKnockbackDirection(net.minecraft.world.entity.LivingEntity target, DamageSource source) {
-         double d0 = target.position().x - this.position().x;
-         double d1 = target.position().z - this.position().z;
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownTrident.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
deleted file mode 100644
index fff7300b9e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
+++ /dev/null
@@ -1,86 +0,0 @@
---- a/net/minecraft/world/entity/projectile/ThrownTrident.java
-+++ b/net/minecraft/world/entity/projectile/ThrownTrident.java
-@@ -23,6 +23,9 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class ThrownTrident extends AbstractArrow {
- 
-@@ -34,16 +37,19 @@
- 
-     public ThrownTrident(EntityType<? extends ThrownTrident> type, Level world) {
-         super(type, world);
-+        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
-     }
- 
-     public ThrownTrident(Level world, LivingEntity owner, ItemStack stack) {
-         super(EntityType.TRIDENT, owner, world, stack, (ItemStack) null);
-+        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
-         this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack));
-         this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil());
-     }
- 
-     public ThrownTrident(Level world, double x, double y, double z, ItemStack stack) {
-         super(EntityType.TRIDENT, x, y, z, world, stack, stack);
-+        this.setBaseDamage(net.minecraft.world.item.TridentItem.BASE_DAMAGE); // Paper - Allow trident custom damage
-         this.entityData.set(ThrownTrident.ID_LOYALTY, this.getLoyaltyFromItem(stack));
-         this.entityData.set(ThrownTrident.ID_FOIL, stack.hasFoil());
-     }
-@@ -76,10 +82,10 @@
-                     }
-                 }
- 
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
-             } else {
-                 if (!(entity instanceof Player) && this.position().distanceTo(entity.getEyePosition()) < (double) entity.getBbWidth() + 1.0D) {
--                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                     return;
-                 }
- 
-@@ -109,8 +115,22 @@
- 
-     public boolean isFoil() {
-         return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL);
-+    }
-+
-+    // Paper start
-+    public void setFoil(boolean foil) {
-+        this.entityData.set(ThrownTrident.ID_FOIL, foil);
-     }
- 
-+    public int getLoyalty() {
-+        return this.entityData.get(ThrownTrident.ID_LOYALTY);
-+    }
-+
-+    public void setLoyalty(byte loyalty) {
-+        this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty);
-+    }
-+    // Paper end
-+
-     @Nullable
-     @Override
-     protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) {
-@@ -120,7 +140,7 @@
-     @Override
-     protected void onHitEntity(EntityHitResult entityHitResult) {
-         Entity entity = entityHitResult.getEntity();
--        float f = 8.0F;
-+        float f = (float) this.getBaseDamage(); // Paper - Allow trident custom damage
-         Entity entity1 = this.getOwner();
-         DamageSource damagesource = this.damageSources().trident(this, (Entity) (entity1 == null ? this : entity1));
-         Level world = this.level();
-@@ -137,7 +157,7 @@
- 
-             world = this.level();
-             if (world instanceof ServerLevel) {
--                worldserver = (ServerLevel) world;
-+                ServerLevel worldserver = (ServerLevel) world; // CraftBukkit - decompile error
-                 EnchantmentHelper.doPostAttackEffectsWithItemSourceOnBreak(worldserver, entity, damagesource, this.getWeaponItem(), (item) -> {
-                     this.kill(worldserver);
-                 });
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/WitherSkull.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/WitherSkull.java.patch
deleted file mode 100644
index 96234163f1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/WitherSkull.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/world/entity/projectile/WitherSkull.java
-+++ b/net/minecraft/world/entity/projectile/WitherSkull.java
-@@ -23,6 +23,10 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
- 
- public class WitherSkull extends AbstractHurtingProjectile {
- 
-@@ -69,11 +73,11 @@
-                     if (entity.isAlive()) {
-                         EnchantmentHelper.doPostAttackEffects(worldserver, entity, damagesource);
-                     } else {
--                        entityliving.heal(5.0F);
-+                        entityliving.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); // CraftBukkit
-                     }
-                 }
-             } else {
--                flag = entity.hurtServer(worldserver, this.damageSources().magic(), 5.0F);
-+                flag = entity.hurtServer(worldserver, this.damageSources().magic().customEventDamager(this), 5.0F); // Paper - Fire EntityDamageByEntityEvent for unowned wither skulls // Paper - fix DamageSource API
-             }
- 
-             if (flag && entity instanceof LivingEntity entityliving) {
-@@ -86,7 +90,7 @@
-                 }
- 
-                 if (b0 > 0) {
--                    entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource());
-+                    entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-                 }
-             }
- 
-@@ -97,8 +101,16 @@
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-         if (!this.level().isClientSide) {
--            this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, Level.ExplosionInteraction.MOB);
--            this.discard();
-+            // CraftBukkit start
-+            // this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB);
-+            ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled()) {
-+                this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
-+            }
-+            // CraftBukkit end
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }

From 5b0289a24827b4abbd52fc9647c4c5c971a2b56d Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 17:50:51 -0800
Subject: [PATCH 036/285] net.minecraft.world.level.storage.loot.predicates

---
 .../loot/predicates/ExplosionCondition.java.patch    | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch (58%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
index 19cd841992..53cc48355e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
 +++ b/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
-@@ -31,7 +31,8 @@
-             RandomSource randomsource = loottableinfo.getRandom();
-             float f = 1.0F / ofloat;
- 
--            return randomsource.nextFloat() <= f;
+@@ -30,7 +_,8 @@
+         if (_float != null) {
+             RandomSource random = context.getRandom();
+             float f = 1.0F / _float;
+-            return random.nextFloat() <= f;
 +            // CraftBukkit - <= to < to allow for plugins to completely disable block drops from explosions
-+            return randomsource.nextFloat() < f;
++            return random.nextFloat() < f;
          } else {
              return true;
          }

From ce9d79a81b2741112ae618cc02f094519925e927 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 17:52:29 -0800
Subject: [PATCH 037/285] net.minecraft.world.entity.projectile.windcharge

---
 .../windcharge/AbstractWindCharge.java.patch  | 38 +++++++++++++++
 .../windcharge/AbstractWindCharge.java.patch  | 48 -------------------
 2 files changed, 38 insertions(+), 48 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch
new file mode 100644
index 0000000000..a764fae4a2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java
++++ b/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java
+@@ -85,7 +_,7 @@
+     }
+ 
+     @Override
+-    public void push(double x, double y, double z) {
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+     }
+ 
+     public abstract void explode(Vec3 pos);
+@@ -98,7 +_,7 @@
+             Vec3 vec3 = Vec3.atLowerCornerOf(unitVec3i).multiply(0.25, 0.25, 0.25);
+             Vec3 vec31 = result.getLocation().add(vec3);
+             this.explode(vec31);
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -106,7 +_,7 @@
+     protected void onHit(HitResult result) {
+         super.onHit(result);
+         if (!this.level().isClientSide) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -140,7 +_,7 @@
+     public void tick() {
+         if (!this.level().isClientSide && this.getBlockY() > this.level().getMaxY() + 30) {
+             this.explode(this.position());
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
+         } else {
+             super.tick();
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch
deleted file mode 100644
index 01844591e6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java.patch
+++ /dev/null
@@ -1,48 +0,0 @@
---- a/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java
-+++ b/net/minecraft/world/entity/projectile/windcharge/AbstractWindCharge.java
-@@ -24,6 +24,9 @@
- import net.minecraft.world.phys.EntityHitResult;
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public abstract class AbstractWindCharge extends AbstractHurtingProjectile implements ItemSupplier {
- 
-@@ -98,7 +101,7 @@
-     }
- 
-     @Override
--    public void push(double deltaX, double deltaY, double deltaZ) {}
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) {} // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
- 
-     public abstract void explode(Vec3 pos);
- 
-@@ -111,7 +114,7 @@
-             Vec3 vec3d1 = blockHitResult.getLocation().add(vec3d);
- 
-             this.explode(vec3d1);
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -120,7 +123,7 @@
-     protected void onHit(HitResult hitResult) {
-         super.onHit(hitResult);
-         if (!this.level().isClientSide) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -155,7 +158,7 @@
-     public void tick() {
-         if (!this.level().isClientSide && this.getBlockY() > this.level().getMaxY() + 30) {
-             this.explode(this.position());
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
-         } else {
-             super.tick();
-         }

From 64500a201f8aeaca21e668b0e16fda0b70087fae Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:12:33 -0800
Subject: [PATCH 038/285] net.minecraft.server

---
 .../net/minecraft/server/Bootstrap.java.patch |   44 +-
 .../net/minecraft/server/Main.java.patch      |  254 ++++
 .../server/MinecraftServer.java.patch         | 1222 +++++++++--------
 .../server/PlayerAdvancements.java.patch      |   54 +-
 .../ReloadableServerRegistries.java.patch     |   38 +
 .../ReloadableServerResources.java.patch      |   12 +-
 .../ServerAdvancementManager.java.patch       |   36 +-
 .../server/ServerFunctionLibrary.java.patch   |   11 +
 .../server/ServerFunctionManager.java.patch   |    2 +-
 .../server/ServerScoreboard.java.patch        |  140 +-
 .../server/ServerTickRateManager.java.patch   |   23 +-
 .../net/minecraft/server/Services.java.patch  |   18 +-
 .../minecraft/server/WorldLoader.java.patch   |    6 +-
 .../net/minecraft/server/Main.java.patch      |  290 ----
 .../ReloadableServerRegistries.java.patch     |   32 -
 .../server/ServerFunctionLibrary.java.patch   |   11 -
 16 files changed, 1083 insertions(+), 1110 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/Bootstrap.java.patch (80%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/Main.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/MinecraftServer.java.patch (54%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/PlayerAdvancements.java.patch (62%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/ReloadableServerResources.java.patch (79%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/ServerAdvancementManager.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/ServerFunctionManager.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/ServerScoreboard.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/ServerTickRateManager.java.patch (74%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/Services.java.patch (84%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/WorldLoader.java.patch (73%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/Main.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/ReloadableServerRegistries.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/ServerFunctionLibrary.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/Bootstrap.java.patch b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
similarity index 80%
rename from paper-server/patches/unapplied/net/minecraft/server/Bootstrap.java.patch
rename to paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
index efce551cae..b1ee24c3e7 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/Bootstrap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/Bootstrap.java
 +++ b/net/minecraft/server/Bootstrap.java
-@@ -17,6 +17,9 @@
+@@ -17,6 +_,9 @@
  import net.minecraft.core.dispenser.DispenseItemBehavior;
  import net.minecraft.core.registries.BuiltInRegistries;
  import net.minecraft.locale.Language;
@@ -10,45 +10,15 @@
  import net.minecraft.world.effect.MobEffect;
  import net.minecraft.world.entity.EntityType;
  import net.minecraft.world.entity.ai.attributes.Attribute;
-@@ -30,7 +33,8 @@
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import org.slf4j.Logger;
- 
--@SuppressForbidden(a = "System.out setup")
-+@SuppressForbidden(reason = "System.out setup")
-+// CraftBukkit end
- public class Bootstrap {
- 
-     public static final PrintStream STDOUT = System.out;
-@@ -42,9 +46,27 @@
- 
-     public static void bootStrap() {
-         if (!Bootstrap.isBootstrapped) {
-+            // CraftBukkit start
-+            /*String name = Bootstrap.class.getSimpleName(); // Paper
-+            switch (name) {
-+                case "DispenserRegistry":
-+                    break;
-+                case "Bootstrap":
-+                    System.err.println("***************************************************************************");
-+                    System.err.println("*** WARNING: This server jar may only be used for development purposes. ***");
-+                    System.err.println("***************************************************************************");
-+                    break;
-+                default:
-+                    System.err.println("**********************************************************************");
-+                    System.err.println("*** WARNING: This server jar is unsupported, use at your own risk. ***");
-+                    System.err.println("**********************************************************************");
-+                    break;
-+            }*/ // Paper
-+            // CraftBukkit end
-             Bootstrap.isBootstrapped = true;
+@@ -43,6 +_,7 @@
+         if (!isBootstrapped) {
+             isBootstrapped = true;
              Instant instant = Instant.now();
- 
 +            io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
              if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
                  throw new IllegalStateException("Unable to load registries");
              } else {
-@@ -56,11 +78,77 @@
+@@ -54,11 +_,77 @@
                      EntitySelectorOptions.bootStrap();
                      DispenseItemBehavior.bootStrap();
                      CauldronInteraction.bootStrap();
@@ -58,8 +28,8 @@
 +                    });
 +                    // Paper end
                      CreativeModeTabs.validate();
-                     Bootstrap.wrapStreams();
-                     Bootstrap.bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
+                     wrapStreams();
+                     bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
                  }
 +                // CraftBukkit start - easier than fixing the decompile
 +                BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
diff --git a/paper-server/patches/sources/net/minecraft/server/Main.java.patch b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
new file mode 100644
index 0000000000..001042f60c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
@@ -0,0 +1,254 @@
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
+@@ -37,6 +_,7 @@
+ import net.minecraft.server.dedicated.DedicatedServerProperties;
+ import net.minecraft.server.dedicated.DedicatedServerSettings;
+ import net.minecraft.server.level.progress.LoggerChunkProgressListener;
++import net.minecraft.server.packs.PackType;
+ import net.minecraft.server.packs.repository.PackRepository;
+ import net.minecraft.server.packs.repository.ServerPacksSource;
+ import net.minecraft.util.Mth;
+@@ -67,8 +_,9 @@
+         reason = "System.out needed before bootstrap"
+     )
+     @DontObfuscate
+-    public static void main(String[] args) {
++    public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args)
+         SharedConstants.tryDetectVersion();
++        /* CraftBukkit start - Replace everything
+         OptionParser optionParser = new OptionParser();
+         OptionSpec<Void> optionSpec = optionParser.accepts("nogui");
+         OptionSpec<Void> optionSpec1 = optionParser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
+@@ -93,41 +_,94 @@
+                 optionParser.printHelpOn(System.err);
+                 return;
+             }
++            */ // CraftBukkit end
++        try {
+ 
+-            Path path = optionSet.valueOf(optionSpec14);
++            Path path = (Path) optionSet.valueOf("pidFile"); // CraftBukkit
+             if (path != null) {
+                 writePidFile(path);
+             }
+ 
+             CrashReport.preload();
+-            if (optionSet.has(optionSpec13)) {
++            if (optionSet.has("jfrProfile")) { // CraftBukkit
+                 JvmProfiler.INSTANCE.start(Environment.SERVER);
+             }
+ 
++            io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper
+             Bootstrap.bootStrap();
+             Bootstrap.validate();
+             Util.startTimerHackThread();
+             Path path1 = Paths.get("server.properties");
+-            DedicatedServerSettings dedicatedServerSettings = new DedicatedServerSettings(path1);
++            DedicatedServerSettings dedicatedServerSettings = new DedicatedServerSettings(optionSet); // CraftBukkit - CLI argument support
+             dedicatedServerSettings.forceSave();
+             RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression);
+             Path path2 = Paths.get("eula.txt");
+             Eula eula = new Eula(path2);
+-            if (optionSet.has(optionSpec1)) {
++            // Paper start - load config files early for access below if needed
++            org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("bukkit-settings"));
++            org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings"));
++            // Paper end - load config files early for access below if needed
++            if (optionSet.has("initSettings")) { // CraftBukkit
++                // CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present
++                File configFile = (File) optionSet.valueOf("bukkit-settings");
++                org.bukkit.configuration.file.YamlConfiguration configuration = org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(configFile);
++                configuration.options().copyDefaults(true);
++                configuration.setDefaults(org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(new java.io.InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/bukkit.yml"), com.google.common.base.Charsets.UTF_8)));
++                configuration.save(configFile);
++
++                File commandFile = (File) optionSet.valueOf("commands-settings");
++                org.bukkit.configuration.file.YamlConfiguration commandsConfiguration = org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(commandFile);
++                commandsConfiguration.options().copyDefaults(true);
++                commandsConfiguration.setDefaults(org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(new java.io.InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/commands.yml"), com.google.common.base.Charsets.UTF_8)));
++                commandsConfiguration.save(commandFile);
++                // CraftBukkit end
+                 LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath());
+                 return;
+             }
+ 
+-            if (!eula.hasAgreedToEULA()) {
++            // Spigot Start
++            boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
++            if (eulaAgreed) {
++                System.err.println("You have used the Spigot command line EULA agreement flag.");
++                System.err.println("By using this setting you are indicating your agreement to Mojang's EULA (https://account.mojang.com/documents/minecraft_eula).");
++                System.err.println("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
++            }
++            // Spigot End
++            if (!eula.hasAgreedToEULA() && !eulaAgreed) { // Spigot
+                 LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
+                 return;
+             }
+ 
+-            File file = new File(optionSet.valueOf(optionSpec9));
+-            Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
+-            String string = Optional.ofNullable(optionSet.valueOf(optionSpec10)).orElse(dedicatedServerSettings.getProperties().levelName);
++            // Paper start - Detect headless JRE
++            String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
++            if (awtException != null) {
++                Main.LOGGER.error("You are using a headless JRE distribution.");
++                Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function.");
++                Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install");
++                Main.LOGGER.error("");
++                Main.LOGGER.error(awtException);
++                return;
++            }
++            // Paper end - Detect headless JRE
++
++            org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init
++
++            // Paper start - fix SPIGOT-5824
++            File file;
++            File userCacheFile = new File(Services.USERID_CACHE_FILE);
++            if (optionSet.has("universe")) {
++                file = (File) optionSet.valueOf("universe"); // CraftBukkit
++                userCacheFile = new File(file, Services.USERID_CACHE_FILE);
++            } else {
++                file = new File(bukkitConfiguration.getString("settings.world-container", "."));
++            }
++            // Paper end - fix SPIGOT-5824
++            Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionSet); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container
++            // CraftBukkit start
++            String string = Optional.ofNullable((String) optionSet.valueOf("world")).orElse(dedicatedServerSettings.getProperties().levelName);
+             LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(file.toPath());
+-            LevelStorageSource.LevelStorageAccess levelStorageAccess = levelStorageSource.validateAndCreateAccess(string);
++            LevelStorageSource.LevelStorageAccess levelStorageAccess = levelStorageSource.validateAndCreateAccess(string, LevelStem.OVERWORLD);
++            // CraftBukkit end
+             Dynamic<?> dataTag;
+             if (levelStorageAccess.hasWorldData()) {
+                 LevelSummary summary;
+@@ -169,12 +_,30 @@
+             }
+ 
+             Dynamic<?> dynamic = dataTag;
+-            boolean hasOptionSpec = optionSet.has(optionSpec7);
++            boolean hasOptionSpec = optionSet.has("safeMode"); // CraftBukkit
+             if (hasOptionSpec) {
+                 LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
+             }
+ 
+             PackRepository packRepository = ServerPacksSource.createPackRepository(levelStorageAccess);
++            // CraftBukkit start
++            File bukkitDataPackFolder = new File(levelStorageAccess.getLevelPath(net.minecraft.world.level.storage.LevelResource.DATAPACK_DIR).toFile(), "bukkit");
++            if (!bukkitDataPackFolder.exists()) {
++                bukkitDataPackFolder.mkdirs();
++            }
++            File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta");
++            try {
++                com.google.common.io.Files.write("{\n"
++                        + "    \"pack\": {\n"
++                        + "        \"description\": \"Data pack for resources provided by Bukkit plugins\",\n"
++                        + "        \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n"
++                        + "    }\n"
++                        + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8);
++            } catch (java.io.IOException ex) {
++                throw new RuntimeException("Could not initialize Bukkit datapack", ex);
++            }
++            java.util.concurrent.atomic.AtomicReference<WorldLoader.DataLoadContext> worldLoader = new java.util.concurrent.atomic.AtomicReference<>();
++            // CraftBukkit end
+ 
+             WorldStem worldStem;
+             try {
+@@ -183,6 +_,7 @@
+                         executor -> WorldLoader.load(
+                             initConfig,
+                             context -> {
++                                worldLoader.set(context); // CraftBukkit
+                                 Registry<LevelStem> registry = context.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
+                                 if (dynamic != null) {
+                                     LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
+@@ -196,7 +_,7 @@
+                                     LevelSettings levelSettings;
+                                     WorldOptions worldOptions;
+                                     WorldDimensions worldDimensions;
+-                                    if (optionSet.has(optionSpec2)) {
++                                    if (optionSet.has("demo")) { // CraftBukkit
+                                         levelSettings = MinecraftServer.DEMO_SETTINGS;
+                                         worldOptions = WorldOptions.DEMO_OPTIONS;
+                                         worldDimensions = WorldPresets.createNormalWorldDimensions(context.datapackWorldgen());
+@@ -211,7 +_,7 @@
+                                             new GameRules(context.dataConfiguration().enabledFeatures()),
+                                             context.dataConfiguration()
+                                         );
+-                                        worldOptions = optionSet.has(optionSpec3) ? properties.worldOptions.withBonusChest(true) : properties.worldOptions;
++                                        worldOptions = optionSet.has("bonusChest") ? properties.worldOptions.withBonusChest(true) : properties.worldOptions; // CraftBukkit
+                                         worldDimensions = properties.createDimensions(context.datapackWorldgen());
+                                     }
+ 
+@@ -237,6 +_,7 @@
+                 return;
+             }
+ 
++            /*
+             RegistryAccess.Frozen frozen = worldStem.registries().compositeAccess();
+             boolean hasOptionSpec1 = optionSet.has(optionSpec6);
+             if (optionSet.has(optionSpec4) || hasOptionSpec1) {
+@@ -245,9 +_,13 @@
+ 
+             WorldData worldData = worldStem.worldData();
+             levelStorageAccess.saveDataTag(frozen, worldData);
++            */
+             final DedicatedServer dedicatedServer = MinecraftServer.spin(
+                 thread1 -> {
+                     DedicatedServer dedicatedServer1 = new DedicatedServer(
++                        // CraftBukkit start
++                        optionSet,
++                        worldLoader.get(),
+                         thread1,
+                         levelStorageAccess,
+                         packRepository,
+@@ -257,17 +_,29 @@
+                         services,
+                         LoggerChunkProgressListener::createFromGameruleRadius
+                     );
++                    /*
+                     dedicatedServer1.setPort(optionSet.valueOf(optionSpec11));
+-                    dedicatedServer1.setDemo(optionSet.has(optionSpec2));
++                     */
++                    dedicatedServer1.setDemo(optionSet.has("demo")); // Paper
++                    /*
+                     dedicatedServer1.setId(optionSet.valueOf(optionSpec12));
+-                    boolean flag = !optionSet.has(optionSpec) && !optionSet.valuesOf(optionSpec15).contains("nogui");
++                     */
++                    boolean flag = !optionSet.has("nogui") && !optionSet.nonOptionArguments().contains("nogui");
+                     if (flag && !GraphicsEnvironment.isHeadless()) {
+                         dedicatedServer1.showGui();
+                     }
+ 
++                    if (optionSet.has("port")) {
++                        int port = (Integer) optionSet.valueOf("port");
++                        if (port > 0) {
++                            dedicatedServer1.setPort(port);
++                        }
++                    }
++
+                     return dedicatedServer1;
+                 }
+             );
++            /* CraftBukkit start
+             Thread thread = new Thread("Server Shutdown Thread") {
+                 @Override
+                 public void run() {
+@@ -276,6 +_,7 @@
+             };
+             thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
+             Runtime.getRuntime().addShutdownHook(thread);
++            */ // CraftBukkit end
+         } catch (Exception var42) {
+             LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", (Throwable)var42);
+         }
+@@ -316,7 +_,7 @@
+         RegistryAccess registryAccess,
+         boolean recreateRegionFiles
+     ) {
+-        LOGGER.info("Forcing world upgrade!");
++        LOGGER.info("Forcing world upgrade! {}", levelStorage.getLevelId()); // CraftBukkit
+ 
+         try (WorldUpgrader worldUpgrader = new WorldUpgrader(levelStorage, dataFixer, registryAccess, eraseCache, recreateRegionFiles)) {
+             Component component = null;
diff --git a/paper-server/patches/unapplied/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/server/MinecraftServer.java.patch
rename to paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index b2aec783a8..21203c0786 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -3,6 +3,9 @@
+@@ -3,6 +_,9 @@
  import com.google.common.base.Preconditions;
  import com.google.common.base.Splitter;
  import com.google.common.collect.ImmutableList;
@@ -10,15 +10,7 @@
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  import com.google.common.collect.Sets;
-@@ -45,7 +48,6 @@
- import java.util.UUID;
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.Executor;
--import java.util.concurrent.RejectedExecutionException;
- import java.util.concurrent.atomic.AtomicReference;
- import java.util.concurrent.locks.LockSupport;
- import java.util.function.BooleanSupplier;
-@@ -84,17 +86,6 @@
+@@ -80,17 +_,6 @@
  import net.minecraft.obfuscate.DontObfuscate;
  import net.minecraft.resources.ResourceKey;
  import net.minecraft.resources.ResourceLocation;
@@ -36,7 +28,7 @@
  import net.minecraft.server.packs.PackType;
  import net.minecraft.server.packs.repository.Pack;
  import net.minecraft.server.packs.repository.PackRepository;
-@@ -116,6 +107,7 @@
+@@ -111,6 +_,7 @@
  import net.minecraft.util.RandomSource;
  import net.minecraft.util.SignatureValidator;
  import net.minecraft.util.TimeUtil;
@@ -44,75 +36,10 @@
  import net.minecraft.util.debugchart.RemoteDebugSampleType;
  import net.minecraft.util.debugchart.SampleLogger;
  import net.minecraft.util.debugchart.TpsDebugDimensions;
-@@ -156,37 +148,71 @@
- import net.minecraft.world.level.biome.BiomeManager;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.entity.FuelValues;
--import net.minecraft.world.level.border.BorderChangeListener;
- import net.minecraft.world.level.border.WorldBorder;
- import net.minecraft.world.level.chunk.storage.ChunkIOErrorReporter;
- import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
- import net.minecraft.world.level.dimension.LevelStem;
--import net.minecraft.world.level.levelgen.Heightmap;
--import net.minecraft.world.level.levelgen.PatrolSpawner;
--import net.minecraft.world.level.levelgen.PhantomSpawner;
- import net.minecraft.world.level.levelgen.WorldOptions;
- import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
- import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
-+import net.minecraft.world.level.storage.WorldData;
-+import org.slf4j.Logger;
-+
-+// CraftBukkit start
-+import com.mojang.serialization.Dynamic;
-+import com.mojang.serialization.Lifecycle;
-+import java.io.File;
-+import java.util.Random;
-+// import jline.console.ConsoleReader; // Paper
-+import joptsimple.OptionSet;
-+import net.minecraft.nbt.NbtException;
-+import net.minecraft.nbt.ReportedNbtException;
-+import net.minecraft.server.bossevents.CustomBossEvents;
-+import net.minecraft.server.dedicated.DedicatedServer;
-+import net.minecraft.server.dedicated.DedicatedServerProperties;
-+import net.minecraft.server.level.DemoMode;
-+import net.minecraft.server.level.PlayerRespawnLogic;
-+import net.minecraft.server.level.ServerChunkCache;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.ServerPlayerGameMode;
-+import net.minecraft.server.level.progress.ChunkProgressListener;
-+import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
-+import net.minecraft.server.network.ServerConnectionListener;
-+import net.minecraft.server.network.TextFilter;
-+import net.minecraft.world.level.levelgen.Heightmap;
-+import net.minecraft.world.level.levelgen.PatrolSpawner;
-+import net.minecraft.world.level.levelgen.PhantomSpawner;
-+import net.minecraft.world.level.levelgen.WorldDimensions;
-+import net.minecraft.world.level.levelgen.presets.WorldPresets;
- import net.minecraft.world.level.storage.CommandStorage;
--import net.minecraft.world.level.storage.DerivedLevelData;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- import net.minecraft.world.level.storage.LevelData;
-+import net.minecraft.world.level.storage.LevelDataAndDimensions;
- import net.minecraft.world.level.storage.LevelResource;
- import net.minecraft.world.level.storage.LevelStorageSource;
-+import net.minecraft.world.level.storage.LevelSummary;
- import net.minecraft.world.level.storage.PlayerDataStorage;
-+import net.minecraft.world.level.storage.PrimaryLevelData;
- import net.minecraft.world.level.storage.ServerLevelData;
--import net.minecraft.world.level.storage.WorldData;
-+import net.minecraft.world.level.validation.ContentValidationException;
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
--import org.slf4j.Logger;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.CraftRegistry;
-+import org.bukkit.event.server.ServerLoadEvent;
-+// CraftBukkit end
+@@ -174,11 +_,13 @@
+ import org.slf4j.Logger;
  
-+
  public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, ChunkIOErrorReporter, CommandSource {
- 
 +    private static MinecraftServer SERVER; // Paper
      public static final Logger LOGGER = LogUtils.getLogger();
 +    public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
@@ -124,15 +51,30 @@
      private static final int OVERLOADED_TICKS_THRESHOLD = 20;
      private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
      private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
-@@ -224,6 +250,7 @@
-     private Map<ResourceKey<Level>, ServerLevel> levels;
+@@ -204,8 +_,8 @@
+     @Nullable
+     private MinecraftServer.TimeProfiler debugCommandProfiler;
+     private boolean debugCommandProfilerDelayStart;
+-    private ServerConnectionListener connection;
+-    public final ChunkProgressListenerFactory progressListenerFactory;
++    private net.minecraft.server.network.ServerConnectionListener connection;
++    public final net.minecraft.server.level.progress.ChunkProgressListenerFactory progressListenerFactory;
+     @Nullable
+     private ServerStatus status;
+     @Nullable
+@@ -215,9 +_,10 @@
+     private String localIp;
+     private int port = -1;
+     private final LayeredRegistryAccess<RegistryLayer> registries;
+-    private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
++    private Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> levels = Maps.newLinkedHashMap();
      private PlayerList playerList;
-     private volatile boolean running;
+     private volatile boolean running = true;
 +    private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
      private boolean stopped;
      private int tickCount;
-     private int ticksUntilAutosave;
-@@ -232,11 +259,15 @@
+     private int ticksUntilAutosave = 6000;
+@@ -226,11 +_,15 @@
      private boolean preventProxyConnections;
      private boolean pvp;
      private boolean allowFlight;
@@ -140,8 +82,8 @@
 -    private String motd;
 +    private net.kyori.adventure.text.Component motd; // Paper - Adventure
      private int playerIdleTimeout;
-     private final long[] tickTimesNanos;
-     private long aggregatedTickTimesNanos;
+     private final long[] tickTimesNanos = new long[100];
+     private long aggregatedTickTimesNanos = 0L;
 +    // Paper start - Add tick times API and /mspt command
 +    public final TickTimes tickTimes5s = new TickTimes(100);
 +    public final TickTimes tickTimes10s = new TickTimes(200);
@@ -150,14 +92,23 @@
      @Nullable
      private KeyPair keyPair;
      @Nullable
-@@ -277,6 +308,28 @@
-     private final SuppressedExceptionCollector suppressedExceptions;
+@@ -252,7 +_,7 @@
+     private final ServerScoreboard scoreboard = new ServerScoreboard(this);
+     @Nullable
+     private CommandStorage commandStorage;
+-    private final CustomBossEvents customBossEvents = new CustomBossEvents();
++    private final net.minecraft.server.bossevents.CustomBossEvents customBossEvents = new net.minecraft.server.bossevents.CustomBossEvents();
+     private final ServerFunctionManager functionManager;
+     private boolean enforceWhitelist;
+     private float smoothedTickTimeMillis;
+@@ -271,10 +_,33 @@
+     private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
      private final DiscontinuousFrame tickFrame;
  
 +    // CraftBukkit start
 +    public final WorldLoader.DataLoadContext worldLoader;
 +    public org.bukkit.craftbukkit.CraftServer server;
-+    public OptionSet options;
++    public joptsimple.OptionSet options;
 +    public org.bukkit.command.ConsoleCommandSender console;
 +    public static int currentTick; // Paper - improve tick loop
 +    public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
@@ -176,79 +127,51 @@
 +    public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
 +    private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
 +
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
-         AtomicReference<S> atomicreference = new AtomicReference();
-         Thread thread = new Thread(() -> {
-@@ -286,19 +339,21 @@
-         thread.setUncaughtExceptionHandler((thread1, throwable) -> {
-             MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable);
-         });
+     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
+         AtomicReference<S> atomicReference = new AtomicReference<>();
+         Thread thread = new Thread(() -> atomicReference.get().runServer(), "Server thread");
+         thread.setUncaughtExceptionHandler((thread1, exception) -> LOGGER.error("Uncaught exception in server thread", exception));
 +        thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority
          if (Runtime.getRuntime().availableProcessors() > 4) {
              thread.setPriority(8);
          }
- 
--        S s0 = (MinecraftServer) serverFactory.apply(thread);
-+        S s0 = serverFactory.apply(thread); // CraftBukkit - decompile error
- 
-         atomicreference.set(s0);
-         thread.start();
-         return s0;
+@@ -286,6 +_,10 @@
      }
  
--    public MinecraftServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, Proxy proxy, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) {
-+    public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
+     public MinecraftServer(
++        // CraftBukkit start
++        joptsimple.OptionSet options,
++        WorldLoader.DataLoadContext worldLoader,
++        // CraftBukkit end
+         Thread serverThread,
+         LevelStorageSource.LevelStorageAccess storageSource,
+         PackRepository packRepository,
+@@ -293,12 +_,13 @@
+         Proxy proxy,
+         DataFixer fixerUpper,
+         Services services,
+-        ChunkProgressListenerFactory progressListenerFactory
++        net.minecraft.server.level.progress.ChunkProgressListenerFactory progressListenerFactory
+     ) {
          super("Server");
 +        SERVER = this; // Paper - better singleton
-         this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
-         this.onMetricsRecordingStopped = (methodprofilerresults) -> {
-             this.stopRecordingMetrics();
-@@ -319,36 +374,67 @@
-         this.scoreboard = new ServerScoreboard(this);
-         this.customBossEvents = new CustomBossEvents();
-         this.suppressedExceptions = new SuppressedExceptionCollector();
--        this.registries = saveLoader.registries();
--        this.worldData = saveLoader.worldData();
+         this.registries = worldStem.registries();
+         this.worldData = worldStem.worldData();
 -        if (!this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) {
-+        this.registries = worldstem.registries();
-+        this.worldData = worldstem.worldData();
 +        if (false && !this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { // CraftBukkit - initialised later
              throw new IllegalStateException("Missing Overworld dimension data");
          } else {
              this.proxy = proxy;
--            this.packRepository = dataPackManager;
--            this.resources = new MinecraftServer.ReloadableResources(saveLoader.resourceManager(), saveLoader.dataPackResources());
--            this.services = apiServices;
--            if (apiServices.profileCache() != null) {
--                apiServices.profileCache().setExecutor(this);
-+            this.packRepository = resourcepackrepository;
-+            this.resources = new MinecraftServer.ReloadableResources(worldstem.resourceManager(), worldstem.dataPackResources());
-+            this.services = services;
-+            if (services.profileCache() != null) {
-+                services.profileCache().setExecutor(this);
+@@ -309,7 +_,7 @@
+                 services.profileCache().setExecutor(this);
              }
  
 -            this.connection = new ServerConnectionListener(this);
-+            // this.connection = new ServerConnection(this); // Spigot
++            // this.connection = new ServerConnectionListener(this); // Spigot
              this.tickRateManager = new ServerTickRateManager(this);
--            this.progressListenerFactory = worldGenerationProgressListenerFactory;
--            this.storageSource = session;
--            this.playerDataStorage = session.createPlayerStorage();
--            this.fixerUpper = dataFixer;
-+            this.progressListenerFactory = worldloadlistenerfactory;
-+            this.storageSource = convertable_conversionsession;
-+            this.playerDataStorage = convertable_conversionsession.createPlayerStorage();
-+            this.fixerUpper = datafixer;
-             this.functionManager = new ServerFunctionManager(this, this.resources.managers.getFunctionLibrary());
-             HolderGetter<Block> holdergetter = this.registries.compositeAccess().lookupOrThrow(Registries.BLOCK).filterFeatures(this.worldData.enabledFeatures());
- 
--            this.structureTemplateManager = new StructureTemplateManager(saveLoader.resourceManager(), session, dataFixer, holdergetter);
--            this.serverThread = serverThread;
-+            this.structureTemplateManager = new StructureTemplateManager(worldstem.resourceManager(), convertable_conversionsession, datafixer, holdergetter);
-+            this.serverThread = thread;
-             this.executor = Util.backgroundExecutor();
-             this.potionBrewing = PotionBrewing.bootstrap(this.worldData.enabledFeatures());
-             this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
+             this.progressListenerFactory = progressListenerFactory;
+             this.storageSource = storageSource;
+@@ -328,6 +_,37 @@
              this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
              this.tickFrame = TracyClient.createDiscontinuousFrame("Server Tick");
          }
@@ -285,58 +208,59 @@
 +        this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
      }
  
-     private void readScoreboard(DimensionDataStorage persistentStateManager) {
-@@ -357,7 +443,7 @@
+     private void readScoreboard(DimensionDataStorage dataStorage) {
+@@ -336,18 +_,13 @@
  
      protected abstract boolean initServer() throws IOException;
  
 -    protected void loadLevel() {
 +    protected void loadLevel(String s) { // CraftBukkit
          if (!JvmProfiler.INSTANCE.isRunning()) {
-             ;
          }
-@@ -365,12 +451,8 @@
+ 
          boolean flag = false;
-         ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
- 
+         ProfiledDuration profiledDuration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
 -        this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
--        ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
-+        this.loadWorld0(s); // CraftBukkit
- 
--        this.createLevels(worldloadlistener);
+-        ChunkProgressListener chunkProgressListener = this.progressListenerFactory
+-            .create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
+-        this.createLevels(chunkProgressListener);
 -        this.forceDifficulty();
--        this.prepareLevels(worldloadlistener);
-         if (profiledduration != null) {
-             profiledduration.finish(true);
+-        this.prepareLevels(chunkProgressListener);
++        this.loadWorld0(s); // CraftBukkit
+         if (profiledDuration != null) {
+             profiledDuration.finish(true);
          }
-@@ -387,23 +469,246 @@
+@@ -364,25 +_,245 @@
+     protected void forceDifficulty() {
+     }
  
-     protected void forceDifficulty() {}
- 
--    protected void createLevels(ChunkProgressListener worldGenerationProgressListener) {
--        ServerLevelData iworlddataserver = this.worldData.overworldData();
--        boolean flag = this.worldData.isDebugWorld();
--        Registry<LevelStem> iregistry = this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM);
--        WorldOptions worldoptions = this.worldData.worldGenOptions();
--        long i = worldoptions.seed();
--        long j = BiomeManager.obfuscateSeed(i);
--        List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
--        LevelStem worlddimension = (LevelStem) iregistry.getValue(LevelStem.OVERWORLD);
--        ServerLevel worldserver = new ServerLevel(this, this.executor, this.storageSource, iworlddataserver, Level.OVERWORLD, worlddimension, worldGenerationProgressListener, flag, j, list, true, (RandomSequences) null);
+-    protected void createLevels(ChunkProgressListener listener) {
+-        ServerLevelData serverLevelData = this.worldData.overworldData();
+-        boolean isDebugWorld = this.worldData.isDebugWorld();
+-        Registry<LevelStem> registry = this.registries.compositeAccess().lookupOrThrow(Registries.LEVEL_STEM);
+-        WorldOptions worldOptions = this.worldData.worldGenOptions();
+-        long seed = worldOptions.seed();
+-        long l = BiomeManager.obfuscateSeed(seed);
+-        List<CustomSpawner> list = ImmutableList.of(
+-            new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverLevelData)
+-        );
+-        LevelStem levelStem = registry.getValue(LevelStem.OVERWORLD);
+-        ServerLevel serverLevel = new ServerLevel(
+-            this, this.executor, this.storageSource, serverLevelData, Level.OVERWORLD, levelStem, listener, isDebugWorld, l, list, true, null
+-        );
+-        this.levels.put(Level.OVERWORLD, serverLevel);
+-        DimensionDataStorage dataStorage = serverLevel.getDataStorage();
+-        this.readScoreboard(dataStorage);
+-        this.commandStorage = new CommandStorage(dataStorage);
+-        WorldBorder worldBorder = serverLevel.getWorldBorder();
 +    // CraftBukkit start
 +    private void loadWorld0(String s) {
 +        LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
- 
--        this.levels.put(Level.OVERWORLD, worldserver);
--        DimensionDataStorage worldpersistentdata = worldserver.getDataStorage();
 +        RegistryAccess.Frozen iregistrycustom_dimension = this.registries.compositeAccess();
 +        Registry<LevelStem> dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
 +        for (LevelStem worldDimension : dimensions) {
 +            ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
- 
--        this.readScoreboard(worldpersistentdata);
--        this.commandStorage = new CommandStorage(worldpersistentdata);
-+            ServerLevel world;
++            net.minecraft.server.level.ServerLevel world;
 +            int dimension = 0;
 +
 +            if (dimensionKey == LevelStem.NETHER) {
@@ -358,9 +282,9 @@
 +            String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT);
 +            String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
 +            if (dimension != 0) {
-+                File newWorld = LevelStorageSource.getStorageFolder(new File(name).toPath(), dimensionKey).toFile();
-+                File oldWorld = LevelStorageSource.getStorageFolder(new File(s).toPath(), dimensionKey).toFile();
-+                File oldLevelDat = new File(new File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
++                java.io.File newWorld = LevelStorageSource.getStorageFolder(new java.io.File(name).toPath(), dimensionKey).toFile();
++                java.io.File oldWorld = LevelStorageSource.getStorageFolder(new java.io.File(s).toPath(), dimensionKey).toFile();
++                java.io.File oldLevelDat = new java.io.File(new java.io.File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
 +
 +                if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
 +                    MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
@@ -376,8 +300,8 @@
 +                            MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
 +                            // Migrate world data too.
 +                            try {
-+                                com.google.common.io.Files.copy(oldLevelDat, new File(new File(name), "level.dat"));
-+                                org.apache.commons.io.FileUtils.copyDirectory(new File(new File(s), "data"), new File(new File(name), "data"));
++                                com.google.common.io.Files.copy(oldLevelDat, new java.io.File(new java.io.File(name), "level.dat"));
++                                org.apache.commons.io.FileUtils.copyDirectory(new java.io.File(new java.io.File(s), "data"), new java.io.File(new java.io.File(name), "data"));
 +                            } catch (IOException exception) {
 +                                MinecraftServer.LOGGER.warn("Unable to migrate world data.");
 +                            }
@@ -394,19 +318,19 @@
 +
 +                try {
 +                    worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
-+                } catch (IOException | ContentValidationException ex) {
++                } catch (IOException | net.minecraft.world.level.validation.ContentValidationException ex) {
 +                    throw new RuntimeException(ex);
 +                }
 +            }
 +
-+            Dynamic<?> dynamic;
++            com.mojang.serialization.Dynamic<?> dynamic;
 +            if (worldSession.hasWorldData()) {
-+                LevelSummary worldinfo;
++                net.minecraft.world.level.storage.LevelSummary worldinfo;
 +
 +                try {
 +                    dynamic = worldSession.getDataTag();
 +                    worldinfo = worldSession.getSummary(dynamic);
-+                } catch (NbtException | ReportedNbtException | IOException ioexception) {
++                } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception) {
 +                    LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
 +
 +                    MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
@@ -415,7 +339,7 @@
 +                    try {
 +                        dynamic = worldSession.getDataTagFallback();
 +                        worldinfo = worldSession.getSummary(dynamic);
-+                    } catch (NbtException | ReportedNbtException | IOException ioexception1) {
++                    } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception1) {
 +                        MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
 +                        MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
 +                        return;
@@ -440,34 +364,34 @@
 +            org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
 +            org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name);
 +
-+            PrimaryLevelData worlddata;
++            net.minecraft.world.level.storage.PrimaryLevelData worlddata;
 +            WorldLoader.DataLoadContext worldloader_a = this.worldLoader;
 +            Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
 +            if (dynamic != null) {
-+                LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
++                net.minecraft.world.level.storage.LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
 +
-+                worlddata = (PrimaryLevelData) leveldataanddimensions.worldData();
++                worlddata = (net.minecraft.world.level.storage.PrimaryLevelData) leveldataanddimensions.worldData();
 +            } else {
 +                LevelSettings worldsettings;
 +                WorldOptions worldoptions;
-+                WorldDimensions worlddimensions;
++                net.minecraft.world.level.levelgen.WorldDimensions worlddimensions;
 +
 +                if (this.isDemo()) {
 +                    worldsettings = MinecraftServer.DEMO_SETTINGS;
 +                    worldoptions = WorldOptions.DEMO_OPTIONS;
-+                    worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
++                    worlddimensions = net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
 +                } else {
-+                    DedicatedServerProperties dedicatedserverproperties = ((DedicatedServer) this).getProperties();
++                    net.minecraft.server.dedicated.DedicatedServerProperties dedicatedserverproperties = ((net.minecraft.server.dedicated.DedicatedServer) this).getProperties();
 +
 +                    worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
 +                    worldoptions = this.options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
 +                    worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
 +                }
 +
-+                WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
-+                Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
++                net.minecraft.world.level.levelgen.WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
++                com.mojang.serialization.Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
 +
-+                worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
++                worlddata = new net.minecraft.world.level.storage.PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
 +            }
 +            worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
 +            if (this.options.has("forceUpgrade")) {
@@ -476,7 +400,7 @@
 +                }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
 +            }
 +
-+            PrimaryLevelData iworlddataserver = worlddata;
++            net.minecraft.world.level.storage.PrimaryLevelData iworlddataserver = worlddata;
 +            boolean flag = worlddata.isDebugWorld();
 +            WorldOptions worldoptions = worlddata.worldGenOptions();
 +            long i = worldoptions.seed();
@@ -493,17 +417,17 @@
 +
 +            if (dimensionKey == LevelStem.OVERWORLD) {
 +                this.worldData = worlddata;
-+                this.worldData.setGameType(((DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
++                this.worldData.setGameType(((net.minecraft.server.dedicated.DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
 +
-+                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                net.minecraft.server.level.progress.ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +
-+                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++                world = new net.minecraft.server.level.ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
 +                DimensionDataStorage worldpersistentdata = world.getDataStorage();
 +                this.readScoreboard(worldpersistentdata);
 +                this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
 +                this.commandStorage = new CommandStorage(worldpersistentdata);
 +            } else {
-+                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                net.minecraft.server.level.progress.ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +                // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
 +                final List<CustomSpawner> spawners;
 +                if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
@@ -511,7 +435,7 @@
 +                } else {
 +                    spawners = Collections.emptyList();
 +                }
-+                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++                world = new net.minecraft.server.level.ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
 +                // Paper end - option to use the dimension_type to check if spawners should be added
 +            }
 +
@@ -527,10 +451,10 @@
 +            }
 +        }
 +        this.forceDifficulty();
-+        for (ServerLevel worldserver : this.getAllLevels()) {
-+            this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
-+            worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
-+            this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
++            this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
++            serverLevel.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
++            this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
 +        }
 +
 +        // Paper start - Configurable player collision; Handle collideRule team for player collision toggle
@@ -554,168 +478,190 @@
 +        io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
 +        io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
 +        ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands();
-+        this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
++        this.server.getPluginManager().callEvent(new org.bukkit.event.server.ServerLoadEvent(org.bukkit.event.server.ServerLoadEvent.LoadType.STARTUP));
 +        this.connection.acceptConnections();
 +    }
 +
-+    public void initWorld(ServerLevel worldserver, ServerLevelData iworlddataserver, WorldData saveData, WorldOptions worldoptions) {
-+        boolean flag = saveData.isDebugWorld();
-+        // CraftBukkit start
-+        if (worldserver.generator != null) {
-+            worldserver.getWorld().getPopulators().addAll(worldserver.generator.getDefaultPopulators(worldserver.getWorld()));
++    public void initWorld(net.minecraft.server.level.ServerLevel serverLevel, ServerLevelData serverLevelData, WorldData saveData, WorldOptions worldOptions) {
++        boolean isDebugWorld = saveData.isDebugWorld();
++        if (serverLevel.generator != null) {
++            serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld()));
 +        }
-         WorldBorder worldborder = worldserver.getWorldBorder();
-+        worldborder.applySettings(iworlddataserver.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent
-+        this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(worldserver.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated
- 
-         if (!iworlddataserver.isInitialized()) {
++        // CraftBukkit start
++        WorldBorder worldborder = serverLevel.getWorldBorder();
++        worldborder.applySettings(serverLevelData.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent
++        this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(serverLevel.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated
++
+         if (!serverLevelData.isInitialized()) {
              try {
-@@ -427,37 +732,31 @@
-             iworlddataserver.setInitialized(true);
-         }
+                 setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld);
+@@ -403,47 +_,30 @@
  
--        this.getPlayerList().addWorldborderListener(worldserver);
+             serverLevelData.setInitialized(true);
+         }
+-
+-        this.getPlayerList().addWorldborderListener(serverLevel);
 -        if (this.worldData.getCustomBossEvents() != null) {
 -            this.getCustomBossEvents().load(this.worldData.getCustomBossEvents(), this.registryAccess());
 -        }
 -
--        RandomSequences randomsequences = worldserver.getRandomSequences();
--        Iterator iterator = iregistry.entrySet().iterator();
+-        RandomSequences randomSequences = serverLevel.getRandomSequences();
 -
--        while (iterator.hasNext()) {
--            Entry<ResourceKey<LevelStem>, LevelStem> entry = (Entry) iterator.next();
--            ResourceKey<LevelStem> resourcekey = (ResourceKey) entry.getKey();
--
--            if (resourcekey != LevelStem.OVERWORLD) {
--                ResourceKey<Level> resourcekey1 = ResourceKey.create(Registries.DIMENSION, resourcekey.location());
--                DerivedLevelData secondaryworlddata = new DerivedLevelData(this.worldData, iworlddataserver);
--                ServerLevel worldserver1 = new ServerLevel(this, this.executor, this.storageSource, secondaryworlddata, resourcekey1, (LevelStem) entry.getValue(), worldGenerationProgressListener, flag, j, ImmutableList.of(), false, randomsequences);
--
--                worldborder.addListener(new BorderChangeListener.DelegateBorderChangeListener(worldserver1.getWorldBorder()));
--                this.levels.put(resourcekey1, worldserver1);
+-        for (Entry<ResourceKey<LevelStem>, LevelStem> entry : registry.entrySet()) {
+-            ResourceKey<LevelStem> resourceKey = entry.getKey();
+-            if (resourceKey != LevelStem.OVERWORLD) {
+-                ResourceKey<Level> resourceKey1 = ResourceKey.create(Registries.DIMENSION, resourceKey.location());
+-                DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, serverLevelData);
+-                ServerLevel serverLevel1 = new ServerLevel(
+-                    this,
+-                    this.executor,
+-                    this.storageSource,
+-                    derivedLevelData,
+-                    resourceKey1,
+-                    entry.getValue(),
+-                    listener,
+-                    isDebugWorld,
+-                    l,
+-                    ImmutableList.of(),
+-                    false,
+-                    randomSequences
+-                );
+-                worldBorder.addListener(new BorderChangeListener.DelegateBorderChangeListener(serverLevel1.getWorldBorder()));
+-                this.levels.put(resourceKey1, serverLevel1);
 -            }
 -        }
 -
--        worldborder.applySettings(iworlddataserver.getWorldBorder());
+-        worldBorder.applySettings(serverLevelData.getWorldBorder());
      }
 +    // CraftBukkit end
  
-     private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) {
-         if (debugWorld) {
-             worldProperties.setSpawn(BlockPos.ZERO.above(80), 0.0F);
+-    private static void setInitialSpawn(ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
++    private static void setInitialSpawn(net.minecraft.server.level.ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
+         if (debug) {
+             levelData.setSpawn(BlockPos.ZERO.above(80), 0.0F);
          } else {
-             ServerChunkCache chunkproviderserver = world.getChunkSource();
--            ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition());
-+            // ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); // Paper - Move down, only attempt to find spawn position if there isn't a fixed spawn position set
+-            ServerChunkCache chunkSource = level.getChunkSource();
+-            ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition());
++            net.minecraft.server.level.ServerChunkCache chunkSource = level.getChunkSource();
 +            // CraftBukkit start
-+            if (world.generator != null) {
-+                Random rand = new Random(world.getSeed());
-+                org.bukkit.Location spawn = world.generator.getFixedSpawnLocation(world.getWorld(), rand);
++            if (level.generator != null) {
++                java.util.Random rand = new java.util.Random(level.getSeed());
++                org.bukkit.Location spawn = level.generator.getFixedSpawnLocation(level.getWorld(), rand);
 +
 +                if (spawn != null) {
-+                    if (spawn.getWorld() != world.getWorld()) {
-+                        throw new IllegalStateException("Cannot set spawn point for " + worldProperties.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")");
++                    if (spawn.getWorld() != level.getWorld()) {
++                        throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")");
 +                    } else {
-+                        worldProperties.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw());
++                        levelData.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw());
 +                        return;
 +                    }
 +                }
 +            }
 +            // CraftBukkit end
-+            ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set
-             int i = chunkproviderserver.getGenerator().getSpawnHeight(world);
++            ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition()); // Paper - Only attempt to find spawn position if there isn't a fixed spawn position set
+             int spawnHeight = chunkSource.getGenerator().getSpawnHeight(level);
+             if (spawnHeight < level.getMinY()) {
+                 BlockPos worldPosition = chunkPos.getWorldPosition();
+@@ -458,7 +_,7 @@
  
-             if (i < world.getMinY()) {
-@@ -516,31 +815,36 @@
-         iworlddataserver.setGameType(GameType.SPECTATOR);
+             for (int i4 = 0; i4 < Mth.square(11); i4++) {
+                 if (i >= -5 && i <= 5 && i1 >= -5 && i1 <= 5) {
+-                    BlockPos spawnPosInChunk = PlayerRespawnLogic.getSpawnPosInChunk(level, new ChunkPos(chunkPos.x + i, chunkPos.z + i1));
++                    BlockPos spawnPosInChunk = net.minecraft.server.level.PlayerRespawnLogic.getSpawnPosInChunk(level, new ChunkPos(chunkPos.x + i, chunkPos.z + i1));
+                     if (spawnPosInChunk != null) {
+                         levelData.setSpawn(spawnPosInChunk, 0.0F);
+                         break;
+@@ -495,26 +_,31 @@
+         serverLevelData.setGameType(GameType.SPECTATOR);
      }
  
--    public void prepareLevels(ChunkProgressListener worldGenerationProgressListener) {
--        ServerLevel worldserver = this.overworld();
+-    public void prepareLevels(ChunkProgressListener listener) {
+-        ServerLevel serverLevel = this.overworld();
 +    // CraftBukkit start
-+    public void prepareLevels(ChunkProgressListener worldloadlistener, ServerLevel worldserver) {
-+        // WorldServer worldserver = this.overworld();
++    public void prepareLevels(net.minecraft.server.level.progress.ChunkProgressListener listener, net.minecraft.server.level.ServerLevel serverLevel) {
 +        this.forceTicks = true;
 +        // CraftBukkit end
- 
-         MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location());
-         BlockPos blockposition = worldserver.getSharedSpawnPos();
- 
--        worldGenerationProgressListener.updateSpawnPos(new ChunkPos(blockposition));
-+        worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
-         ServerChunkCache chunkproviderserver = worldserver.getChunkSource();
- 
+         LOGGER.info("Preparing start region for dimension {}", serverLevel.dimension().location());
+         BlockPos sharedSpawnPos = serverLevel.getSharedSpawnPos();
+         listener.updateSpawnPos(new ChunkPos(sharedSpawnPos));
+-        ServerChunkCache chunkSource = serverLevel.getChunkSource();
++        net.minecraft.server.level.ServerChunkCache chunkSource = serverLevel.getChunkSource();
          this.nextTickTimeNanos = Util.getNanos();
-         worldserver.setDefaultSpawnPos(blockposition, worldserver.getSharedSpawnAngle());
--        int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS);
-+        int i = worldserver.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
-         int j = i > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(i)) : 0;
+         serverLevel.setDefaultSpawnPos(sharedSpawnPos, serverLevel.getSharedSpawnAngle());
+-        int _int = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS);
+-        int i = _int > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(_int)) : 0;
++        int _int = serverLevel.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
++        int i = _int > 0 ? Mth.square(net.minecraft.server.level.progress.ChunkProgressListener.calculateDiameter(_int)) : 0;
  
-         while (chunkproviderserver.getTickingGenerated() < j) {
--            this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+         while (chunkSource.getTickingGenerated() < i) {
+-            this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 -            this.waitUntilNextTick();
 +            // CraftBukkit start
-+            // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
++            // this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 +            this.executeModerately();
          }
  
--        this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+-        this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 -        this.waitUntilNextTick();
--        Iterator iterator = this.levels.values().iterator();
-+        // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
++        // this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 +        this.executeModerately();
-+        // Iterator iterator = this.levels.values().iterator();
  
--        while (iterator.hasNext()) {
--            ServerLevel worldserver1 = (ServerLevel) iterator.next();
+-        for (ServerLevel serverLevel1 : this.levels.values()) {
 +        if (true) {
-+            ServerLevel worldserver1 = worldserver;
++            net.minecraft.server.level.ServerLevel serverLevel1 = serverLevel;
 +            // CraftBukkit end
-             ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
- 
-             if (forcedchunk != null) {
-@@ -555,10 +859,17 @@
+             ForcedChunksSavedData forcedChunksSavedData = serverLevel1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
+             if (forcedChunksSavedData != null) {
+                 LongIterator longIterator = forcedChunksSavedData.getChunks().iterator();
+@@ -527,10 +_,17 @@
              }
          }
  
--        this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+-        this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 -        this.waitUntilNextTick();
--        worldGenerationProgressListener.stop();
--        this.updateMobSpawningFlags();
 +        // CraftBukkit start
 +        // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
 +        this.executeModerately();
 +        // CraftBukkit end
-+        worldloadlistener.stop();
+         listener.stop();
+-        this.updateMobSpawningFlags();
 +        // CraftBukkit start
 +        // this.updateMobSpawningFlags();
-+        worldserver.setSpawnSettings(worldserver.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
++        serverLevel.setSpawnSettings(serverLevel.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
 +
 +        this.forceTicks = false;
 +        // CraftBukkit end
      }
  
      public GameType getDefaultGameType() {
-@@ -588,12 +899,16 @@
-             worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force);
+@@ -550,7 +_,7 @@
+     public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
+         boolean flag = false;
+ 
+-        for (ServerLevel serverLevel : this.getAllLevels()) {
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+             if (!suppressLog) {
+                 LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
+             }
+@@ -559,13 +_,16 @@
+             flag = true;
          }
  
--        ServerLevel worldserver1 = this.overworld();
--        ServerLevelData iworlddataserver = this.worldData.overworldData();
-+        // CraftBukkit start - moved to WorldServer.save
-+        /*
-+        WorldServer worldserver1 = this.overworld();
-+        IWorldDataServer iworlddataserver = this.worldData.overworldData();
- 
-         iworlddataserver.setWorldBorder(worldserver1.getWorldBorder().createSettings());
++        /* // CraftBukkit start - moved to WorldServer.save
+         ServerLevel serverLevel1 = this.overworld();
+         ServerLevelData serverLevelData = this.worldData.overworldData();
+         serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
          this.worldData.setCustomBossEvents(this.getCustomBossEvents().save(this.registryAccess()));
          this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData());
-+        */
++         */
 +        // CraftBukkit end
          if (flush) {
-             Iterator iterator1 = this.getAllLevels().iterator();
+-            for (ServerLevel serverLevel2 : this.getAllLevels()) {
++            for (net.minecraft.server.level.ServerLevel serverLevel2 : this.getAllLevels()) {
+                 LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", serverLevel2.getChunkSource().chunkMap.getStorageName());
+             }
  
-@@ -628,18 +943,46 @@
+@@ -593,23 +_,51 @@
          this.stopServer();
      }
  
@@ -742,7 +688,7 @@
              this.cancelRecordingMetrics();
          }
  
-         MinecraftServer.LOGGER.info("Stopping server");
+         LOGGER.info("Stopping server");
 +        Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
 +        // CraftBukkit start
 +        if (this.server != null) {
@@ -755,17 +701,41 @@
          this.getConnection().stop();
          this.isSaving = true;
          if (this.playerList != null) {
-             MinecraftServer.LOGGER.info("Saving players");
+             LOGGER.info("Saving players");
              this.playerList.saveAll();
 -            this.playerList.removeAll();
 +            this.playerList.removeAll(this.isRestarting); // Paper
 +            try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
          }
  
-         MinecraftServer.LOGGER.info("Saving worlds");
-@@ -693,6 +1036,15 @@
-         } catch (IOException ioexception1) {
-             MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
+         LOGGER.info("Saving worlds");
+ 
+-        for (ServerLevel serverLevel : this.getAllLevels()) {
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+             if (serverLevel != null) {
+                 serverLevel.noSave = false;
+             }
+@@ -618,7 +_,7 @@
+         while (this.levels.values().stream().anyMatch(level -> level.getChunkSource().chunkMap.hasWork())) {
+             this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
+ 
+-            for (ServerLevel serverLevelx : this.getAllLevels()) {
++            for (net.minecraft.server.level.ServerLevel serverLevelx : this.getAllLevels()) {
+                 serverLevelx.getChunkSource().removeTicketsOnClosing();
+                 serverLevelx.getChunkSource().tick(() -> true, false);
+             }
+@@ -628,7 +_,7 @@
+ 
+         this.saveAllChunks(false, true, false);
+ 
+-        for (ServerLevel serverLevelx : this.getAllLevels()) {
++        for (net.minecraft.server.level.ServerLevel serverLevelx : this.getAllLevels()) {
+             if (serverLevelx != null) {
+                 try {
+                     serverLevelx.close();
+@@ -646,6 +_,15 @@
+         } catch (IOException var4) {
+             LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), var4);
          }
 +        // Spigot start
 +        io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.shutdown(); // Paper
@@ -776,32 +746,31 @@
 +            this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
 +        }
 +        // Spigot end
- 
      }
  
-@@ -709,6 +1061,14 @@
+     public String getLocalIp() {
+@@ -661,6 +_,14 @@
      }
  
-     public void halt(boolean waitForShutdown) {
+     public void halt(boolean waitForServer) {
 +        // Paper start - allow passing of the intent to restart
-+        this.safeShutdown(waitForShutdown, false);
++        this.safeShutdown(waitForServer, false);
 +    }
-+    public void safeShutdown(boolean waitForShutdown, boolean isRestarting) {
++    public void safeShutdown(boolean waitForServer, boolean isRestarting) {
 +        this.isRestarting = isRestarting;
 +        this.hasLoggedStop = true; // Paper - Debugging
 +        if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
 +        // Paper end
          this.running = false;
-         if (waitForShutdown) {
+         if (waitForServer) {
              try {
-@@ -720,6 +1080,64 @@
- 
+@@ -671,6 +_,63 @@
+         }
      }
  
 +    // Spigot Start
-+    private static double calcTps(double avg, double exp, double tps)
-+    {
-+        return ( avg * exp ) + ( tps * ( 1 - exp ) );
++    private static double calcTps(double avg, double exp, double tps) {
++        return (avg * exp) + (tps * (1 - exp));
 +    }
 +
 +    // Paper start - Further improve server tick loop
@@ -859,12 +828,8 @@
      protected void runServer() {
          try {
              if (!this.initServer()) {
-@@ -727,9 +1145,27 @@
-             }
- 
-             this.nextTickTimeNanos = Util.getNanos();
--            this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse((Object) null);
-+            this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse(null); // CraftBukkit - decompile error
+@@ -681,6 +_,24 @@
+             this.statusIcon = this.loadStatusIcon().orElse(null);
              this.status = this.buildServerStatus();
  
 +            this.server.spark.enableBeforePlugins(); // Paper - spark
@@ -886,18 +851,18 @@
 +            // Paper end - Add onboarding message for initial server start
 +
              while (this.running) {
-                 long i;
- 
-@@ -744,11 +1180,30 @@
-                     if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) {
-                         long k = j / i;
- 
+                 long l;
+                 if (!this.isPaused() && this.tickRateManager.isSprinting() && this.tickRateManager.checkShouldSprintThisTick()) {
+@@ -693,11 +_,30 @@
+                     if (l1 > OVERLOADED_THRESHOLD_NANOS + 20L * l
+                         && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= OVERLOADED_WARNING_INTERVAL_NANOS + 100L * l) {
+                         long l2 = l1 / l;
 +                        if (this.server.getWarnOnOverload()) // CraftBukkit
-                         MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", j / TimeUtil.NANOSECONDS_PER_MILLISECOND, k);
-                         this.nextTickTimeNanos += k * i;
+                         LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", l1 / TimeUtil.NANOSECONDS_PER_MILLISECOND, l2);
+                         this.nextTickTimeNanos += l2 * l;
                          this.lastOverloadWarningNanos = this.nextTickTimeNanos;
                      }
-+                }
+                 }
 +                // Spigot start
 +                // Paper start - further improve server tick loop
 +                currentTime = Util.getNanos();
@@ -913,22 +878,22 @@
 +                    this.recentTps[1] = tps5.getAverage();
 +                    this.recentTps[2] = tps15.getAverage();
 +                    tickSection = currentTime;
-                 }
++                }
 +                // Paper end - further improve server tick loop
 +                // Spigot end
  
-                 boolean flag = i == 0L;
- 
-@@ -757,6 +1212,8 @@
+                 boolean flag = l == 0L;
+                 if (this.debugCommandProfilerDelayStart) {
+@@ -705,6 +_,8 @@
                      this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
                  }
  
 +                //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
 +                lastTick = currentTime;
-                 this.nextTickTimeNanos += i;
+                 this.nextTickTimeNanos += l;
  
-                 try {
-@@ -830,6 +1287,14 @@
+                 try (Profiler.Scope scope = Profiler.use(this.createProfiler())) {
+@@ -755,6 +_,14 @@
                      this.services.profileCache().clearExecutor();
                  }
  
@@ -942,46 +907,32 @@
 +                io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
                  this.onServerExit();
              }
- 
-@@ -889,9 +1354,16 @@
+         }
+@@ -807,7 +_,14 @@
      }
  
      private boolean haveTime() {
 -        return this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
 +        // CraftBukkit start
 +        return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
-     }
- 
++    }
++
 +    private void executeModerately() {
 +        this.runAllTasks();
 +        java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
 +        // CraftBukkit end
-+    }
-+
+     }
+ 
      public static boolean throwIfFatalException() {
-         RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get();
- 
-@@ -903,7 +1375,7 @@
-     }
- 
-     public static void setFatalException(RuntimeException exception) {
--        MinecraftServer.fatalException.compareAndSet((Object) null, exception);
-+        MinecraftServer.fatalException.compareAndSet(null, exception); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -961,6 +1433,7 @@
+@@ -871,15 +_,16 @@
          if (super.pollTask()) {
              return true;
          } else {
 +            boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
              if (this.tickRateManager.isSprinting() || this.haveTime()) {
-                 Iterator iterator = this.getAllLevels().iterator();
- 
-@@ -968,16 +1441,16 @@
-                     ServerLevel worldserver = (ServerLevel) iterator.next();
- 
-                     if (worldserver.getChunkSource().pollTask()) {
+-                for (ServerLevel serverLevel : this.getAllLevels()) {
++                for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+                     if (serverLevel.getChunkSource().pollTask()) {
 -                        return true;
 +                        ret = true; // Paper - force execution of all worlds, do not just bias the first
                      }
@@ -993,32 +944,26 @@
          }
      }
  
--    public void doRunTask(TickTask ticktask) {
-+    public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error
-         Profiler.get().incrementCounter("runTask");
-         super.doRunTask(ticktask);
-     }
-@@ -1025,27 +1498,45 @@
+@@ -927,26 +_,44 @@
      }
  
-     public void tickServer(BooleanSupplier shouldKeepTicking) {
+     public void tickServer(BooleanSupplier hasTimeLeft) {
 +        org.spigotmc.WatchdogThread.tick(); // Spigot
-         long i = Util.getNanos();
-         int j = this.pauseWhileEmptySeconds() * 20;
- 
+         long nanos = Util.getNanos();
+         int i = this.pauseWhileEmptySeconds() * 20;
 +        this.removeDisabledPluginsBlockingSleep(); // Paper - API to allow/disallow tick sleeping
-         if (j > 0) {
+         if (i > 0) {
 -            if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting()) {
 +            if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping
-                 ++this.emptyTicks;
+                 this.emptyTicks++;
              } else {
                  this.emptyTicks = 0;
              }
  
-             if (this.emptyTicks >= j) {
+             if (this.emptyTicks >= i) {
 +                this.server.spark.tickStart(); // Paper - spark
-                 if (this.emptyTicks == j) {
-                     MinecraftServer.LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhileEmptySeconds());
+                 if (this.emptyTicks == i) {
+                     LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhileEmptySeconds());
                      this.autoSave();
                  }
  
@@ -1028,7 +973,7 @@
 +                while ((task = this.processQueue.poll()) != null) {
 +                    task.run();
 +                }
-+                for (final ServerLevel level : this.levels.values()) {
++                for (final net.minecraft.server.level.ServerLevel level : this.levels.values()) {
 +                    // process unloads
 +                    level.getChunkSource().tick(() -> true, false);
 +                }
@@ -1042,20 +987,19 @@
  
 +        this.server.spark.tickStart(); // Paper - spark
 +        new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper - Server Tick Events
-         ++this.tickCount;
+         this.tickCount++;
          this.tickRateManager.tick();
-         this.tickChildren(shouldKeepTicking);
-@@ -1055,12 +1546,20 @@
+         this.tickChildren(hasTimeLeft);
+@@ -956,11 +_,19 @@
          }
  
-         --this.ticksUntilAutosave;
+         this.ticksUntilAutosave--;
 -        if (this.ticksUntilAutosave <= 0) {
 +        if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit
              this.autoSave();
          }
  
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
+         ProfilerFiller profilerFiller = Profiler.get();
 +        this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
 +        this.server.spark.executeMainThreadTasks(); // Paper - spark
 +        // Paper start - Server Tick Events
@@ -1064,55 +1008,67 @@
 +        new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
 +        // Paper end - Server Tick Events
 +        this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark
-         gameprofilerfiller.push("tallying");
-         long k = Util.getNanos() - i;
-         int l = this.tickCount % 100;
-@@ -1069,12 +1568,17 @@
-         this.aggregatedTickTimesNanos += k;
-         this.tickTimesNanos[l] = k;
-         this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
+         profilerFiller.push("tallying");
+         long l = Util.getNanos() - nanos;
+         int i1 = this.tickCount % 100;
+@@ -968,12 +_,17 @@
+         this.aggregatedTickTimesNanos += l;
+         this.tickTimesNanos[i1] = l;
+         this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float)l / (float)TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
 +        // Paper start - Add tick times API and /mspt command
-+        this.tickTimes5s.add(this.tickCount, k);
-+        this.tickTimes10s.add(this.tickCount, k);
-+        this.tickTimes60s.add(this.tickCount, k);
++        this.tickTimes5s.add(this.tickCount, l);
++        this.tickTimes10s.add(this.tickCount, l);
++        this.tickTimes60s.add(this.tickCount, l);
 +        // Paper end - Add tick times API and /mspt command
-         this.logTickMethodTime(i);
-         gameprofilerfiller.pop();
+         this.logTickMethodTime(nanos);
+         profilerFiller.pop();
      }
  
      private void autoSave() {
 -        this.ticksUntilAutosave = this.computeNextAutosaveInterval();
 +        this.ticksUntilAutosave = this.autosavePeriod; // CraftBukkit
-         MinecraftServer.LOGGER.debug("Autosave started");
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-@@ -1123,7 +1627,7 @@
+         LOGGER.debug("Autosave started");
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("save");
+@@ -1015,7 +_,7 @@
      private ServerStatus buildServerStatus() {
-         ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus();
- 
--        return new ServerStatus(Component.nullToEmpty(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile());
-+        return new ServerStatus(io.papermc.paper.adventure.PaperAdventure.asVanilla(this.motd), Optional.of(serverping_serverpingplayersample), Optional.of(ServerStatus.Version.current()), Optional.ofNullable(this.statusIcon), this.enforceSecureProfile()); // Paper - Adventure
+         ServerStatus.Players players = this.buildPlayerStatus();
+         return new ServerStatus(
+-            Component.nullToEmpty(this.motd),
++            io.papermc.paper.adventure.PaperAdventure.asVanilla(this.motd), // Paper - Adventure
+             Optional.of(players),
+             Optional.of(ServerStatus.Version.current()),
+             Optional.ofNullable(this.statusIcon),
+@@ -1024,17 +_,17 @@
      }
  
      private ServerStatus.Players buildPlayerStatus() {
-@@ -1133,7 +1637,7 @@
+-        List<ServerPlayer> players = this.playerList.getPlayers();
++        List<net.minecraft.server.level.ServerPlayer> players = this.playerList.getPlayers();
+         int maxPlayers = this.getMaxPlayers();
          if (this.hidesOnlinePlayers()) {
-             return new ServerStatus.Players(i, list.size(), List.of());
+             return new ServerStatus.Players(maxPlayers, players.size(), List.of());
          } else {
--            int j = Math.min(list.size(), 12);
-+            int j = Math.min(list.size(), org.spigotmc.SpigotConfig.playerSample); // Paper - PaperServerListPingEvent
-             ObjectArrayList<GameProfile> objectarraylist = new ObjectArrayList(j);
-             int k = Mth.nextInt(this.random, 0, list.size() - j);
+-            int min = Math.min(players.size(), 12);
++            int min = Math.min(players.size(), org.spigotmc.SpigotConfig.playerSample); // Paper - PaperServerListPingEvent
+             ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
+             int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
  
-@@ -1154,24 +1658,72 @@
-         this.getPlayerList().getPlayers().forEach((entityplayer) -> {
-             entityplayer.connection.suspendFlushing();
-         });
+             for (int i = 0; i < min; i++) {
+-                ServerPlayer serverPlayer = players.get(randomInt + i);
++                net.minecraft.server.level.ServerPlayer serverPlayer = players.get(randomInt + i);
+                 list.add(serverPlayer.allowsListing() ? serverPlayer.getGameProfile() : ANONYMOUS_PLAYER_PROFILE);
+             }
+ 
+@@ -1046,17 +_,64 @@
+     protected void tickChildren(BooleanSupplier hasTimeLeft) {
+         ProfilerFiller profilerFiller = Profiler.get();
+         this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
 +        this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
 +        // Paper start - Folia scheduler API
-+        ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).tick();
++        ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
 +        getAllLevels().forEach(level -> {
-+            for (final Entity entity : level.getEntities().getAll()) {
++            for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) {
 +                if (entity.isRemoved()) {
 +                    continue;
 +                }
@@ -1124,12 +1080,11 @@
 +        });
 +        // Paper end - Folia scheduler API
 +        io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
-         gameprofilerfiller.push("commandFunctions");
+         profilerFiller.push("commandFunctions");
          this.getFunctions().tick();
-         gameprofilerfiller.popPush("levels");
--        Iterator iterator = this.getAllLevels().iterator();
-+        //Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; moved down
+         profilerFiller.popPush("levels");
  
+-        for (ServerLevel serverLevel : this.getAllLevels()) {
 +        // CraftBukkit start
 +        // Run tasks that are waiting on processing
 +        while (!this.processQueue.isEmpty()) {
@@ -1138,16 +1093,16 @@
 +
 +        // Send time updates to everyone, it will get the right time from the world the player is in.
 +        // Paper start - Perf: Optimize time updates
-+        for (final ServerLevel level : this.getAllLevels()) {
++        for (final net.minecraft.server.level.ServerLevel level : this.getAllLevels()) {
 +            final boolean doDaylight = level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT);
 +            final long dayTime = level.getDayTime();
 +            long worldTime = level.getGameTime();
 +            final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
 +            for (Player entityhuman : level.players()) {
-+                if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
++                if (!(entityhuman instanceof net.minecraft.server.level.ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
 +                    continue;
 +                }
-+                ServerPlayer entityplayer = (ServerPlayer) entityhuman;
++                net.minecraft.server.level.ServerPlayer entityplayer = (net.minecraft.server.level.ServerPlayer) entityhuman;
 +                long playerTime = entityplayer.getPlayerTime();
 +                ClientboundSetTimePacket packet = (playerTime == dayTime) ? worldPacket :
 +                    new ClientboundSetTimePacket(worldTime, playerTime, doDaylight);
@@ -1157,52 +1112,83 @@
 +        }
 +
 +        this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
-+        Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down
-         while (iterator.hasNext()) {
-             ServerLevel worldserver = (ServerLevel) iterator.next();
-+            worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
-+            worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
- 
-             gameprofilerfiller.push(() -> {
-                 String s = String.valueOf(worldserver);
- 
-                 return s + " " + String.valueOf(worldserver.dimension().location());
-             });
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
++            serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
++            serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
+             profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
 +            /* Drop global time updates
              if (this.tickCount % 20 == 0) {
-                 gameprofilerfiller.push("timeSync");
-                 this.synchronizeTime(worldserver);
-                 gameprofilerfiller.pop();
+                 profilerFiller.push("timeSync");
+                 this.synchronizeTime(serverLevel);
+                 profilerFiller.pop();
              }
 +            // CraftBukkit end */
  
-             gameprofilerfiller.push("tick");
+             profilerFiller.push("tick");
  
-@@ -1186,7 +1738,9 @@
+@@ -1070,7 +_,9 @@
  
-             gameprofilerfiller.pop();
-             gameprofilerfiller.pop();
-+            worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
+             profilerFiller.pop();
+             profilerFiller.pop();
++            serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
          }
 +        this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
  
-         gameprofilerfiller.popPush("connection");
+         profilerFiller.popPush("connection");
          this.tickConnection();
-@@ -1267,6 +1821,22 @@
-         return (ServerLevel) this.levels.get(key);
+@@ -1088,7 +_,7 @@
+ 
+         profilerFiller.popPush("send chunks");
+ 
+-        for (ServerPlayer serverPlayer : this.playerList.getPlayers()) {
++        for (net.minecraft.server.level.ServerPlayer serverPlayer : this.playerList.getPlayers()) {
+             serverPlayer.connection.chunkSender.sendNextChunks(serverPlayer);
+             serverPlayer.connection.resumeFlushing();
+         }
+@@ -1100,7 +_,7 @@
+         this.getConnection().tick();
+     }
+ 
+-    private void synchronizeTime(ServerLevel level) {
++    private void synchronizeTime(net.minecraft.server.level.ServerLevel level) {
+         this.playerList
+             .broadcastAll(
+                 new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)),
+@@ -1112,7 +_,7 @@
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("timeSync");
+ 
+-        for (ServerLevel serverLevel : this.getAllLevels()) {
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+             this.synchronizeTime(serverLevel);
+         }
+ 
+@@ -1139,20 +_,36 @@
+         return this.getServerDirectory().resolve(path);
+     }
+ 
+-    public final ServerLevel overworld() {
++    public final net.minecraft.server.level.ServerLevel overworld() {
+         return this.levels.get(Level.OVERWORLD);
+     }
+ 
+     @Nullable
+-    public ServerLevel getLevel(ResourceKey<Level> dimension) {
++    public net.minecraft.server.level.ServerLevel getLevel(ResourceKey<Level> dimension) {
+         return this.levels.get(dimension);
      }
  
 +    // CraftBukkit start
-+    public void addLevel(ServerLevel level) {
-+        Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
-+        Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++    public void addLevel(net.minecraft.server.level.ServerLevel level) {
++        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> oldLevels = this.levels;
++        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
 +        newLevels.put(level.dimension(), level);
 +        this.levels = Collections.unmodifiableMap(newLevels);
 +    }
 +
-+    public void removeLevel(ServerLevel level) {
-+        Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
-+        Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++    public void removeLevel(net.minecraft.server.level.ServerLevel level) {
++        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> oldLevels = this.levels;
++        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
 +        newLevels.remove(level.dimension());
 +        this.levels = Collections.unmodifiableMap(newLevels);
 +    }
@@ -1211,7 +1197,13 @@
      public Set<ResourceKey<Level>> levelKeys() {
          return this.levels.keySet();
      }
-@@ -1296,7 +1866,7 @@
+ 
+-    public Iterable<ServerLevel> getAllLevels() {
++    public Iterable<net.minecraft.server.level.ServerLevel> getAllLevels() {
+         return this.levels.values();
+     }
+ 
+@@ -1177,7 +_,7 @@
  
      @DontObfuscate
      public String getServerModName() {
@@ -1219,46 +1211,57 @@
 +        return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper
      }
  
-     public SystemReport fillSystemReport(SystemReport details) {
-@@ -1347,7 +1917,7 @@
+     public SystemReport fillSystemReport(SystemReport systemReport) {
+@@ -1212,7 +_,7 @@
  
      @Override
-     public void sendSystemMessage(Component message) {
--        MinecraftServer.LOGGER.info(message.getString());
-+        MinecraftServer.LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(message))); // Paper - Log message with colors
+     public void sendSystemMessage(Component component) {
+-        LOGGER.info(component.getString());
++        LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(component))); // Paper - Log message with colors
      }
  
      public KeyPair getKeyPair() {
-@@ -1385,11 +1955,14 @@
+@@ -1250,11 +_,14 @@
          }
      }
  
--    public void setDifficulty(Difficulty difficulty, boolean forceUpdate) {
--        if (forceUpdate || !this.worldData.isDifficultyLocked()) {
+-    public void setDifficulty(Difficulty difficulty, boolean forced) {
+-        if (forced || !this.worldData.isDifficultyLocked()) {
 -            this.worldData.setDifficulty(this.worldData.isHardcore() ? Difficulty.HARD : difficulty);
 -            this.updateMobSpawningFlags();
 -            this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
 +    // Paper start - per level difficulty
-+    public void setDifficulty(ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
-+        PrimaryLevelData worldData = level.serverLevelData;
++    public void setDifficulty(net.minecraft.server.level.ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
++        net.minecraft.world.level.storage.PrimaryLevelData worldData = level.serverLevelData;
 +        if (forceUpdate || !worldData.isDifficultyLocked()) {
 +            worldData.setDifficulty(worldData.isHardcore() ? Difficulty.HARD : difficulty);
-+            level.setSpawnSettings(worldData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters);
++            level.setSpawnSettings(worldData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters);
 +            // this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
 +            // Paper end - per level difficulty
          }
      }
  
-@@ -1403,7 +1976,7 @@
-         while (iterator.hasNext()) {
-             ServerLevel worldserver = (ServerLevel) iterator.next();
- 
--            worldserver.setSpawnSettings(this.isSpawningMonsters());
-+            worldserver.setSpawnSettings(worldserver.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
-         }
- 
+@@ -1263,8 +_,8 @@
      }
-@@ -1481,10 +2054,20 @@
+ 
+     private void updateMobSpawningFlags() {
+-        for (ServerLevel serverLevel : this.getAllLevels()) {
+-            serverLevel.setSpawnSettings(this.isSpawningMonsters());
++        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
++            serverLevel.setSpawnSettings(serverLevel.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
+         }
+     }
+ 
+@@ -1273,7 +_,7 @@
+         this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
+     }
+ 
+-    private void sendDifficultyUpdate(ServerPlayer player) {
++    private void sendDifficultyUpdate(net.minecraft.server.level.ServerPlayer player) {
+         LevelData levelData = player.level().getLevelData();
+         player.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+     }
+@@ -1340,10 +_,20 @@
  
      @Override
      public String getMotd() {
@@ -1280,124 +1283,145 @@
          this.motd = motd;
      }
  
-@@ -1507,7 +2090,7 @@
+@@ -1365,8 +_,8 @@
+         this.worldData.setGameType(gameMode);
      }
  
-     public ServerConnectionListener getConnection() {
+-    public ServerConnectionListener getConnection() {
 -        return this.connection;
-+        return this.connection == null ? this.connection = new ServerConnectionListener(this) : this.connection; // Spigot
++    public net.minecraft.server.network.ServerConnectionListener getConnection() {
++        return this.connection == null ? this.connection = new net.minecraft.server.network.ServerConnectionListener(this) : this.connection; // Spigot
      }
  
      public boolean isReady() {
-@@ -1593,7 +2176,7 @@
+@@ -1389,7 +_,7 @@
+         return 16;
+     }
+ 
+-    public boolean isUnderSpawnProtection(ServerLevel level, BlockPos pos, Player player) {
++    public boolean isUnderSpawnProtection(net.minecraft.server.level.ServerLevel level, BlockPos pos, Player player) {
+         return false;
+     }
+ 
+@@ -1452,7 +_,7 @@
      @Override
-     public void executeIfPossible(Runnable runnable) {
+     public void executeIfPossible(Runnable task) {
          if (this.isStopped()) {
 -            throw new RejectedExecutionException("Server already shutting down");
 +            throw new io.papermc.paper.util.ServerStopRejectedExecutionException("Server already shutting down"); // Paper - do not prematurely disconnect players on stop
          } else {
-             super.executeIfPossible(runnable);
+             super.executeIfPossible(task);
          }
-@@ -1632,16 +2215,22 @@
+@@ -1479,7 +_,7 @@
+         return this.fixerUpper;
+     }
+ 
+-    public int getSpawnRadius(@Nullable ServerLevel level) {
++    public int getSpawnRadius(@Nullable net.minecraft.server.level.ServerLevel level) {
+         return level != null ? level.getGameRules().getInt(GameRules.RULE_SPAWN_RADIUS) : 10;
+     }
+ 
+@@ -1491,7 +_,13 @@
          return this.functionManager;
      }
  
 +    // Paper start - Add ServerResourcesReloadedEvent
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse
-     public CompletableFuture<Void> reloadResources(Collection<String> dataPacks) {
-+        return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
+     public CompletableFuture<Void> reloadResources(Collection<String> selectedIds) {
++        return this.reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
 +    }
-+    public CompletableFuture<Void> reloadResources(Collection<String> dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
++    public CompletableFuture<Void> reloadResources(Collection<String> selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
 +        // Paper end - Add ServerResourcesReloadedEvent
-         CompletableFuture<Void> completablefuture = CompletableFuture.supplyAsync(() -> {
--            Stream stream = dataPacks.stream();
-+            Stream<String> stream = dataPacks.stream(); // CraftBukkit - decompile error
-             PackRepository resourcepackrepository = this.packRepository;
- 
-             Objects.requireNonNull(this.packRepository);
--            return (ImmutableList) stream.map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList());
-+            return stream.<Pack>map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error // todo: is this needed anymore?
-         }, this).thenCompose((immutablelist) -> {
-             MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist);
--            List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess());
-+            List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess(), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag lifecycle - add cause
- 
-             return ReloadableServerResources.loadResources(resourcemanager, this.registries, list, this.worldData.enabledFeatures(), this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, this.getFunctionCompilationLevel(), this.executor, this).whenComplete((datapackresources, throwable) -> {
-                 if (throwable != null) {
-@@ -1652,6 +2241,7 @@
-                 return new MinecraftServer.ReloadableResources(resourcemanager, datapackresources);
-             });
-         }).thenAcceptAsync((minecraftserver_reloadableresources) -> {
-+            io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), minecraftserver_reloadableresources.managers().commands); // Paper
-             this.resources.close();
-             this.resources = minecraftserver_reloadableresources;
-             this.packRepository.setSelected(dataPacks);
-@@ -1660,11 +2250,23 @@
-             this.worldData.setDataConfiguration(worlddataconfiguration);
-             this.resources.managers.updateStaticRegistryTags();
-             this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
-+            this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
-             this.getPlayerList().saveAll();
-             this.getPlayerList().reloadResources();
-             this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
-             this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
-             this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
-+            org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
-+            // Paper start - brigadier command API
-+            io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below
-+            io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
-+            final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap();
-+            helpMap.clear();
-+            helpMap.initializeGeneralTopics();
-+            helpMap.initializeCommands();
-+            this.server.syncCommands(); // Refresh commands after event
-+            // Paper end
-+            new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
-         }, this);
- 
-         if (this.isSameThread()) {
-@@ -1789,14 +2391,15 @@
+         CompletableFuture<Void> completableFuture = CompletableFuture.<ImmutableList>supplyAsync(
+                 () -> selectedIds.stream().map(this.packRepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()),
+                 this
+@@ -1499,7 +_,7 @@
+             .thenCompose(
+                 list -> {
+                     CloseableResourceManager closeableResourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, list);
+-                    List<Registry.PendingTags<?>> list1 = TagLoader.loadTagsForExistingRegistries(closeableResourceManager, this.registries.compositeAccess());
++                    List<Registry.PendingTags<?>> list1 = TagLoader.loadTagsForExistingRegistries(closeableResourceManager, this.registries.compositeAccess(), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag lifecycle - add cause
+                     return ReloadableServerResources.loadResources(
+                             closeableResourceManager,
+                             this.registries,
+@@ -1520,6 +_,7 @@
+             )
+             .thenAcceptAsync(
+                 reloadableResources -> {
++                    io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), reloadableResources.managers().commands); // Paper
+                     this.resources.close();
+                     this.resources = reloadableResources;
+                     this.packRepository.setSelected(selectedIds);
+@@ -1529,11 +_,23 @@
+                     this.worldData.setDataConfiguration(worldDataConfiguration);
+                     this.resources.managers.updateStaticRegistryTags();
+                     this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
++                    this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
+                     this.getPlayerList().saveAll();
+                     this.getPlayerList().reloadResources();
+                     this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
+                     this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
+                     this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
++                    org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
++                    // Paper start - brigadier command API
++                    io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below
++                    io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
++                    final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap();
++                    helpMap.clear();
++                    helpMap.initializeGeneralTopics();
++                    helpMap.initializeCommands();
++                    this.server.syncCommands(); // Refresh commands after event
++                    // Paper end
++                    new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
+                 },
+                 this
+             );
+@@ -1652,10 +_,11 @@
          if (this.isEnforceWhitelist()) {
-             PlayerList playerlist = source.getServer().getPlayerList();
-             UserWhiteList whitelist = playerlist.getWhiteList();
-+            if (!((DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
-             List<ServerPlayer> list = Lists.newArrayList(playerlist.getPlayers());
-             Iterator iterator = list.iterator();
+             PlayerList playerList = commandSource.getServer().getPlayerList();
+             UserWhiteList whiteList = playerList.getWhiteList();
++            if (!((net.minecraft.server.dedicated.DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
  
-             while (iterator.hasNext()) {
-                 ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
--                if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) {
--                    entityplayer.connection.disconnect((Component) Component.translatable("multiplayer.disconnect.not_whitelisted"));
-+                if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
-+                    entityplayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
+-            for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
+-                if (!whiteList.isWhiteListed(serverPlayer.getGameProfile())) {
+-                    serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted"));
++            for (net.minecraft.server.level.ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
++                if (!whiteList.isWhiteListed(serverPlayer.getGameProfile()) && !this.getPlayerList().isOp(serverPlayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
++                    serverPlayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
                  }
              }
- 
-@@ -1952,7 +2555,7 @@
-             final List<String> list = Lists.newArrayList();
-             final GameRules gamerules = this.getGameRules();
- 
--            gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor(this) {
-+            gamerules.visitGameRuleTypes(new GameRules.GameRuleTypeVisitor() { // CraftBukkit - decompile error
-                 @Override
-                 public <T extends GameRules.Value<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) {
-                     list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key)));
-@@ -2058,7 +2661,7 @@
-             try {
-                 label51:
-                 {
--                    ArrayList arraylist;
-+                    ArrayList<NativeModuleLister.NativeModuleInfo> arraylist; // CraftBukkit - decompile error
- 
-                     try {
-                         arraylist = Lists.newArrayList(NativeModuleLister.listModules());
-@@ -2105,8 +2708,23 @@
-         if (bufferedwriter != null) {
-             bufferedwriter.close();
          }
-+
-+    }
+@@ -1670,7 +_,7 @@
+     }
+ 
+     public CommandSourceStack createCommandSourceStack() {
+-        ServerLevel serverLevel = this.overworld();
++        net.minecraft.server.level.ServerLevel serverLevel = this.overworld();
+         return new CommandSourceStack(
+             this,
+             serverLevel == null ? Vec3.ZERO : Vec3.atLowerCornerOf(serverLevel.getSharedSpawnPos()),
+@@ -1717,7 +_,7 @@
+         return this.overworld().getGameRules();
+     }
+ 
+-    public CustomBossEvents getCustomBossEvents() {
++    public net.minecraft.server.bossevents.CustomBossEvents getCustomBossEvents() {
+         return this.customBossEvents;
+     }
+ 
+@@ -1771,7 +_,7 @@
+         Path path1 = path.resolve("levels");
+ 
+         try {
+-            for (Entry<ResourceKey<Level>, ServerLevel> entry : this.levels.entrySet()) {
++            for (Entry<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> entry : this.levels.entrySet()) {
+                 ResourceLocation resourceLocation = entry.getKey().location();
+                 Path path2 = path1.resolve(resourceLocation.getNamespace()).resolve(resourceLocation.getPath());
+                 Files.createDirectories(path2);
+@@ -1859,6 +_,22 @@
+         }
+     }
+ 
 +
 +    // CraftBukkit start
 +    public boolean isDebugging() {
@@ -1407,47 +1431,71 @@
 +    public static MinecraftServer getServer() {
 +        return SERVER; // Paper
 +    }
- 
++
 +    @Deprecated
 +    public static RegistryAccess getDefaultRegistryAccess() {
-+        return CraftRegistry.getMinecraftRegistry();
-     }
++        return org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry();
++    }
 +    // CraftBukkit end
- 
++
      private ProfilerFiller createProfiler() {
          if (this.willStartRecordingMetrics) {
-@@ -2225,18 +2843,24 @@
+             this.metricsRecorder = ActiveMetricsRecorder.createStarted(
+@@ -1936,12 +_,12 @@
+         return this.resources.managers.fullRegistries();
      }
  
-     public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) {
--        String s1 = params.decorate(message).getString();
-+        // Paper start
-+        net.kyori.adventure.text.Component s1 = io.papermc.paper.adventure.PaperAdventure.asAdventure(params.decorate(message));
+-    public TextFilter createTextFilterForPlayer(ServerPlayer player) {
+-        return TextFilter.DUMMY;
++    public net.minecraft.server.network.TextFilter createTextFilterForPlayer(net.minecraft.server.level.ServerPlayer player) {
++        return net.minecraft.server.network.TextFilter.DUMMY;
+     }
  
-         if (prefix != null) {
--            MinecraftServer.LOGGER.info("[{}] {}", prefix, s1);
-+            MinecraftServer.COMPONENT_LOGGER.info("[{}] {}", prefix, s1);
+-    public ServerPlayerGameMode createGameModeForPlayer(ServerPlayer player) {
+-        return (ServerPlayerGameMode)(this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
++    public net.minecraft.server.level.ServerPlayerGameMode createGameModeForPlayer(net.minecraft.server.level.ServerPlayer player) {
++        return (net.minecraft.server.level.ServerPlayerGameMode)(this.isDemo() ? new net.minecraft.server.level.DemoMode(player) : new net.minecraft.server.level.ServerPlayerGameMode(player));
+     }
+ 
+     @Nullable
+@@ -1980,23 +_,29 @@
+     }
+ 
+     public void logChatMessage(Component content, ChatType.Bound boundChatType, @Nullable String header) {
+-        String string = boundChatType.decorate(content).getString();
++        // Paper start
++        net.kyori.adventure.text.Component string = io.papermc.paper.adventure.PaperAdventure.asAdventure(boundChatType.decorate(content));
+         if (header != null) {
+-            LOGGER.info("[{}] {}", header, string);
++            COMPONENT_LOGGER.info("[{}] {}", header, string);
          } else {
--            MinecraftServer.LOGGER.info("{}", s1);
-+            MinecraftServer.COMPONENT_LOGGER.info("{}", s1);
+-            LOGGER.info("{}", string);
++            COMPONENT_LOGGER.info("{}", string);
 +            // Paper end
          }
- 
      }
- 
-+    public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
-+            new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
 +
++    public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
++        new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
 +    public final ChatDecorator improvedChatDecorator = new io.papermc.paper.adventure.ImprovedChatDecorator(this); // Paper - adventure
+ 
      public ChatDecorator getChatDecorator() {
 -        return ChatDecorator.PLAIN;
 +        return this.improvedChatDecorator; // Paper - support async chat decoration events
      }
  
      public boolean logIPs() {
-@@ -2379,4 +3003,53 @@
-     public static record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) {
+         return true;
+     }
  
+-    public void subscribeToDebugSample(ServerPlayer player, RemoteDebugSampleType sampleType) {
++    public void subscribeToDebugSample(net.minecraft.server.level.ServerPlayer player, RemoteDebugSampleType sampleType) {
+     }
+ 
+     public boolean acceptsTransfers() {
+@@ -2122,4 +_,53 @@
+             };
+         }
      }
 +
 +    // Paper start - Add tick times API and /mspt command
diff --git a/paper-server/patches/unapplied/net/minecraft/server/PlayerAdvancements.java.patch b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/server/PlayerAdvancements.java.patch
rename to paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch
index 93d25149bd..0c15dfbc32 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/PlayerAdvancements.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch
@@ -1,52 +1,52 @@
 --- a/net/minecraft/server/PlayerAdvancements.java
 +++ b/net/minecraft/server/PlayerAdvancements.java
-@@ -50,7 +50,7 @@
- public class PlayerAdvancements {
+@@ -47,7 +_,7 @@
  
+ public class PlayerAdvancements {
      private static final Logger LOGGER = LogUtils.getLogger();
--    private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
-+    private static final Gson GSON = (new GsonBuilder()).create(); // Paper - Remove pretty printing from advancements
+-    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
++    private static final Gson GSON = new GsonBuilder().create(); // Paper - Remove pretty printing from advancements
      private final PlayerList playerList;
      private final Path playerSavePath;
      private AdvancementTree tree;
-@@ -63,6 +63,7 @@
+@@ -60,6 +_,7 @@
      private AdvancementHolder lastSelectedTab;
      private boolean isFirstPacket = true;
      private final Codec<PlayerAdvancements.Data> codec;
 +    public final Map<net.minecraft.advancements.critereon.SimpleCriterionTrigger<?>, Set<CriterionTrigger.Listener<?>>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage
  
-     public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, Path filePath, ServerPlayer owner) {
-         this.playerList = playerManager;
-@@ -162,6 +163,7 @@
+     public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) {
+         this.playerList = playerList;
+@@ -128,6 +_,7 @@
      }
  
      public void save() {
 +        if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return; // Spigot
-         JsonElement jsonelement = (JsonElement) this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow();
+         JsonElement jsonElement = this.codec.encodeStart(JsonOps.INSTANCE, this.asData()).getOrThrow();
  
          try {
-@@ -196,6 +198,7 @@
-             AdvancementHolder advancementholder = loader.get(minecraftkey);
- 
-             if (advancementholder == null) {
-+                if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit
-                 PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath);
+@@ -145,6 +_,7 @@
+         data.forEach((path, progress) -> {
+             AdvancementHolder advancementHolder = advancementManager.get(path);
+             if (advancementHolder == null) {
++                if (!path.getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) return; // CraftBukkit
+                 LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", path, this.playerSavePath);
              } else {
-                 this.startProgress(advancementholder, advancementprogress);
-@@ -223,14 +226,31 @@
-         boolean flag1 = advancementprogress.isDone();
- 
-         if (advancementprogress.grantProgress(criterionName)) {
+                 this.startProgress(advancementHolder, progress);
+@@ -169,14 +_,31 @@
+         AdvancementProgress orStartProgress = this.getOrStartProgress(advancement);
+         boolean isDone = orStartProgress.isDone();
+         if (orStartProgress.grantProgress(criterionKey)) {
 +            // Paper start - Add PlayerAdvancementCriterionGrantEvent
-+            if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.toBukkit(), criterionName).callEvent()) {
-+                advancementprogress.revokeProgress(criterionName);
++            if (!new com.destroystokyo.paper.event.player.PlayerAdvancementCriterionGrantEvent(this.player.getBukkitEntity(), advancement.toBukkit(), criterionKey).callEvent()) {
++                orStartProgress.revokeProgress(criterionKey);
 +                return false;
 +            }
 +            // Paper end - Add PlayerAdvancementCriterionGrantEvent
              this.unregisterListeners(advancement);
              this.progressChanged.add(advancement);
              flag = true;
-             if (!flag1 && advancementprogress.isDone()) {
+             if (!isDone && orStartProgress.isDone()) {
 +                // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
 +                final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> {
 +                    return java.util.Optional.ofNullable(
@@ -57,13 +57,13 @@
 +                this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit
 +                // Paper end
                  advancement.value().rewards().grant(this.player);
-                 advancement.value().display().ifPresent((advancementdisplay) -> {
--                    if (advancementdisplay.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
--                        this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false);
+                 advancement.value().display().ifPresent(displayInfo -> {
+-                    if (displayInfo.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+-                        this.playerList.broadcastSystemMessage(displayInfo.getType().createAnnouncement(advancement, this.player), false);
 +                    // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
 +                    if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
 +                        this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
 +                        // Paper end
                      }
- 
                  });
+             }
diff --git a/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch b/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch
new file mode 100644
index 0000000000..4276054d2f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/ReloadableServerRegistries.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/server/ReloadableServerRegistries.java
++++ b/net/minecraft/server/ReloadableServerRegistries.java
+@@ -48,8 +_,9 @@
+         List<HolderLookup.RegistryLookup<?>> list = TagLoader.buildUpdatedLookups(registryAccess.getAccessForLoading(RegistryLayer.RELOADABLE), postponedTags);
+         HolderLookup.Provider provider = HolderLookup.Provider.create(list.stream());
+         RegistryOps<JsonElement> registryOps = provider.createSerializationContext(JsonOps.INSTANCE);
++        final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper
+         List<CompletableFuture<WritableRegistry<?>>> list1 = LootDataType.values()
+-            .map(lootDataType -> scheduleRegistryLoad((LootDataType<?>)lootDataType, registryOps, resourceManager, backgroundExecutor))
++            .map(lootDataType -> scheduleRegistryLoad((LootDataType<?>)lootDataType, registryOps, resourceManager, backgroundExecutor, conversions)) // Paper
+             .toList();
+         CompletableFuture<List<WritableRegistry<?>>> completableFuture = Util.sequence(list1);
+         return completableFuture.thenApplyAsync(
+@@ -58,19 +_,20 @@
+     }
+ 
+     private static <T> CompletableFuture<WritableRegistry<?>> scheduleRegistryLoad(
+-        LootDataType<T> lootDataType, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor backgroundExecutor
++        LootDataType<T> lootDataType, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor backgroundExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper
+     ) {
+         return CompletableFuture.supplyAsync(
+             () -> {
+                 WritableRegistry<T> writableRegistry = new MappedRegistry<>(lootDataType.registryKey(), Lifecycle.experimental());
++                io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(lootDataType.registryKey(), writableRegistry); // Paper - register reloadable registry
+                 Map<ResourceLocation, T> map = new HashMap<>();
+                 SimpleJsonResourceReloadListener.scanDirectory(resourceManager, lootDataType.registryKey(), ops, lootDataType.codec(), map);
+                 map.forEach(
+-                    (resourceLocation, object) -> writableRegistry.register(
+-                        ResourceKey.create(lootDataType.registryKey(), resourceLocation), (T)object, DEFAULT_REGISTRATION_INFO
++                    (resourceLocation, object) -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, // Paper - register with listeners
++                        ResourceKey.create(lootDataType.registryKey(), resourceLocation), (T)object, DEFAULT_REGISTRATION_INFO, conversions // Paper - register with listeners
+                     )
+                 );
+-                TagLoader.loadTagsForRegistry(resourceManager, writableRegistry);
++                TagLoader.loadTagsForRegistry(resourceManager, writableRegistry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag life cycle - reload
+                 return writableRegistry;
+             },
+             backgroundExecutor
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ReloadableServerResources.java.patch b/paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch
similarity index 79%
rename from paper-server/patches/unapplied/net/minecraft/server/ReloadableServerResources.java.patch
rename to paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch
index 59120184c0..dd754adca3 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/ReloadableServerResources.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ReloadableServerResources.java.patch
@@ -1,16 +1,16 @@
 --- a/net/minecraft/server/ReloadableServerResources.java
 +++ b/net/minecraft/server/ReloadableServerResources.java
-@@ -39,6 +39,7 @@
-         this.postponedTags = pendingTagLoads;
+@@ -39,6 +_,7 @@
+         this.postponedTags = postponedTags;
          this.recipes = new RecipeManager(registries);
-         this.commands = new Commands(environment, CommandBuildContext.simple(registries, enabledFeatures));
+         this.commands = new Commands(commandSelection, CommandBuildContext.simple(registries, enabledFeatures));
 +        io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(registries, enabledFeatures)); // Paper - Brigadier Command API
          this.advancements = new ServerAdvancementManager(registries);
-         this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher());
+         this.functionLibrary = new ServerFunctionLibrary(functionCompilationLevel, this.commands.getDispatcher());
      }
-@@ -83,6 +84,14 @@
+@@ -83,6 +_,14 @@
                      ReloadableServerResources reloadableServerResources = new ReloadableServerResources(
-                         reloadResult.layers(), reloadResult.lookupWithUpdatedTags(), enabledFeatures, environment, pendingTagLoads, functionPermissionLevel
+                         loadResult.layers(), loadResult.lookupWithUpdatedTags(), enabledFeatures, commandSelection, postponedTags, functionCompilationLevel
                      );
 +                    // Paper start - call commands event for bootstraps
 +                    //noinspection ConstantValue
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ServerAdvancementManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/server/ServerAdvancementManager.java.patch
rename to paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch
index 5a5a995288..3be388af55 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/ServerAdvancementManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerAdvancementManager.java.patch
@@ -1,39 +1,31 @@
 --- a/net/minecraft/server/ServerAdvancementManager.java
 +++ b/net/minecraft/server/ServerAdvancementManager.java
-@@ -21,10 +21,14 @@
- import net.minecraft.util.profiling.ProfilerFiller;
- import org.slf4j.Logger;
+@@ -22,7 +_,7 @@
  
-+// CraftBukkit start
-+import java.util.HashMap;
-+// CraftBukkit end
-+
  public class ServerAdvancementManager extends SimpleJsonResourceReloadListener<Advancement> {
- 
      private static final Logger LOGGER = LogUtils.getLogger();
 -    public Map<ResourceLocation, AdvancementHolder> advancements = Map.of();
-+    public Map<ResourceLocation, AdvancementHolder> advancements = new HashMap<>(); // CraftBukkit - SPIGOT-7734: mutable
++    public Map<ResourceLocation, AdvancementHolder> advancements = new java.util.HashMap<>(); // CraftBukkit - SPIGOT-7734: mutable
      private AdvancementTree tree = new AdvancementTree();
      private final HolderLookup.Provider registries;
  
-@@ -37,13 +41,19 @@
+@@ -35,12 +_,18 @@
+     protected void apply(Map<ResourceLocation, Advancement> object, ResourceManager resourceManager, ProfilerFiller profiler) {
          Builder<ResourceLocation, AdvancementHolder> builder = ImmutableMap.builder();
- 
-         prepared.forEach((minecraftkey, advancement) -> {
+         object.forEach((resourceLocation, advancement) -> {
 +            // Spigot start
-+            if (org.spigotmc.SpigotConfig.disabledAdvancements != null && (org.spigotmc.SpigotConfig.disabledAdvancements.contains("*") || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.toString()) || org.spigotmc.SpigotConfig.disabledAdvancements.contains(minecraftkey.getNamespace()))) {
++            if (org.spigotmc.SpigotConfig.disabledAdvancements != null && (org.spigotmc.SpigotConfig.disabledAdvancements.contains("*") || org.spigotmc.SpigotConfig.disabledAdvancements.contains(resourceLocation.toString()) || org.spigotmc.SpigotConfig.disabledAdvancements.contains(resourceLocation.getNamespace()))) {
 +                return;
 +            }
 +            // Spigot end
-             this.validate(minecraftkey, advancement);
-             builder.put(minecraftkey, new AdvancementHolder(minecraftkey, advancement));
+             this.validate(resourceLocation, advancement);
+             builder.put(resourceLocation, new AdvancementHolder(resourceLocation, advancement));
          });
 -        this.advancements = builder.buildOrThrow();
-+        this.advancements = new HashMap<>(builder.buildOrThrow()); // CraftBukkit - SPIGOT-7734: mutable
-         AdvancementTree advancementtree = new AdvancementTree();
++        this.advancements = new java.util.HashMap<>(builder.buildOrThrow()); // CraftBukkit - SPIGOT-7734: mutable
+         AdvancementTree advancementTree = new AdvancementTree();
+         advancementTree.addAll(this.advancements.values());
++        LOGGER.info("Loaded {} advancements", advancementTree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll
  
-         advancementtree.addAll(this.advancements.values());
-+        LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll
-         Iterator iterator = advancementtree.roots().iterator();
- 
-         while (iterator.hasNext()) {
+         for (AdvancementNode advancementNode : advancementTree.roots()) {
+             if (advancementNode.holder().value().display().isPresent()) {
diff --git a/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch
new file mode 100644
index 0000000000..55bb398552
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/ServerFunctionLibrary.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ServerFunctionLibrary.java
++++ b/net/minecraft/server/ServerFunctionLibrary.java
+@@ -113,7 +_,7 @@
+                         return null;
+                     }).join());
+                     this.functions = builder.build();
+-                    this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)pair.getFirst());
++                    this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)pair.getFirst(), null); // Paper - command function tags are not implemented yet
+                 },
+                 gameExecutor
+             );
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ServerFunctionManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/server/ServerFunctionManager.java.patch
rename to paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
index 45ee083997..f78aa0e5e9 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/ServerFunctionManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/ServerFunctionManager.java
 +++ b/net/minecraft/server/ServerFunctionManager.java
-@@ -37,7 +37,7 @@
+@@ -34,7 +_,7 @@
      }
  
      public CommandDispatcher<CommandSourceStack> getDispatcher() {
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ServerScoreboard.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/ServerScoreboard.java.patch
rename to paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
index 712e8444b1..32654cff45 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/ServerScoreboard.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
@@ -1,24 +1,26 @@
 --- a/net/minecraft/server/ServerScoreboard.java
 +++ b/net/minecraft/server/ServerScoreboard.java
-@@ -42,7 +42,7 @@
+@@ -39,9 +_,7 @@
      protected void onScoreChanged(ScoreHolder scoreHolder, Objective objective, Score score) {
          super.onScoreChanged(scoreHolder, objective, score);
          if (this.trackedObjectives.contains(objective)) {
--            this.server.getPlayerList().broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat())));
-+            this.broadcastAll(new ClientboundSetScorePacket(scoreHolder.getScoreboardName(), objective.getName(), score.value(), Optional.ofNullable(score.display()), Optional.ofNullable(score.numberFormat()))); // CraftBukkit
-         }
- 
-         this.setDirty();
-@@ -57,7 +57,7 @@
+-            this.server
+-                .getPlayerList()
+-                .broadcastAll(
++            this.broadcastAll( // CraftBukkit
+                     new ClientboundSetScorePacket(
+                         scoreHolder.getScoreboardName(),
+                         objective.getName(),
+@@ -64,7 +_,7 @@
      @Override
      public void onPlayerRemoved(ScoreHolder scoreHolder) {
          super.onPlayerRemoved(scoreHolder);
--        this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null));
-+        this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), (String) null)); // CraftBukkit
+-        this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), null));
++        this.broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), null)); // CraftBukkit
          this.setDirty();
      }
  
-@@ -65,7 +65,7 @@
+@@ -72,7 +_,7 @@
      public void onPlayerScoreRemoved(ScoreHolder scoreHolder, Objective objective) {
          super.onPlayerScoreRemoved(scoreHolder, objective);
          if (this.trackedObjectives.contains(objective)) {
@@ -27,16 +29,16 @@
          }
  
          this.setDirty();
-@@ -78,7 +78,7 @@
+@@ -84,7 +_,7 @@
          super.setDisplayObjective(slot, objective);
-         if (scoreboardobjective1 != objective && scoreboardobjective1 != null) {
-             if (this.getObjectiveDisplaySlotCount(scoreboardobjective1) > 0) {
+         if (displayObjective != objective && displayObjective != null) {
+             if (this.getObjectiveDisplaySlotCount(displayObjective) > 0) {
 -                this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective));
 +                this.broadcastAll(new ClientboundSetDisplayObjectivePacket(slot, objective)); // CraftBukkit
              } else {
-                 this.stopTrackingObjective(scoreboardobjective1);
+                 this.stopTrackingObjective(displayObjective);
              }
-@@ -86,7 +86,7 @@
+@@ -92,7 +_,7 @@
  
          if (objective != null) {
              if (this.trackedObjectives.contains(objective)) {
@@ -45,16 +47,7 @@
              } else {
                  this.startTrackingObjective(objective);
              }
-@@ -98,7 +98,7 @@
-     @Override
-     public boolean addPlayerToTeam(String scoreHolderName, PlayerTeam team) {
-         if (super.addPlayerToTeam(scoreHolderName, team)) {
--            this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD));
-+            this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.ADD)); // CraftBukkit
-             this.setDirty();
-             return true;
-         } else {
-@@ -106,13 +106,43 @@
+@@ -114,14 +_,42 @@
          }
      }
  
@@ -78,13 +71,17 @@
 +    // Paper end - Multiple Entries with Scoreboards
 +
      @Override
-     public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) {
-         super.removePlayerFromTeam(scoreHolderName, team);
--        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE));
-+        this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, scoreHolderName, ClientboundSetPlayerTeamPacket.Action.REMOVE)); // CraftBukkit
-         this.setDirty();
-     }
- 
+     public void removePlayerFromTeam(String username, PlayerTeam playerTeam) {
+         super.removePlayerFromTeam(username, playerTeam);
+-        this.server
+-            .getPlayerList()
+-            .broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerTeam, username, ClientboundSetPlayerTeamPacket.Action.REMOVE));
+-        this.setDirty();
+-    }
++        this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerTeam, username, ClientboundSetPlayerTeamPacket.Action.REMOVE)); // CraftBukkit
++        this.setDirty();
++    }
++
 +    // Paper start - Multiple Entries with Scoreboards
 +    public void removePlayersFromTeam(java.util.Collection<String> players, PlayerTeam team) {
 +        for (String playerName : players) {
@@ -95,11 +92,10 @@
 +        this.setDirty();
 +    }
 +    // Paper end - Multiple Entries with Scoreboards
-+
+ 
      @Override
      public void onObjectiveAdded(Objective objective) {
-         super.onObjectiveAdded(objective);
-@@ -123,7 +153,7 @@
+@@ -133,7 +_,7 @@
      public void onObjectiveChanged(Objective objective) {
          super.onObjectiveChanged(objective);
          if (this.trackedObjectives.contains(objective)) {
@@ -108,61 +104,61 @@
          }
  
          this.setDirty();
-@@ -142,21 +172,21 @@
+@@ -152,21 +_,21 @@
      @Override
-     public void onTeamAdded(PlayerTeam team) {
-         super.onTeamAdded(team);
--        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
-+        this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); // CraftBukkit
+     public void onTeamAdded(PlayerTeam playerTeam) {
+         super.onTeamAdded(playerTeam);
+-        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true));
++        this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true)); // CraftBukkit
          this.setDirty();
      }
  
      @Override
-     public void onTeamChanged(PlayerTeam team) {
-         super.onTeamChanged(team);
--        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
-+        this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false)); // CraftBukkit
+     public void onTeamChanged(PlayerTeam playerTeam) {
+         super.onTeamChanged(playerTeam);
+-        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, false));
++        this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, false)); // CraftBukkit
          this.setDirty();
      }
  
      @Override
-     public void onTeamRemoved(PlayerTeam team) {
-         super.onTeamRemoved(team);
--        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team));
-+        this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(team)); // CraftBukkit
+     public void onTeamRemoved(PlayerTeam playerTeam) {
+         super.onTeamRemoved(playerTeam);
+-        this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam));
++        this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam)); // CraftBukkit
          this.setDirty();
      }
  
-@@ -207,6 +237,7 @@
+@@ -209,6 +_,7 @@
+         List<Packet<?>> startTrackingPackets = this.getStartTrackingPackets(objective);
  
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-+            if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
-             Iterator iterator1 = list.iterator();
+         for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
++            if (serverPlayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+             for (Packet<?> packet : startTrackingPackets) {
+                 serverPlayer.connection.send(packet);
+             }
+@@ -234,6 +_,7 @@
+         List<Packet<?>> stopTrackingPackets = this.getStopTrackingPackets(objective);
  
-             while (iterator1.hasNext()) {
-@@ -243,6 +274,7 @@
- 
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-+            if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
-             Iterator iterator1 = list.iterator();
- 
-             while (iterator1.hasNext()) {
-@@ -287,6 +319,16 @@
-         return this.createData().load(nbt, registries);
+         for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
++            if (serverPlayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+             for (Packet<?> packet : stopTrackingPackets) {
+                 serverPlayer.connection.send(packet);
+             }
+@@ -267,6 +_,16 @@
+     private ScoreboardSaveData createData(CompoundTag tag, HolderLookup.Provider registries) {
+         return this.createData().load(tag, registries);
      }
- 
++
 +    // CraftBukkit start - Send to players
-+    private void broadcastAll(Packet packet) {
-+        for (ServerPlayer entityplayer : (List<ServerPlayer>) this.server.getPlayerList().players) {
-+            if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) {
-+                entityplayer.connection.send(packet);
++    private void broadcastAll(Packet<?> packet) {
++        for (ServerPlayer serverPlayer : this.server.getPlayerList().players) {
++            if (serverPlayer.getBukkitEntity().getScoreboard().getHandle() == this) {
++                serverPlayer.connection.send(packet);
 +            }
 +        }
 +    }
 +    // CraftBukkit end
-+
-     public static enum Method {
  
-         CHANGE, REMOVE;
+     public static enum Method {
+         CHANGE,
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ServerTickRateManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
similarity index 74%
rename from paper-server/patches/unapplied/net/minecraft/server/ServerTickRateManager.java.patch
rename to paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
index 9720b17dc9..d5767c2dab 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/ServerTickRateManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/ServerTickRateManager.java
 +++ b/net/minecraft/server/ServerTickRateManager.java
-@@ -59,8 +59,14 @@
+@@ -58,8 +_,14 @@
      }
  
      public boolean stopSprinting() {
@@ -16,34 +16,31 @@
              return true;
          } else {
              return false;
-@@ -78,7 +84,7 @@
+@@ -76,14 +_,20 @@
          return flag;
      }
  
 -    private void finishTickSprint() {
 +    private void finishTickSprint(boolean sendLog) { // CraftBukkit - add sendLog parameter
-         long i = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
-         double d0 = Math.max(1.0D, (double) this.sprintTimeSpend) / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND;
-         int j = (int) ((double) (TimeUtil.MILLISECONDS_PER_SECOND * i) / d0);
-@@ -86,9 +92,13 @@
- 
+         long l = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
+         double d = Math.max(1.0, (double)this.sprintTimeSpend) / TimeUtil.NANOSECONDS_PER_MILLISECOND;
+         int i = (int)(TimeUtil.MILLISECONDS_PER_SECOND * l / d);
+         String string = String.format("%.2f", l == 0L ? this.millisecondsPerTick() : d / l);
          this.scheduledCurrentSprintTicks = 0L;
          this.sprintTimeSpend = 0L;
--        this.server.createCommandSourceStack().sendSuccess(() -> {
--            return Component.translatable("commands.tick.sprint.report", j, s);
--        }, true);
+-        this.server.createCommandSourceStack().sendSuccess(() -> Component.translatable("commands.tick.sprint.report", i, string), true);
 +        // CraftBukkit start - add sendLog parameter
 +        if (sendLog) {
 +            this.server.createCommandSourceStack().sendSuccess(() -> {
-+                return Component.translatable("commands.tick.sprint.report", j, s);
++                return Component.translatable("commands.tick.sprint.report", i, string);
 +            }, true);
 +        }
 +        // CraftBukkit end
          this.remainingSprintTicks = 0L;
          this.setFrozen(this.previousIsFrozen);
          this.server.onTickRateChanged();
-@@ -102,7 +112,7 @@
-             --this.remainingSprintTicks;
+@@ -97,7 +_,7 @@
+             this.remainingSprintTicks--;
              return true;
          } else {
 -            this.finishTickSprint();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/Services.java.patch b/paper-server/patches/sources/net/minecraft/server/Services.java.patch
similarity index 84%
rename from paper-server/patches/unapplied/net/minecraft/server/Services.java.patch
rename to paper-server/patches/sources/net/minecraft/server/Services.java.patch
index c556957aab..86364d06cd 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/Services.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Services.java.patch
@@ -1,38 +1,38 @@
 --- a/net/minecraft/server/Services.java
 +++ b/net/minecraft/server/Services.java
-@@ -10,16 +10,32 @@
- import net.minecraft.server.players.GameProfileCache;
+@@ -11,15 +_,31 @@
  import net.minecraft.util.SignatureValidator;
  
-+
  public record Services(
 -    MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache
 +    MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache, @javax.annotation.Nullable io.papermc.paper.configuration.PaperConfigurations paperConfigurations // Paper - add paper configuration files
  ) {
 -    private static final String USERID_CACHE_FILE = "usercache.json";
+-
+-    public static Services create(YggdrasilAuthenticationService authenticationService, File profileRepository) {
++    public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public
++
 +    // Paper start - add paper configuration files
 +    public Services(MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache) {
 +        this(sessionService, servicesKeySet, profileRepository, profileCache, null);
 +    }
- 
--    public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory) {
++
 +    @Override
 +    public io.papermc.paper.configuration.PaperConfigurations paperConfigurations() {
 +        return java.util.Objects.requireNonNull(this.paperConfigurations);
 +    }
 +    // Paper end - add paper configuration files
-+    public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public
 +
-+    public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter
++    public static Services create(YggdrasilAuthenticationService authenticationService, File profileRepository, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - add optionset to load paper config files; add userCacheFile parameter
          MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService();
          GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository();
--        GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json"));
+-        GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(profileRepository, "usercache.json"));
 -        return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache);
 +        GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - use specified user cache file
 +        // Paper start - load paper config files from cli options
 +        final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath();
 +        final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath();
-+        io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings"));
++        io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, profileRepository.toPath(), (File) optionSet.valueOf("spigot-settings"));
 +        return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache, paperConfigurations);
 +        // Paper end - load paper config files from cli options
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/WorldLoader.java.patch b/paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/server/WorldLoader.java.patch
rename to paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch
index c95f9d743c..a2c0814152 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/WorldLoader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/WorldLoader.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/WorldLoader.java
 +++ b/net/minecraft/server/WorldLoader.java
-@@ -37,7 +37,7 @@
+@@ -37,7 +_,7 @@
              CloseableResourceManager closeableResourceManager = pair.getSecond();
              LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.createRegistryAccess();
              List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(
 -                closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC)
 +                closeableResourceManager, layeredRegistryAccess.getLayer(RegistryLayer.STATIC), io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL // Paper - tag lifecycle - add cause
              );
-             RegistryAccess.Frozen frozen = layeredRegistryAccess.getAccessForLoading(RegistryLayer.WORLDGEN);
-             List<HolderLookup.RegistryLookup<?>> list2 = TagLoader.buildUpdatedLookups(frozen, list);
+             RegistryAccess.Frozen accessForLoading = layeredRegistryAccess.getAccessForLoading(RegistryLayer.WORLDGEN);
+             List<HolderLookup.RegistryLookup<?>> list1 = TagLoader.buildUpdatedLookups(accessForLoading, list);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/Main.java.patch b/paper-server/patches/unapplied/net/minecraft/server/Main.java.patch
deleted file mode 100644
index a9ae49e37f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/Main.java.patch
+++ /dev/null
@@ -1,290 +0,0 @@
---- a/net/minecraft/server/Main.java
-+++ b/net/minecraft/server/Main.java
-@@ -38,6 +38,7 @@
- import net.minecraft.server.dedicated.DedicatedServerProperties;
- import net.minecraft.server.dedicated.DedicatedServerSettings;
- import net.minecraft.server.level.progress.LoggerChunkProgressListener;
-+import net.minecraft.server.packs.PackType;
- import net.minecraft.server.packs.repository.PackRepository;
- import net.minecraft.server.packs.repository.ServerPacksSource;
- import net.minecraft.util.Mth;
-@@ -55,22 +56,31 @@
- import net.minecraft.world.level.levelgen.WorldOptions;
- import net.minecraft.world.level.levelgen.presets.WorldPresets;
- import net.minecraft.world.level.storage.LevelDataAndDimensions;
-+import net.minecraft.world.level.storage.LevelResource;
- import net.minecraft.world.level.storage.LevelStorageSource;
- import net.minecraft.world.level.storage.LevelSummary;
- import net.minecraft.world.level.storage.PrimaryLevelData;
--import net.minecraft.world.level.storage.WorldData;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import com.google.common.base.Charsets;
-+import java.io.InputStreamReader;
-+import java.util.concurrent.atomic.AtomicReference;
-+import net.minecraft.SharedConstants;
-+import org.bukkit.configuration.file.YamlConfiguration;
-+// CraftBukkit end
-+
- public class Main {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
- 
-     public Main() {}
- 
--    @SuppressForbidden(a = "System.out needed before bootstrap")
-+    @SuppressForbidden(reason = "System.out needed before bootstrap") // CraftBukkit - decompile error
-     @DontObfuscate
--    public static void main(String[] args) {
-+    public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring)
-         SharedConstants.tryDetectVersion();
-+        /* CraftBukkit start - Replace everything
-         OptionParser optionparser = new OptionParser();
-         OptionSpec<Void> optionspec = optionparser.accepts("nogui");
-         OptionSpec<Void> optionspec1 = optionparser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
-@@ -90,50 +100,104 @@
-         OptionSpec<String> optionspec15 = optionparser.nonOptions();
- 
-         try {
--            OptionSet optionset = optionparser.parse(args);
-+            OptionSet optionset = optionparser.parse(astring);
- 
-             if (optionset.has(optionspec8)) {
-                 optionparser.printHelpOn(System.err);
-                 return;
-             }
-+            */ // CraftBukkit end
- 
--            Path path = (Path) optionset.valueOf(optionspec14);
-+        try {
- 
-+            Path path = (Path) optionset.valueOf("pidFile"); // CraftBukkit
-+
-             if (path != null) {
-                 Main.writePidFile(path);
-             }
- 
-             CrashReport.preload();
--            if (optionset.has(optionspec13)) {
-+            if (optionset.has("jfrProfile")) { // CraftBukkit
-                 JvmProfiler.INSTANCE.start(Environment.SERVER);
-             }
- 
-+            io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper
-             Bootstrap.bootStrap();
-             Bootstrap.validate();
-             Util.startTimerHackThread();
-             Path path1 = Paths.get("server.properties");
--            DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(path1);
-+            DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support
- 
-             dedicatedserversettings.forceSave();
-             RegionFileVersion.configure(dedicatedserversettings.getProperties().regionFileComression);
-             Path path2 = Paths.get("eula.txt");
-             Eula eula = new Eula(path2);
-+            // Paper start - load config files early for access below if needed
-+            org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("bukkit-settings"));
-+            org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("spigot-settings"));
-+            // Paper end - load config files early for access below if needed
- 
--            if (optionset.has(optionspec1)) {
-+            if (optionset.has("initSettings")) { // CraftBukkit
-+                // CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present
-+                File configFile = (File) optionset.valueOf("bukkit-settings");
-+                YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
-+                configuration.options().copyDefaults(true);
-+                configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8)));
-+                configuration.save(configFile);
-+
-+                File commandFile = (File) optionset.valueOf("commands-settings");
-+                YamlConfiguration commandsConfiguration = YamlConfiguration.loadConfiguration(commandFile);
-+                commandsConfiguration.options().copyDefaults(true);
-+                commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)));
-+                commandsConfiguration.save(commandFile);
-+                // CraftBukkit end
-                 Main.LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath());
-                 return;
-             }
- 
--            if (!eula.hasAgreedToEULA()) {
-+            // Spigot Start
-+            boolean eulaAgreed = Boolean.getBoolean( "com.mojang.eula.agree" );
-+            if ( eulaAgreed )
-+            {
-+                System.err.println( "You have used the Spigot command line EULA agreement flag." );
-+                System.err.println( "By using this setting you are indicating your agreement to Mojang's EULA (https://account.mojang.com/documents/minecraft_eula)." );
-+                System.err.println( "If you do not agree to the above EULA please stop your server and remove this flag immediately." );
-+            }
-+            // Spigot End
-+            if (!eula.hasAgreedToEULA() && !eulaAgreed) { // Spigot
-                 Main.LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
-                 return;
-             }
- 
--            File file = new File((String) optionset.valueOf(optionspec9));
--            Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
--            String s = (String) Optional.ofNullable((String) optionset.valueOf(optionspec10)).orElse(dedicatedserversettings.getProperties().levelName);
-+            // Paper start - Detect headless JRE
-+            String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
-+            if (awtException != null) {
-+                Main.LOGGER.error("You are using a headless JRE distribution.");
-+                Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function.");
-+                Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install");
-+                Main.LOGGER.error("");
-+                Main.LOGGER.error(awtException);
-+                return;
-+            }
-+            // Paper end - Detect headless JRE
-+
-+            org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init
-+            // Paper start - fix SPIGOT-5824
-+            File file;
-+            File userCacheFile = new File(Services.USERID_CACHE_FILE);
-+            if (optionset.has("universe")) {
-+                file = (File) optionset.valueOf("universe"); // CraftBukkit
-+                userCacheFile = new File(file, Services.USERID_CACHE_FILE);
-+            } else {
-+                file = new File(bukkitConfiguration.getString("settings.world-container", "."));
-+            }
-+            // Paper end - fix SPIGOT-5824
-+            Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - pass OptionSet to load paper config files; override authentication service; fix world-container
-+            // CraftBukkit start
-+            String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName);
-             LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath());
--            LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s);
-+            LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s, LevelStem.OVERWORLD);
-+            // CraftBukkit end
-             Dynamic dynamic;
- 
-             if (convertable_conversionsession.hasWorldData()) {
-@@ -174,13 +238,31 @@
-             }
- 
-             Dynamic<?> dynamic1 = dynamic;
--            boolean flag = optionset.has(optionspec7);
-+            boolean flag = optionset.has("safeMode"); // CraftBukkit
- 
-             if (flag) {
-                 Main.LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
-             }
- 
-             PackRepository resourcepackrepository = ServerPacksSource.createPackRepository(convertable_conversionsession);
-+            // CraftBukkit start
-+            File bukkitDataPackFolder = new File(convertable_conversionsession.getLevelPath(LevelResource.DATAPACK_DIR).toFile(), "bukkit");
-+            if (!bukkitDataPackFolder.exists()) {
-+                bukkitDataPackFolder.mkdirs();
-+            }
-+            File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta");
-+            try {
-+                com.google.common.io.Files.write("{\n"
-+                        + "    \"pack\": {\n"
-+                        + "        \"description\": \"Data pack for resources provided by Bukkit plugins\",\n"
-+                        + "        \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n"
-+                        + "    }\n"
-+                        + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8);
-+            } catch (java.io.IOException ex) {
-+                throw new RuntimeException("Could not initialize Bukkit datapack", ex);
-+            }
-+            AtomicReference<WorldLoader.DataLoadContext> worldLoader = new AtomicReference<>();
-+            // CraftBukkit end
- 
-             WorldStem worldstem;
- 
-@@ -189,6 +271,7 @@
- 
-                 worldstem = (WorldStem) Util.blockUntilDone((executor) -> {
-                     return WorldLoader.load(worldloader_c, (worldloader_a) -> {
-+                        worldLoader.set(worldloader_a); // CraftBukkit
-                         Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
- 
-                         if (dynamic1 != null) {
-@@ -201,7 +284,7 @@
-                             WorldOptions worldoptions;
-                             WorldDimensions worlddimensions;
- 
--                            if (optionset.has(optionspec2)) {
-+                            if (optionset.has("demo")) { // CraftBukkit
-                                 worldsettings = MinecraftServer.DEMO_SETTINGS;
-                                 worldoptions = WorldOptions.DEMO_OPTIONS;
-                                 worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
-@@ -209,7 +292,7 @@
-                                 DedicatedServerProperties dedicatedserverproperties = dedicatedserversettings.getProperties();
- 
-                                 worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
--                                worldoptions = optionset.has(optionspec3) ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
-+                                worldoptions = optionset.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; // CraftBukkit
-                                 worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
-                             }
- 
-@@ -225,32 +308,47 @@
-                 return;
-             }
- 
--            RegistryAccess.Frozen iregistrycustom_dimension = worldstem.registries().compositeAccess();
-+            /*
-+            IRegistryCustom.Dimension iregistrycustom_dimension = worldstem.registries().compositeAccess();
-             boolean flag1 = optionset.has(optionspec6);
- 
-             if (optionset.has(optionspec4) || flag1) {
--                Main.forceUpgrade(convertable_conversionsession, DataFixers.getDataFixer(), optionset.has(optionspec5), () -> {
-+                forceUpgrade(convertable_conversionsession, DataConverterRegistry.getDataFixer(), optionset.has(optionspec5), () -> {
-                     return true;
-                 }, iregistrycustom_dimension, flag1);
-             }
- 
--            WorldData savedata = worldstem.worldData();
-+            SaveData savedata = worldstem.worldData();
- 
-             convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
-+            */
-             final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
--                DedicatedServer dedicatedserver1 = new DedicatedServer(thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
-+                DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
- 
-+                /*
-                 dedicatedserver1.setPort((Integer) optionset.valueOf(optionspec11));
--                dedicatedserver1.setDemo(optionset.has(optionspec2));
-+                */
-+                dedicatedserver1.setDemo(optionset.has("demo")); // Paper
-+                /*
-                 dedicatedserver1.setId((String) optionset.valueOf(optionspec12));
--                boolean flag2 = !optionset.has(optionspec) && !optionset.valuesOf(optionspec15).contains("nogui");
-+                */
-+                boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui");
- 
-+                if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui
-                 if (flag2 && !GraphicsEnvironment.isHeadless()) {
-                     dedicatedserver1.showGui();
-                 }
- 
-+                if (optionset.has("port")) {
-+                    int port = (Integer) optionset.valueOf("port");
-+                    if (port > 0) {
-+                        dedicatedserver1.setPort(port);
-+                    }
-+                }
-+
-                 return dedicatedserver1;
-             });
-+            /* CraftBukkit start
-             Thread thread = new Thread("Server Shutdown Thread") {
-                 public void run() {
-                     dedicatedserver.halt(true);
-@@ -259,6 +357,7 @@
- 
-             thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(Main.LOGGER));
-             Runtime.getRuntime().addShutdownHook(thread);
-+            */ // CraftBukkit end
-         } catch (Exception exception1) {
-             Main.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", exception1);
-         }
-@@ -295,7 +394,7 @@
-     }
- 
-     public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, RegistryAccess dynamicRegistryManager, boolean recreateRegionFiles) {
--        Main.LOGGER.info("Forcing world upgrade!");
-+        Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit
-         WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dynamicRegistryManager, eraseCache, recreateRegionFiles);
- 
-         try {
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ReloadableServerRegistries.java.patch b/paper-server/patches/unapplied/net/minecraft/server/ReloadableServerRegistries.java.patch
deleted file mode 100644
index 6c906a073c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/ReloadableServerRegistries.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/server/ReloadableServerRegistries.java
-+++ b/net/minecraft/server/ReloadableServerRegistries.java
-@@ -50,8 +50,9 @@
-         );
-         HolderLookup.Provider provider = HolderLookup.Provider.create(list.stream());
-         RegistryOps<JsonElement> registryOps = provider.createSerializationContext(JsonOps.INSTANCE);
-+        final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryOps.lookupProvider); // Paper
-         List<CompletableFuture<WritableRegistry<?>>> list2 = LootDataType.values()
--            .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor))
-+            .map(type -> scheduleRegistryLoad((LootDataType<?>)type, registryOps, resourceManager, prepareExecutor, conversions)) // Paper
-             .toList();
-         CompletableFuture<List<WritableRegistry<?>>> completableFuture = Util.sequence(list2);
-         return completableFuture.thenApplyAsync(
-@@ -60,14 +61,15 @@
-     }
- 
-     private static <T> CompletableFuture<WritableRegistry<?>> scheduleRegistryLoad(
--        LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor
-+        LootDataType<T> type, RegistryOps<JsonElement> ops, ResourceManager resourceManager, Executor prepareExecutor, io.papermc.paper.registry.data.util.Conversions conversions // Paper
-     ) {
-         return CompletableFuture.supplyAsync(() -> {
-             WritableRegistry<T> writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental());
-+            io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry
-             Map<ResourceLocation, T> map = new HashMap<>();
-             SimpleJsonResourceReloadListener.scanDirectory(resourceManager, type.registryKey(), ops, type.codec(), map);
--            map.forEach((id, value) -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO));
--            TagLoader.loadTagsForRegistry(resourceManager, writableRegistry);
-+            map.forEach((id, value) -> io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(writableRegistry, ResourceKey.create(type.registryKey(), id), value, DEFAULT_REGISTRATION_INFO, conversions)); // Paper - register with listeners
-+            TagLoader.loadTagsForRegistry(resourceManager, writableRegistry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // Paper - tag life cycle - reload
-             return writableRegistry;
-         }, prepareExecutor);
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/ServerFunctionLibrary.java.patch b/paper-server/patches/unapplied/net/minecraft/server/ServerFunctionLibrary.java.patch
deleted file mode 100644
index a5f6fd5796..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/ServerFunctionLibrary.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/server/ServerFunctionLibrary.java
-+++ b/net/minecraft/server/ServerFunctionLibrary.java
-@@ -113,7 +113,7 @@
-                             return null;
-                         }).join());
-                     this.functions = builder.build();
--                    this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)intermediate.getFirst());
-+                    this.tags = this.tagsLoader.build((Map<ResourceLocation, List<TagLoader.EntryWithSource>>)intermediate.getFirst(), null); // Paper - command function tags are not implemented yet
-                 },
-                 applyExecutor
-             );

From d064a57573d9fef31ddf40555478c7e64fecd885 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:24:28 -0800
Subject: [PATCH 039/285] fix a bunch of imports

---
 .../server/MinecraftServer.java.patch         | 328 +++---------------
 1 file changed, 45 insertions(+), 283 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index 21203c0786..ee2f5f535b 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -1,41 +1,5 @@
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -3,6 +_,9 @@
- import com.google.common.base.Preconditions;
- import com.google.common.base.Splitter;
- import com.google.common.collect.ImmutableList;
-+import co.aikar.timings.Timings;
-+import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
-+import com.google.common.base.Stopwatch;
- import com.google.common.collect.Lists;
- import com.google.common.collect.Maps;
- import com.google.common.collect.Sets;
-@@ -80,17 +_,6 @@
- import net.minecraft.obfuscate.DontObfuscate;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.resources.ResourceLocation;
--import net.minecraft.server.bossevents.CustomBossEvents;
--import net.minecraft.server.level.DemoMode;
--import net.minecraft.server.level.PlayerRespawnLogic;
--import net.minecraft.server.level.ServerChunkCache;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.server.level.ServerPlayer;
--import net.minecraft.server.level.ServerPlayerGameMode;
--import net.minecraft.server.level.progress.ChunkProgressListener;
--import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
--import net.minecraft.server.network.ServerConnectionListener;
--import net.minecraft.server.network.TextFilter;
- import net.minecraft.server.packs.PackType;
- import net.minecraft.server.packs.repository.Pack;
- import net.minecraft.server.packs.repository.PackRepository;
-@@ -111,6 +_,7 @@
- import net.minecraft.util.RandomSource;
- import net.minecraft.util.SignatureValidator;
- import net.minecraft.util.TimeUtil;
-+import net.minecraft.util.datafix.DataFixers;
- import net.minecraft.util.debugchart.RemoteDebugSampleType;
- import net.minecraft.util.debugchart.SampleLogger;
- import net.minecraft.util.debugchart.TpsDebugDimensions;
 @@ -174,11 +_,13 @@
  import org.slf4j.Logger;
  
@@ -51,23 +15,8 @@
      private static final int OVERLOADED_TICKS_THRESHOLD = 20;
      private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
      private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
-@@ -204,8 +_,8 @@
-     @Nullable
-     private MinecraftServer.TimeProfiler debugCommandProfiler;
-     private boolean debugCommandProfilerDelayStart;
--    private ServerConnectionListener connection;
--    public final ChunkProgressListenerFactory progressListenerFactory;
-+    private net.minecraft.server.network.ServerConnectionListener connection;
-+    public final net.minecraft.server.level.progress.ChunkProgressListenerFactory progressListenerFactory;
-     @Nullable
-     private ServerStatus status;
-     @Nullable
-@@ -215,9 +_,10 @@
-     private String localIp;
-     private int port = -1;
-     private final LayeredRegistryAccess<RegistryLayer> registries;
--    private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
-+    private Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> levels = Maps.newLinkedHashMap();
+@@ -218,6 +_,7 @@
+     private Map<ResourceKey<Level>, ServerLevel> levels = Maps.newLinkedHashMap();
      private PlayerList playerList;
      private volatile boolean running = true;
 +    private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
@@ -92,15 +41,6 @@
      @Nullable
      private KeyPair keyPair;
      @Nullable
-@@ -252,7 +_,7 @@
-     private final ServerScoreboard scoreboard = new ServerScoreboard(this);
-     @Nullable
-     private CommandStorage commandStorage;
--    private final CustomBossEvents customBossEvents = new CustomBossEvents();
-+    private final net.minecraft.server.bossevents.CustomBossEvents customBossEvents = new net.minecraft.server.bossevents.CustomBossEvents();
-     private final ServerFunctionManager functionManager;
-     private boolean enforceWhitelist;
-     private float smoothedTickTimeMillis;
 @@ -271,10 +_,33 @@
      private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
      private final DiscontinuousFrame tickFrame;
@@ -146,12 +86,8 @@
          Thread serverThread,
          LevelStorageSource.LevelStorageAccess storageSource,
          PackRepository packRepository,
-@@ -293,12 +_,13 @@
-         Proxy proxy,
-         DataFixer fixerUpper,
-         Services services,
--        ChunkProgressListenerFactory progressListenerFactory
-+        net.minecraft.server.level.progress.ChunkProgressListenerFactory progressListenerFactory
+@@ -296,9 +_,10 @@
+         ChunkProgressListenerFactory progressListenerFactory
      ) {
          super("Server");
 +        SERVER = this; // Paper - better singleton
@@ -260,7 +196,7 @@
 +        Registry<LevelStem> dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
 +        for (LevelStem worldDimension : dimensions) {
 +            ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
-+            net.minecraft.server.level.ServerLevel world;
++            ServerLevel world;
 +            int dimension = 0;
 +
 +            if (dimensionKey == LevelStem.NETHER) {
@@ -419,15 +355,15 @@
 +                this.worldData = worlddata;
 +                this.worldData.setGameType(((net.minecraft.server.dedicated.DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
 +
-+                net.minecraft.server.level.progress.ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +
-+                world = new net.minecraft.server.level.ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
 +                DimensionDataStorage worldpersistentdata = world.getDataStorage();
 +                this.readScoreboard(worldpersistentdata);
 +                this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
 +                this.commandStorage = new CommandStorage(worldpersistentdata);
 +            } else {
-+                net.minecraft.server.level.progress.ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +                // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
 +                final List<CustomSpawner> spawners;
 +                if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
@@ -435,7 +371,7 @@
 +                } else {
 +                    spawners = Collections.emptyList();
 +                }
-+                world = new net.minecraft.server.level.ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
 +                // Paper end - option to use the dimension_type to check if spawners should be added
 +            }
 +
@@ -451,7 +387,7 @@
 +            }
 +        }
 +        this.forceDifficulty();
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
++        for (ServerLevel serverLevel : this.getAllLevels()) {
 +            this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
 +            serverLevel.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
 +            this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
@@ -482,7 +418,7 @@
 +        this.connection.acceptConnections();
 +    }
 +
-+    public void initWorld(net.minecraft.server.level.ServerLevel serverLevel, ServerLevelData serverLevelData, WorldData saveData, WorldOptions worldOptions) {
++    public void initWorld(ServerLevel serverLevel, ServerLevelData serverLevelData, WorldData saveData, WorldOptions worldOptions) {
 +        boolean isDebugWorld = saveData.isDebugWorld();
 +        if (serverLevel.generator != null) {
 +            serverLevel.getWorld().getPopulators().addAll(serverLevel.generator.getDefaultPopulators(serverLevel.getWorld()));
@@ -535,14 +471,12 @@
      }
 +    // CraftBukkit end
  
--    private static void setInitialSpawn(ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
-+    private static void setInitialSpawn(net.minecraft.server.level.ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
+     private static void setInitialSpawn(ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
          if (debug) {
              levelData.setSpawn(BlockPos.ZERO.above(80), 0.0F);
          } else {
--            ServerChunkCache chunkSource = level.getChunkSource();
+             ServerChunkCache chunkSource = level.getChunkSource();
 -            ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition());
-+            net.minecraft.server.level.ServerChunkCache chunkSource = level.getChunkSource();
 +            // CraftBukkit start
 +            if (level.generator != null) {
 +                java.util.Random rand = new java.util.Random(level.getSeed());
@@ -562,15 +496,6 @@
              int spawnHeight = chunkSource.getGenerator().getSpawnHeight(level);
              if (spawnHeight < level.getMinY()) {
                  BlockPos worldPosition = chunkPos.getWorldPosition();
-@@ -458,7 +_,7 @@
- 
-             for (int i4 = 0; i4 < Mth.square(11); i4++) {
-                 if (i >= -5 && i <= 5 && i1 >= -5 && i1 <= 5) {
--                    BlockPos spawnPosInChunk = PlayerRespawnLogic.getSpawnPosInChunk(level, new ChunkPos(chunkPos.x + i, chunkPos.z + i1));
-+                    BlockPos spawnPosInChunk = net.minecraft.server.level.PlayerRespawnLogic.getSpawnPosInChunk(level, new ChunkPos(chunkPos.x + i, chunkPos.z + i1));
-                     if (spawnPosInChunk != null) {
-                         levelData.setSpawn(spawnPosInChunk, 0.0F);
-                         break;
 @@ -495,26 +_,31 @@
          serverLevelData.setGameType(GameType.SPECTATOR);
      }
@@ -578,20 +503,18 @@
 -    public void prepareLevels(ChunkProgressListener listener) {
 -        ServerLevel serverLevel = this.overworld();
 +    // CraftBukkit start
-+    public void prepareLevels(net.minecraft.server.level.progress.ChunkProgressListener listener, net.minecraft.server.level.ServerLevel serverLevel) {
++    public void prepareLevels(ChunkProgressListener listener, ServerLevel serverLevel) {
 +        this.forceTicks = true;
 +        // CraftBukkit end
          LOGGER.info("Preparing start region for dimension {}", serverLevel.dimension().location());
          BlockPos sharedSpawnPos = serverLevel.getSharedSpawnPos();
          listener.updateSpawnPos(new ChunkPos(sharedSpawnPos));
--        ServerChunkCache chunkSource = serverLevel.getChunkSource();
-+        net.minecraft.server.level.ServerChunkCache chunkSource = serverLevel.getChunkSource();
+         ServerChunkCache chunkSource = serverLevel.getChunkSource();
          this.nextTickTimeNanos = Util.getNanos();
          serverLevel.setDefaultSpawnPos(sharedSpawnPos, serverLevel.getSharedSpawnAngle());
 -        int _int = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS);
--        int i = _int > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(_int)) : 0;
 +        int _int = serverLevel.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS); // CraftBukkit - per-world
-+        int i = _int > 0 ? Mth.square(net.minecraft.server.level.progress.ChunkProgressListener.calculateDiameter(_int)) : 0;
+         int i = _int > 0 ? Mth.square(ChunkProgressListener.calculateDiameter(_int)) : 0;
  
          while (chunkSource.getTickingGenerated() < i) {
 -            this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
@@ -608,7 +531,7 @@
  
 -        for (ServerLevel serverLevel1 : this.levels.values()) {
 +        if (true) {
-+            net.minecraft.server.level.ServerLevel serverLevel1 = serverLevel;
++            ServerLevel serverLevel1 = serverLevel;
 +            // CraftBukkit end
              ForcedChunksSavedData forcedChunksSavedData = serverLevel1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
              if (forcedChunksSavedData != null) {
@@ -634,16 +557,7 @@
      }
  
      public GameType getDefaultGameType() {
-@@ -550,7 +_,7 @@
-     public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
-         boolean flag = false;
- 
--        for (ServerLevel serverLevel : this.getAllLevels()) {
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
-             if (!suppressLog) {
-                 LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
-             }
-@@ -559,13 +_,16 @@
+@@ -559,11 +_,14 @@
              flag = true;
          }
  
@@ -656,12 +570,9 @@
 +         */
 +        // CraftBukkit end
          if (flush) {
--            for (ServerLevel serverLevel2 : this.getAllLevels()) {
-+            for (net.minecraft.server.level.ServerLevel serverLevel2 : this.getAllLevels()) {
+             for (ServerLevel serverLevel2 : this.getAllLevels()) {
                  LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", serverLevel2.getChunkSource().chunkMap.getStorageName());
-             }
- 
-@@ -593,23 +_,51 @@
+@@ -593,18 +_,46 @@
          this.stopServer();
      }
  
@@ -709,30 +620,6 @@
          }
  
          LOGGER.info("Saving worlds");
- 
--        for (ServerLevel serverLevel : this.getAllLevels()) {
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
-             if (serverLevel != null) {
-                 serverLevel.noSave = false;
-             }
-@@ -618,7 +_,7 @@
-         while (this.levels.values().stream().anyMatch(level -> level.getChunkSource().chunkMap.hasWork())) {
-             this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
- 
--            for (ServerLevel serverLevelx : this.getAllLevels()) {
-+            for (net.minecraft.server.level.ServerLevel serverLevelx : this.getAllLevels()) {
-                 serverLevelx.getChunkSource().removeTicketsOnClosing();
-                 serverLevelx.getChunkSource().tick(() -> true, false);
-             }
-@@ -628,7 +_,7 @@
- 
-         this.saveAllChunks(false, true, false);
- 
--        for (ServerLevel serverLevelx : this.getAllLevels()) {
-+        for (net.minecraft.server.level.ServerLevel serverLevelx : this.getAllLevels()) {
-             if (serverLevelx != null) {
-                 try {
-                     serverLevelx.close();
 @@ -646,6 +_,15 @@
          } catch (IOException var4) {
              LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), var4);
@@ -930,8 +817,7 @@
          } else {
 +            boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
              if (this.tickRateManager.isSprinting() || this.haveTime()) {
--                for (ServerLevel serverLevel : this.getAllLevels()) {
-+                for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+                 for (ServerLevel serverLevel : this.getAllLevels()) {
                      if (serverLevel.getChunkSource().pollTask()) {
 -                        return true;
 +                        ret = true; // Paper - force execution of all worlds, do not just bias the first
@@ -973,7 +859,7 @@
 +                while ((task = this.processQueue.poll()) != null) {
 +                    task.run();
 +                }
-+                for (final net.minecraft.server.level.ServerLevel level : this.levels.values()) {
++                for (final ServerLevel level : this.levels.values()) {
 +                    // process unloads
 +                    level.getChunkSource().tick(() -> true, false);
 +                }
@@ -1039,13 +925,7 @@
              Optional.of(players),
              Optional.of(ServerStatus.Version.current()),
              Optional.ofNullable(this.statusIcon),
-@@ -1024,17 +_,17 @@
-     }
- 
-     private ServerStatus.Players buildPlayerStatus() {
--        List<ServerPlayer> players = this.playerList.getPlayers();
-+        List<net.minecraft.server.level.ServerPlayer> players = this.playerList.getPlayers();
-         int maxPlayers = this.getMaxPlayers();
+@@ -1029,7 +_,7 @@
          if (this.hidesOnlinePlayers()) {
              return new ServerStatus.Players(maxPlayers, players.size(), List.of());
          } else {
@@ -1054,12 +934,6 @@
              ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
              int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
  
-             for (int i = 0; i < min; i++) {
--                ServerPlayer serverPlayer = players.get(randomInt + i);
-+                net.minecraft.server.level.ServerPlayer serverPlayer = players.get(randomInt + i);
-                 list.add(serverPlayer.allowsListing() ? serverPlayer.getGameProfile() : ANONYMOUS_PLAYER_PROFILE);
-             }
- 
 @@ -1046,17 +_,64 @@
      protected void tickChildren(BooleanSupplier hasTimeLeft) {
          ProfilerFiller profilerFiller = Profiler.get();
@@ -1084,7 +958,6 @@
          this.getFunctions().tick();
          profilerFiller.popPush("levels");
  
--        for (ServerLevel serverLevel : this.getAllLevels()) {
 +        // CraftBukkit start
 +        // Run tasks that are waiting on processing
 +        while (!this.processQueue.isEmpty()) {
@@ -1093,16 +966,16 @@
 +
 +        // Send time updates to everyone, it will get the right time from the world the player is in.
 +        // Paper start - Perf: Optimize time updates
-+        for (final net.minecraft.server.level.ServerLevel level : this.getAllLevels()) {
++        for (final ServerLevel level : this.getAllLevels()) {
 +            final boolean doDaylight = level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT);
 +            final long dayTime = level.getDayTime();
 +            long worldTime = level.getGameTime();
 +            final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
 +            for (Player entityhuman : level.players()) {
-+                if (!(entityhuman instanceof net.minecraft.server.level.ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
++                if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
 +                    continue;
 +                }
-+                net.minecraft.server.level.ServerPlayer entityplayer = (net.minecraft.server.level.ServerPlayer) entityhuman;
++                ServerPlayer entityplayer = (ServerPlayer) entityhuman;
 +                long playerTime = entityplayer.getPlayerTime();
 +                ClientboundSetTimePacket packet = (playerTime == dayTime) ? worldPacket :
 +                    new ClientboundSetTimePacket(worldTime, playerTime, doDaylight);
@@ -1112,7 +985,7 @@
 +        }
 +
 +        this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
+         for (ServerLevel serverLevel : this.getAllLevels()) {
 +            serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
 +            serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
              profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
@@ -1136,59 +1009,21 @@
  
          profilerFiller.popPush("connection");
          this.tickConnection();
-@@ -1088,7 +_,7 @@
- 
-         profilerFiller.popPush("send chunks");
- 
--        for (ServerPlayer serverPlayer : this.playerList.getPlayers()) {
-+        for (net.minecraft.server.level.ServerPlayer serverPlayer : this.playerList.getPlayers()) {
-             serverPlayer.connection.chunkSender.sendNextChunks(serverPlayer);
-             serverPlayer.connection.resumeFlushing();
-         }
-@@ -1100,7 +_,7 @@
-         this.getConnection().tick();
-     }
- 
--    private void synchronizeTime(ServerLevel level) {
-+    private void synchronizeTime(net.minecraft.server.level.ServerLevel level) {
-         this.playerList
-             .broadcastAll(
-                 new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)),
-@@ -1112,7 +_,7 @@
-         ProfilerFiller profilerFiller = Profiler.get();
-         profilerFiller.push("timeSync");
- 
--        for (ServerLevel serverLevel : this.getAllLevels()) {
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
-             this.synchronizeTime(serverLevel);
-         }
- 
-@@ -1139,20 +_,36 @@
-         return this.getServerDirectory().resolve(path);
-     }
- 
--    public final ServerLevel overworld() {
-+    public final net.minecraft.server.level.ServerLevel overworld() {
-         return this.levels.get(Level.OVERWORLD);
-     }
- 
-     @Nullable
--    public ServerLevel getLevel(ResourceKey<Level> dimension) {
-+    public net.minecraft.server.level.ServerLevel getLevel(ResourceKey<Level> dimension) {
+@@ -1148,6 +_,22 @@
          return this.levels.get(dimension);
      }
  
 +    // CraftBukkit start
-+    public void addLevel(net.minecraft.server.level.ServerLevel level) {
-+        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> oldLevels = this.levels;
-+        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++    public void addLevel(ServerLevel level) {
++        Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
++        Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
 +        newLevels.put(level.dimension(), level);
 +        this.levels = Collections.unmodifiableMap(newLevels);
 +    }
 +
-+    public void removeLevel(net.minecraft.server.level.ServerLevel level) {
-+        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> oldLevels = this.levels;
-+        Map<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++    public void removeLevel(ServerLevel level) {
++        Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
++        Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
 +        newLevels.remove(level.dimension());
 +        this.levels = Collections.unmodifiableMap(newLevels);
 +    }
@@ -1197,12 +1032,6 @@
      public Set<ResourceKey<Level>> levelKeys() {
          return this.levels.keySet();
      }
- 
--    public Iterable<ServerLevel> getAllLevels() {
-+    public Iterable<net.minecraft.server.level.ServerLevel> getAllLevels() {
-         return this.levels.values();
-     }
- 
 @@ -1177,7 +_,7 @@
  
      @DontObfuscate
@@ -1231,7 +1060,7 @@
 -            this.updateMobSpawningFlags();
 -            this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
 +    // Paper start - per level difficulty
-+    public void setDifficulty(net.minecraft.server.level.ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
++    public void setDifficulty(ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
 +        net.minecraft.world.level.storage.PrimaryLevelData worldData = level.serverLevelData;
 +        if (forceUpdate || !worldData.isDifficultyLocked()) {
 +            worldData.setDifficulty(worldData.isHardcore() ? Difficulty.HARD : difficulty);
@@ -1241,26 +1070,15 @@
          }
      }
  
-@@ -1263,8 +_,8 @@
-     }
+@@ -1264,7 +_,7 @@
  
      private void updateMobSpawningFlags() {
--        for (ServerLevel serverLevel : this.getAllLevels()) {
+         for (ServerLevel serverLevel : this.getAllLevels()) {
 -            serverLevel.setSpawnSettings(this.isSpawningMonsters());
-+        for (net.minecraft.server.level.ServerLevel serverLevel : this.getAllLevels()) {
 +            serverLevel.setSpawnSettings(serverLevel.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
          }
      }
  
-@@ -1273,7 +_,7 @@
-         this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
-     }
- 
--    private void sendDifficultyUpdate(ServerPlayer player) {
-+    private void sendDifficultyUpdate(net.minecraft.server.level.ServerPlayer player) {
-         LevelData levelData = player.level().getLevelData();
-         player.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
-     }
 @@ -1340,10 +_,20 @@
  
      @Override
@@ -1283,26 +1101,15 @@
          this.motd = motd;
      }
  
-@@ -1365,8 +_,8 @@
-         this.worldData.setGameType(gameMode);
+@@ -1366,7 +_,7 @@
      }
  
--    public ServerConnectionListener getConnection() {
+     public ServerConnectionListener getConnection() {
 -        return this.connection;
-+    public net.minecraft.server.network.ServerConnectionListener getConnection() {
-+        return this.connection == null ? this.connection = new net.minecraft.server.network.ServerConnectionListener(this) : this.connection; // Spigot
++        return this.connection == null ? this.connection = new ServerConnectionListener(this) : this.connection; // Spigot
      }
  
      public boolean isReady() {
-@@ -1389,7 +_,7 @@
-         return 16;
-     }
- 
--    public boolean isUnderSpawnProtection(ServerLevel level, BlockPos pos, Player player) {
-+    public boolean isUnderSpawnProtection(net.minecraft.server.level.ServerLevel level, BlockPos pos, Player player) {
-         return false;
-     }
- 
 @@ -1452,7 +_,7 @@
      @Override
      public void executeIfPossible(Runnable task) {
@@ -1312,15 +1119,6 @@
          } else {
              super.executeIfPossible(task);
          }
-@@ -1479,7 +_,7 @@
-         return this.fixerUpper;
-     }
- 
--    public int getSpawnRadius(@Nullable ServerLevel level) {
-+    public int getSpawnRadius(@Nullable net.minecraft.server.level.ServerLevel level) {
-         return level != null ? level.getGameRules().getInt(GameRules.RULE_SPAWN_RADIUS) : 10;
-     }
- 
 @@ -1491,7 +_,13 @@
          return this.functionManager;
      }
@@ -1382,42 +1180,14 @@
              UserWhiteList whiteList = playerList.getWhiteList();
 +            if (!((net.minecraft.server.dedicated.DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
  
--            for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
+             for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
 -                if (!whiteList.isWhiteListed(serverPlayer.getGameProfile())) {
 -                    serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted"));
-+            for (net.minecraft.server.level.ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
 +                if (!whiteList.isWhiteListed(serverPlayer.getGameProfile()) && !this.getPlayerList().isOp(serverPlayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420)
 +                    serverPlayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
                  }
              }
          }
-@@ -1670,7 +_,7 @@
-     }
- 
-     public CommandSourceStack createCommandSourceStack() {
--        ServerLevel serverLevel = this.overworld();
-+        net.minecraft.server.level.ServerLevel serverLevel = this.overworld();
-         return new CommandSourceStack(
-             this,
-             serverLevel == null ? Vec3.ZERO : Vec3.atLowerCornerOf(serverLevel.getSharedSpawnPos()),
-@@ -1717,7 +_,7 @@
-         return this.overworld().getGameRules();
-     }
- 
--    public CustomBossEvents getCustomBossEvents() {
-+    public net.minecraft.server.bossevents.CustomBossEvents getCustomBossEvents() {
-         return this.customBossEvents;
-     }
- 
-@@ -1771,7 +_,7 @@
-         Path path1 = path.resolve("levels");
- 
-         try {
--            for (Entry<ResourceKey<Level>, ServerLevel> entry : this.levels.entrySet()) {
-+            for (Entry<ResourceKey<Level>, net.minecraft.server.level.ServerLevel> entry : this.levels.entrySet()) {
-                 ResourceLocation resourceLocation = entry.getKey().location();
-                 Path path2 = path1.resolve(resourceLocation.getNamespace()).resolve(resourceLocation.getPath());
-                 Files.createDirectories(path2);
 @@ -1859,6 +_,22 @@
          }
      }
@@ -1441,20 +1211,12 @@
      private ProfilerFiller createProfiler() {
          if (this.willStartRecordingMetrics) {
              this.metricsRecorder = ActiveMetricsRecorder.createStarted(
-@@ -1936,12 +_,12 @@
-         return this.resources.managers.fullRegistries();
+@@ -1941,7 +_,7 @@
      }
  
--    public TextFilter createTextFilterForPlayer(ServerPlayer player) {
--        return TextFilter.DUMMY;
-+    public net.minecraft.server.network.TextFilter createTextFilterForPlayer(net.minecraft.server.level.ServerPlayer player) {
-+        return net.minecraft.server.network.TextFilter.DUMMY;
-     }
- 
--    public ServerPlayerGameMode createGameModeForPlayer(ServerPlayer player) {
+     public ServerPlayerGameMode createGameModeForPlayer(ServerPlayer player) {
 -        return (ServerPlayerGameMode)(this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
-+    public net.minecraft.server.level.ServerPlayerGameMode createGameModeForPlayer(net.minecraft.server.level.ServerPlayer player) {
-+        return (net.minecraft.server.level.ServerPlayerGameMode)(this.isDemo() ? new net.minecraft.server.level.DemoMode(player) : new net.minecraft.server.level.ServerPlayerGameMode(player));
++        return (this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
      }
  
      @Nullable
@@ -1489,7 +1251,7 @@
      }
  
 -    public void subscribeToDebugSample(ServerPlayer player, RemoteDebugSampleType sampleType) {
-+    public void subscribeToDebugSample(net.minecraft.server.level.ServerPlayer player, RemoteDebugSampleType sampleType) {
++    public void subscribeToDebugSample(ServerPlaye player, RemoteDebugSampleType sampleType) {
      }
  
      public boolean acceptsTransfers() {

From c62af3a5b7dcf5abf9e702a78c150f526f1b30a8 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:35:46 -0800
Subject: [PATCH 040/285] net.minecraft.world.level.saveddata.maps

---
 .../maps/MapItemSavedData.java.patch          | 175 ++++++++++++++
 .../maps/MapItemSavedData.java.patch          | 216 ------------------
 2 files changed, 175 insertions(+), 216 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
new file mode 100644
index 0000000000..b03e362a20
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
@@ -0,0 +1,175 @@
+--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -68,6 +_,13 @@
+     private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
+     private int trackedDecorationCount;
+ 
++    // CraftBukkit start
++    public final org.bukkit.craftbukkit.map.CraftMapView mapView;
++    private org.bukkit.craftbukkit.CraftServer server;
++    public java.util.UUID uniqueId = null;
++    public MapId id;
++    // CraftBukkit end
++
+     public static SavedData.Factory<MapItemSavedData> factory() {
+         return new SavedData.Factory<>(() -> {
+             throw new IllegalStateException("Should never create an empty map saved data");
+@@ -82,6 +_,10 @@
+         this.trackingPosition = trackingPosition;
+         this.unlimitedTracking = unlimitedTracking;
+         this.locked = locked;
++        // CraftBukkit start
++        this.mapView = new org.bukkit.craftbukkit.map.CraftMapView(this);
++        this.server = (org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer();
++        // CraftBukkit end
+     }
+ 
+     public static MapItemSavedData createFresh(
+@@ -100,9 +_,47 @@
+     }
+ 
+     public static MapItemSavedData load(CompoundTag tag, HolderLookup.Provider levelRegistry) {
+-        ResourceKey<Level> resourceKey = DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, tag.get("dimension")))
+-            .resultOrPartial(LOGGER::error)
+-            .orElseThrow(() -> new IllegalArgumentException("Invalid map dimension: " + tag.get("dimension")));
++        // Paper start - fix "Not a string" spam
++        Tag dimension = tag.get("dimension");
++        if (dimension instanceof final net.minecraft.nbt.NumericTag numericTag && numericTag.getAsInt() >= org.bukkit.craftbukkit.CraftWorld.CUSTOM_DIMENSION_OFFSET) {
++            long least = tag.getLong("UUIDLeast");
++            long most = tag.getLong("UUIDMost");
++
++            if (least != 0L && most != 0L) {
++                java.util.UUID uuid = new java.util.UUID(most, least);
++                org.bukkit.craftbukkit.CraftWorld world = (org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(uuid);
++                if (world != null) {
++                    dimension = net.minecraft.nbt.StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH));
++                } else {
++                    dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
++                }
++            } else {
++                dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
++            }
++        }
++        com.mojang.serialization.DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error
++        // Paper end - fix "Not a string" spam
++        // CraftBukkit start
++        ResourceKey<Level> resourceKey = dataresult.resultOrPartial(LOGGER::error).orElseGet(() -> {
++            long least = tag.getLong("UUIDLeast");
++            long most = tag.getLong("UUIDMost");
++
++            if (least != 0L && most != 0L) {
++                java.util.UUID uniqueId = new java.util.UUID(most, least);
++
++                org.bukkit.craftbukkit.CraftWorld world = (org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(uniqueId);
++                // Check if the stored world details are correct.
++                if (world == null) {
++                    /* All Maps which do not have their valid world loaded are set to a dimension which hopefully won't be reached.
++                       This is to prevent them being corrupted with the wrong map data. */
++                    // PAIL: Use Vanilla exception handling for now
++                } else {
++                    return world.getHandle().dimension();
++                }
++            }
++            throw new IllegalArgumentException("Invalid map dimension: " + String.valueOf(tag.get("dimension")));
++            // CraftBukkit end
++        });
+         int _int = tag.getInt("xCenter");
+         int _int1 = tag.getInt("zCenter");
+         byte b = (byte)Mth.clamp(tag.getByte("scale"), 0, 4);
+@@ -154,6 +_,25 @@
+             .encodeStart(NbtOps.INSTANCE, this.dimension.location())
+             .resultOrPartial(LOGGER::error)
+             .ifPresent(dimension -> tag.put("dimension", dimension));
++        // CraftBukkit start
++        if (true) {
++            if (this.uniqueId == null) {
++                for (org.bukkit.World world : this.server.getWorlds()) {
++                    org.bukkit.craftbukkit.CraftWorld cWorld = (org.bukkit.craftbukkit.CraftWorld) world;
++                    if (cWorld.getHandle().dimension() == this.dimension) {
++                        this.uniqueId = cWorld.getUID();
++                        break;
++                    }
++                }
++            }
++            /* Perform a second check to see if a matching world was found, this is a necessary
++               change incase Maps are forcefully unlinked from a World and lack a UID.*/
++            if (this.uniqueId != null) {
++                tag.putLong("UUIDLeast", this.uniqueId.getLeastSignificantBits());
++                tag.putLong("UUIDMost", this.uniqueId.getMostSignificantBits());
++            }
++        }
++        // CraftBukkit end
+         tag.putInt("xCenter", this.centerX);
+         tag.putInt("zCenter", this.centerZ);
+         tag.putByte("scale", this.scale);
+@@ -233,10 +_,12 @@
+             }
+ 
+             MapFrame mapFrame1 = new MapFrame(pos, frame.getDirection().get2DDataValue() * 90, frame.getId());
++            if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps
+             this.addDecoration(
+                 MapDecorationTypes.FRAME, player.level(), getFrameKey(frame.getId()), pos.getX(), pos.getZ(), frame.getDirection().get2DDataValue() * 90, null
+             );
+             this.frameMarkers.put(mapFrame1.getId(), mapFrame1);
++            } // Paper - Limit item frame cursors on maps
+         }
+ 
+         MapDecorations mapDecorations = mapStack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY);
+@@ -421,7 +_,7 @@
+                 return true;
+             }
+ 
+-            if (!this.isTrackedCountOverLimit(256)) {
++            if (!this.isTrackedCountOverLimit(((Level) accessor).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps
+                 this.bannerMarkers.put(mapBanner.getId(), mapBanner);
+                 this.addDecoration(mapBanner.getDecoration(), accessor, mapBanner.getId(), d, d1, 180.0, mapBanner.name().orElse(null));
+                 return true;
+@@ -521,7 +_,7 @@
+             this.player = player;
+         }
+ 
+-        private MapItemSavedData.MapPatch createPatch() {
++        private MapItemSavedData.MapPatch createPatch(byte[] buffer) { // CraftBukkit
+             int i = this.minDirtyX;
+             int i1 = this.minDirtyY;
+             int i2 = this.maxDirtyX + 1 - this.minDirtyX;
+@@ -530,7 +_,7 @@
+ 
+             for (int i4 = 0; i4 < i2; i4++) {
+                 for (int i5 = 0; i5 < i3; i5++) {
+-                    bytes[i4 + i5 * i2] = MapItemSavedData.this.colors[i + i4 + (i1 + i5) * 128];
++                    bytes[i4 + i5 * i2] = buffer[i + i4 + (i1 + i5) * 128]; // CraftBukkit
+                 }
+             }
+ 
+@@ -540,17 +_,27 @@
+         @Nullable
+         Packet<?> nextUpdatePacket(MapId mapId) {
+             MapItemSavedData.MapPatch mapPatch;
++            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
+             if (this.dirtyData) {
+                 this.dirtyData = false;
+-                mapPatch = this.createPatch();
++                mapPatch = this.createPatch(render.buffer); // CraftBukkit
+             } else {
+                 mapPatch = null;
+             }
+ 
+             Collection<MapDecoration> collection;
+-            if (this.dirtyDecorations && this.tick++ % 5 == 0) {
++            if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet
+                 this.dirtyDecorations = false;
+-                collection = MapItemSavedData.this.decorations.values();
++                // CraftBukkit start
++                java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
++
++                for (org.bukkit.map.MapCursor cursor : render.cursors) {
++                    if (cursor.isVisible()) {
++                        icons.add(new MapDecoration(org.bukkit.craftbukkit.map.CraftMapCursor.CraftType.bukkitToMinecraftHolder(cursor.getType()), cursor.getX(), cursor.getY(), cursor.getDirection(), Optional.ofNullable(io.papermc.paper.adventure.PaperAdventure.asVanilla(cursor.caption()))));
++                    }
++                }
++                collection = icons;
++                // CraftBukkit end
+             } else {
+                 collection = null;
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
deleted file mode 100644
index 452050da4c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
+++ /dev/null
@@ -1,216 +0,0 @@
---- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-@@ -47,6 +47,17 @@
- import net.minecraft.world.level.saveddata.SavedData;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import io.papermc.paper.adventure.PaperAdventure; // Paper
-+import java.util.UUID;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.CraftServer;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.map.CraftMapCursor;
-+import org.bukkit.craftbukkit.map.CraftMapView;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
-+// CraftBukkit end
-+
- public class MapItemSavedData extends SavedData {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -70,6 +81,13 @@
-     private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
-     private int trackedDecorationCount;
- 
-+    // CraftBukkit start
-+    public final CraftMapView mapView;
-+    private CraftServer server;
-+    public UUID uniqueId = null;
-+    public MapId id;
-+    // CraftBukkit end
-+
-     public static SavedData.Factory<MapItemSavedData> factory() {
-         return new SavedData.Factory<>(() -> {
-             throw new IllegalStateException("Should never create an empty map saved data");
-@@ -84,6 +102,10 @@
-         this.trackingPosition = showDecorations;
-         this.unlimitedTracking = unlimitedTracking;
-         this.locked = locked;
-+        // CraftBukkit start
-+        this.mapView = new CraftMapView(this);
-+        this.server = (CraftServer) org.bukkit.Bukkit.getServer();
-+        // CraftBukkit end
-     }
- 
-     public static MapItemSavedData createFresh(double centerX, double centerZ, byte scale, boolean showDecorations, boolean unlimitedTracking, ResourceKey<Level> dimension) {
-@@ -101,12 +123,49 @@
-     }
- 
-     public static MapItemSavedData load(CompoundTag nbt, HolderLookup.Provider registries) {
--        DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension")));
-+        // Paper start - fix "Not a string" spam
-+        Tag dimension = nbt.get("dimension");
-+        if (dimension instanceof final net.minecraft.nbt.NumericTag numericTag && numericTag.getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) {
-+            long least = nbt.getLong("UUIDLeast");
-+            long most = nbt.getLong("UUIDMost");
-+
-+            if (least != 0L && most != 0L) {
-+                UUID uuid = new UUID(most, least);
-+                CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid);
-+                if (world != null) {
-+                    dimension = net.minecraft.nbt.StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH));
-+                } else {
-+                    dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
-+                }
-+            } else {
-+                dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
-+            }
-+        }
-+        DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error
-+        // Paper end - fix "Not a string" spam
-         Logger logger = MapItemSavedData.LOGGER;
- 
-         Objects.requireNonNull(logger);
--        ResourceKey<Level> resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElseThrow(() -> {
--            return new IllegalArgumentException("Invalid map dimension: " + String.valueOf(nbt.get("dimension")));
-+        // CraftBukkit start
-+        ResourceKey<Level> resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElseGet(() -> {
-+            long least = nbt.getLong("UUIDLeast");
-+            long most = nbt.getLong("UUIDMost");
-+
-+            if (least != 0L && most != 0L) {
-+                UUID uniqueId = new UUID(most, least);
-+
-+                CraftWorld world = (CraftWorld) Bukkit.getWorld(uniqueId);
-+                // Check if the stored world details are correct.
-+                if (world == null) {
-+                    /* All Maps which do not have their valid world loaded are set to a dimension which hopefully won't be reached.
-+                       This is to prevent them being corrupted with the wrong map data. */
-+                    // PAIL: Use Vanilla exception handling for now
-+                } else {
-+                    return world.getHandle().dimension();
-+                }
-+            }
-+            throw new IllegalArgumentException("Invalid map dimension: " + String.valueOf(nbt.get("dimension")));
-+            // CraftBukkit end
-         });
-         int i = nbt.getInt("xCenter");
-         int j = nbt.getInt("zCenter");
-@@ -131,7 +190,8 @@
-             MapBanner mapiconbanner = (MapBanner) iterator.next();
- 
-             worldmap.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner);
--            worldmap.addDecoration(mapiconbanner.getDecoration(), (LevelAccessor) null, mapiconbanner.getId(), (double) mapiconbanner.pos().getX(), (double) mapiconbanner.pos().getZ(), 180.0D, (Component) mapiconbanner.name().orElse((Object) null));
-+            // CraftBukkit - decompile error
-+            worldmap.addDecoration(mapiconbanner.getDecoration(), (LevelAccessor) null, mapiconbanner.getId(), (double) mapiconbanner.pos().getX(), (double) mapiconbanner.pos().getZ(), 180.0D, (Component) mapiconbanner.name().orElse(null));
-         }
- 
-         ListTag nbttaglist = nbt.getList("frames", 10);
-@@ -150,13 +210,32 @@
- 
-     @Override
-     public CompoundTag save(CompoundTag nbt, HolderLookup.Provider registries) {
--        DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.dimension.location());
-+        DataResult<Tag> dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.dimension.location()); // CraftBukkit - decompile error
-         Logger logger = MapItemSavedData.LOGGER;
- 
-         Objects.requireNonNull(logger);
-         dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
-             nbt.put("dimension", nbtbase);
-         });
-+        // CraftBukkit start
-+        if (true) {
-+            if (this.uniqueId == null) {
-+                for (org.bukkit.World world : this.server.getWorlds()) {
-+                    CraftWorld cWorld = (CraftWorld) world;
-+                    if (cWorld.getHandle().dimension() == this.dimension) {
-+                        this.uniqueId = cWorld.getUID();
-+                        break;
-+                    }
-+                }
-+            }
-+            /* Perform a second check to see if a matching world was found, this is a necessary
-+               change incase Maps are forcefully unlinked from a World and lack a UID.*/
-+            if (this.uniqueId != null) {
-+                nbt.putLong("UUIDLeast", this.uniqueId.getLeastSignificantBits());
-+                nbt.putLong("UUIDMost", this.uniqueId.getMostSignificantBits());
-+            }
-+        }
-+        // CraftBukkit end
-         nbt.putInt("xCenter", this.centerX);
-         nbt.putInt("zCenter", this.centerZ);
-         nbt.putByte("scale", this.scale);
-@@ -247,8 +326,10 @@
- 
-             MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId());
- 
-+            if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps
-             this.addDecoration(MapDecorationTypes.FRAME, player.level(), MapItemSavedData.getFrameKey(entityitemframe.getId()), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null);
-             this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1);
-+            } // Paper - Limit item frame cursors on maps
-         }
- 
-         MapDecorations mapdecorations = (MapDecorations) stack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY);
-@@ -441,9 +522,9 @@
-                 return true;
-             }
- 
--            if (!this.isTrackedCountOverLimit(256)) {
-+            if (!this.isTrackedCountOverLimit(((Level) world).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps
-                 this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner);
--                this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse((Object) null));
-+                this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse(null)); // CraftBukkit - decompile error
-                 return true;
-             }
-         }
-@@ -554,7 +635,7 @@
-             this.player = entityhuman;
-         }
- 
--        private MapItemSavedData.MapPatch createPatch() {
-+        private MapItemSavedData.MapPatch createPatch(byte[] buffer) { // CraftBukkit
-             int i = this.minDirtyX;
-             int j = this.minDirtyY;
-             int k = this.maxDirtyX + 1 - this.minDirtyX;
-@@ -563,7 +644,7 @@
- 
-             for (int i1 = 0; i1 < k; ++i1) {
-                 for (int j1 = 0; j1 < l; ++j1) {
--                    abyte[i1 + j1 * k] = MapItemSavedData.this.colors[i + i1 + (j + j1) * 128];
-+                    abyte[i1 + j1 * k] = buffer[i + i1 + (j + j1) * 128]; // CraftBukkit
-                 }
-             }
- 
-@@ -573,19 +654,29 @@
-         @Nullable
-         Packet<?> nextUpdatePacket(MapId mapId) {
-             MapItemSavedData.MapPatch worldmap_c;
-+            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
- 
-             if (this.dirtyData) {
-                 this.dirtyData = false;
--                worldmap_c = this.createPatch();
-+                worldmap_c = this.createPatch(render.buffer); // CraftBukkit
-             } else {
-                 worldmap_c = null;
-             }
- 
-             Collection collection;
- 
--            if (this.dirtyDecorations && this.tick++ % 5 == 0) {
-+            if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet
-                 this.dirtyDecorations = false;
--                collection = MapItemSavedData.this.decorations.values();
-+                // CraftBukkit start
-+                java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
-+
-+                for (org.bukkit.map.MapCursor cursor : render.cursors) {
-+                    if (cursor.isVisible()) {
-+                        icons.add(new MapDecoration(CraftMapCursor.CraftType.bukkitToMinecraftHolder(cursor.getType()), cursor.getX(), cursor.getY(), cursor.getDirection(), Optional.ofNullable(PaperAdventure.asVanilla(cursor.caption()))));
-+                    }
-+                }
-+                collection = icons;
-+                // CraftBukkit end
-             } else {
-                 collection = null;
-             }

From 9793846a7e93d64e74ef2eb03acda187d1f5c906 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:42:44 -0800
Subject: [PATCH 041/285] net.minecraft.world.level.redstone

---
 .../CollectingNeighborUpdater.java.patch      |  6 +--
 .../DefaultRedstoneWireEvaluator.java.patch   | 20 ++++++++
 ...perimentalRedstoneWireEvaluator.java.patch | 20 ++++++++
 .../level/redstone/NeighborUpdater.java.patch | 32 ++++++++++++
 .../DefaultRedstoneWireEvaluator.java.patch   | 31 ------------
 ...perimentalRedstoneWireEvaluator.java.patch | 31 ------------
 .../level/redstone/NeighborUpdater.java.patch | 50 -------------------
 7 files changed, 75 insertions(+), 115 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch (79%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/redstone/NeighborUpdater.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch
similarity index 79%
rename from paper-server/patches/unapplied/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch
index d055cf38c0..3fd8f69544 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
 +++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
-@@ -135,7 +135,7 @@
+@@ -133,7 +_,7 @@
                  orientation = this.orientation.withFront(direction);
              }
  
--            NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false);
-+            NeighborUpdater.executeUpdate(world, blockState, blockPos, this.sourceBlock, orientation, false, this.sourcePos); // Paper - Add source block to BlockPhysicsEvent
+-            NeighborUpdater.executeUpdate(level, blockState, blockPos, this.sourceBlock, orientation, false);
++            NeighborUpdater.executeUpdate(level, blockState, blockPos, this.sourceBlock, orientation, false, this.sourcePos); // Paper - Add source block to BlockPhysicsEvent
              if (this.idx < NeighborUpdater.UPDATE_ORDER.length && NeighborUpdater.UPDATE_ORDER[this.idx] == this.skipDirection) {
                  this.idx++;
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
new file mode 100644
index 0000000000..f5b42d763b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
++++ b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
+@@ -17,7 +_,16 @@
+     @Override
+     public void updatePowerStrength(Level level, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean updateShape) {
+         int i = this.calculateTargetStrength(level, pos);
+-        if (state.getValue(RedStoneWireBlock.POWER) != i) {
++        // CraftBukkit start
++        int oldPower = state.getValue(RedStoneWireBlock.POWER);
++        if (oldPower != i) {
++            org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), oldPower, i);
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            i = event.getNewCurrent();
++        }
++        if (oldPower != i) {
++            // CraftBukkit end
+             if (level.getBlockState(pos) == state) {
+                 level.setBlock(pos, state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(i)), 2);
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
new file mode 100644
index 0000000000..a5eebb497d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java
++++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java
+@@ -36,7 +_,16 @@
+             int intValue = entry.getIntValue();
+             int i = unpackPower(intValue);
+             BlockState blockState = level.getBlockState(blockPos);
+-            if (blockState.is(this.wireBlock) && !blockState.getValue(RedStoneWireBlock.POWER).equals(i)) {
++            // CraftBukkit start
++            int oldPower = blockState.getValue(RedStoneWireBlock.POWER); // Paper - Call BlockRedstoneEvent properly; get the previous power from the right state
++            if (oldPower != i) {
++                org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos), oldPower, i);
++                level.getCraftServer().getPluginManager().callEvent(event);
++
++                i = event.getNewCurrent();
++            }
++            if (blockState.is((net.minecraft.world.level.block.Block) this.wireBlock) && oldPower != i) {
++                // CraftBukkit end
+                 int i1 = 2;
+                 if (!updateShape || !flag) {
+                     i1 |= 128;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
new file mode 100644
index 0000000000..e4a2c7c33c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/redstone/NeighborUpdater.java
++++ b/net/minecraft/world/level/redstone/NeighborUpdater.java
+@@ -42,8 +_,29 @@
+     }
+ 
+     static void executeUpdate(Level level, BlockState state, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
++        // Paper start - Add source block to BlockPhysicsEvent
++        executeUpdate(level, state, pos, neighborBlock, orientation, movedByPiston, pos);
++    }
++
++    static void executeUpdate(Level level, BlockState state, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston, BlockPos sourcePos) {
++        // Paper end - Add source block to BlockPhysicsEvent
+         try {
++            // CraftBukkit start
++            org.bukkit.craftbukkit.CraftWorld cworld = level.getWorld();
++            if (cworld != null) {
++                org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(state), org.bukkit.craftbukkit.block.CraftBlock.at(level, sourcePos)); // Paper - Add source block to BlockPhysicsEvent
++                level.getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    return;
++                }
++            }
++            // CraftBukkit end
+             state.handleNeighborChanged(level, pos, neighborBlock, orientation, movedByPiston);
++            // Spigot Start
++        } catch (StackOverflowError ex) {
++            level.lastPhysicsProblem = new BlockPos(pos);
++            // Spigot End
+         } catch (Throwable var9) {
+             CrashReport crashReport = CrashReport.forThrowable(var9, "Exception while updating neighbours");
+             CrashReportCategory crashReportCategory = crashReport.addCategory("Block being updated");
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
deleted file mode 100644
index 8862572ac8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
-+++ b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
-@@ -9,6 +9,10 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.RedStoneWireBlock;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+// CraftBukkit end
- 
- public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator {
- 
-@@ -20,7 +24,16 @@
-     public void updatePowerStrength(Level world, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) {
-         int i = this.calculateTargetStrength(world, pos);
- 
--        if ((Integer) state.getValue(RedStoneWireBlock.POWER) != i) {
-+        // CraftBukkit start
-+        int oldPower = state.getValue(RedStoneWireBlock.POWER);
-+        if (oldPower != i) {
-+            BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(world, pos), oldPower, i);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            i = event.getNewCurrent();
-+        }
-+        if (oldPower != i) {
-+            // CraftBukkit end
-             if (world.getBlockState(pos) == state) {
-                 world.setBlock(pos, (BlockState) state.setValue(RedStoneWireBlock.POWER, i), 2);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
deleted file mode 100644
index 73e662622f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java
-+++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java
-@@ -16,6 +16,10 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.EnumProperty;
- import net.minecraft.world.level.block.state.properties.RedstoneSide;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+// CraftBukkit end
- 
- public class ExperimentalRedstoneWireEvaluator extends RedstoneWireEvaluator {
- 
-@@ -41,7 +45,16 @@
-             int j = ExperimentalRedstoneWireEvaluator.unpackPower(i);
-             BlockState iblockdata1 = world.getBlockState(blockposition1);
- 
--            if (iblockdata1.is((Block) this.wireBlock) && !((Integer) iblockdata1.getValue(RedStoneWireBlock.POWER)).equals(j)) {
-+            // CraftBukkit start
-+            int oldPower = iblockdata1.getValue(RedStoneWireBlock.POWER); // Paper - Call BlockRedstoneEvent properly; get the previous power from the right state
-+            if (oldPower != j) {
-+                BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(world, blockposition1), oldPower, j);
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                j = event.getNewCurrent();
-+            }
-+            if (iblockdata1.is((Block) this.wireBlock) && oldPower != j) {
-+                // CraftBukkit end
-                 int k = 2;
- 
-                 if (!blockAdded || !flag1) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
deleted file mode 100644
index a8973333ac..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- a/net/minecraft/world/level/redstone/NeighborUpdater.java
-+++ b/net/minecraft/world/level/redstone/NeighborUpdater.java
-@@ -8,11 +8,17 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
- import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.block.data.CraftBlockData;
-+import org.bukkit.event.block.BlockPhysicsEvent;
-+// CraftBukkit end
- 
- public interface NeighborUpdater {
- 
-@@ -49,8 +55,29 @@
-     }
- 
-     static void executeUpdate(Level world, BlockState state, BlockPos pos, Block sourceBlock, @Nullable Orientation orientation, boolean notify) {
-+        // Paper start - Add source block to BlockPhysicsEvent
-+        executeUpdate(world, state, pos, sourceBlock, orientation, notify, pos);
-+    }
-+
-+    static void executeUpdate(Level world, BlockState state, BlockPos pos, Block sourceBlock, @Nullable Orientation orientation, boolean notify, BlockPos sourcePos) {
-+        // Paper end - Add source block to BlockPhysicsEvent
-         try {
-+            // CraftBukkit start
-+            CraftWorld cworld = ((ServerLevel) world).getWorld();
-+            if (cworld != null) {
-+                BlockPhysicsEvent event = new BlockPhysicsEvent(CraftBlock.at(world, pos), CraftBlockData.fromData(state), CraftBlock.at(world, sourcePos)); // Paper - Add source block to BlockPhysicsEvent
-+                ((ServerLevel) world).getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+            }
-+            // CraftBukkit end
-             state.handleNeighborChanged(world, pos, sourceBlock, orientation, notify);
-+            // Spigot Start
-+        } catch (StackOverflowError ex) {
-+            world.lastPhysicsProblem = new BlockPos(pos);
-+            // Spigot End
-         } catch (Throwable throwable) {
-             CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception while updating neighbours");
-             CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being updated");

From aa7204fd62767e48aa5e52bbf0f191ca91318761 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:46:27 -0800
Subject: [PATCH 042/285] net.minecraft.commands.arguments.selector

---
 .../selector/EntitySelector.java.patch        |  2 +-
 .../selector/EntitySelectorParser.java.patch  | 31 +++++++------------
 .../selector/SelectorPattern.java.patch       |  8 ++---
 3 files changed, 16 insertions(+), 25 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/arguments/selector/EntitySelector.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch (53%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch (66%)

diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
index cb23767a27..c89791f903 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/commands/arguments/selector/EntitySelector.java
 +++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
-@@ -93,7 +93,7 @@
+@@ -105,7 +_,7 @@
      }
  
      private void checkPermissions(CommandSourceStack source) throws CommandSyntaxException {
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
index 088ffce992..33d39ea636 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
@@ -1,24 +1,15 @@
 --- a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
 +++ b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
-@@ -133,7 +133,7 @@
-         boolean flag;
+@@ -122,7 +_,7 @@
+     }
  
-         if (source instanceof SharedSuggestionProvider icompletionprovider) {
--            if (icompletionprovider.hasPermission(2)) {
-+            if (source instanceof net.minecraft.commands.CommandSourceStack stack ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") : icompletionprovider.hasPermission(2)) { // Paper - Fix EntityArgument permissions
-                 flag = true;
-                 return flag;
-             }
-@@ -158,7 +158,7 @@
-             axisalignedbb = this.createAabb(this.deltaX == null ? 0.0D : this.deltaX, this.deltaY == null ? 0.0D : this.deltaY, this.deltaZ == null ? 0.0D : this.deltaZ);
-         }
+     public static <S> boolean allowSelectors(S suggestionProvider) {
+-        return suggestionProvider instanceof SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2);
++        return suggestionProvider instanceof net.minecraft.commands.CommandSourceStack stack ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") : suggestionProvider instanceof net.minecraft.commands.SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2); // Paper - Fix EntityArgument permissions
+     }
  
--        Function function;
-+        Function<Vec3, Vec3> function; // CraftBukkit - decompile error
- 
-         if (this.x == null && this.y == null && this.z == null) {
-             function = (vec3d) -> {
-@@ -215,8 +215,10 @@
+     public EntitySelector getSelector() {
+@@ -198,8 +_,10 @@
          };
      }
  
@@ -30,8 +21,8 @@
 +        // CraftBukkit end
          this.suggestions = this::suggestSelector;
          if (!this.reader.canRead()) {
-             throw EntitySelectorParser.ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader);
-@@ -505,6 +507,12 @@
+             throw ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader);
+@@ -467,6 +_,12 @@
      }
  
      public EntitySelector parse() throws CommandSyntaxException {
@@ -44,7 +35,7 @@
          this.startPosition = this.reader.getCursor();
          this.suggestions = this::suggestNameOrSelector;
          if (this.reader.canRead() && this.reader.peek() == '@') {
-@@ -513,7 +521,7 @@
+@@ -475,7 +_,7 @@
              }
  
              this.reader.skip();
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch
similarity index 66%
rename from paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch
index ed6f4da14b..970df07ef3 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/SelectorPattern.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/commands/arguments/selector/SelectorPattern.java
 +++ b/net/minecraft/commands/arguments/selector/SelectorPattern.java
-@@ -13,7 +13,7 @@
-             EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(selector), true);
-             return DataResult.success(new SelectorPattern(selector, entitySelectorParser.parse()));
+@@ -13,7 +_,7 @@
+             EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(pattern), true);
+             return DataResult.success(new SelectorPattern(pattern, entitySelectorParser.parse()));
          } catch (CommandSyntaxException var2) {
--            return DataResult.error(() -> "Invalid selector component: " + selector + ": " + var2.getMessage());
+-            return DataResult.error(() -> "Invalid selector component: " + pattern + ": " + var2.getMessage());
 +            return DataResult.error(() -> "Invalid selector component"); // Paper - limit selector error message
          }
      }

From 368d2116ba27005ca48b85f31795dbee2f56e269 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 13 Dec 2024 19:53:42 -0800
Subject: [PATCH 043/285] net.minecraft.world.entity.raid

---
 .../world/entity/raid/Raid.java.patch         | 136 +++++++++---------
 .../world/entity/raid/Raider.java.patch       |  54 +++++++
 .../world/entity/raid/Raids.java.patch        |  11 +-
 .../world/entity/raid/Raider.java.patch       |  83 -----------
 4 files changed, 128 insertions(+), 156 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/raid/Raid.java.patch (61%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/raid/Raids.java.patch (95%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raider.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raid.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
index 789e719f42..34ed85451b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raid.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
@@ -1,30 +1,30 @@
 --- a/net/minecraft/world/entity/raid/Raid.java
 +++ b/net/minecraft/world/entity/raid/Raid.java
-@@ -107,6 +107,11 @@
+@@ -104,6 +_,11 @@
      private Raid.RaidStatus status;
      private int celebrationTicks;
-     private Optional<BlockPos> waveSpawnPos;
+     private Optional<BlockPos> waveSpawnPos = Optional.empty();
 +    // Paper start
 +    private static final String PDC_NBT_KEY = "BukkitValues";
 +    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry PDC_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
 +    public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY);
 +    // Paper end
  
-     public Raid(int id, ServerLevel world, BlockPos pos) {
-         this.raidEvent = new ServerBossEvent(Raid.RAID_NAME_COMPONENT, BossEvent.BossBarColor.RED, BossEvent.BossBarOverlay.NOTCHED_10);
-@@ -150,6 +155,11 @@
-                 this.heroesOfTheVillage.add(NbtUtils.loadUUID(nbtbase));
+     public Raid(int id, ServerLevel level, BlockPos center) {
+         this.id = id;
+@@ -136,6 +_,11 @@
+                 this.heroesOfTheVillage.add(NbtUtils.loadUUID(tag));
              }
          }
 +        // Paper start
-+        if (nbt.contains(PDC_NBT_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
-+            this.persistentDataContainer.putAll(nbt.getCompound(PDC_NBT_KEY));
++        if (compound.contains(PDC_NBT_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
++            this.persistentDataContainer.putAll(compound.getCompound(PDC_NBT_KEY));
 +        }
 +        // Paper end
- 
      }
  
-@@ -177,6 +187,12 @@
+     public boolean isOver() {
+@@ -162,6 +_,12 @@
          return this.status == Raid.RaidStatus.LOSS;
      }
  
@@ -37,15 +37,15 @@
      public float getTotalHealth() {
          return this.totalHealth;
      }
-@@ -281,6 +297,7 @@
- 
+@@ -252,6 +_,7 @@
+                 boolean flag = this.active;
                  this.active = this.level.hasChunkAt(this.center);
                  if (this.level.getDifficulty() == Difficulty.PEACEFUL) {
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.PEACE); // CraftBukkit
                      this.stop();
                      return;
                  }
-@@ -300,13 +317,16 @@
+@@ -271,13 +_,16 @@
                  if (!this.level.isVillage(this.center)) {
                      if (this.groupsSpawned > 0) {
                          this.status = Raid.RaidStatus.LOSS;
@@ -56,33 +56,33 @@
                      }
                  }
  
-                 ++this.ticksActive;
+                 this.ticksActive++;
                  if (this.ticksActive >= 48000L) {
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.TIMEOUT); // CraftBukkit
                      this.stop();
                      return;
                  }
-@@ -374,6 +394,7 @@
+@@ -346,6 +_,7 @@
                      }
  
-                     if (j > 5) {
+                     if (i > 5) {
 +                        org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE);  // CraftBukkit
                          this.stop();
                          break;
                      }
-@@ -386,6 +407,7 @@
+@@ -357,6 +_,7 @@
+                     } else {
                          this.status = Raid.RaidStatus.VICTORY;
-                         Iterator iterator = this.heroesOfTheVillage.iterator();
  
 +                        List<org.bukkit.entity.Player> winners = new java.util.ArrayList<>(); // CraftBukkit
-                         while (iterator.hasNext()) {
-                             UUID uuid = (UUID) iterator.next();
+                         for (UUID uuid : this.heroesOfTheVillage) {
                              Entity entity = this.level.getEntity(uuid);
-@@ -400,10 +422,12 @@
- 
-                                         entityplayer.awardStat(Stats.RAID_WIN);
-                                         CriteriaTriggers.RAID_WIN.trigger(entityplayer);
-+                                        winners.add(entityplayer.getBukkitEntity()); // CraftBukkit
+                             if (entity instanceof LivingEntity) {
+@@ -368,10 +_,12 @@
+                                     if (livingEntity instanceof ServerPlayer serverPlayer) {
+                                         serverPlayer.awardStat(Stats.RAID_WIN);
+                                         CriteriaTriggers.RAID_WIN.trigger(serverPlayer);
++                                        winners.add(serverPlayer.getBukkitEntity()); // CraftBukkit
                                      }
                                  }
                              }
@@ -91,84 +91,84 @@
                      }
                  }
  
-@@ -411,6 +435,7 @@
+@@ -379,6 +_,7 @@
              } else if (this.isOver()) {
-                 ++this.celebrationTicks;
+                 this.celebrationTicks++;
                  if (this.celebrationTicks >= 600) {
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.FINISHED); // CraftBukkit
                      this.stop();
                      return;
                  }
-@@ -544,6 +569,10 @@
-         int j = araid_wave.length;
-         int k = 0;
+@@ -491,6 +_,10 @@
+         DifficultyInstance currentDifficultyAt = this.level.getCurrentDifficultyAt(pos);
+         boolean shouldSpawnBonusGroup = this.shouldSpawnBonusGroup();
  
 +        // CraftBukkit start
 +        Raider leader = null;
 +        List<Raider> raiders = new java.util.ArrayList<>();
 +        // CraftBukkit end
-         while (k < j) {
-             Raid.RaiderType raid_wave = araid_wave[k];
-             int l = this.getDefaultNumSpawns(raid_wave, i, flag1) + this.getPotentialBonusSpawns(raid_wave, this.random, i, difficultydamagescaler, flag1);
-@@ -559,9 +588,11 @@
-                             entityraider.setPatrolLeader(true);
-                             this.setLeader(i, entityraider);
-                             flag = true;
-+                            leader = entityraider; // CraftBukkit
-                         }
+         for (Raid.RaiderType raiderType : Raid.RaiderType.VALUES) {
+             int i1 = this.getDefaultNumSpawns(raiderType, i, shouldSpawnBonusGroup)
+                 + this.getPotentialBonusSpawns(raiderType, this.random, i, currentDifficultyAt, shouldSpawnBonusGroup);
+@@ -506,9 +_,11 @@
+                     raider.setPatrolLeader(true);
+                     this.setLeader(i, raider);
+                     flag = true;
++                    leader = raider; // CraftBukkit
+                 }
  
-                         this.joinRaid(i, entityraider, pos, false);
-+                        raiders.add(entityraider); // CraftBukkit
-                         if (raid_wave.entityType == EntityType.RAVAGER) {
-                             Raider entityraider1 = null;
- 
-@@ -580,6 +611,7 @@
-                                 this.joinRaid(i, entityraider1, pos, false);
-                                 entityraider1.moveTo(pos, 0.0F, 0.0F);
-                                 entityraider1.startRiding(entityraider);
-+                                raiders.add(entityraider); // CraftBukkit
-                             }
-                         }
- 
-@@ -597,6 +629,7 @@
-         ++this.groupsSpawned;
+                 this.joinRaid(i, raider, pos, false);
++                raiders.add(raider); // CraftBukkit
+                 if (raiderType.entityType == EntityType.RAVAGER) {
+                     Raider raider1 = null;
+                     if (i == this.getNumGroups(Difficulty.NORMAL)) {
+@@ -526,6 +_,7 @@
+                         this.joinRaid(i, raider1, pos, false);
+                         raider1.moveTo(pos, 0.0F, 0.0F);
+                         raider1.startRiding(raider);
++                        raiders.add(raider); // CraftBukkit
+                     }
+                 }
+             }
+@@ -535,6 +_,7 @@
+         this.groupsSpawned++;
          this.updateBossbar();
          this.setDirty();
 +        org.bukkit.craftbukkit.event.CraftEventFactory.callRaidSpawnWaveEvent(this, leader, raiders); // CraftBukkit
      }
  
-     public void joinRaid(int wave, Raider raider, @Nullable BlockPos pos, boolean existing) {
-@@ -612,7 +645,7 @@
-                 raider.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(pos), EntitySpawnReason.EVENT, (SpawnGroupData) null);
+     public void joinRaid(int wave, Raider raider, @Nullable BlockPos pos, boolean isRecruited) {
+@@ -549,7 +_,7 @@
+                 raider.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(pos), EntitySpawnReason.EVENT, null);
                  raider.applyRaidBuffs(this.level, wave, false);
                  raider.setOnGround(true);
 -                this.level.addFreshEntityWithPassengers(raider);
 +                this.level.addFreshEntityWithPassengers(raider, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.RAID); // CraftBukkit
              }
          }
- 
-@@ -839,6 +872,11 @@
+     }
+@@ -770,6 +_,11 @@
          }
  
-         nbt.put("HeroesOfTheVillage", nbttaglist);
+         compound.put("HeroesOfTheVillage", listTag);
 +        // Paper start
 +        if (!this.persistentDataContainer.isEmpty()) {
-+            nbt.put(PDC_NBT_KEY, this.persistentDataContainer.toTagCompound());
++            compound.put(PDC_NBT_KEY, this.persistentDataContainer.toTagCompound());
 +        }
 +        // Paper end
-         return nbt;
+         return compound;
      }
  
-@@ -865,6 +903,12 @@
-         this.heroesOfTheVillage.add(entity.getUUID());
+@@ -802,6 +_,12 @@
+     public void addHeroOfTheVillage(Entity player) {
+         this.heroesOfTheVillage.add(player.getUUID());
      }
- 
++
 +    // CraftBukkit start - a method to get all raiders
 +    public java.util.Collection<Raider> getRaiders() {
 +        return this.groupRaiderMap.values().stream().flatMap(Set::stream).collect(java.util.stream.Collectors.toSet());
 +    }
 +    // CraftBukkit end
-+
-     private static enum RaidStatus {
  
-         ONGOING, VICTORY, LOSS, STOPPED;
+     static enum RaidStatus {
+         ONGOING,
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch
new file mode 100644
index 0000000000..59f462b6cc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raider.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/entity/raid/Raider.java
++++ b/net/minecraft/world/entity/raid/Raider.java
+@@ -212,17 +_,24 @@
+         if (this.hasActiveRaid()
+             && !flag
+             && ItemStack.matches(item, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) {
++            // Paper start - EntityPickupItemEvent fixes
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, false).isCancelled()) {
++                return;
++            }
++            // Paper end - EntityPickupItemEvent fixes
+             EquipmentSlot equipmentSlot = EquipmentSlot.HEAD;
+             ItemStack itemBySlot = this.getItemBySlot(equipmentSlot);
+             double d = this.getEquipmentDropChance(equipmentSlot);
+             if (!itemBySlot.isEmpty() && Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d) {
++                this.forceDrops = true; // Paper - Add missing forceDrop toggles
+                 this.spawnAtLocation(level, itemBySlot);
++                this.forceDrops = false; // Paper - Add missing forceDrop toggles
+             }
+ 
+             this.onItemPickup(entity);
+             this.setItemSlot(equipmentSlot, item);
+             this.take(entity, item.getCount());
+-            entity.discard();
++            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             this.getCurrentRaid().setLeader(this.getWave(), this);
+             this.setPatrolLeader(true);
+         } else {
+@@ -296,7 +_,7 @@
+ 
+             for (Raider raider : getServerLevel(this.mob)
+                 .getNearbyEntities(Raider.class, this.shoutTargeting, this.mob, this.mob.getBoundingBox().inflate(8.0, 8.0, 8.0))) {
+-                raider.setTarget(this.mob.getTarget());
++                raider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
+             }
+         }
+ 
+@@ -307,7 +_,7 @@
+             if (target != null) {
+                 for (Raider raider : getServerLevel(this.mob)
+                     .getNearbyEntities(Raider.class, this.shoutTargeting, this.mob, this.mob.getBoundingBox().inflate(8.0, 8.0, 8.0))) {
+-                    raider.setTarget(target);
++                    raider.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
+                     raider.setAggressive(true);
+                 }
+ 
+@@ -392,6 +_,7 @@
+         }
+ 
+         private boolean cannotPickUpBanner() {
++            if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items
+             if (!this.mob.hasActiveRaid()) {
+                 return true;
+             } else if (this.mob.getCurrentRaid().isOver()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raids.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raids.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch
index b787b60fa1..89f05b3b2d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raids.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raids.java.patch
@@ -1,16 +1,17 @@
 --- a/net/minecraft/world/entity/raid/Raids.java
 +++ b/net/minecraft/world/entity/raid/Raids.java
-@@ -115,11 +115,23 @@
- 
-                 Raid raid = this.getOrCreateRaid(player.serverLevel(), blockposition2);
+@@ -112,11 +_,23 @@
+                 }
  
+                 Raid raid = this.getOrCreateRaid(player.serverLevel(), blockPos);
 +                /* CraftBukkit - moved down
                  if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) {
                      this.raidMap.put(raid.getId(), raid);
                  }
-+                */
- 
+-
 -                if (!raid.isStarted() || raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel()) {
++                */
++
 +                if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished
 +                    // CraftBukkit start
 +                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raider.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raider.java.patch
deleted file mode 100644
index 7f33d0dc93..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/raid/Raider.java.patch
+++ /dev/null
@@ -1,83 +0,0 @@
---- a/net/minecraft/world/entity/raid/Raider.java
-+++ b/net/minecraft/world/entity/raid/Raider.java
-@@ -40,6 +40,9 @@
- import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public abstract class Raider extends PatrollingMonster {
- 
-@@ -225,18 +228,25 @@
-         boolean flag = this.hasActiveRaid() && this.getCurrentRaid().getLeader(this.getWave()) != null;
- 
-         if (this.hasActiveRaid() && !flag && ItemStack.matches(itemstack, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) {
-+            // Paper start - EntityPickupItemEvent fixes
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) {
-+                return;
-+            }
-+            // Paper end - EntityPickupItemEvent fixes
-             EquipmentSlot enumitemslot = EquipmentSlot.HEAD;
-             ItemStack itemstack1 = this.getItemBySlot(enumitemslot);
-             double d0 = (double) this.getEquipmentDropChance(enumitemslot);
- 
-             if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) {
-+                this.forceDrops = true; // Paper - Add missing forceDrop toggles
-                 this.spawnAtLocation(world, itemstack1);
-+                this.forceDrops = false; // Paper - Add missing forceDrop toggles
-             }
- 
-             this.onItemPickup(itemEntity);
-             this.setItemSlot(enumitemslot, itemstack);
-             this.take(itemEntity, itemstack.getCount());
--            itemEntity.discard();
-+            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             this.getCurrentRaid().setLeader(this.getWave(), this);
-             this.setPatrolLeader(true);
-         } else {
-@@ -290,7 +300,7 @@
-         @Nullable
-         private ItemEntity pursuedBannerItemEntity;
- 
--        public ObtainRaidLeaderBannerGoal(final Raider entityraider) {
-+        public ObtainRaidLeaderBannerGoal(final T entityraider) { // CraftBukkit - decompile error
-             this.mob = entityraider;
-             this.setFlags(EnumSet.of(Goal.Flag.MOVE));
-         }
-@@ -335,6 +345,7 @@
-         }
- 
-         private boolean cannotPickUpBanner() {
-+            if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items
-             if (!this.mob.hasActiveRaid()) {
-                 return true;
-             } else if (this.mob.getCurrentRaid().isOver()) {
-@@ -518,7 +529,7 @@
-         }
-     }
- 
--    protected static class HoldGroundAttackGoal extends Goal {
-+    public static class HoldGroundAttackGoal extends Goal {
- 
-         private final Raider mob;
-         private final float hostileRadiusSqr;
-@@ -547,7 +558,7 @@
-             while (iterator.hasNext()) {
-                 Raider entityraider = (Raider) iterator.next();
- 
--                entityraider.setTarget(this.mob.getTarget());
-+                entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
-             }
- 
-         }
-@@ -564,7 +575,7 @@
-                 while (iterator.hasNext()) {
-                     Raider entityraider = (Raider) iterator.next();
- 
--                    entityraider.setTarget(entityliving);
-+                    entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
-                     entityraider.setAggressive(true);
-                 }
- 

From f25c1a33a07f78b259a1d16f9c94aa33998d2718 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sat, 14 Dec 2024 05:05:32 +0100
Subject: [PATCH 044/285] Finish block entity

---
 .../AbstractFurnaceBlockEntity.java.patch     | 295 +++++++++++++++
 .../block/entity/BannerBlockEntity.java.patch |  71 ++++
 .../block/entity/BarrelBlockEntity.java.patch |   2 +-
 .../block/entity/BeaconBlockEntity.java.patch | 242 ++++++++++++
 .../entity/BeehiveBlockEntity.java.patch      | 244 ++++++++++++
 .../level/block/entity/BlockEntity.java.patch | 133 +++++++
 .../entity/BrewingStandBlockEntity.java.patch | 186 +++++++++
 .../entity/BrushableBlockEntity.java.patch    |  23 ++
 ...alibratedSculkSensorBlockEntity.java.patch |   6 +-
 .../entity/CampfireBlockEntity.java.patch     | 104 +++++
 .../block/entity/ChestBlockEntity.java.patch  |   2 +-
 .../ChiseledBookShelfBlockEntity.java.patch   |   2 +-
 .../entity/ConduitBlockEntity.java.patch      |  62 +++
 .../entity/ContainerOpenersCounter.java.patch |  64 ++--
 .../entity/CrafterBlockEntity.java.patch      |  50 +++
 .../entity/DecoratedPotBlockEntity.java.patch |  31 +-
 .../entity/DispenserBlockEntity.java.patch    |  39 ++
 .../block/entity/HopperBlockEntity.java.patch |   2 +-
 .../block/entity/JigsawBlockEntity.java.patch |  14 +-
 .../entity/JukeboxBlockEntity.java.patch      |  57 +--
 .../entity/LecternBlockEntity.java.patch      | 139 +++++++
 ...andomizableContainerBlockEntity.java.patch |   6 +-
 .../entity/SculkSensorBlockEntity.java.patch  |  33 +-
 .../SculkShriekerBlockEntity.java.patch       |  10 +-
 .../entity/ShulkerBoxBlockEntity.java.patch   |  41 +-
 .../block/entity/SignBlockEntity.java.patch   | 183 +++++++++
 .../block/entity/SkullBlockEntity.java.patch  |  36 +-
 .../AbstractFurnaceBlockEntity.java.patch     | 356 ------------------
 .../block/entity/BannerBlockEntity.java.patch |  81 ----
 .../block/entity/BeaconBlockEntity.java.patch | 259 -------------
 .../entity/BeehiveBlockEntity.java.patch      | 306 ---------------
 .../level/block/entity/BlockEntity.java.patch | 153 --------
 .../block/entity/BlockEntityType.java.patch   |  20 -
 .../entity/BrewingStandBlockEntity.java.patch | 232 ------------
 .../entity/BrushableBlockEntity.java.patch    |  36 --
 .../entity/CampfireBlockEntity.java.patch     | 121 ------
 .../entity/ConduitBlockEntity.java.patch      | 108 ------
 .../entity/CrafterBlockEntity.java.patch      |  68 ----
 .../entity/DispenserBlockEntity.java.patch    |  50 ---
 .../entity/LecternBlockEntity.java.patch      | 164 --------
 .../block/entity/SignBlockEntity.java.patch   | 269 -------------
 41 files changed, 1904 insertions(+), 2396 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch (92%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch (58%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch (52%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch (54%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch (85%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch (72%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch (83%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch (56%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch (76%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntityType.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
new file mode 100644
index 0000000000..622ee5f7ef
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -0,0 +1,295 @@
+--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+@@ -20,7 +_,6 @@
+ import net.minecraft.world.ContainerHelper;
+ import net.minecraft.world.WorldlyContainer;
+ import net.minecraft.world.entity.ExperienceOrb;
+-import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.entity.player.StackedItemContents;
+ import net.minecraft.world.inventory.ContainerData;
+ import net.minecraft.world.inventory.RecipeCraftingHolder;
+@@ -99,11 +_,44 @@
+     };
+     public final Reference2IntOpenHashMap<ResourceKey<Recipe<?>>> recipesUsed = new Reference2IntOpenHashMap<>();
+     private final RecipeManager.CachedCheck<SingleRecipeInput, ? extends AbstractCookingRecipe> quickCheck;
++    public final RecipeType<? extends AbstractCookingRecipe> recipeType; // Paper - cook speed multiplier API
++    public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API
+ 
+     protected AbstractFurnaceBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState, RecipeType<? extends AbstractCookingRecipe> recipeType) {
+         super(type, pos, blockState);
+         this.quickCheck = RecipeManager.createCheck(recipeType);
+-    }
++        this.recipeType = recipeType; // Paper - cook speed multiplier API
++    }
++
++    // CraftBukkit start - add fields and methods
++    private int maxStack = MAX_STACK;
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++
++    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
+ 
+     private boolean isLit() {
+         return this.litTimeRemaining > 0;
+@@ -121,8 +_,19 @@
+         CompoundTag compound = tag.getCompound("RecipesUsed");
+ 
+         for (String string : compound.getAllKeys()) {
+-            this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(string)), compound.getInt(string));
+-        }
++            // Paper start - Validate ResourceLocation
++            final ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
++            if (resourceLocation != null) {
++                this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, resourceLocation), tag.getInt(string));
++            }
++            // Paper end - Validate ResourceLocation
++        }
++
++        // Paper start - cook speed multiplier API
++        if (tag.contains("Paper.CookSpeedMultiplier")) {
++            this.cookSpeedMultiplier = tag.getDouble("Paper.CookSpeedMultiplier");
++        }
++        // Paper end - cook speed multiplier API
+     }
+ 
+     @Override
+@@ -132,6 +_,7 @@
+         tag.putShort("cooking_total_time", (short)this.cookingTotalTime);
+         tag.putShort("lit_time_remaining", (short)this.litTimeRemaining);
+         tag.putShort("lit_total_time", (short)this.litTotalTime);
++        tag.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API
+         ContainerHelper.saveAllItems(tag, this.items, registries);
+         CompoundTag compoundTag = new CompoundTag();
+         this.recipesUsed.forEach((recipe, count) -> compoundTag.putInt(recipe.location().toString(), count));
+@@ -160,11 +_,22 @@
+ 
+             int maxStackSize = furnace.getMaxStackSize();
+             if (!furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
+-                furnace.litTimeRemaining = furnace.getBurnDuration(level.fuelValues(), itemStack);
++                // CraftBukkit start
++                org.bukkit.craftbukkit.inventory.CraftItemStack fuel = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++
++                org.bukkit.event.inventory.FurnaceBurnEvent furnaceBurnEvent = new org.bukkit.event.inventory.FurnaceBurnEvent(
++                    org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                    fuel,
++                    furnace.getBurnDuration(level.fuelValues(), itemStack)
++                );
++                if (!furnaceBurnEvent.callEvent()) return;
++                // CraftBukkit end
++
++                furnace.litTimeRemaining = furnaceBurnEvent.getBurnTime(); // CraftBukkit - respect event output
+                 furnace.litTotalTime = furnace.litTimeRemaining;
+-                if (furnace.isLit()) {
++                if (furnace.isLit() && furnaceBurnEvent.isBurning()) { // CraftBukkit - respect event output
+                     flag = true;
+-                    if (flag2) {
++                    if (flag2 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent
+                         Item item = itemStack.getItem();
+                         itemStack.shrink(1);
+                         if (itemStack.isEmpty()) {
+@@ -175,11 +_,28 @@
+             }
+ 
+             if (furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
++                // CraftBukkit start
++                if (recipeHolder != null && furnace.cookingTimer == 0) {
++                    org.bukkit.craftbukkit.inventory.CraftItemStack source = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(furnace.items.get(0));
++                    org.bukkit.inventory.CookingRecipe<?> recipe = (org.bukkit.inventory.CookingRecipe<?>) recipeHolder.toBukkitRecipe();
++
++                    org.bukkit.event.inventory.FurnaceStartSmeltEvent event = new org.bukkit.event.inventory.FurnaceStartSmeltEvent(
++                        org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                        source,
++                        recipe,
++                        getTotalCookTime(level, furnace, furnace.recipeType, furnace.cookSpeedMultiplier) // Paper - cook speed multiplier API
++                    );
++                    event.callEvent();
++
++                    furnace.cookingTotalTime = event.getTotalCookTime();
++                }
++                // CraftBukkit end
++
+                 furnace.cookingTimer++;
+-                if (furnace.cookingTimer == furnace.cookingTotalTime) {
++                if (furnace.cookingTimer >= furnace.cookingTotalTime) { // Paper - cook speed multiplier API
+                     furnace.cookingTimer = 0;
+-                    furnace.cookingTotalTime = getTotalCookTime(level, furnace);
+-                    if (burn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize)) {
++                    furnace.cookingTotalTime = getTotalCookTime(level, furnace, furnace.recipeType, furnace.cookSpeedMultiplier); // Paper - cook speed multiplier API
++                    if (burn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, maxStackSize, level, furnace.worldPosition)) { // CraftBukkit
+                         furnace.setRecipeUsed(recipeHolder);
+                     }
+ 
+@@ -233,17 +_,47 @@
+         @Nullable RecipeHolder<? extends AbstractCookingRecipe> recipe,
+         SingleRecipeInput recipeInput,
+         NonNullList<ItemStack> items,
+-        int maxStackSize
++        int maxStackSize,
++        net.minecraft.world.level.Level level, // CraftBukkit
++        BlockPos blockPos // CraftBukkit
+     ) {
+         if (recipe != null && canBurn(registryAccess, recipe, recipeInput, items, maxStackSize)) {
+-            ItemStack itemStack = items.get(0);
+-            ItemStack itemStack1 = recipe.value().assemble(recipeInput, registryAccess);
+-            ItemStack itemStack2 = items.get(2);
++            ItemStack itemStack = items.get(0); final ItemStack ingredient = itemStack; // Paper - OBFHELPER
++            ItemStack itemStack1 = recipe.value().assemble(recipeInput, registryAccess); ItemStack result = itemStack1; // Paper - OBFHELPER
++            ItemStack itemStack2 = items.get(2); final ItemStack existingResults = itemStack2; // Paper - OBFHELPER
++            // CraftBukkit start - fire FurnaceSmeltEvent
++            org.bukkit.craftbukkit.inventory.CraftItemStack apiIngredient = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(ingredient);
++            org.bukkit.inventory.ItemStack apiResult = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(result);
++
++            org.bukkit.event.inventory.FurnaceSmeltEvent furnaceSmeltEvent = new org.bukkit.event.inventory.FurnaceSmeltEvent(
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
++                apiIngredient,
++                apiResult,
++                (org.bukkit.inventory.CookingRecipe<?>) recipe.toBukkitRecipe() // Paper - Add recipe to cook events
++            );
++            if (!furnaceSmeltEvent.callEvent()) return false;
++
++            apiResult = furnaceSmeltEvent.getResult();
++            itemStack1 = result = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(apiResult);
++
++            if (!result.isEmpty()) {
++                if (existingResults.isEmpty()) {
++                    items.set(2, result.copy());
++                } else if (org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(existingResults).isSimilar(apiResult)) {
++                    existingResults.grow(result.getCount());
++                } else {
++                    return false;
++                }
++            }
++
++            /*
+             if (itemStack2.isEmpty()) {
+                 items.set(2, itemStack1.copy());
+             } else if (ItemStack.isSameItemSameComponents(itemStack2, itemStack1)) {
+                 itemStack2.grow(1);
+             }
++            */
++            // CraftBukkit end
+ 
+             if (itemStack.is(Blocks.WET_SPONGE.asItem()) && !items.get(1).isEmpty() && items.get(1).is(Items.BUCKET)) {
+                 items.set(1, new ItemStack(Items.WATER_BUCKET));
+@@ -260,9 +_,16 @@
+         return fuelValues.burnDuration(stack);
+     }
+ 
+-    public static int getTotalCookTime(ServerLevel level, AbstractFurnaceBlockEntity furnace) {
++    private static int getTotalCookTime(@Nullable ServerLevel level, AbstractFurnaceBlockEntity furnace, RecipeType<? extends AbstractCookingRecipe> recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API
+         SingleRecipeInput singleRecipeInput = new SingleRecipeInput(furnace.getItem(0));
+-        return furnace.quickCheck.getRecipeFor(singleRecipeInput, level).map(recipe -> recipe.value().cookingTime()).orElse(200);
++        // Paper start - cook speed multiplier API
++        /* Scale the recipe's cooking time to the current cookSpeedMultiplier */
++        int cookTime = level != null
++            ? furnace.quickCheck.getRecipeFor(singleRecipeInput, level).map(holder -> holder.value().cookingTime()).orElse(200)
++            /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */
++            : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singleRecipeInput, level).map(holder -> holder.value().cookingTime()).orElse(200));
++        return (int) Math.ceil (cookTime / cookSpeedMultiplier);
++        // Paper end - cook speed multiplier AP
+     }
+ 
+     @Override
+@@ -306,7 +_,7 @@
+         this.items.set(index, stack);
+         stack.limitSize(this.getMaxStackSize(stack));
+         if (index == 0 && !flag && this.level instanceof ServerLevel serverLevel) {
+-            this.cookingTotalTime = getTotalCookTime(serverLevel, this);
++            this.cookingTotalTime = getTotalCookTime(serverLevel, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API
+             this.cookingTimer = 0;
+             this.setChanged();
+         }
+@@ -339,11 +_,11 @@
+     }
+ 
+     @Override
+-    public void awardUsedRecipes(Player player, List<ItemStack> items) {
++    public void awardUsedRecipes(net.minecraft.world.entity.player.Player player, List<ItemStack> items) {
+     }
+ 
+-    public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
+-        List<RecipeHolder<?>> recipesToAwardAndPopExperience = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position());
++    public void awardUsedRecipesAndPopExperience(ServerPlayer player, ItemStack itemstack, int amount) { // CraftBukkit
++        List<RecipeHolder<?>> recipesToAwardAndPopExperience = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position(), this.worldPosition, player, itemstack, amount); // CraftBukkit - overload for exp spawn events
+         player.awardRecipes(recipesToAwardAndPopExperience);
+ 
+         for (RecipeHolder<?> recipeHolder : recipesToAwardAndPopExperience) {
+@@ -356,26 +_,52 @@
+     }
+ 
+     public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec) {
++    // CraftBukkit start
++        return this.getRecipesToAwardAndPopExperience(level, popVec, this.worldPosition, null, null, 0);
++    }
++    public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec, BlockPos blockPos, ServerPlayer serverPlayer, ItemStack itemStack, int amount) {
++    // CraftBukkit end
+         List<RecipeHolder<?>> list = Lists.newArrayList();
+ 
+         for (Entry<ResourceKey<Recipe<?>>> entry : this.recipesUsed.reference2IntEntrySet()) {
+             level.recipeAccess().byKey(entry.getKey()).ifPresent(recipe -> {
++                if (!(recipe.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes
+                 list.add((RecipeHolder<?>)recipe);
+-                createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipe.value()).experience());
++                createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipe.value()).experience(), blockPos, serverPlayer, itemStack, amount);
+             });
+         }
+ 
+         return list;
+     }
+ 
+-    private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience) {
++    private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience, BlockPos blockPos, ServerPlayer serverPlayer, ItemStack itemStack, int amount) { // CraftBukkit
+         int floor = Mth.floor(recipeIndex * experience);
+         float fraction = Mth.frac(recipeIndex * experience);
+         if (fraction != 0.0F && Math.random() < fraction) {
+             floor++;
+         }
+ 
+-        ExperienceOrb.award(level, popVec, floor);
++        // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent
++        org.bukkit.event.block.BlockExpEvent event;
++        if (amount != 0) {
++            event = new org.bukkit.event.inventory.FurnaceExtractEvent(
++                serverPlayer.getBukkitEntity(),
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
++                org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(itemStack.getItem()),
++                amount,
++                floor
++            );
++        } else {
++            event = new org.bukkit.event.block.BlockExpEvent(
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos),
++                floor
++            );
++        }
++        event.callEvent();
++        floor = event.getExpToDrop();
++        // CraftBukkit end
++
++        ExperienceOrb.award(level, popVec, floor, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, serverPlayer); // Paper
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
new file mode 100644
index 0000000000..152410ce51
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java
+@@ -23,7 +_,7 @@
+     public static final int MAX_PATTERNS = 6;
+     private static final String TAG_PATTERNS = "patterns";
+     @Nullable
+-    private Component name;
++    public Component name; // Paper - AT public
+     public DyeColor baseColor;
+     private BannerPatternLayers patterns = BannerPatternLayers.EMPTY;
+ 
+@@ -50,7 +_,7 @@
+     @Override
+     protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
+         super.saveAdditional(tag, registries);
+-        if (!this.patterns.equals(BannerPatternLayers.EMPTY)) {
++        if (!this.patterns.equals(BannerPatternLayers.EMPTY) || serialisingForNetwork.get()) { // Paper - always send patterns to client
+             tag.put("patterns", BannerPatternLayers.CODEC.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.patterns).getOrThrow());
+         }
+ 
+@@ -70,7 +_,7 @@
+             BannerPatternLayers.CODEC
+                 .parse(registries.createSerializationContext(NbtOps.INSTANCE), tag.get("patterns"))
+                 .resultOrPartial(string -> LOGGER.error("Failed to parse banner patterns: '{}'", string))
+-                .ifPresent(bannerPatternLayers -> this.patterns = bannerPatternLayers);
++                .ifPresent(bannerPatternLayers -> this.setPatterns(bannerPatternLayers)); // CraftBukkit - apply limits
+         }
+     }
+ 
+@@ -79,9 +_,18 @@
+         return ClientboundBlockEntityDataPacket.create(this);
+     }
+ 
++    // Paper start - always send patterns to client
++    ThreadLocal<Boolean> serialisingForNetwork = ThreadLocal.withInitial(() -> Boolean.FALSE);
+     @Override
+     public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
++        final Boolean wasSerialisingForNetwork = serialisingForNetwork.get();
++        try {
++            serialisingForNetwork.set(Boolean.TRUE);
+         return this.saveWithoutMetadata(registries);
++        } finally {
++            serialisingForNetwork.set(wasSerialisingForNetwork);
++        }
++        // Paper end - always send patterns to client
+     }
+ 
+     public BannerPatternLayers getPatterns() {
+@@ -101,7 +_,7 @@
+     @Override
+     protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
+         super.applyImplicitComponents(componentInput);
+-        this.patterns = componentInput.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
++        this.setPatterns(componentInput.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY)); // CraftBukkit - apply limits
+         this.name = componentInput.get(DataComponents.CUSTOM_NAME);
+     }
+ 
+@@ -117,4 +_,13 @@
+         tag.remove("patterns");
+         tag.remove("CustomName");
+     }
++
++    // CraftBukkit start
++    public void setPatterns(BannerPatternLayers bannerpatternlayers) {
++        if (bannerpatternlayers.layers().size() > 20) {
++            bannerpatternlayers = new BannerPatternLayers(java.util.List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
++        }
++        this.patterns = bannerpatternlayers;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
index 24372abac2..05d3f943e3 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -24,7 +24,7 @@
 +    }
 +
 +    @Override
-+    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++    public List<HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
new file mode 100644
index 0000000000..acc3c3c614
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
@@ -0,0 +1,242 @@
+--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+@@ -106,6 +_,51 @@
+             return 3;
+         }
+     };
++    // CraftBukkit start - add fields and methods
++    public org.bukkit.potion.PotionEffect getPrimaryEffect() {
++        return (this.primaryPower != null)
++            ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
++                this.primaryPower,
++                BeaconBlockEntity.getLevel(this.levels),
++                BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
++                true,
++                true
++            ))
++            : null;
++    }
++
++    public org.bukkit.potion.PotionEffect getSecondaryEffect() {
++        return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower))
++            ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
++                this.secondaryPower,
++                BeaconBlockEntity.getLevel(this.levels),
++                BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
++                true,
++                true
++            ))
++            : null;
++    }
++    // CraftBukkit end
++    // Paper start - Custom beacon ranges
++    private final String PAPER_RANGE_TAG = "Paper.Range";
++    private double effectRange = -1;
++
++    public double getEffectRange() {
++        if (this.effectRange < 0) {
++            return this.levels * 10 + 10;
++        } else {
++            return effectRange;
++        }
++    }
++
++    public void setEffectRange(double range) {
++        this.effectRange = range;
++    }
++
++    public void resetEffectRange() {
++        this.effectRange = -1;
++    }
++    // Paper end - Custom beacon ranges
+ 
+     @Nullable
+     static Holder<MobEffect> filterEffect(@Nullable Holder<MobEffect> effect) {
+@@ -163,17 +_,26 @@
+             blockEntity.lastCheckY++;
+         }
+ 
+-        int i = blockEntity.levels;
++        int i = blockEntity.levels; final int originalLevels = i; // Paper - OBFHELPER
+         if (level.getGameTime() % 80L == 0L) {
+             if (!blockEntity.beamSections.isEmpty()) {
+                 blockEntity.levels = updateBase(level, x, y, z);
+             }
+ 
+             if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
+-                applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower);
++                applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges
+                 playSound(level, pos, SoundEvents.BEACON_AMBIENT);
+             }
+         }
++        // Paper start - beacon activation/deactivation events
++        if (originalLevels <= 0 && blockEntity.levels > 0) {
++            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++            new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent();
++        } else if (originalLevels > 0 && blockEntity.levels <= 0) {
++            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++            new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
++        }
++        // Paper end - beacon activation/deactivation events
+ 
+         if (blockEntity.lastCheckY >= height) {
+             blockEntity.lastCheckY = level.getMinY() - 1;
+@@ -224,36 +_,99 @@
+ 
+     @Override
+     public void setRemoved() {
++        // Paper start - beacon activation/deactivation events
++        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition);
++        new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
++        // Paper end - beacon activation/deactivation events
++        // Paper start - fix MC-153086
++        if (this.levels > 0 && !this.beamSections.isEmpty()) {
+         playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
++        }
++        // Paper end
+         super.setRemoved();
+     }
+ 
++    @io.papermc.paper.annotation.DoNotUse // Paper - pass beacon block entity
+     private static void applyEffects(
+         Level level, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect
+     ) {
++    // Paper start - pass beacon block entity
++        applyEffects(level, pos, beaconLevel, primaryEffect, secondaryEffect, null);
++    }
++    private static void applyEffects(
++        Level level, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect, @Nullable BeaconBlockEntity blockEntity
++    ) {
++    // Paper emd - pass beacon block entity
+         if (!level.isClientSide && primaryEffect != null) {
+-            double d = beaconLevel * 10 + 10;
+-            int i = 0;
+-            if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
+-                i = 1;
+-            }
+-
+-            int i1 = (9 + beaconLevel * 2) * 20;
+-            AABB aabb = new AABB(pos).inflate(d).expandTowards(0.0, level.getHeight(), 0.0);
+-            List<Player> entitiesOfClass = level.getEntitiesOfClass(Player.class, aabb);
+-
+-            for (Player player : entitiesOfClass) {
+-                player.addEffect(new MobEffectInstance(primaryEffect, i1, i, true, true));
+-            }
+-
+-            if (beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null) {
+-                for (Player player : entitiesOfClass) {
+-                    player.addEffect(new MobEffectInstance(secondaryEffect, i1, 0, true, true));
++            double d = computeBeaconRange(beaconLevel); // Paper - diff out applyEffects logic components - see below
++            int i = computeEffectAmplifier(beaconLevel, primaryEffect, secondaryEffect); // Paper - diff out applyEffects logic components - see below
++
++            int i1 = computeEffectDuration(beaconLevel); // Paper - diff out applyEffects logic components - see below
++            List<Player> entitiesOfClass = getHumansInRange(level, pos, beaconLevel, blockEntity); // Paper - diff out applyEffects logic components - see below
++
++            applyEffectsAndCallEvent(level, pos, entitiesOfClass, new MobEffectInstance(primaryEffect, i1, i, true, true), true); // Paper - BeaconEffectEvent
++
++            if (hasSecondaryEffect(beaconLevel, primaryEffect, secondaryEffect)) { // Paper - diff out applyEffects logic components - see below
++                applyEffectsAndCallEvent(level, pos, entitiesOfClass, new MobEffectInstance(secondaryEffect, i1, 0, true, true), false); // Paper - BeaconEffectEvent
++            }
++        }
++    }
++
++    // Paper start - diff out applyEffects logic components
++    // Generally smarter than spigot trying to split the logic up, as that diff is giant.
++    private static int computeEffectDuration(final int beaconLevel) {
++        return (9 + beaconLevel * 2) * 20; // Diff from applyEffects
++    }
++
++    private static int computeEffectAmplifier(final int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect) {
++        int i = 0;
++        if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
++            i = 1;
++        }
++        return i;
++    }
++
++    private static double computeBeaconRange(final int beaconLevel) {
++        return beaconLevel * 10 + 10; // Diff from applyEffects
++    }
++
++    public static List<Player> getHumansInRange(final Level level, final BlockPos pos, final int beaconLevel, final @Nullable BeaconBlockEntity blockEntity) {
++        final double d = blockEntity != null ? blockEntity.getEffectRange() : computeBeaconRange(beaconLevel);
++        AABB aabb = new AABB(pos).inflate(d).expandTowards(0.0, level.getHeight(), 0.0); // Diff from applyEffects
++        // Improve performance of human lookup by switching to a global player iteration when searching over 128 blocks
++        List<Player> list;
++        if (d <= 128.0) {
++            list = level.getEntitiesOfClass(Player.class, aabb); // Diff from applyEffect
++        } else {
++            list = new java.util.ArrayList<>();
++            for (final Player player : level.players()) {
++                if (!net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(player)) continue;
++                if (player.getBoundingBox().intersects(aabb)) {
++                    list.add(player);
+                 }
+             }
+         }
+-    }
+-
++        return list;
++    }
++
++    private static boolean hasSecondaryEffect(final int beaconLevel, final Holder<MobEffect> primaryEffect, final @Nullable Holder<MobEffect> secondaryEffect) {
++        return beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null;
++    }
++    // Paper end - diff out applyEffects logic components
++
++    // Paper start - BeaconEffectEvent
++    private static void applyEffectsAndCallEvent(final Level level, final BlockPos position, final List<Player> players, final MobEffectInstance mobEffectInstance, final boolean isPrimary) {
++        final org.bukkit.potion.PotionEffect apiEffect = org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(mobEffectInstance);
++        final org.bukkit.craftbukkit.block.CraftBlock apiBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, position);
++        for (final Player player : players) {
++            final com.destroystokyo.paper.event.block.BeaconEffectEvent event = new com.destroystokyo.paper.event.block.BeaconEffectEvent(
++                apiBlock, apiEffect, (org.bukkit.entity.Player) player.getBukkitEntity(), isPrimary
++            );
++            if (!event.callEvent()) continue;
++            player.addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(event.getEffect()));
++        }
++    }
++    // Paper end - BeaconEffectEvent
+     public static void playSound(Level level, BlockPos pos, SoundEvent sound) {
+         level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
+     }
+@@ -282,7 +_,7 @@
+     private static Holder<MobEffect> loadEffect(CompoundTag tag, String key) {
+         if (tag.contains(key, 8)) {
+             ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getString(key));
+-            return resourceLocation == null ? null : BuiltInRegistries.MOB_EFFECT.get(resourceLocation).map(BeaconBlockEntity::filterEffect).orElse(null);
++            return resourceLocation == null ? null : BuiltInRegistries.MOB_EFFECT.get(resourceLocation).orElse(null); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598)
+         } else {
+             return null;
+         }
+@@ -293,11 +_,13 @@
+         super.loadAdditional(tag, registries);
+         this.primaryPower = loadEffect(tag, "primary_effect");
+         this.secondaryPower = loadEffect(tag, "secondary_effect");
++        this.levels = tag.getInt("Levels"); // CraftBukkit - SPIGOT-5053, use where available
+         if (tag.contains("CustomName", 8)) {
+             this.name = parseCustomNameSafe(tag.getString("CustomName"), registries);
+         }
+ 
+         this.lockKey = LockCode.fromTag(tag, registries);
++        this.effectRange = tag.contains(PAPER_RANGE_TAG, 6) ? tag.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges
+     }
+ 
+     @Override
+@@ -311,6 +_,7 @@
+         }
+ 
+         this.lockKey.addToTag(tag, registries);
++        tag.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges
+     }
+ 
+     public void setCustomName(@Nullable Component name) {
+@@ -326,7 +_,7 @@
+     @Nullable
+     @Override
+     public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
+-        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName())
++        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) // Paper - Add BlockLockCheckEvent
+             ? new BeaconMenu(containerId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos()))
+             : null;
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
new file mode 100644
index 0000000000..2ab50074f8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
@@ -0,0 +1,244 @@
+--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+@@ -83,6 +_,7 @@
+     private List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
+     @Nullable
+     public BlockPos savedFlowerPos;
++    public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
+ 
+     public BeehiveBlockEntity(BlockPos pos, BlockState blockState) {
+         super(BlockEntityType.BEEHIVE, pos, blockState);
+@@ -116,7 +_,7 @@
+     }
+ 
+     public boolean isFull() {
+-        return this.stored.size() == 3;
++        return this.stored.size() == this.maxBees; // CraftBukkit
+     }
+ 
+     public void emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
+@@ -127,7 +_,7 @@
+                     Bee bee = (Bee)entity;
+                     if (player.position().distanceToSqr(entity.position()) <= 16.0) {
+                         if (!this.isSedated()) {
+-                            bee.setTarget(player);
++                            bee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+                         } else {
+                             bee.setStayOutOfHiveCountdown(400);
+                         }
+@@ -138,8 +_,14 @@
+     }
+ 
+     private List<Entity> releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
++        // CraftBukkit start - This allows us to bypass the night/rain/emergency check
++        return this.releaseBees(state, releaseStatus, false);
++    }
++
++    public List<Entity> releaseBees(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, boolean force) {
++        // CraftBukkit end - This allows us to bypass t he night/rain/emergecny check
+         List<Entity> list = Lists.newArrayList();
+-        this.stored.removeIf(data -> releaseOccupant(this.level, this.worldPosition, state, data.toOccupant(), list, releaseStatus, this.savedFlowerPos));
++        this.stored.removeIf(data -> releaseOccupant(this.level, this.worldPosition, state, data.toOccupant(), list, releaseStatus, this.savedFlowerPos, force)); // CraftBukkit - This allows us to bypass t he night/rain/emergecny check
+         if (!list.isEmpty()) {
+             super.setChanged();
+         }
+@@ -152,6 +_,11 @@
+         return this.stored.size();
+     }
+ 
++    // Paper start - Add EntityBlockStorage clearEntities
++    public void clearBees() {
++        this.stored.clear();
++    }
++    // Paper end - Add EntityBlockStorage clearEntities
+     public static int getHoneyLevel(BlockState state) {
+         return state.getValue(BeehiveBlock.HONEY_LEVEL);
+     }
+@@ -162,7 +_,16 @@
+     }
+ 
+     public void addOccupant(Bee bee) {
+-        if (this.stored.size() < 3) {
++        if (this.stored.size() < this.maxBees) { // CraftBukkit
++            // CraftBukkit start
++            if (this.level != null) {
++                org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(bee.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.getBlockPos()));
++                if (!event.callEvent()) {
++                    bee.setStayOutOfHiveCountdown(400);
++                    return;
++                }
++            }
++            // CraftBukkit end
+             bee.stopRiding();
+             bee.ejectPassengers();
+             bee.dropLeash();
+@@ -187,7 +_,7 @@
+                 this.level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(bee, this.getBlockState()));
+             }
+ 
+-            bee.discard();
++            bee.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause
+             super.setChanged();
+         }
+     }
+@@ -205,7 +_,21 @@
+         BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
+         @Nullable BlockPos storedFlowerPos
+     ) {
+-        if (Bee.isNightOrRaining(level) && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
++        // CraftBukkit start
++        return releaseOccupant(level, pos, state, occupant, storedInHives, releaseStatus, storedFlowerPos, false);
++    }
++    private static boolean releaseOccupant(
++        Level level,
++        BlockPos pos,
++        BlockState state,
++        BeehiveBlockEntity.Occupant occupant,
++        @Nullable List<Entity> storedInHives,
++        BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
++        @Nullable BlockPos storedFlowerPos,
++        boolean force
++    ) {
++        if (!force && Bee.isNightOrRaining(level) && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
++        // CraftBukkit end
+             return false;
+         } else {
+             Direction direction = state.getValue(BeehiveBlock.FACING);
+@@ -216,6 +_,17 @@
+             } else {
+                 Entity entity = occupant.createEntity(level, pos);
+                 if (entity != null) {
++                    // CraftBukkit start
++                    if (entity instanceof Bee) {
++                        float bbWidth = entity.getBbWidth();
++                        double d = flag ? 0.0 : 0.55 + bbWidth / 2.0F;
++                        double d1 = pos.getX() + 0.5 + d * direction.getStepX();
++                        double d2 = pos.getY() + 0.5 - entity.getBbHeight() / 2.0F;
++                        double d3 = pos.getZ() + 0.5 + d * direction.getStepZ();
++                        entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
++                    }
++                    if (!level.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BEEHIVE)) return false; // CraftBukkit - SpawnReason, moved from below
++                    // CraftBukkit end
+                     if (entity instanceof Bee bee) {
+                         if (storedFlowerPos != null && !bee.hasSavedFlowerPos() && level.random.nextFloat() < 0.9F) {
+                             bee.setSavedFlowerPos(storedFlowerPos);
+@@ -231,7 +_,13 @@
+                                         i--;
+                                     }
+ 
+-                                    level.setBlockAndUpdate(pos, state.setValue(BeehiveBlock.HONEY_LEVEL, Integer.valueOf(honeyLevel + i)));
++                                    // Paper start - Fire EntityChangeBlockEvent in more places
++                                    BlockState newBlockState = state.setValue(BeehiveBlock.HONEY_LEVEL, Integer.valueOf(honeyLevel + i));
++
++                                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, newBlockState)) {
++                                        level.setBlockAndUpdate(pos, newBlockState);
++                                    }
++                                    // Paper end - Fire EntityChangeBlockEvent in more places
+                                 }
+                             }
+                         }
+@@ -240,17 +_,19 @@
+                             storedInHives.add(bee);
+                         }
+ 
++                        /* CraftBukkit start - move up
+                         float bbWidth = entity.getBbWidth();
+                         double d = flag ? 0.0 : 0.55 + bbWidth / 2.0F;
+                         double d1 = pos.getX() + 0.5 + d * direction.getStepX();
+                         double d2 = pos.getY() + 0.5 - entity.getBbHeight() / 2.0F;
+                         double d3 = pos.getZ() + 0.5 + d * direction.getStepZ();
+                         entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
++                        */ // CraftBukkit end
+                     }
+ 
+                     level.playSound(null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
+                     level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, level.getBlockState(pos)));
+-                    return level.addFreshEntity(entity);
++                    return true; // CraftBukkit - moved up
+                 } else {
+                     return false;
+                 }
+@@ -276,6 +_,11 @@
+                     flag = true;
+                     iterator.remove();
+                 }
++                // Paper start - Fix bees aging inside; use exitTickCounter to keep actual bee life
++                else {
++                    beeData.exitTickCounter = beeData.occupant.minTicksInHive / 2;
++                }
++                // Paper end - Fix bees aging inside; use exitTickCounter to keep actual bee life
+             }
+         }
+ 
+@@ -299,7 +_,7 @@
+     @Override
+     protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
+         super.loadAdditional(tag, registries);
+-        this.stored.clear();
++        this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
+         if (tag.contains("bees")) {
+             BeehiveBlockEntity.Occupant.LIST_CODEC
+                 .parse(NbtOps.INSTANCE, tag.get("bees"))
+@@ -308,6 +_,11 @@
+         }
+ 
+         this.savedFlowerPos = NbtUtils.readBlockPos(tag, "flower_pos").orElse(null);
++        // CraftBukkit start
++        if (tag.contains("Bukkit.MaxEntities")) {
++            this.maxBees = tag.getInt("Bukkit.MaxEntities");
++        }
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -317,12 +_,13 @@
+         if (this.hasSavedFlowerPos()) {
+             tag.put("flower_pos", NbtUtils.writeBlockPos(this.savedFlowerPos));
+         }
++        tag.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit
+     }
+ 
+     @Override
+     protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
+         super.applyImplicitComponents(componentInput);
+-        this.stored.clear();
++        this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
+         List<BeehiveBlockEntity.Occupant> list = componentInput.getOrDefault(DataComponents.BEES, List.of());
+         list.forEach(this::storeBee);
+     }
+@@ -345,15 +_,18 @@
+ 
+     static class BeeData {
+         private final BeehiveBlockEntity.Occupant occupant;
++        private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts
+         private int ticksInHive;
+ 
+         BeeData(BeehiveBlockEntity.Occupant occupant) {
+             this.occupant = occupant;
+             this.ticksInHive = occupant.ticksInHive();
++            this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives
+         }
+ 
+         public boolean tick() {
+-            return this.ticksInHive++ > this.occupant.minTicksInHive;
++            this.ticksInHive++; // Paper - Fix bees aging inside hives
++            return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives
+         }
+ 
+         public BeehiveBlockEntity.Occupant toOccupant() {
+@@ -424,6 +_,7 @@
+         }
+ 
+         private static void setBeeReleaseData(int ticksInHive, Bee bee) {
++            if (!bee.ageLocked) { // Paper - Honor ageLock
+             int age = bee.getAge();
+             if (age < 0) {
+                 bee.setAge(Math.min(0, age + ticksInHive));
+@@ -432,6 +_,7 @@
+             }
+ 
+             bee.setInLoveTime(Math.max(0, bee.getInLoveTime() - ticksInHive));
++            }  // Paper - Honor ageLock
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
new file mode 100644
index 0000000000..8ed4390459
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
@@ -0,0 +1,133 @@
+--- a/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -26,6 +_,10 @@
+ import org.slf4j.Logger;
+ 
+ public abstract class BlockEntity {
++    // CraftBukkit start - data containers
++    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
++    public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer;
++    // CraftBukkit end
+     private static final Logger LOGGER = LogUtils.getLogger();
+     private final BlockEntityType<?> type;
+     @Nullable
+@@ -40,6 +_,7 @@
+         this.worldPosition = pos.immutable();
+         this.validateBlockState(blockState);
+         this.blockState = blockState;
++        this.persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
+     }
+ 
+     private void validateBlockState(BlockState state) {
+@@ -70,6 +_,14 @@
+     }
+ 
+     protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
++        // Paper start - read persistent data container
++        this.persistentDataContainer.clear(); // Paper - clear instead of init
++
++        net.minecraft.nbt.Tag persistentDataTag = tag.get("PublicBukkitValues");
++        if (persistentDataTag instanceof CompoundTag) {
++            this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
++        }
++        // Paper end - read persistent data container
+     }
+ 
+     public final void loadWithComponents(CompoundTag tag, HolderLookup.Provider registries) {
+@@ -106,12 +_,22 @@
+             .encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.components)
+             .resultOrPartial(string -> LOGGER.warn("Failed to save components: {}", string))
+             .ifPresent(tag -> compoundTag.merge((CompoundTag)tag));
++        // CraftBukkit start - store container
++        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
++            compoundTag.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
++        }
++        // CraftBukkit end
+         return compoundTag;
+     }
+ 
+     public final CompoundTag saveCustomOnly(HolderLookup.Provider registries) {
+         CompoundTag compoundTag = new CompoundTag();
+         this.saveAdditional(compoundTag, registries);
++        // Paper start - store PDC here as well
++        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
++            compoundTag.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
++        }
++        // Paper end
+         return compoundTag;
+     }
+ 
+@@ -220,7 +_,12 @@
+     public void fillCrashReportCategory(CrashReportCategory reportCategory) {
+         reportCategory.setDetail("Name", this::getNameForReporting);
+         if (this.level != null) {
+-            CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, this.getBlockState());
++            // Paper start - Prevent block entity and entity crashes
++            BlockState block = this.getBlockState();
++            if (block != null) {
++                CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, block);
++            }
++            // Paper end - Prevent block entity and entity crashes
+             CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition));
+         }
+     }
+@@ -247,10 +_,16 @@
+     }
+ 
+     public final void applyComponents(DataComponentMap components, DataComponentPatch patch) {
++        // CraftBukkit start
++        this.applyComponentsSet(components, patch);
++    }
++
++    public final Set<DataComponentType<?>> applyComponentsSet(DataComponentMap components, DataComponentPatch patch) {
++        // CraftBukkit end
+         final Set<DataComponentType<?>> set = new HashSet<>();
+         set.add(DataComponents.BLOCK_ENTITY_DATA);
+         set.add(DataComponents.BLOCK_STATE);
+-        final DataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
++        final PatchedDataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
+         this.applyImplicitComponents(new BlockEntity.DataComponentInput() {
+             @Nullable
+             @Override
+@@ -267,6 +_,10 @@
+         });
+         DataComponentPatch dataComponentPatch = patch.forget(set::contains);
+         this.components = dataComponentPatch.split().added();
++        // CraftBukkit start
++        set.remove(DataComponents.BLOCK_ENTITY_DATA); // Remove as never actually added by applyImplicitComponents
++        return set;
++        // CraftBukkit end
+     }
+ 
+     protected void collectImplicitComponents(DataComponentMap.Builder components) {
+@@ -300,6 +_,30 @@
+             return null;
+         }
+     }
++
++    // CraftBukkit start - add method
++    public org.bukkit.inventory.InventoryHolder getOwner() {
++        // Paper start
++        return getOwner(true);
++    }
++    public org.bukkit.inventory.InventoryHolder getOwner(boolean useSnapshot) {
++        // Paper end
++        if (this.level == null) return null;
++        org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
++        // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists
++        org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
++        return state instanceof final org.bukkit.inventory.InventoryHolder inventoryHolder ? inventoryHolder : null;
++    }
++    // CraftBukkit end
++
++    // Paper start - Sanitize sent data
++    public CompoundTag sanitizeSentNbt(CompoundTag tag) {
++        tag.remove("PublicBukkitValues");
++
++        return tag;
++    }
++    // Paper end - Sanitize sent data
++
+ 
+     static class ComponentHelper {
+         public static final Codec<DataComponentMap> COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
new file mode 100644
index 0000000000..769760f7d6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -0,0 +1,186 @@
+--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+@@ -8,7 +_,6 @@
+ import net.minecraft.core.NonNullList;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.chat.Component;
+-import net.minecraft.tags.ItemTags;
+ import net.minecraft.world.ContainerHelper;
+ import net.minecraft.world.Containers;
+ import net.minecraft.world.WorldlyContainer;
+@@ -36,6 +_,7 @@
+     public static final int NUM_DATA_VALUES = 2;
+     private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
+     public int brewTime;
++    public int recipeBrewTime = 400; // Paper - Add recipeBrewTime
+     private boolean[] lastPotionCount;
+     private Item ingredient;
+     public int fuel;
+@@ -45,6 +_,7 @@
+             return switch (index) {
+                 case 0 -> BrewingStandBlockEntity.this.brewTime;
+                 case 1 -> BrewingStandBlockEntity.this.fuel;
++                case 2 -> BrewingStandBlockEntity.this.recipeBrewTime; // Paper - Add recipeBrewTime
+                 default -> 0;
+             };
+         }
+@@ -57,14 +_,50 @@
+                     break;
+                 case 1:
+                     BrewingStandBlockEntity.this.fuel = value;
++                // Paper start - Add recipeBrewTime
++                    break;
++                case 2:
++                    BrewingStandBlockEntity.this.recipeBrewTime = value;
++                    break;
++                // Paper end - Add recipeBrewTime
+             }
+         }
+ 
+         @Override
+         public int getCount() {
+-            return 2;
++            return 3; // Paper - Add recipeBrewTime
+         }
+     };
++    // CraftBukkit start - add fields and methods
++    // private int lastTick = MinecraftServer.currentTick; // Paper - remove anti tick skipping measures / wall time
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++    private int maxStack = MAX_STACK;
++
++    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<HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
++        return this.items;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++    // CraftBukkit end
+ 
+     public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
+         super(BlockEntityType.BREWING_STAND, pos, state);
+@@ -92,9 +_,22 @@
+ 
+     public static void serverTick(Level level, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) {
+         ItemStack itemStack = blockEntity.items.get(4);
+-        if (blockEntity.fuel <= 0 && itemStack.is(ItemTags.BREWING_FUEL)) {
+-            blockEntity.fuel = 20;
+-            itemStack.shrink(1);
++        if (blockEntity.fuel <= 0 && itemStack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)) {
++            // CraftBukkit start
++            org.bukkit.event.inventory.BrewingStandFuelEvent event = new org.bukkit.event.inventory.BrewingStandFuelEvent(
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack),
++                20
++            );
++            if (!event.callEvent()) {
++                return;
++            }
++
++            blockEntity.fuel = event.getFuelPower();
++            if (blockEntity.fuel > 0 && event.isConsuming()) {
++                itemStack.shrink(1);
++            }
++            // CraftBukkit end
+             setChanged(level, pos, state);
+         }
+ 
+@@ -105,7 +_,7 @@
+             blockEntity.brewTime--;
+             boolean flag1 = blockEntity.brewTime == 0;
+             if (flag1 && isBrewable) {
+-                doBrew(level, pos, blockEntity.items);
++                doBrew(level, pos, blockEntity.items, blockEntity); // CraftBukkit
+             } else if (!isBrewable || !itemStack1.is(blockEntity.ingredient)) {
+                 blockEntity.brewTime = 0;
+             }
+@@ -114,6 +_,14 @@
+         } else if (isBrewable && blockEntity.fuel > 0) {
+             blockEntity.fuel--;
+             blockEntity.brewTime = 400;
++            // CraftBukkit start
++            org.bukkit.event.block.BrewingStartEvent event = new org.bukkit.event.block.BrewingStartEvent(
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack1), 400);
++            event.callEvent();
++            blockEntity.recipeBrewTime = event.getRecipeBrewTime(); // Paper - use recipe brew time from event
++            blockEntity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event
++            // CraftBukkit end
+             blockEntity.ingredient = itemStack1.getItem();
+             setChanged(level, pos, state);
+         }
+@@ -164,13 +_,37 @@
+         }
+     }
+ 
+-    private static void doBrew(Level level, BlockPos pos, NonNullList<ItemStack> items) {
++    private static void doBrew(Level level, BlockPos pos, NonNullList<ItemStack> items, BrewingStandBlockEntity brewingStandBlockEntity) { // CraftBukkit
+         ItemStack itemStack = items.get(3);
+         PotionBrewing potionBrewing = level.potionBrewing();
+ 
++        // CraftBukkit start
++        org.bukkit.inventory.InventoryHolder owner = brewingStandBlockEntity.getOwner();
++        java.util.List<org.bukkit.inventory.ItemStack> brewResults = new java.util.ArrayList<>(3);
+         for (int i = 0; i < 3; i++) {
+-            items.set(i, potionBrewing.mix(itemStack, items.get(i)));
+-        }
++            brewResults.add(i, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(potionBrewing.mix(itemStack, items.get(i))));
++        }
++
++        if (owner != null) {
++            org.bukkit.event.inventory.BrewEvent event = new org.bukkit.event.inventory.BrewEvent(
++                org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                (org.bukkit.inventory.BrewerInventory) owner.getInventory(),
++                brewResults,
++                brewingStandBlockEntity.fuel
++            );
++            if (!event.callEvent()) {
++                return;
++            }
++
++            for (int i = 0; i < 3; i++) {
++                if (i < brewResults.size()) {
++                    items.set(i, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(brewResults.get(i)));
++                } else {
++                    items.set(i, ItemStack.EMPTY);
++                }
++            }
++        }
++        // CraftBukkit end
+ 
+         itemStack.shrink(1);
+         ItemStack craftingRemainder = itemStack.getItem().getCraftingRemainder();
+@@ -209,13 +_,13 @@
+ 
+     @Override
+     public boolean canPlaceItem(int index, ItemStack stack) {
++        PotionBrewing potionBrewing = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up
+         if (index == 3) {
+-            PotionBrewing potionBrewing = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY;
+             return potionBrewing.isIngredient(stack);
+         } else {
+             return index == 4
+-                ? stack.is(ItemTags.BREWING_FUEL)
+-                : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE))
++                ? stack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)
++                : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack)) // Paper - Custom Potion Mixes
+                     && this.getItem(index).isEmpty();
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
new file mode 100644
index 0000000000..b8231aa9ed
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
+@@ -138,7 +_,10 @@
+             double d5 = blockPos.getZ() + 0.5 * d1 + d2;
+             ItemEntity itemEntity = new ItemEntity(level, d3, d4, d5, this.item.split(level.random.nextInt(21) + 10));
+             itemEntity.setDeltaMovement(Vec3.ZERO);
+-            level.addFreshEntity(itemEntity);
++            // CraftBukkit start
++            org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition);
++            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), (ServerPlayer) player, java.util.List.of(itemEntity));
++            // CraftBukkit end
+             this.item = ItemStack.EMPTY;
+         }
+     }
+@@ -167,7 +_,7 @@
+ 
+     private boolean tryLoadLootTable(CompoundTag tag) {
+         if (tag.contains("LootTable", 8)) {
+-            this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(tag.getString("LootTable")));
++            this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(tag.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation
+             this.lootTableSeed = tag.getLong("LootTableSeed");
+             return true;
+         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch
similarity index 92%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch
index 944379decd..196b1b49d7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
-@@ -20,6 +20,12 @@
+@@ -20,6 +_,12 @@
      public VibrationSystem.User createVibrationUser() {
          return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos());
      }
@@ -12,8 +12,8 @@
 +    // Paper end - Configurable sculk sensor listener range
  
      protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser {
-         public VibrationUser(final BlockPos pos) {
-@@ -28,6 +34,7 @@
+         public VibrationUser(final BlockPos pos1) {
+@@ -28,6 +_,7 @@
  
          @Override
          public int getListenerRadius() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
new file mode 100644
index 0000000000..3d53a28694
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
@@ -0,0 +1,104 @@
+--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+@@ -36,6 +_,7 @@
+     private final NonNullList<ItemStack> items = NonNullList.withSize(4, ItemStack.EMPTY);
+     public final int[] cookingProgress = new int[4];
+     public final int[] cookingTime = new int[4];
++    public final boolean[] stopCooking = new boolean[4]; // Paper - Add more Campfire API
+ 
+     public CampfireBlockEntity(BlockPos pos, BlockState blockState) {
+         super(BlockEntityType.CAMPFIRE, pos, blockState);
+@@ -54,14 +_,42 @@
+             ItemStack itemStack = campfire.items.get(i);
+             if (!itemStack.isEmpty()) {
+                 flag = true;
++                if (!campfire.stopCooking[i]) { // Paper - Add more Campfire API
+                 campfire.cookingProgress[i]++;
++                } // Paper - Add more Campfire API
+                 if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) {
+                     SingleRecipeInput singleRecipeInput = new SingleRecipeInput(itemStack);
+-                    ItemStack itemStack1 = check.getRecipeFor(singleRecipeInput, level)
++                    final var optionalCookingRecipe = check.getRecipeFor(singleRecipeInput, level);
++                    ItemStack itemStack1 = optionalCookingRecipe
+                         .map(recipe -> recipe.value().assemble(singleRecipeInput, level.registryAccess()))
+                         .orElse(itemStack);
+                     if (itemStack1.isItemEnabled(level.enabledFeatures())) {
+-                        Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), itemStack1);
++                        // CraftBukkit start - fire BlockCookEvent
++                        org.bukkit.craftbukkit.inventory.CraftItemStack source = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++                        org.bukkit.inventory.ItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemStack1);
++
++                        org.bukkit.event.block.BlockCookEvent blockCookEvent = new org.bukkit.event.block.BlockCookEvent(
++                            org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                            source,
++                            result,
++                            (org.bukkit.inventory.CookingRecipe<?>) optionalCookingRecipe.map(RecipeHolder::toBukkitRecipe).orElse(null) // Paper -Add recipe to cook events
++                        );
++
++                        if (!blockCookEvent.callEvent()) {
++                            return;
++                        }
++
++                        result = blockCookEvent.getResult();
++                        itemStack1 = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(result);
++                        // CraftBukkit end
++                        // Paper start - Fix item locations dropped from campfires
++                        double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR;
++                        while (!itemStack1.isEmpty()) {
++                            net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(level, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemStack1.split(level.random.nextInt(21) + 10));
++                            droppedItem.setDeltaMovement(level.random.triangle(0.0D, deviation), level.random.triangle(0.2D, deviation), level.random.triangle(0.0D, deviation));
++                            level.addFreshEntity(droppedItem);
++                        }
++                        // Paper end - Fix item locations dropped from campfires
+                         campfire.items.set(i, ItemStack.EMPTY);
+                         level.sendBlockUpdated(pos, state, state, 3);
+                         level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
+@@ -133,6 +_,17 @@
+             int[] intArray = tag.getIntArray("CookingTotalTimes");
+             System.arraycopy(intArray, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, intArray.length));
+         }
++
++        // Paper start - Add more Campfire API
++        if (tag.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) {
++            byte[] abyte = tag.getByteArray("Paper.StopCooking");
++            boolean[] cookingState = new boolean[4];
++            for (int index = 0; index < abyte.length; index++) {
++                cookingState[index] = abyte[index] == 1;
++            }
++            System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length));
++        }
++        // Paper end - Add more Campfire API
+     }
+ 
+     @Override
+@@ -141,6 +_,13 @@
+         ContainerHelper.saveAllItems(tag, this.items, true, registries);
+         tag.putIntArray("CookingTimes", this.cookingProgress);
+         tag.putIntArray("CookingTotalTimes", this.cookingTime);
++        // Paper start - Add more Campfire API
++        byte[] cookingState = new byte[4];
++        for (int index = 0; index < cookingState.length; index++) {
++            cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0);
++        }
++        tag.putByteArray("Paper.StopCooking", cookingState);
++        // Paper end - Add more Campfire API
+     }
+ 
+     @Override
+@@ -165,7 +_,15 @@
+                     return false;
+                 }
+ 
+-                this.cookingTime[i] = recipeFor.get().value().cookingTime();
++                // CraftBukkit start
++                org.bukkit.event.block.CampfireStartEvent event = new org.bukkit.event.block.CampfireStartEvent(
++                    org.bukkit.craftbukkit.block.CraftBlock.at(this.level,this.worldPosition),
++                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack),
++                    (org.bukkit.inventory.CampfireRecipe) recipeFor.get().toBukkitRecipe()
++                );
++                this.level.getCraftServer().getPluginManager().callEvent(event);
++                this.cookingTime[i] = event.getTotalCookTime(); // i -> event.getTotalCookTime()
++                // CraftBukkit end
+                 this.cookingProgress[i] = 0;
+                 this.items.set(i, stack.consumeAndReturn(1, entity));
+                 level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState()));
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
index d3845d7cfa..9b690b9eea 100644
--- 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
@@ -20,7 +20,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++    public List<HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index 6248b82627..85aac7b304 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -24,7 +24,7 @@
 +    }
 +
 +    @Override
-+    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++    public List<HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
new file mode 100644
index 0000000000..2ca6be14cc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+@@ -9,6 +_,7 @@
+ import net.minecraft.core.particles.ParticleTypes;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
++import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.sounds.SoundEvent;
+ import net.minecraft.sounds.SoundEvents;
+ import net.minecraft.sounds.SoundSource;
+@@ -168,8 +_,20 @@
+     }
+ 
+     private static void applyEffects(Level level, BlockPos pos, List<BlockPos> positions) {
++        // CraftBukkit start
++        ConduitBlockEntity.applyEffects(level, pos, ConduitBlockEntity.getRange(positions));
++    }
++
++    public static int getRange(List<BlockPos> positions) {
++        // CraftBukkit end
+         int size = positions.size();
+         int i = size / 7 * 16;
++        // CraftBukkit start
++        return i;
++    }
++
++    private static void applyEffects(Level level, BlockPos pos, int i) { // i = effect range in blocks
++        // CraftBukkit end
+         int x = pos.getX();
+         int y = pos.getY();
+         int z = pos.getZ();
+@@ -185,6 +_,12 @@
+     }
+ 
+     private static void updateDestroyTarget(Level level, BlockPos pos, BlockState state, List<BlockPos> positions, ConduitBlockEntity blockEntity) {
++        // CraftBukkit start - add "damageTarget" boolean
++        ConduitBlockEntity.updateDestroyTarget(level, pos, state, positions, blockEntity, true);
++    }
++
++    public static void updateDestroyTarget(Level level, BlockPos pos, BlockState state, List<BlockPos> positions, ConduitBlockEntity blockEntity, boolean damageTarget) {
++        // CraftBukkit end
+         LivingEntity livingEntity = blockEntity.destroyTarget;
+         int size = positions.size();
+         if (size < 42) {
+@@ -203,7 +_,8 @@
+             blockEntity.destroyTarget = null;
+         }
+ 
+-        if (blockEntity.destroyTarget != null) {
++        if (damageTarget && blockEntity.destroyTarget != null) { // CraftBukkit
++            if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic(), 4.0F)) // CraftBukkit
+             level.playSound(
+                 null,
+                 blockEntity.destroyTarget.getX(),
+@@ -214,7 +_,6 @@
+                 1.0F,
+                 1.0F
+             );
+-            blockEntity.destroyTarget.hurt(level.damageSources().magic(), 4.0F);
+         }
+ 
+         if (livingEntity != blockEntity.destroyTarget) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
index b594786d46..e999d53387 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
@@ -1,76 +1,76 @@
 --- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
 +++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
-@@ -17,6 +17,7 @@
+@@ -13,6 +_,7 @@
      private static final int CHECK_TICK_DELAY = 5;
      private int openCount;
      private double maxInteractionRange;
-+    public boolean opened; // CraftBukkit
++    public boolean opened; // CraftBukki
  
-     public ContainerOpenersCounter() {}
+     protected abstract void onOpen(Level level, BlockPos pos, BlockState state);
  
-@@ -26,11 +27,36 @@
+@@ -20,10 +_,36 @@
  
-     protected abstract void openerCountChanged(Level world, BlockPos pos, BlockState state, int oldViewerCount, int newViewerCount);
+     protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount);
  
 +    // CraftBukkit start
-+    public void onAPIOpen(Level world, BlockPos blockposition, BlockState iblockdata) {
-+        this.onOpen(world, blockposition, iblockdata);
++    public void onAPIOpen(Level level, BlockPos blockPos, BlockState blockState) {
++        this.onOpen(level, blockPos, blockState);
 +    }
 +
-+    public void onAPIClose(Level world, BlockPos blockposition, BlockState iblockdata) {
-+        this.onClose(world, blockposition, iblockdata);
++    public void onAPIClose(Level level, BlockPos blockPos, BlockState blockState) {
++        this.onClose(level, blockPos, blockState);
 +    }
 +
-+    public void openerAPICountChanged(Level world, BlockPos blockposition, BlockState iblockdata, int i, int j) {
-+        this.openerCountChanged(world, blockposition, iblockdata, i, j);
++    public void openerAPICountChanged(Level world, BlockPos blockPos, BlockState blockState, int count, int openCount) {
++        this.openerCountChanged(world, blockPos, blockState, count, openCount);
 +    }
-+    // CraftBukkit end
++    // CraftBukkit en
 +
      protected abstract boolean isOwnContainer(Player player);
  
-     public void incrementOpeners(Player player, Level world, BlockPos pos, BlockState state) {
+     public void incrementOpeners(Player player, Level level, BlockPos pos, BlockState state) {
 +        int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
          int i = this.openCount++;
- 
++
 +        // CraftBukkit start - Call redstone event
-+        if (world.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
++        if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
 +            int newPower = Math.max(0, Math.min(15, this.openCount));
 +
 +            if (oldPower != newPower) {
-+                org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, oldPower, newPower);
++                org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
 +            }
 +        }
-+        // CraftBukkit end
++        // CraftBukkit en
 +
          if (i == 0) {
-             this.onOpen(world, pos, state);
-             world.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_OPEN, pos);
-@@ -42,8 +68,20 @@
+             this.onOpen(level, pos, state);
+             level.gameEvent(player, GameEvent.CONTAINER_OPEN, pos);
+@@ -35,7 +_,20 @@
      }
  
-     public void decrementOpeners(Player player, Level world, BlockPos pos, BlockState state) {
+     public void decrementOpeners(Player player, Level level, BlockPos pos, BlockState state) {
 +        int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
 +        if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative
          int i = this.openCount--;
- 
++
 +        // CraftBukkit start - Call redstone event
-+        if (world.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
++        if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
 +            int newPower = Math.max(0, Math.min(15, this.openCount));
 +
 +            if (oldPower != newPower) {
-+                org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, oldPower, newPower);
++                org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
 +            }
 +        }
 +        // CraftBukkit end
 +
          if (this.openCount == 0) {
-             this.onClose(world, pos, state);
-             world.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_CLOSE, pos);
-@@ -72,6 +110,7 @@
+             this.onClose(level, pos, state);
+             level.gameEvent(player, GameEvent.CONTAINER_CLOSE, pos);
+@@ -60,6 +_,7 @@
          }
  
-         int i = list.size();
-+        if (this.opened) i++; // CraftBukkit - add dummy count from API
-         int j = this.openCount;
- 
-         if (j != i) {
+         int size = playersWithContainerOpen.size();
++        if (this.opened) size++; // CraftBukkit - add dummy count from API
+         int i = this.openCount;
+         if (i != size) {
+             boolean flag = size != 0;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
new file mode 100644
index 0000000000..212d7b7f55
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
+@@ -56,6 +_,47 @@
+         }
+     };
+ 
++    // CraftBukkit start - add fields and methods
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++    private int maxStack = MAX_STACK;
++
++    @Override
++    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
++        return this.items;
++    }
++
++    @Override
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.add(who);
++    }
++
++    @Override
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.remove(who);
++    }
++
++    @Override
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    @Override
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        if (this.level == null) return null;
++        return io.papermc.paper.util.MCUtil.toLocation(this.level, this.worldPosition);
++    }
++    // CraftBukkit en
++
+     public CrafterBlockEntity(BlockPos pos, BlockState state) {
+         super(BlockEntityType.CRAFTER, pos, state);
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
index 6c93436b46..afb231c15d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
@@ -1,48 +1,37 @@
 --- a/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
-@@ -20,8 +20,59 @@
- import net.minecraft.world.level.storage.loot.LootTable;
+@@ -20,6 +_,48 @@
  import net.minecraft.world.ticks.ContainerSingleItem;
  
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.Arrays;
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
  public class DecoratedPotBlockEntity extends BlockEntity implements RandomizableContainer, ContainerSingleItem.BlockContainerSingleItem {
- 
++
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new ArrayList<>();
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
 +    @Override
 +    public List<ItemStack> getContents() {
-+        return Arrays.asList(this.item);
++        return java.util.List.of(this.item);
 +    }
 +
 +    @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<HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
 +    @Override
 +    public int getMaxStackSize() {
-+       return this.maxStack;
++        return this.maxStack;
 +    }
 +
 +    @Override
@@ -51,9 +40,9 @@
 +    }
 +
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        if (this.level == null) return null;
-+        return CraftLocation.toBukkit(this.worldPosition, this.level.getWorld());
++        return org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.worldPosition, this.level.getWorld());
 +    }
 +    // CraftBukkit end
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
new file mode 100644
index 0000000000..b7ffd808ca
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
+@@ -17,6 +_,36 @@
+     public static final int CONTAINER_SIZE = 9;
+     private NonNullList<ItemStack> items = NonNullList.withSize(9, ItemStack.EMPTY);
+ 
++    // 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 List<HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++    // CraftBukkit end
++
+     protected DispenserBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
+         super(type, pos, blockState);
+     }
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
index 20e268ca9c..23642e8281 100644
--- 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
@@ -20,7 +20,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<org.bukkit.entity.HumanEntity> getViewers() {
++    public java.util.List<HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch
index 11519f80c2..a023b4d2ec 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JigsawBlockEntity.java.patch
@@ -1,16 +1,16 @@
 --- a/net/minecraft/world/level/block/entity/JigsawBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/JigsawBlockEntity.java
-@@ -131,7 +131,12 @@
-     public void generate(ServerLevel world, int maxDepth, boolean keepJigsaws) {
+@@ -131,7 +_,12 @@
+     public void generate(ServerLevel level, int maxDepth, boolean keepJigsaws) {
          BlockPos blockPos = this.getBlockPos().relative(this.getBlockState().getValue(JigsawBlock.ORIENTATION).front());
-         Registry<StructureTemplatePool> registry = world.registryAccess().lookupOrThrow(Registries.TEMPLATE_POOL);
--        Holder<StructureTemplatePool> holder = registry.getOrThrow(this.pool);
+         Registry<StructureTemplatePool> registry = level.registryAccess().lookupOrThrow(Registries.TEMPLATE_POOL);
+-        Holder<StructureTemplatePool> orThrow = registry.getOrThrow(this.pool);
 +        // Paper start - Replace getHolderOrThrow with a null check
-+        Holder<StructureTemplatePool> holder = registry.get(this.pool).orElse(null);
-+        if (holder == null) {
++        Holder<StructureTemplatePool> orThrow = registry.get(this.pool).orElse(null);
++        if (orThrow == null) {
 +            return;
 +        }
 +        // Paper end - Replace getHolderOrThrow with a null check
-         JigsawPlacement.generateJigsaw(world, holder, this.target, maxDepth, blockPos, keepJigsaws);
+         JigsawPlacement.generateJigsaw(level, orThrow, this.target, maxDepth, blockPos, keepJigsaws);
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index fa8fa48ae9..35ee987e0f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -1,40 +1,27 @@
 --- a/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
-@@ -19,13 +19,57 @@
- import net.minecraft.world.phys.Vec3;
+@@ -20,6 +_,44 @@
  import net.minecraft.world.ticks.ContainerSingleItem;
  
-+// CraftBukkit start
-+import java.util.Collections;
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
  public class JukeboxBlockEntity extends BlockEntity implements ContainerSingleItem.BlockContainerSingleItem {
- 
-     public static final String SONG_ITEM_TAG_ID = "RecordItem";
-     public static final String TICKS_SINCE_SONG_STARTED_TAG_ID = "ticks_since_song_started";
-     private ItemStack item;
-     private final JukeboxSongPlayer jukeboxSongPlayer;
++
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +    public boolean opened;
- 
++
 +    @Override
-+    public List<ItemStack> getContents() {
++    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
 +        return Collections.singletonList(this.item);
 +    }
 +
 +    @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);
 +    }
 +
@@ -49,16 +36,16 @@
 +    }
 +
 +    @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 JukeboxBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.JUKEBOX, pos, state);
-         this.item = ItemStack.EMPTY;
-@@ -137,7 +181,7 @@
+     public static final String SONG_ITEM_TAG_ID = "RecordItem";
+     public static final String TICKS_SINCE_SONG_STARTED_TAG_ID = "ticks_since_song_started";
+     private ItemStack item = ItemStack.EMPTY;
+@@ -126,7 +_,7 @@
  
      @Override
      public int getMaxStackSize() {
@@ -67,20 +54,14 @@
      }
  
      @Override
-@@ -156,12 +200,17 @@
-     }
- 
+@@ -147,9 +_,14 @@
      @VisibleForTesting
--    public void setSongItemWithoutPlaying(ItemStack stack) {
--        this.item = stack;
--        JukeboxSong.fromStack(this.level.registryAccess(), stack).ifPresent((holder) -> {
--            this.jukeboxSongPlayer.setSongWithoutPlaying(holder, 0L);
-+    public void setSongItemWithoutPlaying(ItemStack itemstack, long ticksSinceSongStarted) { // CraftBukkit - add argument
-+        this.item = itemstack;
+     public void setSongItemWithoutPlaying(ItemStack stack) {
+         this.item = stack;
+-        JukeboxSong.fromStack(this.level.registryAccess(), stack)
 +        this.jukeboxSongPlayer.song = null; // CraftBukkit - reset
-+        JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), itemstack).ifPresent((holder) -> { // Paper - fallback to other RegistyrAccess if no level
-+            this.jukeboxSongPlayer.setSongWithoutPlaying(holder, ticksSinceSongStarted); // CraftBukkit - add argument
-         });
++        JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), stack) // Paper - fallback to other RegistyrAccess if no level
+             .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, 0L));
 -        this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
 +        // CraftBukkit start - add null check for level
 +        if (this.level != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
new file mode 100644
index 0000000000..5f04d187fe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -0,0 +1,139 @@
+--- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
+@@ -33,6 +_,51 @@
+     public static final int SLOT_BOOK = 0;
+     public static final int NUM_SLOTS = 1;
+     public final Container bookAccess = new Container() {
++        // CraftBukkit start - add fields and methods
++        public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++        private int maxStack = 1;
++
++        @Override
++        public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
++            return java.util.List.of(LecternBlockEntity.this.book);
++        }
++
++        @Override
++        public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++            this.transaction.add(who);
++        }
++
++        @Override
++        public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++            this.transaction.remove(who);
++        }
++
++        @Override
++        public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++            return this.transaction;
++        }
++
++        @Override
++        public void setMaxStackSize(int i) {
++            this.maxStack = i;
++        }
++
++        @Override
++        public org.bukkit.Location getLocation() {
++            if (LecternBlockEntity.this.level == null) return null;
++            return io.papermc.paper.util.MCUtil.toLocation(LecternBlockEntity.this.level, LecternBlockEntity.this.worldPosition);
++        }
++
++        @Override
++        public org.bukkit.inventory.InventoryHolder getOwner() {
++            return LecternBlockEntity.this.getOwner();
++        }
++
++        public LecternBlockEntity getLectern() {
++            return LecternBlockEntity.this;
++        }
++        // CraftBukkit end
++
+         @Override
+         public int getContainerSize() {
+             return 1;
+@@ -76,11 +_,19 @@
+ 
+         @Override
+         public void setItem(int slot, ItemStack stack) {
++            // CraftBukkit start
++            if (slot == 0) {
++                LecternBlockEntity.this.setBook(stack);
++                if (LecternBlockEntity.this.getLevel() != null) {
++                    LecternBlock.resetBookState(null, LecternBlockEntity.this.getLevel(), LecternBlockEntity.this.getBlockPos(), LecternBlockEntity.this.getBlockState(), LecternBlockEntity.this.hasBook());
++                }
++            }
++            // CraftBukkit end
+         }
+ 
+         @Override
+         public int getMaxStackSize() {
+-            return 1;
++            return maxStack;
+         }
+ 
+         @Override
+@@ -158,7 +_,7 @@
+         if (i != this.page) {
+             this.page = i;
+             this.setChanged();
+-            LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState());
++            if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit
+         }
+     }
+ 
+@@ -179,6 +_,36 @@
+         return stack;
+     }
+ 
++    // CraftBukkit start
++    private final CommandSource commandSource = new CommandSource() {
++
++        @Override
++        public void sendSystemMessage(Component message) {
++        }
++
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack commandSourceStack) {
++            return commandSourceStack.getEntity() != null
++                ? commandSourceStack.getEntity().getBukkitEntity()
++                : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(commandSourceStack, LecternBlockEntity.this);
++        }
++
++        @Override
++        public boolean acceptsSuccess() {
++            return false;
++        }
++
++        @Override
++        public boolean acceptsFailure() {
++            return false;
++        }
++
++        @Override
++        public boolean shouldInformAdmins() {
++            return false;
++        }
++    };
++    // CraftBukkit end
+     private CommandSourceStack createCommandSourceStack(@Nullable Player player, ServerLevel level) {
+         String string;
+         Component component;
+@@ -191,7 +_,7 @@
+         }
+ 
+         Vec3 vec3 = Vec3.atCenterOf(this.worldPosition);
+-        return new CommandSourceStack(CommandSource.NULL, vec3, Vec2.ZERO, level, 2, string, component, level.getServer(), player);
++        return new CommandSourceStack(this.commandSource, vec3, Vec2.ZERO, level, 2, string, component, level.getServer(), player); // CraftBukkit - commandSource
+     }
+ 
+     @Override
+@@ -223,7 +_,7 @@
+ 
+     @Override
+     public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
+-        return new LecternMenu(containerId, this.bookAccess, this.dataAccess);
++        return new LecternMenu(containerId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch
similarity index 85%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch
index 16ff81b3cd..cb89a7e6d1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-@@ -115,4 +115,13 @@
-         nbt.remove("LootTable");
-         nbt.remove("LootTableSeed");
+@@ -115,4 +_,13 @@
+         tag.remove("LootTable");
+         tag.remove("LootTableSeed");
      }
 +
 +    // Paper start - LootTable API
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch
similarity index 72%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch
index 112cb33190..1f46bbe4dc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java.patch
@@ -1,20 +1,20 @@
 --- a/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
-@@ -26,6 +26,7 @@
+@@ -26,6 +_,7 @@
      private final VibrationSystem.Listener vibrationListener;
      private final VibrationSystem.User vibrationUser = this.createVibrationUser();
      public int lastVibrationFrequency;
 +    @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range
  
-     protected SculkSensorBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
-         super(type, pos, state);
-@@ -52,8 +53,16 @@
+     protected SculkSensorBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
+         super(type, pos, blockState);
+@@ -52,8 +_,16 @@
                  .resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string))
-                 .ifPresent(listener -> this.vibrationData = listener);
+                 .ifPresent(data -> this.vibrationData = data);
          }
 +        // Paper start - Configurable sculk sensor listener range
-+        if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) {
-+            this.rangeOverride = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY);
++        if (tag.contains(PAPER_LISTENER_RANGE_NBT_KEY)) {
++            this.rangeOverride = tag.getInt(PAPER_LISTENER_RANGE_NBT_KEY);
 +        } else {
 +            this.rangeOverride = null;
 +        }
@@ -23,23 +23,24 @@
  
 +    protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range
      @Override
-     protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-         super.saveAdditional(nbt, registries);
-@@ -63,7 +72,13 @@
+     protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
+         super.saveAdditional(tag, registries);
+@@ -63,7 +_,13 @@
              .encodeStart(registryOps, this.vibrationData)
              .resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string))
-             .ifPresent(listenerNbt -> nbt.put("listener", listenerNbt));
-+        this.saveRangeOverride(nbt); // Paper - Configurable sculk sensor listener range
-     }
+             .ifPresent(tag1 -> tag.put("listener", tag1));
+-    }
++        this.saveRangeOverride(tag); // Paper - Configurable sculk sensor listener range
++    }
 +    // Paper start - Configurable sculk sensor listener range
-+    protected void saveRangeOverride(CompoundTag nbt) {
-+        if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
++    protected void saveRangeOverride(CompoundTag tag) {
++        if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) tag.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
 +    }
 +    // Paper end - Configurable sculk sensor listener range
  
      @Override
      public VibrationSystem.Data getVibrationData() {
-@@ -100,6 +115,7 @@
+@@ -100,6 +_,7 @@
  
          @Override
          public int getListenerRadius() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch
index fb8ab267d1..f6de079656 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java
-@@ -105,6 +105,13 @@
+@@ -105,6 +_,13 @@
  
      @Nullable
      public static ServerPlayer tryGetPlayer(@Nullable Entity entity) {
@@ -14,12 +14,12 @@
          if (entity instanceof ServerPlayer) {
              return (ServerPlayer)entity;
          } else {
-@@ -190,7 +197,7 @@
-     private boolean trySummonWarden(ServerLevel world) {
+@@ -190,7 +_,7 @@
+     private boolean trySummonWarden(ServerLevel level) {
          return this.warningLevel >= 4
              && SpawnUtil.trySpawnMob(
--                    EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false
-+                    EntityType.WARDEN, EntitySpawnReason.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null // Paper - Entity#getEntitySpawnReason
+-                    EntityType.WARDEN, EntitySpawnReason.TRIGGERED, level, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false
++                    EntityType.WARDEN, EntitySpawnReason.TRIGGERED, level, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null // Paper - Entity#getEntitySpawnReason
                  )
                  .isPresent();
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
index 384ab32533..297c0cd136 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
@@ -1,22 +1,11 @@
 --- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
-@@ -33,6 +33,10 @@
- import net.minecraft.world.level.material.PushReaction;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer {
- 
-@@ -52,6 +56,37 @@
+@@ -49,6 +_,37 @@
      @Nullable
      private final DyeColor color;
  
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +    public boolean opened;
 +
@@ -24,15 +13,15 @@
 +        return this.itemStacks;
 +    }
 +
-+    public void onOpen(CraftHumanEntity who) {
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.add(who);
 +    }
 +
-+    public void onClose(CraftHumanEntity who) {
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
@@ -46,22 +35,22 @@
 +    }
 +    // CraftBukkit end
 +
-     public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState state) {
-         super(BlockEntityType.SHULKER_BOX, pos, state);
-         this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY);
-@@ -184,6 +219,7 @@
+     public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState blockState) {
+         super(BlockEntityType.SHULKER_BOX, pos, blockState);
+         this.color = color;
+@@ -167,6 +_,7 @@
              }
  
-             ++this.openCount;
-+            if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
+             this.openCount++;
++            if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call
              this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
              if (this.openCount == 1) {
-                 this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_OPEN, this.worldPosition);
-@@ -197,6 +233,7 @@
+                 this.level.gameEvent(player, GameEvent.CONTAINER_OPEN, this.worldPosition);
+@@ -180,6 +_,7 @@
      public void stopOpen(Player player) {
          if (!this.remove && !player.isSpectator()) {
-             --this.openCount;
+             this.openCount--;
 +            if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
              this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
              if (this.openCount <= 0) {
-                 this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_CLOSE, this.worldPosition);
+                 this.level.gameEvent(player, GameEvent.CONTAINER_CLOSE, this.worldPosition);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
new file mode 100644
index 0000000000..a656788ab2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
@@ -0,0 +1,183 @@
+--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
+@@ -54,11 +_,16 @@
+         return new SignText();
+     }
+ 
+-    public boolean isFacingFrontText(Player player) {
++    public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) {
++        // Paper start - More Sign Block API
++        return this.isFacingFrontText(player.getX(), player.getZ());
++    }
++    public boolean isFacingFrontText(double x, double z) {
++        // Paper end - More Sign Block API
+         if (this.getBlockState().getBlock() instanceof SignBlock signBlock) {
+             Vec3 signHitboxCenterPosition = signBlock.getSignHitboxCenterPosition(this.getBlockState());
+-            double d = player.getX() - (this.getBlockPos().getX() + signHitboxCenterPosition.x);
+-            double d1 = player.getZ() - (this.getBlockPos().getZ() + signHitboxCenterPosition.z);
++            double d = x - ((double) this.getBlockPos().getX() + signHitboxCenterPosition.x); // Paper - More Sign Block API
++            double d1 = z - ((double) this.getBlockPos().getZ() + signHitboxCenterPosition.z); // Paper - More Sign Block AP
+             float yRotationDegrees = signBlock.getYRotationDegrees(this.getBlockState());
+             float f = (float)(Mth.atan2(d1, d) * 180.0F / (float)Math.PI) - 90.0F;
+             return Mth.degreesDifferenceAbs(yRotationDegrees, f) <= 90.0F;
+@@ -143,11 +_,13 @@
+ 
+     public void updateSignText(Player player, boolean isFrontText, List<FilteredText> filteredText) {
+         if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
+-            this.updateText(signText -> this.setMessages(player, filteredText, signText), isFrontText);
++            this.updateText(signText -> this.setMessages(player, filteredText, signText, isFrontText), isFrontText); // CraftBukkit
+             this.setAllowedPlayerEditor(null);
+             this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
+         } else {
+             LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
++            if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update
++            ((net.minecraft.server.level.ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
+         }
+     }
+ 
+@@ -156,18 +_,40 @@
+         return this.setText(updater.apply(text), isFrontText);
+     }
+ 
+-    private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text) {
++    private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text, boolean front) { // CraftBukkit
++        SignText originalText = text; // CraftBukkit
+         for (int i = 0; i < filteredText.size(); i++) {
+             FilteredText filteredText1 = filteredText.get(i);
+             Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
+             if (player.isTextFilteringEnabled()) {
+-                text = text.setMessage(i, Component.literal(filteredText1.filteredOrEmpty()).setStyle(style));
++                text = text.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style)); // Paper - filter sign text to chat only
+             } else {
+                 text = text.setMessage(
+-                    i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(filteredText1.filteredOrEmpty()).setStyle(style)
++                    i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(net.minecraft.util.StringUtil.filterText(filteredText1.filteredOrEmpty())).setStyle(style) // Paper - filter sign text to chat only
+                 );
+             }
+         }
++
++        // CraftBukkit start
++        org.bukkit.entity.Player apiPlayer = ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity();
++        List<net.kyori.adventure.text.Component> lines = new java.util.ArrayList<>(); // Paper - adventure
++
++        for (int i = 0; i < filteredText.size(); ++i) {
++            lines.add(io.papermc.paper.adventure.PaperAdventure.asAdventure(text.getMessage(i, player.isTextFilteringEnabled()))); // Paper - Adventure
++        }
++
++        org.bukkit.event.block.SignChangeEvent event = new org.bukkit.event.block.SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition), apiPlayer, new java.util.ArrayList<>(lines), (front) ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK); // Paper - Adventure
++        if (!event.callEvent()) {
++            return originalText;
++        }
++
++        Component[] components = org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.lines()); // Paper - Adventure
++        for (int i = 0; i < components.length; i++) {
++            if (!java.util.Objects.equals(lines.get(i), event.line(i))) { // Paper - Adventure
++                text = text.setMessage(i, components[i]);
++            }
++        }
++        // CraftBukkit end
+ 
+         return text;
+     }
+@@ -207,7 +_,23 @@
+             Style style = component.getStyle();
+             ClickEvent clickEvent = style.getClickEvent();
+             if (clickEvent != null && clickEvent.getAction() == ClickEvent.Action.RUN_COMMAND) {
+-                player.getServer().getCommands().performPrefixedCommand(createCommandSourceStack(player, level, pos), clickEvent.getValue());
++                // Paper start - Fix commands from signs not firing command events
++                String command = clickEvent.getValue().startsWith("/") ? clickEvent.getValue() : "/" + clickEvent.getValue();
++                if (org.spigotmc.SpigotConfig.logCommands)  {
++                    LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command);
++                }
++                io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent(
++                    (org.bukkit.entity.Player) player.getBukkitEntity(),
++                    command,
++                    new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()),
++                    (org.bukkit.block.Sign) org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition).getState(),
++                    frontText ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK
++                );
++                if (!event.callEvent()) {
++                    return false;
++                }
++                player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), level, pos), event.getMessage());
++                // Paper end - Fix commands from signs not firing command events
+                 flag = true;
+             }
+         }
+@@ -215,10 +_,55 @@
+         return flag;
+     }
+ 
+-    private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level level, BlockPos pos) {
++    // CraftBukkit start
++    private final CommandSource commandSource = new CommandSource() {
++
++        @Override
++        public void sendSystemMessage(Component message) {}
++
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack commandSourceStack) {
++            return commandSourceStack.getEntity() != null ? commandSourceStack.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(commandSourceStack, SignBlockEntity.this);
++        }
++
++        @Override
++        public boolean acceptsSuccess() {
++            return false;
++        }
++
++        @Override
++        public boolean acceptsFailure() {
++            return false;
++        }
++
++        @Override
++        public boolean shouldInformAdmins() {
++            return false;
++        }
++    };
++
++    private CommandSourceStack createCommandSourceStack(@Nullable Player player, Level world, BlockPos pos) {
++        // CraftBukkit end
+         String string = player == null ? "Sign" : player.getName().getString();
+         Component component = (Component)(player == null ? Component.literal("Sign") : player.getDisplayName());
+-        return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player);
++
++        // Paper start - Fix commands from signs not firing command events
++        CommandSource commandSource = world.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) {
++            @Override
++            public void sendSystemMessage(Component message) {
++                if (player instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) {
++                    serverPlayer.sendSystemMessage(message);
++                }
++            }
++
++            @Override
++            public boolean acceptsFailure() {
++                return true;
++            }
++        } : this.commandSource;
++        // Paper end - Fix commands from signs not firing command events
++        // CraftBukkit - this
++        return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, string, (Component) component, world.getServer(), player); // Paper - Fix commands from signs not firing command events
+     }
+ 
+     @Override
+@@ -237,12 +_,17 @@
+ 
+     @Nullable
+     public UUID getPlayerWhoMayEdit() {
++        // CraftBukkit start - unnecessary sign ticking removed, so do this lazily
++        if (this.level != null && this.playerWhoMayEdit != null) {
++            this.clearInvalidPlayerWhoMayEdit(this, this.level, this.playerWhoMayEdit);
++        }
++        // CraftBukkit end
+         return this.playerWhoMayEdit;
+     }
+ 
+     private void markUpdated() {
+         this.setChanged();
+-        this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
++        if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122)
+     }
+ 
+     public boolean isWaxed() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
similarity index 76%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
index a683a1cb1b..9261f76fc9 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-@@ -41,7 +41,7 @@
+@@ -41,7 +_,7 @@
      @Nullable
      private static LoadingCache<String, CompletableFuture<Optional<GameProfile>>> profileCacheByName;
      @Nullable
@@ -9,45 +9,45 @@
      public static final Executor CHECKED_MAIN_THREAD_EXECUTOR = runnable -> {
          Executor executor = mainThreadExecutor;
          if (executor != null) {
-@@ -76,9 +76,9 @@
+@@ -76,9 +_,9 @@
          profileCacheById = CacheBuilder.newBuilder()
              .expireAfterAccess(Duration.ofMinutes(10L))
              .maximumSize(256L)
 -            .build(new CacheLoader<UUID, CompletableFuture<Optional<GameProfile>>>() {
 +            .build(new CacheLoader<>() { // Paper - player profile events
                  @Override
--                public CompletableFuture<Optional<GameProfile>> load(UUID uUID) {
-+                public CompletableFuture<Optional<GameProfile>> load(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> uUID) { // Paper - player profile events
-                     return SkullBlockEntity.fetchProfileById(uUID, apiServices, booleanSupplier);
+-                public CompletableFuture<Optional<GameProfile>> load(UUID id) {
++                public CompletableFuture<Optional<GameProfile>> load(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id) { // Paper - player profile events
+                     return SkullBlockEntity.fetchProfileById(id, services, booleanSupplier);
                  }
              });
-@@ -89,23 +89,29 @@
+@@ -89,23 +_,29 @@
              .getAsync(name)
              .thenCompose(
                  optional -> {
 -                    LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
 +                    LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById; // Paper - player profile events
                      return loadingCache != null && !optional.isEmpty()
--                        ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional2 -> optional2.or(() -> optional))
-+                        ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional2 -> optional2.or(() -> optional)) // Paper - player profile events
+-                        ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional1 -> optional1.or(() -> optional))
++                        ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional1 -> optional1.or(() -> optional))  // Paper - player profile events
                          : CompletableFuture.completedFuture(Optional.empty());
                  }
              );
      }
  
--    static CompletableFuture<Optional<GameProfile>> fetchProfileById(UUID uuid, Services apiServices, BooleanSupplier booleanSupplier) {
-+    static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> pair, Services apiServices, BooleanSupplier booleanSupplier) { // Paper
+-    static CompletableFuture<Optional<GameProfile>> fetchProfileById(UUID id, Services services, BooleanSupplier cacheUninitialized) {
++    static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id, Services services, BooleanSupplier cacheUninitialized) { // Pape
          return CompletableFuture.supplyAsync(() -> {
-             if (booleanSupplier.getAsBoolean()) {
+             if (cacheUninitialized.getAsBoolean()) {
                  return Optional.empty();
              } else {
--                ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true);
+-                ProfileResult profileResult = services.sessionService().fetchProfile(id, true);
 +                // Paper start - fill player profile events
-+                if (apiServices.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) {
-+                    final GameProfile profile = pair.getSecond() != null ? pair.getSecond() : new com.mojang.authlib.GameProfile(pair.getFirst(), "");
++                if (services.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) {
++                    final GameProfile profile = id.getSecond() != null ? id.getSecond() : new com.mojang.authlib.GameProfile(id.getFirst(), "");
 +                    return Optional.ofNullable(paperService.fetchProfile(profile, true)).map(ProfileResult::profile);
 +                }
-+                ProfileResult profileResult = apiServices.sessionService().fetchProfile(pair.getFirst(), true);
++                ProfileResult profileResult = services.sessionService().fetchProfile(id.getFirst(), true);
 +                // Paper end - fill player profile events
                  return Optional.ofNullable(profileResult).map(ProfileResult::profile);
              }
@@ -56,13 +56,13 @@
      }
  
      public static void clear() {
-@@ -210,9 +216,11 @@
+@@ -210,9 +_,11 @@
              : CompletableFuture.completedFuture(Optional.empty());
      }
  
--    public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID uuid) {
+-    public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID profileUuid) {
 -        LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
--        return loadingCache != null ? loadingCache.getUnchecked(uuid) : CompletableFuture.completedFuture(Optional.empty());
+-        return loadingCache != null ? loadingCache.getUnchecked(profileUuid) : CompletableFuture.completedFuture(Optional.empty());
 +    // Paper start - player profile events
 +    public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID uuid, @Nullable String name) {
 +        LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID,  @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
deleted file mode 100644
index 8438fd826a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ /dev/null
@@ -1,356 +0,0 @@
---- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
-@@ -22,7 +22,6 @@
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.WorldlyContainer;
- import net.minecraft.world.entity.ExperienceOrb;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.player.StackedItemContents;
- import net.minecraft.world.inventory.ContainerData;
- import net.minecraft.world.inventory.RecipeCraftingHolder;
-@@ -41,6 +40,20 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftItemType;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.block.BlockExpEvent;
-+import org.bukkit.event.inventory.FurnaceBurnEvent;
-+import org.bukkit.event.inventory.FurnaceExtractEvent;
-+import org.bukkit.event.inventory.FurnaceSmeltEvent;
-+import org.bukkit.event.inventory.FurnaceStartSmeltEvent;
-+import org.bukkit.inventory.CookingRecipe;
-+// CraftBukkit end
- 
- public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible {
- 
-@@ -65,6 +78,8 @@
-     protected final ContainerData dataAccess;
-     public final Reference2IntOpenHashMap<ResourceKey<Recipe<?>>> recipesUsed;
-     private final RecipeManager.CachedCheck<SingleRecipeInput, ? extends AbstractCookingRecipe> quickCheck;
-+    public final RecipeType<? extends AbstractCookingRecipe> recipeType; // Paper - cook speed multiplier API
-+    public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API
- 
-     protected AbstractFurnaceBlockEntity(BlockEntityType<?> blockEntityType, BlockPos pos, BlockState state, RecipeType<? extends AbstractCookingRecipe> recipeType) {
-         super(blockEntityType, pos, state);
-@@ -110,9 +125,40 @@
-             }
-         };
-         this.recipesUsed = new Reference2IntOpenHashMap();
--        this.quickCheck = RecipeManager.createCheck(recipeType);
-+        this.quickCheck = RecipeManager.createCheck((RecipeType<AbstractCookingRecipe>) recipeType); // CraftBukkit - decompile error // Eclipse fail
-+        this.recipeType = recipeType; // Paper - cook speed multiplier API
-     }
- 
-+    // CraftBukkit start - add fields and methods
-+    private int maxStack = MAX_STACK;
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
-+
-+    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
-+
-     private boolean isLit() {
-         return this.litTimeRemaining > 0;
-     }
-@@ -132,9 +178,18 @@
-         while (iterator.hasNext()) {
-             String s = (String) iterator.next();
- 
--            this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)), nbttagcompound1.getInt(s));
-+            // Paper start - Validate ResourceLocation
-+            final ResourceLocation resourceLocation = ResourceLocation.tryParse(s);
-+            if (resourceLocation != null) {
-+                this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, resourceLocation), nbttagcompound1.getInt(s));
-+            }
-         }
- 
-+        // Paper start - cook speed multiplier API
-+        if (nbt.contains("Paper.CookSpeedMultiplier")) {
-+            this.cookSpeedMultiplier = nbt.getDouble("Paper.CookSpeedMultiplier");
-+        }
-+        // Paper end - cook speed multiplier API
-     }
- 
-     @Override
-@@ -144,6 +199,7 @@
-         nbt.putShort("cooking_total_time", (short) this.cookingTotalTime);
-         nbt.putShort("lit_time_remaining", (short) this.litTimeRemaining);
-         nbt.putShort("lit_total_time", (short) this.litTotalTime);
-+        nbt.putDouble("Paper.CookSpeedMultiplier", this.cookSpeedMultiplier); // Paper - cook speed multiplier API
-         ContainerHelper.saveAllItems(nbt, this.items, registries);
-         CompoundTag nbttagcompound1 = new CompoundTag();
- 
-@@ -175,7 +231,7 @@
-             RecipeHolder recipeholder;
- 
-             if (flag2) {
--                recipeholder = (RecipeHolder) blockEntity.quickCheck.getRecipeFor(singlerecipeinput, world).orElse((Object) null);
-+                recipeholder = (RecipeHolder) blockEntity.quickCheck.getRecipeFor(singlerecipeinput, world).orElse(null); // CraftBukkit - decompile error
-             } else {
-                 recipeholder = null;
-             }
-@@ -183,11 +239,22 @@
-             int i = blockEntity.getMaxStackSize();
- 
-             if (!blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) {
--                blockEntity.litTimeRemaining = blockEntity.getBurnDuration(world.fuelValues(), itemstack);
-+                // CraftBukkit start
-+                CraftItemStack fuel = CraftItemStack.asCraftMirror(itemstack);
-+
-+                FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(CraftBlock.at(world, pos), fuel, blockEntity.getBurnDuration(world.fuelValues(), itemstack));
-+                world.getCraftServer().getPluginManager().callEvent(furnaceBurnEvent);
-+
-+                if (furnaceBurnEvent.isCancelled()) {
-+                    return;
-+                }
-+
-+                blockEntity.litTimeRemaining = furnaceBurnEvent.getBurnTime();
-                 blockEntity.litTotalTime = blockEntity.litTimeRemaining;
--                if (blockEntity.isLit()) {
-+                if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) {
-+                    // CraftBukkit end
-                     flag1 = true;
--                    if (flag3) {
-+                    if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent
-                         Item item = itemstack.getItem();
- 
-                         itemstack.shrink(1);
-@@ -199,11 +266,23 @@
-             }
- 
-             if (blockEntity.isLit() && AbstractFurnaceBlockEntity.canBurn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) {
-+                // CraftBukkit start
-+                if (recipeholder != null && blockEntity.cookingTimer == 0) {
-+                    CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0));
-+                    CookingRecipe<?> recipe = (CookingRecipe<?>) recipeholder.toBukkitRecipe();
-+
-+                    FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(world, pos), source, recipe, AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier)); // Paper - cook speed multiplier API
-+                    world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                    blockEntity.cookingTotalTime = event.getTotalCookTime();
-+                }
-+                // CraftBukkit end
-+
-                 ++blockEntity.cookingTimer;
--                if (blockEntity.cookingTimer == blockEntity.cookingTotalTime) {
-+                if (blockEntity.cookingTimer >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API
-                     blockEntity.cookingTimer = 0;
--                    blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity);
--                    if (AbstractFurnaceBlockEntity.burn(world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) {
-+                    blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity, blockEntity.recipeType, blockEntity.cookSpeedMultiplier); // Paper - cook speed multiplier API
-+                    if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, world.registryAccess(), recipeholder, singlerecipeinput, blockEntity.items, i)) { // CraftBukkit
-                         blockEntity.setRecipeUsed(recipeholder);
-                     }
- 
-@@ -242,20 +321,47 @@
-         }
-     }
- 
--    private static boolean burn(RegistryAccess dynamicRegistryManager, @Nullable RecipeHolder<? extends AbstractCookingRecipe> recipe, SingleRecipeInput input, NonNullList<ItemStack> inventory, int maxCount) {
--        if (recipe != null && AbstractFurnaceBlockEntity.canBurn(dynamicRegistryManager, recipe, input, inventory, maxCount)) {
--            ItemStack itemstack = (ItemStack) inventory.get(0);
--            ItemStack itemstack1 = ((AbstractCookingRecipe) recipe.value()).assemble(input, dynamicRegistryManager);
--            ItemStack itemstack2 = (ItemStack) inventory.get(2);
-+    private static boolean burn(Level world, BlockPos blockposition, RegistryAccess iregistrycustom, @Nullable RecipeHolder<? extends AbstractCookingRecipe> recipeholder, SingleRecipeInput singlerecipeinput, NonNullList<ItemStack> nonnulllist, int i) { // CraftBukkit
-+        if (recipeholder != null && AbstractFurnaceBlockEntity.canBurn(iregistrycustom, recipeholder, singlerecipeinput, nonnulllist, i)) {
-+            ItemStack itemstack = (ItemStack) nonnulllist.get(0);
-+            ItemStack itemstack1 = ((AbstractCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, iregistrycustom);
-+            ItemStack itemstack2 = (ItemStack) nonnulllist.get(2);
- 
-+            // CraftBukkit start - fire FurnaceSmeltEvent
-+            CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
-+            org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
-+
-+            FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe<?>) recipeholder.toBukkitRecipe()); // Paper - Add recipe to cook events
-+            world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent);
-+
-+            if (furnaceSmeltEvent.isCancelled()) {
-+                return false;
-+            }
-+
-+            result = furnaceSmeltEvent.getResult();
-+            itemstack1 = CraftItemStack.asNMSCopy(result);
-+
-+            if (!itemstack1.isEmpty()) {
-+                if (itemstack2.isEmpty()) {
-+                    nonnulllist.set(2, itemstack1.copy());
-+                } else if (CraftItemStack.asCraftMirror(itemstack2).isSimilar(result)) {
-+                    itemstack2.grow(itemstack1.getCount());
-+                } else {
-+                    return false;
-+                }
-+            }
-+
-+            /*
-             if (itemstack2.isEmpty()) {
--                inventory.set(2, itemstack1.copy());
-+                nonnulllist.set(2, itemstack1.copy());
-             } else if (ItemStack.isSameItemSameComponents(itemstack2, itemstack1)) {
-                 itemstack2.grow(1);
-             }
-+            */
-+            // CraftBukkit end
- 
--            if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) inventory.get(1)).isEmpty() && ((ItemStack) inventory.get(1)).is(Items.BUCKET)) {
--                inventory.set(1, new ItemStack(Items.WATER_BUCKET));
-+            if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) nonnulllist.get(1)).isEmpty() && ((ItemStack) nonnulllist.get(1)).is(Items.BUCKET)) {
-+                nonnulllist.set(1, new ItemStack(Items.WATER_BUCKET));
-             }
- 
-             itemstack.shrink(1);
-@@ -269,12 +375,14 @@
-         return fuelRegistry.burnDuration(stack);
-     }
- 
--    public static int getTotalCookTime(ServerLevel world, AbstractFurnaceBlockEntity furnace) {
-+    public static int getTotalCookTime(@Nullable ServerLevel world, AbstractFurnaceBlockEntity furnace, RecipeType<? extends AbstractCookingRecipe> recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API
-         SingleRecipeInput singlerecipeinput = new SingleRecipeInput(furnace.getItem(0));
- 
--        return (Integer) furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> {
--            return ((AbstractCookingRecipe) recipeholder.value()).cookingTime();
--        }).orElse(200);
-+        // Paper start - cook speed multiplier API
-+        /* Scale the recipe's cooking time to the current cookSpeedMultiplier */
-+        int cookTime = world != null ? furnace.quickCheck.getRecipeFor(singlerecipeinput, world).map(holder -> holder.value().cookingTime()).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singlerecipeinput, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(holder -> holder.value().cookingTime()).orElse(200));
-+        return (int) Math.ceil (cookTime / cookSpeedMultiplier);
-+        // Paper end - cook speed multiplier API
-     }
- 
-     @Override
-@@ -320,12 +428,11 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this);
-+                this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(worldserver, this, this.recipeType, this.cookSpeedMultiplier); // Paper - cook speed multiplier API
-                 this.cookingTimer = 0;
-                 this.setChanged();
-             }
-         }
--
-     }
- 
-     @Override
-@@ -358,19 +465,19 @@
-     }
- 
-     @Override
--    public void awardUsedRecipes(Player player, List<ItemStack> ingredients) {}
-+    public void awardUsedRecipes(net.minecraft.world.entity.player.Player player, List<ItemStack> ingredients) {}
- 
--    public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
--        List<RecipeHolder<?>> list = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position());
-+    public void awardUsedRecipesAndPopExperience(ServerPlayer entityplayer, ItemStack itemstack, int amount) { // CraftBukkit
-+        List<RecipeHolder<?>> list = this.getRecipesToAwardAndPopExperience(entityplayer.serverLevel(), entityplayer.position(), this.worldPosition, entityplayer, itemstack, amount); // CraftBukkit
- 
--        player.awardRecipes(list);
-+        entityplayer.awardRecipes(list);
-         Iterator iterator = list.iterator();
- 
-         while (iterator.hasNext()) {
-             RecipeHolder<?> recipeholder = (RecipeHolder) iterator.next();
- 
-             if (recipeholder != null) {
--                player.triggerRecipeCrafted(recipeholder, this.items);
-+                entityplayer.triggerRecipeCrafted(recipeholder, this.items);
-             }
-         }
- 
-@@ -378,41 +485,56 @@
-     }
- 
-     public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel world, Vec3 pos) {
-+        // CraftBukkit start
-+        return this.getRecipesToAwardAndPopExperience(world, pos, this.worldPosition, null, null, 0);
-+    }
-+
-+    public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel worldserver, Vec3 vec3d, BlockPos blockposition, ServerPlayer entityplayer, ItemStack itemstack, int amount) {
-+        // CraftBukkit end
-         List<RecipeHolder<?>> list = Lists.newArrayList();
-         ObjectIterator objectiterator = this.recipesUsed.reference2IntEntrySet().iterator();
- 
-         while (objectiterator.hasNext()) {
-             Entry<ResourceKey<Recipe<?>>> entry = (Entry) objectiterator.next();
- 
--            world.recipeAccess().byKey((ResourceKey) entry.getKey()).ifPresent((recipeholder) -> {
-+            worldserver.recipeAccess().byKey(entry.getKey()).ifPresent((recipeholder) -> { // CraftBukkit - decompile error
-+                if (!(recipeholder.value() instanceof AbstractCookingRecipe)) return; // Paper - don't process non-cooking recipes
-                 list.add(recipeholder);
--                AbstractFurnaceBlockEntity.createExperience(world, pos, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).experience());
-+                AbstractFurnaceBlockEntity.createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).experience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit
-             });
-         }
- 
-         return list;
-     }
- 
--    private static void createExperience(ServerLevel world, Vec3 pos, int multiplier, float experience) {
--        int j = Mth.floor((float) multiplier * experience);
--        float f1 = Mth.frac((float) multiplier * experience);
-+    private static void createExperience(ServerLevel worldserver, Vec3 vec3d, int i, float f, BlockPos blockposition, net.minecraft.world.entity.player.Player entityhuman, ItemStack itemstack, int amount) { // CraftBukkit
-+        int j = Mth.floor((float) i * f);
-+        float f1 = Mth.frac((float) i * f);
- 
-         if (f1 != 0.0F && Math.random() < (double) f1) {
-             ++j;
-         }
- 
--        ExperienceOrb.award(world, pos, j);
-+        // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent
-+        BlockExpEvent event;
-+        if (amount != 0) {
-+            event = new FurnaceExtractEvent((Player) entityhuman.getBukkitEntity(), CraftBlock.at(worldserver, blockposition), CraftItemType.minecraftToBukkit(itemstack.getItem()), amount, j);
-+        } else {
-+            event = new BlockExpEvent(CraftBlock.at(worldserver, blockposition), j);
-+        }
-+        worldserver.getCraftServer().getPluginManager().callEvent(event);
-+        j = event.getExpToDrop();
-+        // CraftBukkit end
-+
-+        ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper
-     }
- 
-     @Override
-     public void fillStackedContents(StackedItemContents finder) {
--        Iterator iterator = this.items.iterator();
-+        // Paper start - don't account fuel stack (fixes MC-243057)
-+        finder.accountStack(this.items.get(SLOT_INPUT));
-+        finder.accountStack(this.items.get(SLOT_RESULT));
-+        // Paper end
- 
--        while (iterator.hasNext()) {
--            ItemStack itemstack = (ItemStack) iterator.next();
--
--            finder.accountStack(itemstack);
--        }
--
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
deleted file mode 100644
index 3b8ce9376c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
+++ /dev/null
@@ -1,81 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java
-@@ -19,13 +19,17 @@
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.List;
-+// CraftBukkit end
-+
- public class BannerBlockEntity extends BlockEntity implements Nameable {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-     public static final int MAX_PATTERNS = 6;
-     private static final String TAG_PATTERNS = "patterns";
-     @Nullable
--    private Component name;
-+    public Component name; // Paper - public
-     public DyeColor baseColor;
-     private BannerPatternLayers patterns;
- 
-@@ -53,7 +57,7 @@
-     @Override
-     protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-         super.saveAdditional(nbt, registries);
--        if (!this.patterns.equals(BannerPatternLayers.EMPTY)) {
-+        if (!this.patterns.equals(BannerPatternLayers.EMPTY) || serialisingForNetwork.get()) { // Paper - always send patterns to client
-             nbt.put("patterns", (Tag) BannerPatternLayers.CODEC.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), this.patterns).getOrThrow());
-         }
- 
-@@ -74,7 +78,7 @@
-             BannerPatternLayers.CODEC.parse(registries.createSerializationContext(NbtOps.INSTANCE), nbt.get("patterns")).resultOrPartial((s) -> {
-                 BannerBlockEntity.LOGGER.error("Failed to parse banner patterns: '{}'", s);
-             }).ifPresent((bannerpatternlayers) -> {
--                this.patterns = bannerpatternlayers;
-+                this.setPatterns(bannerpatternlayers); // CraftBukkit - apply limits
-             });
-         }
- 
-@@ -85,9 +89,18 @@
-         return ClientboundBlockEntityDataPacket.create(this);
-     }
- 
-+    // Paper start - always send patterns to client
-+    ThreadLocal<Boolean> serialisingForNetwork = ThreadLocal.withInitial(() -> Boolean.FALSE);
-     @Override
-     public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
-+        final Boolean wasSerialisingForNetwork = serialisingForNetwork.get();
-+        try {
-+            serialisingForNetwork.set(Boolean.TRUE);
-         return this.saveWithoutMetadata(registries);
-+        } finally {
-+            serialisingForNetwork.set(wasSerialisingForNetwork);
-+        }
-+        // Paper end - always send patterns to client
-     }
- 
-     public BannerPatternLayers getPatterns() {
-@@ -108,7 +121,7 @@
-     @Override
-     protected void applyImplicitComponents(BlockEntity.DataComponentInput components) {
-         super.applyImplicitComponents(components);
--        this.patterns = (BannerPatternLayers) components.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
-+        this.setPatterns((BannerPatternLayers) components.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY)); // CraftBukkit - apply limits
-         this.name = (Component) components.get(DataComponents.CUSTOM_NAME);
-     }
- 
-@@ -124,4 +137,13 @@
-         nbt.remove("patterns");
-         nbt.remove("CustomName");
-     }
-+
-+    // CraftBukkit start
-+    public void setPatterns(BannerPatternLayers bannerpatternlayers) {
-+        if (bannerpatternlayers.layers().size() > 20) {
-+            bannerpatternlayers = new BannerPatternLayers(List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
-+        }
-+        this.patterns = bannerpatternlayers;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
deleted file mode 100644
index 736be5da6c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
+++ /dev/null
@@ -1,259 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
-@@ -1,5 +1,6 @@
- package net.minecraft.world.level.block.entity;
- 
-+import com.destroystokyo.paper.event.block.BeaconEffectEvent;
- import com.google.common.collect.ImmutableList;
- import com.google.common.collect.Lists;
- import java.util.Collection;
-@@ -45,6 +46,11 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.levelgen.Heightmap;
- import net.minecraft.world.phys.AABB;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.potion.CraftPotionUtil;
-+import org.bukkit.potion.PotionEffect;
-+// CraftBukkit end
- 
- public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Nameable {
- 
-@@ -71,7 +77,36 @@
-     public Component name;
-     public LockCode lockKey;
-     private final ContainerData dataAccess;
-+    // CraftBukkit start - add fields and methods
-+    public PotionEffect getPrimaryEffect() {
-+        return (this.primaryPower != null) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.primaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null;
-+    }
- 
-+    public PotionEffect getSecondaryEffect() {
-+        return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null;
-+    }
-+    // CraftBukkit end
-+    // Paper start - Custom beacon ranges
-+    private final String PAPER_RANGE_TAG = "Paper.Range";
-+    private double effectRange = -1;
-+
-+    public double getEffectRange() {
-+        if (this.effectRange < 0) {
-+            return this.levels * 10 + 10;
-+        } else {
-+            return effectRange;
-+        }
-+    }
-+
-+    public void setEffectRange(double range) {
-+        this.effectRange = range;
-+    }
-+
-+    public void resetEffectRange() {
-+        this.effectRange = -1;
-+    }
-+    // Paper end - Custom beacon ranges
-+
-     @Nullable
-     static Holder<MobEffect> filterEffect(@Nullable Holder<MobEffect> effect) {
-         return BeaconBlockEntity.VALID_EFFECTS.contains(effect) ? effect : null;
-@@ -186,10 +221,19 @@
-             }
- 
-             if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
--                BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower);
-+                BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges
-                 BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT);
-             }
-         }
-+        // Paper start - beacon activation/deactivation events
-+        if (i1 <= 0 && blockEntity.levels > 0) {
-+            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+            new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent();
-+        } else if (i1 > 0 && blockEntity.levels <= 0) {
-+            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+            new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
-+        }
-+        // Paper end - beacon activation/deactivation events
- 
-         if (blockEntity.lastCheckY >= l) {
-             blockEntity.lastCheckY = world.getMinY() - 1;
-@@ -247,43 +291,123 @@
- 
-     @Override
-     public void setRemoved() {
-+        // Paper start - beacon activation/deactivation events
-+        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition);
-+        new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
-+        // Paper end - beacon activation/deactivation events
-+        // Paper start - fix MC-153086
-+        if (this.levels > 0 && !this.beamSections.isEmpty()) {
-         BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
-+        }
-+        // Paper end
-         super.setRemoved();
-     }
- 
--    private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect) {
--        if (!world.isClientSide && primaryEffect != null) {
--            double d0 = (double) (beaconLevel * 10 + 10);
-+    // CraftBukkit start - split into components
-+    private static byte getAmplification(int i, @Nullable Holder<MobEffect> holder, @Nullable Holder<MobEffect> holder1) {
-+        {
-             byte b0 = 0;
- 
--            if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
-+            if (i >= 4 && Objects.equals(holder, holder1)) {
-                 b0 = 1;
-             }
- 
--            int j = (9 + beaconLevel * 2) * 20;
--            AABB axisalignedbb = (new AABB(pos)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D);
--            List<Player> list = world.getEntitiesOfClass(Player.class, axisalignedbb);
-+            return b0;
-+        }
-+    }
-+
-+    private static int getLevel(int i) {
-+        {
-+            int j = (9 + i * 2) * 20;
-+            return j;
-+        }
-+    }
-+
-+    public static List getHumansInRange(Level world, BlockPos blockposition, int i) {
-+        // Paper start - Custom beacon ranges
-+        return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null);
-+    }
-+    public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) {
-+        // Paper end - Custom beacon ranges
-+        {
-+            double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges
-+
-+            AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D);
-+            // Paper start - Perf: optimize player lookup for beacons
-+            List<Player> list;
-+            if (d0 <= 128.0) {
-+                list = world.getEntitiesOfClass(Player.class, axisalignedbb);
-+            } else {
-+                list = new java.util.ArrayList<>();
-+                for (Player player : world.players()) {
-+                    if (player.isSpectator()) {
-+                        continue;
-+                    }
-+                    if (player.getBoundingBox().intersects(axisalignedbb)) {
-+                        list.add(player);
-+                    }
-+                }
-+            }
-+            // Paper end - Perf: optimize player lookup for beacons
-+
-+            return list;
-+        }
-+    }
-+
-+    private static void applyEffect(List list, @Nullable Holder<MobEffect> holder, int j, int b0, boolean isPrimary, BlockPos worldPosition) { // Paper - BeaconEffectEvent
-+        if (!list.isEmpty()) { // Paper - BeaconEffectEvent
-             Iterator iterator = list.iterator();
- 
-             Player entityhuman;
-+            // Paper start - BeaconEffectEvent
-+            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(((Player) list.get(0)).level(), worldPosition);
-+            PotionEffect effect = CraftPotionUtil.toBukkit(new MobEffectInstance(holder, j, b0, true, true));
-+            // Paper end - BeaconEffectEvent
- 
-             while (iterator.hasNext()) {
--                entityhuman = (Player) iterator.next();
--                entityhuman.addEffect(new MobEffectInstance(primaryEffect, j, b0, true, true));
-+                // Paper start - BeaconEffectEvent
-+                entityhuman = (ServerPlayer) iterator.next();
-+                BeaconEffectEvent event = new BeaconEffectEvent(block, effect, (org.bukkit.entity.Player) entityhuman.getBukkitEntity(), isPrimary);
-+                if (CraftEventFactory.callEvent(event).isCancelled()) continue;
-+                entityhuman.addEffect(new MobEffectInstance(CraftPotionUtil.fromBukkit(event.getEffect())), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON);
-+                // Paper end - BeaconEffectEvent
-             }
-+        }
-+    }
- 
--            if (beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null) {
--                iterator = list.iterator();
--
--                while (iterator.hasNext()) {
--                    entityhuman = (Player) iterator.next();
--                    entityhuman.addEffect(new MobEffectInstance(secondaryEffect, j, 0, true, true));
--                }
-+    private static boolean hasSecondaryEffect(int i, @Nullable Holder<MobEffect> holder, @Nullable Holder<MobEffect> holder1) {
-+        {
-+            if (i >= 4 && !Objects.equals(holder, holder1) && holder1 != null) {
-+                return true;
-             }
- 
-+            return false;
-         }
-     }
- 
-+    private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect) {
-+    // Paper start - Custom beacon ranges
-+        BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null);
-+    }
-+    private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect, @Nullable BeaconBlockEntity blockEntity) {
-+        // Paper end - Custom beacon ranges
-+        if (!world.isClientSide && primaryEffect != null) {
-+            double d0 = (double) (beaconLevel * 10 + 10);
-+            byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect);
-+
-+            int j = BeaconBlockEntity.getLevel(beaconLevel);
-+            List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges
-+
-+            BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent
-+
-+            if (BeaconBlockEntity.hasSecondaryEffect(beaconLevel, primaryEffect, secondaryEffect)) {
-+                BeaconBlockEntity.applyEffect(list, secondaryEffect, j, 0, false, pos); // Paper - BeaconEffectEvent
-+            }
-+        }
-+
-+    }
-+    // CraftBukkit end
-+
-     public static void playSound(Level world, BlockPos pos, SoundEvent sound) {
-         world.playSound((Player) null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
-     }
-@@ -316,7 +440,7 @@
-         if (nbt.contains(key, 8)) {
-             ResourceLocation minecraftkey = ResourceLocation.tryParse(nbt.getString(key));
- 
--            return minecraftkey == null ? null : (Holder) BuiltInRegistries.MOB_EFFECT.get(minecraftkey).map(BeaconBlockEntity::filterEffect).orElse((Object) null);
-+            return minecraftkey == null ? null : (Holder) BuiltInRegistries.MOB_EFFECT.get(minecraftkey).orElse(null); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598)
-         } else {
-             return null;
-         }
-@@ -327,11 +451,13 @@
-         super.loadAdditional(nbt, registries);
-         this.primaryPower = BeaconBlockEntity.loadEffect(nbt, "primary_effect");
-         this.secondaryPower = BeaconBlockEntity.loadEffect(nbt, "secondary_effect");
-+        this.levels = nbt.getInt("Levels"); // CraftBukkit - SPIGOT-5053, use where available
-         if (nbt.contains("CustomName", 8)) {
-             this.name = parseCustomNameSafe(nbt.getString("CustomName"), registries);
-         }
- 
-         this.lockKey = LockCode.fromTag(nbt, registries);
-+        this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges
-     }
- 
-     @Override
-@@ -345,6 +471,7 @@
-         }
- 
-         this.lockKey.addToTag(nbt, registries);
-+        nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges
-     }
- 
-     public void setCustomName(@Nullable Component customName) {
-@@ -360,7 +487,7 @@
-     @Nullable
-     @Override
-     public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
--        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null;
-+        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
deleted file mode 100644
index 8457946183..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
+++ /dev/null
@@ -1,306 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
-@@ -43,6 +43,10 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
-+
- public class BeehiveBlockEntity extends BlockEntity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -56,6 +60,7 @@
-     private List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
-     @Nullable
-     public BlockPos savedFlowerPos;
-+    public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
- 
-     public BeehiveBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.BEEHIVE, pos, state);
-@@ -95,7 +100,7 @@
-     }
- 
-     public boolean isFull() {
--        return this.stored.size() == 3;
-+        return this.stored.size() == this.maxBees; // CraftBukkit
-     }
- 
-     public void emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus beeState) {
-@@ -112,7 +117,7 @@
- 
-                     if (player.position().distanceToSqr(entity.position()) <= 16.0D) {
-                         if (!this.isSedated()) {
--                            entitybee.setTarget(player);
-+                            entitybee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
-                         } else {
-                             entitybee.setStayOutOfHiveCountdown(400);
-                         }
-@@ -124,10 +129,16 @@
-     }
- 
-     private List<Entity> releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus beeState) {
-+        // CraftBukkit start - This allows us to bypass the night/rain/emergency check
-+        return this.releaseBees(state, beeState, false);
-+    }
-+
-+    public List<Entity> releaseBees(BlockState iblockdata, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) {
-         List<Entity> list = Lists.newArrayList();
- 
-         this.stored.removeIf((tileentitybeehive_hivebee) -> {
--            return BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, state, tileentitybeehive_hivebee.toOccupant(), list, beeState, this.savedFlowerPos);
-+            return BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, tileentitybeehive_hivebee.toOccupant(), list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
-+            // CraftBukkit end
-         });
-         if (!list.isEmpty()) {
-             super.setChanged();
-@@ -141,6 +152,11 @@
-         return this.stored.size();
-     }
- 
-+    // Paper start - Add EntityBlockStorage clearEntities
-+    public void clearBees() {
-+        this.stored.clear();
-+    }
-+    // Paper end - Add EntityBlockStorage clearEntities
-     public static int getHoneyLevel(BlockState state) {
-         return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL);
-     }
-@@ -151,7 +167,17 @@
-     }
- 
-     public void addOccupant(Bee entity) {
--        if (this.stored.size() < 3) {
-+        if (this.stored.size() < this.maxBees) { // CraftBukkit
-+            // CraftBukkit start
-+            if (this.level != null) {
-+                org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.getBlockPos()));
-+                org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+                if (event.isCancelled()) {
-+                    entity.setStayOutOfHiveCountdown(400);
-+                    return;
-+                }
-+            }
-+            // CraftBukkit end
-             entity.stopRiding();
-             entity.ejectPassengers();
-             entity.dropLeash();
-@@ -167,7 +193,7 @@
-                 this.level.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, this.getBlockState()));
-             }
- 
--            entity.discard();
-+            entity.discard(EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause
-             super.setChanged();
-         }
-     }
-@@ -177,32 +203,50 @@
-     }
- 
-     private static boolean releaseOccupant(Level world, BlockPos pos, BlockState state, BeehiveBlockEntity.Occupant bee, @Nullable List<Entity> entities, BeehiveBlockEntity.BeeReleaseStatus beeState, @Nullable BlockPos flowerPos) {
--        if (Bee.isNightOrRaining(world) && beeState != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
-+        // CraftBukkit start - This allows us to bypass the night/rain/emergency check
-+        return BeehiveBlockEntity.releaseOccupant(world, pos, state, bee, entities, beeState, flowerPos, false);
-+    }
-+
-+    private static boolean releaseOccupant(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.Occupant tileentitybeehive_c, @Nullable List<Entity> list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) {
-+        if (!force && Bee.isNightOrRaining(world) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
-+            // CraftBukkit end
-             return false;
-         } else {
--            Direction enumdirection = (Direction) state.getValue(BeehiveBlock.FACING);
--            BlockPos blockposition2 = pos.relative(enumdirection);
-+            Direction enumdirection = (Direction) iblockdata.getValue(BeehiveBlock.FACING);
-+            BlockPos blockposition2 = blockposition.relative(enumdirection);
-             boolean flag = !world.getBlockState(blockposition2).getCollisionShape(world, blockposition2).isEmpty();
- 
--            if (flag && beeState != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
-+            if (flag && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
-                 return false;
-             } else {
--                Entity entity = bee.createEntity(world, pos);
-+                Entity entity = tileentitybeehive_c.createEntity(world, blockposition);
- 
-                 if (entity != null) {
-+                    // CraftBukkit start
-                     if (entity instanceof Bee) {
-+                        float f = entity.getBbWidth();
-+                        double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F);
-+                        double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX();
-+                        double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F);
-+                        double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ();
-+
-+                        entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
-+                    }
-+                    if (!world.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BEEHIVE)) return false; // CraftBukkit - SpawnReason, moved from below
-+                    // CraftBukkit end
-+                    if (entity instanceof Bee) {
-                         Bee entitybee = (Bee) entity;
- 
--                        if (flowerPos != null && !entitybee.hasSavedFlowerPos() && world.random.nextFloat() < 0.9F) {
--                            entitybee.setSavedFlowerPos(flowerPos);
-+                        if (blockposition1 != null && !entitybee.hasSavedFlowerPos() && world.random.nextFloat() < 0.9F) {
-+                            entitybee.setSavedFlowerPos(blockposition1);
-                         }
- 
--                        if (beeState == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) {
-+                        if (tileentitybeehive_releasestatus == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) {
-                             entitybee.dropOffNectar();
--                            if (state.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> {
-+                            if (iblockdata.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> {
-                                 return blockbase_blockdata.hasProperty(BeehiveBlock.HONEY_LEVEL);
-                             })) {
--                                int i = BeehiveBlockEntity.getHoneyLevel(state);
-+                                int i = BeehiveBlockEntity.getHoneyLevel(iblockdata);
- 
-                                 if (i < 5) {
-                                     int j = world.random.nextInt(100) == 0 ? 2 : 1;
-@@ -211,27 +255,35 @@
-                                         --j;
-                                     }
- 
--                                    world.setBlockAndUpdate(pos, (BlockState) state.setValue(BeehiveBlock.HONEY_LEVEL, i + j));
-+                                    // Paper start - Fire EntityChangeBlockEvent in more places
-+                                    BlockState newBlockState = iblockdata.setValue(BeehiveBlock.HONEY_LEVEL, i + j);
-+
-+                                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entitybee, blockposition, newBlockState)) {
-+                                        world.setBlockAndUpdate(blockposition, newBlockState);
-+                                    }
-+                                    // Paper end - Fire EntityChangeBlockEvent in more places
-                                 }
-                             }
-                         }
- 
--                        if (entities != null) {
--                            entities.add(entitybee);
-+                        if (list != null) {
-+                            list.add(entitybee);
-                         }
- 
-+                        /* // CraftBukkit start
-                         float f = entity.getBbWidth();
-                         double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F);
--                        double d1 = (double) pos.getX() + 0.5D + d0 * (double) enumdirection.getStepX();
--                        double d2 = (double) pos.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F);
--                        double d3 = (double) pos.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ();
-+                        double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX();
-+                        double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F);
-+                        double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ();
- 
-                         entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
-+                         */ // CraftBukkit end
-                     }
- 
--                    world.playSound((Player) null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
--                    world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, world.getBlockState(pos)));
--                    return world.addFreshEntity(entity);
-+                    world.playSound((Player) null, blockposition, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, world.getBlockState(blockposition)));
-+                    return true; // return this.world.addFreshEntity(entity); // CraftBukkit - moved up
-                 } else {
-                     return false;
-                 }
-@@ -256,6 +308,10 @@
-                 if (BeehiveBlockEntity.releaseOccupant(world, pos, state, tileentitybeehive_hivebee.toOccupant(), (List) null, tileentitybeehive_releasestatus, flowerPos)) {
-                     flag = true;
-                     iterator.remove();
-+                    // CraftBukkit start
-+                } else {
-+                    tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.occupant.minTicksInHive / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - Fix bees aging inside hives; use exitTickCounter to keep actual bee life
-+                    // CraftBukkit end
-                 }
-             }
-         }
-@@ -282,7 +338,7 @@
-     @Override
-     protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-         super.loadAdditional(nbt, registries);
--        this.stored.clear();
-+        this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
-         if (nbt.contains("bees")) {
-             BeehiveBlockEntity.Occupant.LIST_CODEC.parse(NbtOps.INSTANCE, nbt.get("bees")).resultOrPartial((s) -> {
-                 BeehiveBlockEntity.LOGGER.error("Failed to parse bees: '{}'", s);
-@@ -291,7 +347,12 @@
-             });
-         }
- 
--        this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse((Object) null);
-+        this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse(null); // CraftBukkit - decompile error
-+        // CraftBukkit start
-+        if (nbt.contains("Bukkit.MaxEntities")) {
-+            this.maxBees = nbt.getInt("Bukkit.MaxEntities");
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -301,13 +362,14 @@
-         if (this.hasSavedFlowerPos()) {
-             nbt.put("flower_pos", NbtUtils.writeBlockPos(this.savedFlowerPos));
-         }
-+        nbt.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit
- 
-     }
- 
-     @Override
-     protected void applyImplicitComponents(BlockEntity.DataComponentInput components) {
-         super.applyImplicitComponents(components);
--        this.stored.clear();
-+        this.stored = Lists.newArrayList(); // CraftBukkit - SPIGOT-7790: create new copy (may be modified in physics event triggered by honey change)
-         List<BeehiveBlockEntity.Occupant> list = (List) components.getOrDefault(DataComponents.BEES, List.of());
- 
-         list.forEach(this::storeBee);
-@@ -348,7 +410,7 @@
-             CompoundTag nbttagcompound = new CompoundTag();
- 
-             entity.save(nbttagcompound);
--            List list = BeehiveBlockEntity.IGNORED_BEE_TAGS;
-+            List<String> list = BeehiveBlockEntity.IGNORED_BEE_TAGS; // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(nbttagcompound);
-             list.forEach(nbttagcompound::remove);
-@@ -367,7 +429,7 @@
-         @Nullable
-         public Entity createEntity(Level world, BlockPos pos) {
-             CompoundTag nbttagcompound = this.entityData.copyTag();
--            List list = BeehiveBlockEntity.IGNORED_BEE_TAGS;
-+            List<String> list = BeehiveBlockEntity.IGNORED_BEE_TAGS; // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(nbttagcompound);
-             list.forEach(nbttagcompound::remove);
-@@ -391,6 +453,7 @@
-         }
- 
-         private static void setBeeReleaseData(int ticksInHive, Bee beeEntity) {
-+            if (!beeEntity.ageLocked) { // Paper - Honor ageLock
-             int j = beeEntity.getAge();
- 
-             if (j < 0) {
-@@ -400,21 +463,25 @@
-             }
- 
-             beeEntity.setInLoveTime(Math.max(0, beeEntity.getInLoveTime() - ticksInHive));
-+            } // Paper - Honor ageLock
-         }
-     }
- 
-     private static class BeeData {
- 
-         private final BeehiveBlockEntity.Occupant occupant;
-+        private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts
-         private int ticksInHive;
- 
-         BeeData(BeehiveBlockEntity.Occupant data) {
-             this.occupant = data;
-             this.ticksInHive = data.ticksInHive();
-+            this.exitTickCounter = this.ticksInHive; // Paper - Fix bees aging inside hives
-         }
- 
-         public boolean tick() {
--            return this.ticksInHive++ > this.occupant.minTicksInHive;
-+            this.ticksInHive++; // Paper - Fix bees aging inside hives
-+            return this.exitTickCounter++ > this.occupant.minTicksInHive; // Paper - Fix bees aging inside hives
-         }
- 
-         public BeehiveBlockEntity.Occupant toOccupant() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntity.java.patch
deleted file mode 100644
index aca157f8aa..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntity.java.patch
+++ /dev/null
@@ -1,153 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
-@@ -26,8 +26,18 @@
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
-+import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
-+
- public abstract class BlockEntity {
- 
-+    // CraftBukkit start - data containers
-+    private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
-+    public CraftPersistentDataContainer persistentDataContainer;
-+    // CraftBukkit end
-     private static final Logger LOGGER = LogUtils.getLogger();
-     private final BlockEntityType<?> type;
-     @Nullable
-@@ -43,6 +53,7 @@
-         this.worldPosition = pos.immutable();
-         this.validateBlockState(state);
-         this.blockState = state;
-+        this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
-     }
- 
-     private void validateBlockState(BlockState state) {
-@@ -74,7 +85,16 @@
-         return this.level != null;
-     }
- 
--    protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {}
-+    // CraftBukkit start - read container
-+    protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-+        this.persistentDataContainer.clear(); // Paper - clear instead of init
-+
-+        net.minecraft.nbt.Tag persistentDataTag = nbt.get("PublicBukkitValues");
-+        if (persistentDataTag instanceof CompoundTag) {
-+            this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
-+        }
-+    }
-+    // CraftBukkit end
- 
-     public final void loadWithComponents(CompoundTag nbt, HolderLookup.Provider registries) {
-         this.loadAdditional(nbt, registries);
-@@ -114,6 +134,11 @@
-         }).ifPresent((nbtbase) -> {
-             nbttagcompound.merge((CompoundTag) nbtbase);
-         });
-+        // CraftBukkit start - store container
-+        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
-+            nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
-+        }
-+        // CraftBukkit end
-         return nbttagcompound;
-     }
- 
-@@ -121,6 +146,11 @@
-         CompoundTag nbttagcompound = new CompoundTag();
- 
-         this.saveAdditional(nbttagcompound, registries);
-+        // Paper start - store PDC here as well
-+        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
-+            nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
-+        }
-+        // Paper end
-         return nbttagcompound;
-     }
- 
-@@ -234,7 +264,12 @@
-     public void fillCrashReportCategory(CrashReportCategory crashReportSection) {
-         crashReportSection.setDetail("Name", this::getNameForReporting);
-         if (this.level != null) {
--            CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.getBlockState());
-+            // Paper start - Prevent block entity and entity crashes
-+            BlockState block = this.getBlockState();
-+            if (block != null) {
-+                CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, block);
-+            }
-+            // Paper end - Prevent block entity and entity crashes
-             CrashReportCategory.populateBlockDetails(crashReportSection, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition));
-         }
-     }
-@@ -263,13 +298,19 @@
-     }
- 
-     public final void applyComponents(DataComponentMap defaultComponents, DataComponentPatch components) {
-+        // CraftBukkit start
-+        this.applyComponentsSet(defaultComponents, components);
-+    }
-+
-+    public final Set<DataComponentType<?>> applyComponentsSet(DataComponentMap datacomponentmap, DataComponentPatch datacomponentpatch) {
-+        // CraftBukkit end
-         final Set<DataComponentType<?>> set = new HashSet();
- 
-         set.add(DataComponents.BLOCK_ENTITY_DATA);
-         set.add(DataComponents.BLOCK_STATE);
--        final PatchedDataComponentMap patcheddatacomponentmap = PatchedDataComponentMap.fromPatch(defaultComponents, components);
-+        final PatchedDataComponentMap patcheddatacomponentmap = PatchedDataComponentMap.fromPatch(datacomponentmap, datacomponentpatch);
- 
--        this.applyImplicitComponents(new BlockEntity.DataComponentInput(this) {
-+        this.applyImplicitComponents(new BlockEntity.DataComponentInput() { // CraftBukkit - decompile error
-             @Nullable
-             @Override
-             public <T> T get(DataComponentType<T> type) {
-@@ -284,9 +325,13 @@
-             }
-         });
-         Objects.requireNonNull(set);
--        DataComponentPatch datacomponentpatch1 = components.forget(set::contains);
-+        DataComponentPatch datacomponentpatch1 = datacomponentpatch.forget(set::contains);
- 
-         this.components = datacomponentpatch1.split().added();
-+        // CraftBukkit start
-+        set.remove(DataComponents.BLOCK_ENTITY_DATA); // Remove as never actually added by applyImplicitComponents
-+        return set;
-+        // CraftBukkit end
-     }
- 
-     protected void collectImplicitComponents(DataComponentMap.Builder builder) {}
-@@ -321,6 +366,30 @@
-         }
-     }
- 
-+    // CraftBukkit start - add method
-+    public InventoryHolder getOwner() {
-+        // Paper start
-+        return getOwner(true);
-+    }
-+    public InventoryHolder getOwner(boolean useSnapshot) {
-+        // Paper end
-+        if (this.level == null) return null;
-+        org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
-+        // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists
-+        org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
-+        if (state instanceof InventoryHolder) return (InventoryHolder) state;
-+        return null;
-+    }
-+    // CraftBukkit end
-+
-+    // Paper start - Sanitize sent data
-+    public CompoundTag sanitizeSentNbt(CompoundTag tag) {
-+        tag.remove("PublicBukkitValues");
-+
-+        return tag;
-+    }
-+    // Paper end - Sanitize sent data
-+
-     private static class ComponentHelper {
- 
-         public static final Codec<DataComponentMap> COMPONENTS_CODEC = DataComponentMap.CODEC.optionalFieldOf("components", DataComponentMap.EMPTY).codec();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntityType.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntityType.java.patch
deleted file mode 100644
index c2e5be1463..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BlockEntityType.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BlockEntityType.java
-+++ b/net/minecraft/world/level/block/entity/BlockEntityType.java
-@@ -66,7 +66,7 @@
-     public static final BlockEntityType<CrafterBlockEntity> CRAFTER = BlockEntityType.register("crafter", CrafterBlockEntity::new, Blocks.CRAFTER);
-     public static final BlockEntityType<TrialSpawnerBlockEntity> TRIAL_SPAWNER = BlockEntityType.register("trial_spawner", TrialSpawnerBlockEntity::new, Blocks.TRIAL_SPAWNER);
-     public static final BlockEntityType<VaultBlockEntity> VAULT = BlockEntityType.register("vault", VaultBlockEntity::new, Blocks.VAULT);
--    private static final Set<BlockEntityType<?>> OP_ONLY_CUSTOM_DATA = Set.of(BlockEntityType.COMMAND_BLOCK, BlockEntityType.LECTERN, BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN, BlockEntityType.MOB_SPAWNER, BlockEntityType.TRIAL_SPAWNER);
-+    private static final Set<BlockEntityType<?>> OP_ONLY_CUSTOM_DATA = Set.of(BlockEntityType.COMMAND_BLOCK, BlockEntityType.LECTERN, BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN, BlockEntityType.MOB_SPAWNER, BlockEntityType.TRIAL_SPAWNER); // CraftBukkit // Paper - Allow chests to be placed with NBT data
-     private final BlockEntityType.BlockEntitySupplier<? extends T> factory;
-     public final Set<Block> validBlocks;
-     private final Holder.Reference<BlockEntityType<?>> builtInRegistryHolder;
-@@ -110,7 +110,7 @@
-     public T getBlockEntity(BlockGetter world, BlockPos pos) {
-         BlockEntity tileentity = world.getBlockEntity(pos);
- 
--        return tileentity != null && tileentity.getType() == this ? tileentity : null;
-+        return tileentity != null && tileentity.getType() == this ? (T) tileentity : null; // CraftBukkit - decompile error
-     }
- 
-     public boolean onlyOpCanSetNbt() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
deleted file mode 100644
index 39dec9ecc2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ /dev/null
@@ -1,232 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
-@@ -8,7 +8,6 @@
- import net.minecraft.core.NonNullList;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
--import net.minecraft.tags.ItemTags;
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.Containers;
- import net.minecraft.world.WorldlyContainer;
-@@ -23,6 +22,20 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.BrewingStandBlock;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.List;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.tags.ItemTags;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.event.block.BrewingStartEvent;
-+import org.bukkit.event.inventory.BrewEvent;
-+import org.bukkit.event.inventory.BrewingStandFuelEvent;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
- 
- public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer {
- 
-@@ -37,11 +50,42 @@
-     public static final int NUM_DATA_VALUES = 2;
-     private NonNullList<ItemStack> items;
-     public int brewTime;
-+    public int recipeBrewTime = 400; // Paper - Add recipeBrewTime
-     private boolean[] lastPotionCount;
-     private Item ingredient;
-     public int fuel;
-     protected final ContainerData dataAccess;
-+    // CraftBukkit start - add fields and methods
-+    // private int lastTick = MinecraftServer.currentTick; // Paper - remove anti tick skipping measures / wall time
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
-+    private int maxStack = MAX_STACK;
- 
-+    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;
-+    }
-+
-+    public List<ItemStack> getContents() {
-+        return this.items;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+    }
-+    // CraftBukkit end
-+
-     public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.BREWING_STAND, pos, state);
-         this.items = NonNullList.withSize(5, ItemStack.EMPTY);
-@@ -57,6 +101,11 @@
-                     case 1:
-                         j = BrewingStandBlockEntity.this.fuel;
-                         break;
-+                    // Paper start - Add recipeBrewTime
-+                    case 2:
-+                        j = BrewingStandBlockEntity.this.recipeBrewTime;
-+                        break;
-+                    // Paper end - Add recipeBrewTime
-                     default:
-                         j = 0;
-                 }
-@@ -72,13 +121,18 @@
-                         break;
-                     case 1:
-                         BrewingStandBlockEntity.this.fuel = value;
-+                        // Paper start - Add recipeBrewTime
-+                    case 2:
-+                        BrewingStandBlockEntity.this.recipeBrewTime = value;
-+                        break;
-+                    // Paper end - Add recipeBrewTime
-                 }
- 
-             }
- 
-             @Override
-             public int getCount() {
--                return 2;
-+                return 3; // Paper - Add recipeBrewTime
-             }
-         };
-     }
-@@ -107,8 +161,19 @@
-         ItemStack itemstack = (ItemStack) blockEntity.items.get(4);
- 
-         if (blockEntity.fuel <= 0 && itemstack.is(ItemTags.BREWING_FUEL)) {
--            blockEntity.fuel = 20;
--            itemstack.shrink(1);
-+            // CraftBukkit start
-+            BrewingStandFuelEvent event = new BrewingStandFuelEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack), 20);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+
-+            blockEntity.fuel = event.getFuelPower();
-+            if (blockEntity.fuel > 0 && event.isConsuming()) {
-+                itemstack.shrink(1);
-+            }
-+            // CraftBukkit end
-             setChanged(world, pos, state);
-         }
- 
-@@ -116,12 +181,15 @@
-         boolean flag1 = blockEntity.brewTime > 0;
-         ItemStack itemstack1 = (ItemStack) blockEntity.items.get(3);
- 
-+        // Paper - remove anti tick skipping measures / wall time
-+
-         if (flag1) {
--            --blockEntity.brewTime;
--            boolean flag2 = blockEntity.brewTime == 0;
-+            --blockEntity.brewTime; // Paper - remove anti tick skipping measures / wall time - revert to vanilla
-+            boolean flag2 = blockEntity.brewTime <= 0; // == -> <=
-+            // CraftBukkit end
- 
-             if (flag2 && flag) {
--                BrewingStandBlockEntity.doBrew(world, pos, blockEntity.items);
-+                BrewingStandBlockEntity.doBrew(world, pos, blockEntity.items, blockEntity); // CraftBukkit
-             } else if (!flag || !itemstack1.is(blockEntity.ingredient)) {
-                 blockEntity.brewTime = 0;
-             }
-@@ -129,7 +197,12 @@
-             setChanged(world, pos, state);
-         } else if (flag && blockEntity.fuel > 0) {
-             --blockEntity.fuel;
--            blockEntity.brewTime = 400;
-+            // CraftBukkit start
-+            BrewingStartEvent event = new BrewingStartEvent(CraftBlock.at(world, pos), CraftItemStack.asCraftMirror(itemstack1), 400);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+            blockEntity.recipeBrewTime = event.getRecipeBrewTime(); // Paper - use recipe brew time from event
-+            blockEntity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event
-+            // CraftBukkit end
-             blockEntity.ingredient = itemstack1.getItem();
-             setChanged(world, pos, state);
-         }
-@@ -185,14 +258,36 @@
-         }
-     }
- 
--    private static void doBrew(Level world, BlockPos pos, NonNullList<ItemStack> slots) {
--        ItemStack itemstack = (ItemStack) slots.get(3);
-+    private static void doBrew(Level world, BlockPos blockposition, NonNullList<ItemStack> nonnulllist, BrewingStandBlockEntity tileentitybrewingstand) { // CraftBukkit
-+        ItemStack itemstack = (ItemStack) nonnulllist.get(3);
-         PotionBrewing potionbrewer = world.potionBrewing();
- 
-+        // CraftBukkit start
-+        InventoryHolder owner = tileentitybrewingstand.getOwner();
-+        List<org.bukkit.inventory.ItemStack> brewResults = new ArrayList<>(3);
-         for (int i = 0; i < 3; ++i) {
--            slots.set(i, potionbrewer.mix(itemstack, (ItemStack) slots.get(i)));
-+            brewResults.add(i, CraftItemStack.asCraftMirror(potionbrewer.mix(itemstack, (ItemStack) nonnulllist.get(i))));
-         }
- 
-+        if (owner != null) {
-+            BrewEvent event = new BrewEvent(CraftBlock.at(world, blockposition), (org.bukkit.inventory.BrewerInventory) owner.getInventory(), brewResults, tileentitybrewingstand.fuel);
-+            org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+        }
-+        // CraftBukkit end
-+
-+        for (int i = 0; i < 3; ++i) {
-+            // CraftBukkit start - validate index in case it is cleared by plugins
-+            if (i < brewResults.size()) {
-+                nonnulllist.set(i, CraftItemStack.asNMSCopy(brewResults.get(i)));
-+            } else {
-+                nonnulllist.set(i, ItemStack.EMPTY);
-+            }
-+            // CraftBukkit end
-+        }
-+
-         itemstack.shrink(1);
-         ItemStack itemstack1 = itemstack.getItem().getCraftingRemainder();
- 
-@@ -200,12 +295,12 @@
-             if (itemstack.isEmpty()) {
-                 itemstack = itemstack1;
-             } else {
--                Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1);
-+                Containers.dropItemStack(world, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack1);
-             }
-         }
- 
--        slots.set(3, itemstack);
--        world.levelEvent(1035, pos, 0);
-+        nonnulllist.set(3, itemstack);
-+        world.levelEvent(1035, blockposition, 0);
-     }
- 
-     @Override
-@@ -231,12 +326,12 @@
- 
-     @Override
-     public boolean canPlaceItem(int slot, ItemStack stack) {
-+        PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up
-         if (slot == 3) {
--            PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY;
- 
-             return potionbrewer.isIngredient(stack);
-         } else {
--            return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty();
-+            return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
deleted file mode 100644
index 57515e3834..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
+++ /dev/null
@@ -1,36 +0,0 @@
---- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
-@@ -31,6 +31,12 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.Arrays;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
-+
- public class BrushableBlockEntity extends BlockEntity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -151,7 +157,10 @@
-             ItemEntity entityitem = new ItemEntity(world, d3, d4, d5, this.item.split(world.random.nextInt(21) + 10));
- 
-             entityitem.setDeltaMovement(Vec3.ZERO);
--            world.addFreshEntity(entityitem);
-+            // CraftBukkit start
-+            org.bukkit.block.Block bblock = CraftBlock.at(this.level, this.worldPosition);
-+            CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), (ServerPlayer) player, Arrays.asList(entityitem));
-+            // CraftBukkit end
-             this.item = ItemStack.EMPTY;
-         }
- 
-@@ -185,7 +194,7 @@
- 
-     private boolean tryLoadLootTable(CompoundTag nbt) {
-         if (nbt.contains("LootTable", 8)) {
--            this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable")));
-+            this.lootTable = net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl)); // Paper - Validate ResourceLocation
-             this.lootTableSeed = nbt.getLong("LootTableSeed");
-             return true;
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
deleted file mode 100644
index aea8cb9be4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
+++ /dev/null
@@ -1,121 +0,0 @@
---- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
-@@ -31,6 +31,14 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockCookEvent;
-+import org.bukkit.event.block.CampfireStartEvent;
-+import org.bukkit.inventory.CampfireRecipe;
-+// CraftBukkit end
-+
- public class CampfireBlockEntity extends BlockEntity implements Clearable {
- 
-     private static final int BURN_COOL_SPEED = 2;
-@@ -38,12 +46,14 @@
-     private final NonNullList<ItemStack> items;
-     public final int[] cookingProgress;
-     public final int[] cookingTime;
-+    public final boolean[] stopCooking; // Paper - Add more Campfire API
- 
-     public CampfireBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.CAMPFIRE, pos, state);
-         this.items = NonNullList.withSize(4, ItemStack.EMPTY);
-         this.cookingProgress = new int[4];
-         this.cookingTime = new int[4];
-+        this.stopCooking = new boolean[4]; // Paper - Add more Campfire API
-     }
- 
-     public static void cookTick(ServerLevel world, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity, RecipeManager.CachedCheck<SingleRecipeInput, CampfireCookingRecipe> recipeMatchGetter) {
-@@ -54,16 +64,42 @@
- 
-             if (!itemstack.isEmpty()) {
-                 flag = true;
-+                if (!blockEntity.stopCooking[i]) { // Paper - Add more Campfire API
-                 int j = blockEntity.cookingProgress[i]++;
-+                } // Paper - Add more Campfire API
- 
-                 if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) {
-                     SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack);
--                    ItemStack itemstack1 = (ItemStack) recipeMatchGetter.getRecipeFor(singlerecipeinput, world).map((recipeholder) -> {
-+                    // Paper start - add recipe to cook events
-+                    final Optional<RecipeHolder<CampfireCookingRecipe>> recipeHolderOptional = recipeMatchGetter.getRecipeFor(singlerecipeinput, world);
-+                    ItemStack itemstack1 = (ItemStack) recipeHolderOptional.map((recipeholder) -> {
-+                    // Paper end - add recipe to cook events
-                         return ((CampfireCookingRecipe) recipeholder.value()).assemble(singlerecipeinput, world.registryAccess());
-                     }).orElse(itemstack);
- 
-                     if (itemstack1.isItemEnabled(world.enabledFeatures())) {
--                        Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1);
-+                        // CraftBukkit start - fire BlockCookEvent
-+                        CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
-+                        org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
-+
-+                        BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe<?>) recipeHolderOptional.map(RecipeHolder::toBukkitRecipe).orElse(null)); // Paper - Add recipe to cook events
-+                        world.getCraftServer().getPluginManager().callEvent(blockCookEvent);
-+
-+                        if (blockCookEvent.isCancelled()) {
-+                            return;
-+                        }
-+
-+                        result = blockCookEvent.getResult();
-+                        itemstack1 = CraftItemStack.asNMSCopy(result);
-+                        // CraftBukkit end
-+                        // Paper start - Fix item locations dropped from campfires
-+                        double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR;
-+                        while (!itemstack1.isEmpty()) {
-+                            net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10));
-+                            droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation));
-+                            world.addFreshEntity(droppedItem);
-+                        }
-+                        // Paper end - Fix item locations dropped from campfires
-                         blockEntity.items.set(i, ItemStack.EMPTY);
-                         world.sendBlockUpdated(pos, state, state, 3);
-                         world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
-@@ -143,6 +179,16 @@
-             System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length));
-         }
- 
-+        // Paper start - Add more Campfire API
-+        if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) {
-+            byte[] abyte = nbt.getByteArray("Paper.StopCooking");
-+            boolean[] cookingState = new boolean[4];
-+            for (int index = 0; index < abyte.length; index++) {
-+                cookingState[index] = abyte[index] == 1;
-+            }
-+            System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length));
-+        }
-+        // Paper end - Add more Campfire API
-     }
- 
-     @Override
-@@ -151,6 +197,13 @@
-         ContainerHelper.saveAllItems(nbt, this.items, true, registries);
-         nbt.putIntArray("CookingTimes", this.cookingProgress);
-         nbt.putIntArray("CookingTotalTimes", this.cookingTime);
-+        // Paper start - Add more Campfire API
-+        byte[] cookingState = new byte[4];
-+        for (int index = 0; index < cookingState.length; index++) {
-+            cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0);
-+        }
-+        nbt.putByteArray("Paper.StopCooking", cookingState);
-+        // Paper end - Add more Campfire API
-     }
- 
-     @Override
-@@ -177,7 +230,11 @@
-                     return false;
-                 }
- 
--                this.cookingTime[i] = ((CampfireCookingRecipe) ((RecipeHolder) optional.get()).value()).cookingTime();
-+                // CraftBukkit start
-+                CampfireStartEvent event = new CampfireStartEvent(CraftBlock.at(this.level,this.worldPosition), CraftItemStack.asCraftMirror(stack), (CampfireRecipe) optional.get().toBukkitRecipe());
-+                this.level.getCraftServer().getPluginManager().callEvent(event);
-+                this.cookingTime[i] = event.getTotalCookTime(); // i -> event.getTotalCookTime()
-+                // CraftBukkit end
-                 this.cookingProgress[i] = 0;
-                 this.items.set(i, stack.consumeAndReturn(1, entity));
-                 world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState()));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
deleted file mode 100644
index 6dd6c8627e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
+++ /dev/null
@@ -1,108 +0,0 @@
---- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
-@@ -10,6 +10,7 @@
- import net.minecraft.core.particles.ParticleTypes;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
-@@ -187,11 +188,23 @@
-     }
- 
-     private static void applyEffects(Level world, BlockPos pos, List<BlockPos> activatingBlocks) {
--        int i = activatingBlocks.size();
-+        // CraftBukkit start
-+        ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks));
-+    }
-+
-+    public static int getRange(List<BlockPos> list) {
-+        // CraftBukkit end
-+        int i = list.size();
-         int j = i / 7 * 16;
--        int k = pos.getX();
--        int l = pos.getY();
--        int i1 = pos.getZ();
-+        // CraftBukkit start
-+        return j;
-+    }
-+
-+    private static void applyEffects(Level world, BlockPos blockposition, int j) { // j = effect range in blocks
-+        // CraftBukkit end
-+        int k = blockposition.getX();
-+        int l = blockposition.getY();
-+        int i1 = blockposition.getZ();
-         AABB axisalignedbb = (new AABB((double) k, (double) l, (double) i1, (double) (k + 1), (double) (l + 1), (double) (i1 + 1))).inflate((double) j).expandTowards(0.0D, (double) world.getHeight(), 0.0D);
-         List<Player> list1 = world.getEntitiesOfClass(Player.class, axisalignedbb);
- 
-@@ -201,8 +214,8 @@
-             while (iterator.hasNext()) {
-                 Player entityhuman = (Player) iterator.next();
- 
--                if (pos.closerThan(entityhuman.blockPosition(), (double) j) && entityhuman.isInWaterOrRain()) {
--                    entityhuman.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true));
-+                if (blockposition.closerThan(entityhuman.blockPosition(), (double) j) && entityhuman.isInWaterOrRain()) {
-+                    entityhuman.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONDUIT); // CraftBukkit
-                 }
-             }
- 
-@@ -210,33 +223,42 @@
-     }
- 
-     private static void updateDestroyTarget(Level world, BlockPos pos, BlockState state, List<BlockPos> activatingBlocks, ConduitBlockEntity blockEntity) {
--        LivingEntity entityliving = blockEntity.destroyTarget;
--        int i = activatingBlocks.size();
-+        // CraftBukkit start - add "damageTarget" boolean
-+        ConduitBlockEntity.updateDestroyTarget(world, pos, state, activatingBlocks, blockEntity, true);
-+    }
- 
-+    public static void updateDestroyTarget(Level world, BlockPos blockposition, BlockState iblockdata, List<BlockPos> list, ConduitBlockEntity tileentityconduit, boolean damageTarget) {
-+        // CraftBukkit end
-+        LivingEntity entityliving = tileentityconduit.destroyTarget;
-+        int i = list.size();
-+
-         if (i < 42) {
--            blockEntity.destroyTarget = null;
--        } else if (blockEntity.destroyTarget == null && blockEntity.destroyTargetUUID != null) {
--            blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID);
--            blockEntity.destroyTargetUUID = null;
--        } else if (blockEntity.destroyTarget == null) {
--            List<LivingEntity> list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> {
-+            tileentityconduit.destroyTarget = null;
-+        } else if (tileentityconduit.destroyTarget == null && tileentityconduit.destroyTargetUUID != null) {
-+            tileentityconduit.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, blockposition, tileentityconduit.destroyTargetUUID);
-+            tileentityconduit.destroyTargetUUID = null;
-+        } else if (tileentityconduit.destroyTarget == null) {
-+            List<LivingEntity> list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition), (entityliving1) -> {
-                 return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain();
-             });
- 
-             if (!list1.isEmpty()) {
--                blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size()));
-+                tileentityconduit.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size()));
-             }
--        } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) {
--            blockEntity.destroyTarget = null;
-+        } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), 8.0D)) {
-+            tileentityconduit.destroyTarget = null;
-         }
- 
--        if (blockEntity.destroyTarget != null) {
--            world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
--            blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F);
-+        // CraftBukkit start
-+        if (damageTarget && tileentityconduit.destroyTarget != null) {
-+            if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), 4.0F)) {
-+                world.playSound(null, tileentityconduit.destroyTarget.getX(), tileentityconduit.destroyTarget.getY(), tileentityconduit.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
-+            }
-+            // CraftBukkit end
-         }
- 
--        if (entityliving != blockEntity.destroyTarget) {
--            world.sendBlockUpdated(pos, state, state, 2);
-+        if (entityliving != tileentityconduit.destroyTarget) {
-+            world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 2);
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
deleted file mode 100644
index e71ac50ca3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
+++ /dev/null
@@ -1,68 +0,0 @@
---- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
-@@ -22,6 +22,11 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.CrafterBlock;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer {
- 
-@@ -35,12 +40,52 @@
-     private NonNullList<ItemStack> items;
-     public int craftingTicksRemaining;
-     protected final ContainerData containerData;
-+    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<>();
-+    private int maxStack = MAX_STACK;
- 
-+    @Override
-+    public List<ItemStack> getContents() {
-+        return this.items;
-+    }
-+
-+    @Override
-+    public void onOpen(CraftHumanEntity who) {
-+        this.transaction.add(who);
-+    }
-+
-+    @Override
-+    public void onClose(CraftHumanEntity who) {
-+        this.transaction.remove(who);
-+    }
-+
-+    @Override
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    @Override
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+    }
-+
-+    @Override
-+    public 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());
-+    }
-+    // CraftBukkit end
-+
-     public CrafterBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.CRAFTER, pos, state);
-         this.items = NonNullList.withSize(9, ItemStack.EMPTY);
-         this.craftingTicksRemaining = 0;
--        this.containerData = new ContainerData(this) {
-+        this.containerData = new ContainerData() { // CraftBukkit - decompile error
-             private final int[] slotStates = new int[9];
-             private int triggered = 0;
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
deleted file mode 100644
index ae1688f953..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
-@@ -13,12 +13,47 @@
- import net.minecraft.world.inventory.DispenserMenu;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class DispenserBlockEntity extends RandomizableContainerBlockEntity {
- 
-     public static final int CONTAINER_SIZE = 9;
-     private NonNullList<ItemStack> items;
- 
-+    // 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 DispenserBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
-         super(type, pos, state);
-         this.items = NonNullList.withSize(9, ItemStack.EMPTY);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
deleted file mode 100644
index afaf4c6781..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
+++ /dev/null
@@ -1,164 +0,0 @@
---- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
-@@ -28,6 +28,17 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.Arrays;
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.block.Lectern;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
- 
- public class LecternBlockEntity extends BlockEntity implements Clearable, MenuProvider {
- 
-@@ -35,8 +46,55 @@
-     public static final int NUM_DATA = 1;
-     public static final int SLOT_BOOK = 0;
-     public static final int NUM_SLOTS = 1;
--    public final Container bookAccess = new Container() {
-+    // CraftBukkit start - add fields and methods
-+    public final Container bookAccess = new LecternInventory();
-+    public class LecternInventory implements Container {
-+
-+        public List<HumanEntity> transaction = new ArrayList<>();
-+        private int maxStack = 1;
-+
-         @Override
-+        public List<ItemStack> getContents() {
-+            return Arrays.asList(LecternBlockEntity.this.book);
-+        }
-+
-+        @Override
-+        public void onOpen(CraftHumanEntity who) {
-+            this.transaction.add(who);
-+        }
-+
-+        @Override
-+        public void onClose(CraftHumanEntity who) {
-+            this.transaction.remove(who);
-+        }
-+
-+        @Override
-+        public List<HumanEntity> getViewers() {
-+            return this.transaction;
-+        }
-+
-+        @Override
-+        public void setMaxStackSize(int i) {
-+            this.maxStack = i;
-+        }
-+
-+        @Override
-+        public Location getLocation() {
-+            if (LecternBlockEntity.this.level == null) return null;
-+            return CraftLocation.toBukkit(LecternBlockEntity.this.worldPosition, LecternBlockEntity.this.level.getWorld());
-+        }
-+
-+        @Override
-+        public InventoryHolder getOwner() {
-+            return (Lectern) LecternBlockEntity.this.getOwner();
-+        }
-+
-+        public LecternBlockEntity getLectern() {
-+            return LecternBlockEntity.this;
-+        }
-+        // CraftBukkit end
-+
-+        @Override
-         public int getContainerSize() {
-             return 1;
-         }
-@@ -80,11 +138,20 @@
-         }
- 
-         @Override
--        public void setItem(int slot, ItemStack stack) {}
-+        // CraftBukkit start
-+        public void setItem(int slot, ItemStack stack) {
-+            if (slot == 0) {
-+                LecternBlockEntity.this.setBook(stack);
-+                if (LecternBlockEntity.this.getLevel() != null) {
-+                    LecternBlock.resetBookState(null, LecternBlockEntity.this.getLevel(), LecternBlockEntity.this.getBlockPos(), LecternBlockEntity.this.getBlockState(), LecternBlockEntity.this.hasBook());
-+                }
-+            }
-+        }
-+        // CraftBukkit end
- 
-         @Override
-         public int getMaxStackSize() {
--            return 1;
-+            return this.maxStack; // CraftBukkit
-         }
- 
-         @Override
-@@ -164,7 +231,7 @@
-         if (j != this.page) {
-             this.page = j;
-             this.setChanged();
--            LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState());
-+            if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit
-         }
- 
-     }
-@@ -189,6 +256,35 @@
-         return book;
-     }
- 
-+    // CraftBukkit start
-+    private final CommandSource commandSource = new CommandSource() {
-+
-+        @Override
-+        public void sendSystemMessage(Component message) {
-+        }
-+
-+        @Override
-+        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+            return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, LecternBlockEntity.this);
-+        }
-+
-+        @Override
-+        public boolean acceptsSuccess() {
-+            return false;
-+        }
-+
-+        @Override
-+        public boolean acceptsFailure() {
-+            return false;
-+        }
-+
-+        @Override
-+        public boolean shouldInformAdmins() {
-+            return false;
-+        }
-+    };
-+    // CraftBukkit end
-+
-     private CommandSourceStack createCommandSourceStack(@Nullable Player player, ServerLevel world) {
-         String s;
-         Object object;
-@@ -203,7 +299,8 @@
- 
-         Vec3 vec3d = Vec3.atCenterOf(this.worldPosition);
- 
--        return new CommandSourceStack(CommandSource.NULL, vec3d, Vec2.ZERO, world, 2, s, (Component) object, world.getServer(), player);
-+        // CraftBukkit - commandSource
-+        return new CommandSourceStack(this.commandSource, vec3d, Vec2.ZERO, world, 2, s, (Component) object, world.getServer(), player);
-     }
- 
-     @Override
-@@ -236,7 +333,7 @@
- 
-     @Override
-     public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
--        return new LecternMenu(syncId, this.bookAccess, this.dataAccess);
-+        return new LecternMenu(syncId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
deleted file mode 100644
index 788235f327..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
+++ /dev/null
@@ -1,269 +0,0 @@
---- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
-@@ -22,12 +22,12 @@
- import net.minecraft.network.chat.Style;
- import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.server.network.FilteredText;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.SignBlock;
-@@ -35,6 +35,12 @@
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
-+import org.bukkit.block.sign.Side;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.block.SignChangeEvent;
-+// CraftBukkit end
- 
- public class SignBlockEntity extends BlockEntity {
- 
-@@ -61,13 +67,18 @@
-         return new SignText();
-     }
- 
--    public boolean isFacingFrontText(Player player) {
-+    public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) {
-+        // Paper start - More Sign Block API
-+        return this.isFacingFrontText(player.getX(), player.getZ());
-+    }
-+    public boolean isFacingFrontText(double x, double z) {
-+        // Paper end - More Sign Block API
-         Block block = this.getBlockState().getBlock();
- 
-         if (block instanceof SignBlock blocksign) {
-             Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState());
--            double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x);
--            double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z);
-+            double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - More Sign Block API
-+            double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - More Sign Block API
-             float f = blocksign.getYRotationDegrees(this.getBlockState());
-             float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F;
- 
-@@ -101,7 +112,7 @@
-     protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-         super.saveAdditional(nbt, registries);
-         DynamicOps<Tag> dynamicops = registries.createSerializationContext(NbtOps.INSTANCE);
--        DataResult dataresult = SignText.DIRECT_CODEC.encodeStart(dynamicops, this.frontText);
-+        DataResult<Tag> dataresult = SignText.DIRECT_CODEC.encodeStart(dynamicops, this.frontText); // CraftBukkit - decompile error
-         Logger logger = SignBlockEntity.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -121,7 +132,7 @@
-     protected void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
-         super.loadAdditional(nbt, registries);
-         DynamicOps<Tag> dynamicops = registries.createSerializationContext(NbtOps.INSTANCE);
--        DataResult dataresult;
-+        DataResult<SignText> dataresult; // CraftBukkit - decompile error
-         Logger logger;
- 
-         if (nbt.contains("front_text")) {
-@@ -161,7 +172,7 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             try {
--                return ComponentUtils.updateForEntity(SignBlockEntity.createCommandSourceStack((Player) null, worldserver, this.worldPosition), text, (Entity) null, 0);
-+                return ComponentUtils.updateForEntity(this.createCommandSourceStack((net.minecraft.world.entity.player.Player) null, worldserver, this.worldPosition), text, (Entity) null, 0);
-             } catch (CommandSyntaxException commandsyntaxexception) {
-                 ;
-             }
-@@ -170,15 +181,17 @@
-         return text;
-     }
- 
--    public void updateSignText(Player player, boolean front, List<FilteredText> messages) {
-+    public void updateSignText(net.minecraft.world.entity.player.Player player, boolean front, List<FilteredText> messages) {
-         if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
-             this.updateText((signtext) -> {
--                return this.setMessages(player, messages, signtext);
-+                return this.setMessages(player, messages, signtext, front); // CraftBukkit
-             }, front);
-             this.setAllowedPlayerEditor((UUID) null);
-             this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
-         } else {
-             SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
-+            if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update
-+            ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
-         }
-     }
- 
-@@ -188,19 +201,43 @@
-         return this.setText((SignText) textChanger.apply(signtext), front);
-     }
- 
--    private SignText setMessages(Player player, List<FilteredText> messages, SignText text) {
--        for (int i = 0; i < messages.size(); ++i) {
--            FilteredText filteredtext = (FilteredText) messages.get(i);
--            Style chatmodifier = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
-+    private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List<FilteredText> list, SignText signtext, boolean front) { // CraftBukkit
-+        SignText originalText = signtext; // CraftBukkit
-+        for (int i = 0; i < list.size(); ++i) {
-+            FilteredText filteredtext = (FilteredText) list.get(i);
-+            Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle();
- 
--            if (player.isTextFilteringEnabled()) {
--                text = text.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier));
-+            if (entityhuman.isTextFilteringEnabled()) {
-+                signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only
-             } else {
--                text = text.setMessage(i, Component.literal(filteredtext.raw()).setStyle(chatmodifier), Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier));
-+                signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only
-             }
-         }
- 
--        return text;
-+        // CraftBukkit start
-+        Player player = ((ServerPlayer) entityhuman).getBukkitEntity();
-+        List<net.kyori.adventure.text.Component> lines = new java.util.ArrayList<>(); // Paper - adventure
-+
-+        for (int i = 0; i < list.size(); ++i) {
-+            lines.add(io.papermc.paper.adventure.PaperAdventure.asAdventure(signtext.getMessage(i, entityhuman.isTextFilteringEnabled()))); // Paper - Adventure
-+        }
-+
-+        SignChangeEvent event = new SignChangeEvent(CraftBlock.at(this.level, this.worldPosition), player, new java.util.ArrayList<>(lines), (front) ? Side.FRONT : Side.BACK); // Paper - Adventure
-+        entityhuman.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+        if (event.isCancelled()) {
-+            return originalText;
-+        }
-+
-+        Component[] components = org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.lines()); // Paper - Adventure
-+        for (int i = 0; i < components.length; i++) {
-+            if (!Objects.equals(lines.get(i), event.line(i))) { // Paper - Adventure
-+                signtext = signtext.setMessage(i, components[i]);
-+            }
-+        }
-+        // CraftBukkit end
-+
-+        return signtext;
-     }
- 
-     public boolean setText(SignText text, boolean front) {
-@@ -227,11 +264,11 @@
-         }
-     }
- 
--    public boolean canExecuteClickCommands(boolean front, Player player) {
-+    public boolean canExecuteClickCommands(boolean front, net.minecraft.world.entity.player.Player player) {
-         return this.isWaxed() && this.getText(front).hasAnyClickCommands(player);
-     }
- 
--    public boolean executeClickCommandsIfPresent(Player player, Level world, BlockPos pos, boolean front) {
-+    public boolean executeClickCommandsIfPresent(net.minecraft.world.entity.player.Player player, Level world, BlockPos pos, boolean front) {
-         boolean flag1 = false;
-         Component[] aichatbasecomponent = this.getText(front).getMessages(player.isTextFilteringEnabled());
-         int i = aichatbasecomponent.length;
-@@ -242,7 +279,17 @@
-             ClickEvent chatclickable = chatmodifier.getClickEvent();
- 
-             if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) {
--                player.getServer().getCommands().performPrefixedCommand(SignBlockEntity.createCommandSourceStack(player, world, pos), chatclickable.getValue());
-+                // Paper start - Fix commands from signs not firing command events
-+                String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue();
-+                if (org.spigotmc.SpigotConfig.logCommands)  {
-+                    LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command);
-+                }
-+                io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent((org.bukkit.entity.Player) player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) CraftBlock.at(this.level, this.worldPosition).getState(), front ? Side.FRONT : Side.BACK);
-+                if (!event.callEvent()) {
-+                    return false;
-+                }
-+                player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), world, pos), event.getMessage());
-+                // Paper end - Fix commands from signs not firing command events
-                 flag1 = true;
-             }
-         }
-@@ -250,11 +297,55 @@
-         return flag1;
-     }
- 
--    private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level world, BlockPos pos) {
-+    // CraftBukkit start
-+    private final CommandSource commandSource = new CommandSource() {
-+
-+        @Override
-+        public void sendSystemMessage(Component message) {}
-+
-+        @Override
-+        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+            return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitEntity() : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, SignBlockEntity.this);
-+        }
-+
-+        @Override
-+        public boolean acceptsSuccess() {
-+            return false;
-+        }
-+
-+        @Override
-+        public boolean acceptsFailure() {
-+            return false;
-+        }
-+
-+        @Override
-+        public boolean shouldInformAdmins() {
-+            return false;
-+        }
-+    };
-+
-+    private CommandSourceStack createCommandSourceStack(@Nullable net.minecraft.world.entity.player.Player player, Level world, BlockPos pos) {
-+        // CraftBukkit end
-         String s = player == null ? "Sign" : player.getName().getString();
-         Object object = player == null ? Component.literal("Sign") : player.getDisplayName();
- 
--        return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player);
-+        // Paper start - Fix commands from signs not firing command events
-+        CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) {
-+            @Override
-+            public void sendSystemMessage(Component message) {
-+                if (player instanceof final ServerPlayer serverPlayer) {
-+                    serverPlayer.sendSystemMessage(message);
-+                }
-+            }
-+
-+            @Override
-+            public boolean acceptsFailure() {
-+                return true;
-+            }
-+        } : this.commandSource;
-+        // Paper end - Fix commands from signs not firing command events
-+        // CraftBukkit - this
-+        return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events
-     }
- 
-     @Override
-@@ -273,12 +364,17 @@
- 
-     @Nullable
-     public UUID getPlayerWhoMayEdit() {
-+        // CraftBukkit start - unnecessary sign ticking removed, so do this lazily
-+        if (this.level != null && this.playerWhoMayEdit != null) {
-+            this.clearInvalidPlayerWhoMayEdit(this, this.level, this.playerWhoMayEdit);
-+        }
-+        // CraftBukkit end
-         return this.playerWhoMayEdit;
-     }
- 
-     private void markUpdated() {
-         this.setChanged();
--        this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
-+        if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122)
-     }
- 
-     public boolean isWaxed() {
-@@ -296,7 +392,7 @@
-     }
- 
-     public boolean playerIsTooFarAwayToEdit(UUID uuid) {
--        Player entityhuman = this.level.getPlayerByUUID(uuid);
-+        net.minecraft.world.entity.player.Player entityhuman = this.level.getPlayerByUUID(uuid);
- 
-         return entityhuman == null || !entityhuman.canInteractWithBlock(this.getBlockPos(), 4.0D);
-     }

From afa25753deca7d3cc69514fe82f87458a213edcc Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sat, 14 Dec 2024 05:48:25 +0100
Subject: [PATCH 045/285] Fix compile issue in MinecraftServer

---
 .../net/minecraft/server/MinecraftServer.java.patch  | 10 +---------
 .../block/entity/ContainerOpenersCounter.java.patch  |  4 ++--
 .../level/block/entity/JukeboxBlockEntity.java.patch | 12 ++++++++----
 3 files changed, 11 insertions(+), 15 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index ee2f5f535b..2e1e050e3a 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -1220,7 +1220,7 @@
      }
  
      @Nullable
-@@ -1980,23 +_,29 @@
+@@ -1980,16 +_,22 @@
      }
  
      public void logChatMessage(Component content, ChatType.Bound boundChatType, @Nullable String header) {
@@ -1247,14 +1247,6 @@
      }
  
      public boolean logIPs() {
-         return true;
-     }
- 
--    public void subscribeToDebugSample(ServerPlayer player, RemoteDebugSampleType sampleType) {
-+    public void subscribeToDebugSample(ServerPlaye player, RemoteDebugSampleType sampleType) {
-     }
- 
-     public boolean acceptsTransfers() {
 @@ -2122,4 +_,53 @@
              };
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
index e999d53387..2f6c3c484c 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
@@ -21,8 +21,8 @@
 +        this.onClose(level, blockPos, blockState);
 +    }
 +
-+    public void openerAPICountChanged(Level world, BlockPos blockPos, BlockState blockState, int count, int openCount) {
-+        this.openerCountChanged(world, blockPos, blockState, count, openCount);
++    public void openerAPICountChanged(Level level, BlockPos blockPos, BlockState blockState, int count, int openCount) {
++        this.openerCountChanged(level, blockPos, blockState, count, openCount);
 +    }
 +    // CraftBukkit en
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index 35ee987e0f..cd2c18e407 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -54,15 +54,19 @@
      }
  
      @Override
-@@ -147,9 +_,14 @@
+@@ -145,11 +_,16 @@
+     }
+ 
      @VisibleForTesting
-     public void setSongItemWithoutPlaying(ItemStack stack) {
+-    public void setSongItemWithoutPlaying(ItemStack stack) {
++    public void setSongItemWithoutPlaying(ItemStack stack, final long ticksSinceSongStarted) { // CraftBukkit - passed ticks since song started
          this.item = stack;
 -        JukeboxSong.fromStack(this.level.registryAccess(), stack)
+-            .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, 0L));
+-        this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
 +        this.jukeboxSongPlayer.song = null; // CraftBukkit - reset
 +        JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), stack) // Paper - fallback to other RegistyrAccess if no level
-             .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, 0L));
--        this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
++            .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, ticksSinceSongStarted)); // CraftBukkit - passed ticks since song started
 +        // CraftBukkit start - add null check for level
 +        if (this.level != null) {
 +            this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());

From 03daab51f79185a3e5588eced1d79e3f27d20713 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 11:41:23 +0100
Subject: [PATCH 046/285] Entity class

---
 .../minecraft/world/entity/Entity.java.patch  | 1264 +++++++----------
 1 file changed, 527 insertions(+), 737 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/Entity.java.patch (58%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index a274089f56..06d3a35857 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -59,6 +59,8 @@
+@@ -55,6 +_,8 @@
  import net.minecraft.network.protocol.Packet;
  import net.minecraft.network.protocol.game.ClientGamePacketListener;
  import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
@@ -9,7 +9,7 @@
  import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
  import net.minecraft.network.protocol.game.VecDeltaCodec;
  import net.minecraft.network.syncher.EntityDataAccessor;
-@@ -101,8 +103,6 @@
+@@ -97,8 +_,6 @@
  import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.ClipContext;
  import net.minecraft.world.level.Explosion;
@@ -18,20 +18,19 @@
  import net.minecraft.world.level.block.Block;
  import net.minecraft.world.level.block.Blocks;
  import net.minecraft.world.level.block.FenceGateBlock;
-@@ -138,9 +138,153 @@
- import net.minecraft.world.scores.ScoreHolder;
+@@ -135,7 +_,151 @@
  import net.minecraft.world.scores.Team;
  import org.slf4j.Logger;
+ 
++// CraftBukkit start
 +import net.minecraft.world.level.GameRules;
 +import net.minecraft.world.level.ItemLike;
 +import net.minecraft.world.level.Level;
 +import org.bukkit.Bukkit;
 +import org.bukkit.Location;
-+import org.bukkit.Server;
 +import org.bukkit.block.BlockFace;
 +import org.bukkit.command.CommandSender;
 +import org.bukkit.entity.Hanging;
-+import org.bukkit.entity.LivingEntity;
 +import org.bukkit.entity.Vehicle;
 +import org.bukkit.event.entity.EntityCombustByEntityEvent;
 +import org.bukkit.event.hanging.HangingBreakByEntityEvent;
@@ -44,7 +43,6 @@
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.craftbukkit.event.CraftPortalEvent;
 +import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Pose;
 +import org.bukkit.event.entity.EntityAirChangeEvent;
 +import org.bukkit.event.entity.EntityCombustEvent;
 +import org.bukkit.event.entity.EntityDismountEvent;
@@ -59,9 +57,9 @@
 +import org.bukkit.event.player.PlayerTeleportEvent;
 +import org.bukkit.plugin.PluginManager;
 +// CraftBukkit end
- 
++
  public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder {
- 
++
 +    // CraftBukkit start
 +    private static final int CURRENT_LEVEL = 2;
 +    public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
@@ -172,19 +170,28 @@
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final String ID_TAG = "id";
      public static final String PASSENGERS_TAG = "Passengers";
-@@ -224,7 +368,7 @@
-     private static final EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
-     private static final EntityDataAccessor<Boolean> DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
-     private static final EntityDataAccessor<Boolean> DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN);
--    protected static final EntityDataAccessor<Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE);
-+    protected static final EntityDataAccessor<net.minecraft.world.entity.Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE);
-     private static final EntityDataAccessor<Integer> DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT);
-     private EntityInLevelCallback levelCallback;
-     private final VecDeltaCodec packetPositionCodec;
-@@ -253,15 +397,78 @@
-     private final List<Entity.Movement> movementThisTick;
-     private final Set<BlockState> blocksInside;
-     private final LongSet visitedBlocks;
+@@ -196,7 +_,7 @@
+     public double zOld;
+     public boolean noPhysics;
+     private boolean wasOnFire;
+-    protected final RandomSource random = RandomSource.create();
++    protected final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+     public int tickCount;
+     private int remainingFireTicks = -this.getFireImmuneTicks();
+     protected boolean wasTouchingWater;
+@@ -233,7 +_,7 @@
+     protected UUID uuid = Mth.createInsecureUUID(this.random);
+     protected String stringUUID = this.uuid.toString();
+     private boolean hasGlowingTag;
+-    private final Set<String> tags = Sets.newHashSet();
++    private final Set<String> tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl
+     private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
+     private long pistonDeltasGameTime;
+     private EntityDimensions dimensions;
+@@ -250,6 +_,68 @@
+     private final List<Entity.Movement> movementThisTick = new ArrayList<>();
+     private final Set<BlockState> blocksInside = new ReferenceArraySet<>();
+     private final LongSet visitedBlocks = new LongOpenHashSet();
 +    // CraftBukkit start
 +    public boolean forceDrops;
 +    public boolean persist = true;
@@ -218,7 +225,7 @@
 +    public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
 +    public boolean fixedPose = false; // Paper - Expand Pose API
 +    private final int despawnTime; // Paper - entity despawn time limit
- 
++
 +    public void setOrigin(@javax.annotation.Nonnull Location location) {
 +        this.origin = location.toVector();
 +        this.originWorld = location.getWorld().getUID();
@@ -247,65 +254,44 @@
 +        return this.dimensions.makeBoundingBox(x, y, z);
 +    }
 +    // Paper end
-+
-     public Entity(EntityType<?> type, Level world) {
-         this.id = Entity.ENTITY_COUNTER.incrementAndGet();
-+        this.despawnTime = type == EntityType.PLAYER ? -1 : world.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit
-         this.passengers = ImmutableList.of();
-         this.deltaMovement = Vec3.ZERO;
-         this.bb = Entity.INITIAL_AABB;
-         this.stuckSpeedMultiplier = Vec3.ZERO;
-         this.nextStep = 1.0F;
--        this.random = RandomSource.create();
-+        this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
-         this.remainingFireTicks = -this.getFireImmuneTicks();
-         this.fluidHeight = new Object2DoubleArrayMap(2);
-         this.fluidOnEyes = new HashSet();
-@@ -270,7 +477,7 @@
-         this.packetPositionCodec = new VecDeltaCodec();
-         this.uuid = Mth.createInsecureUUID(this.random);
-         this.stringUUID = this.uuid.toString();
--        this.tags = Sets.newHashSet();
-+        this.tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl
-         this.pistonDeltas = new double[]{0.0D, 0.0D, 0.0D};
-         this.mainSupportingBlockPos = Optional.empty();
-         this.onGroundNoBlocks = false;
-@@ -284,6 +491,13 @@
+ 
+     public Entity(EntityType<?> entityType, Level level) {
+         this.type = entityType;
+@@ -258,6 +_,13 @@
          this.position = Vec3.ZERO;
          this.blockPosition = BlockPos.ZERO;
          this.chunkPosition = ChunkPos.ZERO;
 +        // Spigot start
-+        if (world != null) {
-+            this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig);
++        if (level != null) {
++            this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, level.spigotConfig);
 +        } else {
 +            this.defaultActivationState = false;
 +        }
 +        // Spigot end
-         SynchedEntityData.Builder datawatcher_a = new SynchedEntityData.Builder(this);
+         SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
+         builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
+         builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
+@@ -271,6 +_,7 @@
+         this.entityData = builder.build();
+         this.setPos(0.0, 0.0, 0.0);
+         this.eyeHeight = this.dimensions.eyeHeight();
++        this.despawnTime = type == EntityType.PLAYER ? -1 : level.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit
+     }
  
-         datawatcher_a.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0);
-@@ -292,7 +506,7 @@
-         datawatcher_a.define(Entity.DATA_CUSTOM_NAME, Optional.empty());
-         datawatcher_a.define(Entity.DATA_SILENT, false);
-         datawatcher_a.define(Entity.DATA_NO_GRAVITY, false);
--        datawatcher_a.define(Entity.DATA_POSE, Pose.STANDING);
-+        datawatcher_a.define(Entity.DATA_POSE, net.minecraft.world.entity.Pose.STANDING);
-         datawatcher_a.define(Entity.DATA_TICKS_FROZEN, 0);
-         this.defineSynchedData(datawatcher_a);
-         this.entityData = datawatcher_a.build();
-@@ -354,7 +568,7 @@
+     public boolean isColliding(BlockPos pos, BlockState state) {
+@@ -324,7 +_,7 @@
      }
  
      public boolean addTag(String tag) {
--        return this.tags.size() >= 1024 ? false : this.tags.add(tag);
+-        return this.tags.size() < 1024 && this.tags.add(tag);
 +        return this.tags.add(tag); // Paper - fully limit tag size - replace set impl
      }
  
      public boolean removeTag(String tag) {
-@@ -362,20 +576,68 @@
+@@ -332,12 +_,18 @@
      }
  
-     public void kill(ServerLevel world) {
+     public void kill(ServerLevel level) {
 -        this.remove(Entity.RemovalReason.KILLED);
 +        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
          this.gameEvent(GameEvent.ENTITY_DIE);
@@ -315,16 +301,15 @@
 -        this.remove(Entity.RemovalReason.DISCARDED);
 +        // CraftBukkit start - add Bukkit remove cause
 +        this.discard(null);
-     }
- 
++    }
++
 +    public final void discard(EntityRemoveEvent.Cause cause) {
 +        this.remove(Entity.RemovalReason.DISCARDED, cause);
 +        // CraftBukkit end
-+    }
-+
-     protected abstract void defineSynchedData(SynchedEntityData.Builder builder);
+     }
  
-     public SynchedEntityData getEntityData() {
+     protected abstract void defineSynchedData(SynchedEntityData.Builder builder);
+@@ -346,6 +_,48 @@
          return this.entityData;
      }
  
@@ -370,29 +355,29 @@
 +    }
 +    // Paper end
 +
+     @Override
      public boolean equals(Object object) {
-         return object instanceof Entity ? ((Entity) object).id == this.id : false;
-     }
-@@ -385,22 +647,39 @@
+         return object instanceof Entity && ((Entity)object).id == this.id;
+@@ -357,7 +_,13 @@
      }
  
      public void remove(Entity.RemovalReason reason) {
 -        this.setRemoved(reason);
 +        // CraftBukkit start - add Bukkit remove cause
 +        this.setRemoved(reason, null);
-     }
- 
++    }
++
 +    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
 +        this.setRemoved(entity_removalreason, cause);
 +        // CraftBukkit end
-+    }
-+
-     public void onClientRemoval() {}
+     }
  
-     public void onRemoval(Entity.RemovalReason reason) {}
+     public void onClientRemoval() {
+@@ -367,7 +_,18 @@
+     }
  
--    public void setPose(Pose pose) {
-+    public void setPose(net.minecraft.world.entity.Pose pose) {
+     public void setPose(Pose pose) {
+-        this.entityData.set(DATA_POSE, pose);
 +        if (this.fixedPose) return; // Paper - Expand Pose API
 +        // CraftBukkit start
 +        if (pose == this.getPose()) {
@@ -400,59 +385,48 @@
 +        }
 +        // Paper start - Don't fire sync event during generation
 +        if (!this.generation) {
-+            this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()]));
++            this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), org.bukkit.entity.Pose.values()[pose.ordinal()]));
 +        }
 +        // Paper end - Don't fire sync event during generation
 +        // CraftBukkit end
-         this.entityData.set(Entity.DATA_POSE, pose);
++        this.entityData.set(Entity.DATA_POSE, pose);
      }
  
--    public Pose getPose() {
--        return (Pose) this.entityData.get(Entity.DATA_POSE);
-+    public net.minecraft.world.entity.Pose getPose() {
-+        return (net.minecraft.world.entity.Pose) this.entityData.get(Entity.DATA_POSE);
+     public Pose getPose() {
+@@ -390,6 +_,32 @@
      }
  
--    public boolean hasPose(Pose pose) {
-+    public boolean hasPose(net.minecraft.world.entity.Pose pose) {
-         return this.getPose() == pose;
-     }
- 
-@@ -417,6 +696,33 @@
-     }
- 
-     public void setRot(float yaw, float pitch) {
+     protected void setRot(float yRot, float xRot) {
 +        // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
-+        if (Float.isNaN(yaw)) {
-+            yaw = 0;
++        if (Float.isNaN(yRot)) {
++            yRot = 0;
 +        }
 +
-+        if (yaw == Float.POSITIVE_INFINITY || yaw == Float.NEGATIVE_INFINITY) {
++        if (yRot == Float.POSITIVE_INFINITY || yRot == Float.NEGATIVE_INFINITY) {
 +            if (this instanceof ServerPlayer) {
 +                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw");
 +                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
 +            }
-+            yaw = 0;
++            yRot = 0;
 +        }
 +
 +        // pitch was sometimes set to NaN, so we need to set it back to 0
-+        if (Float.isNaN(pitch)) {
-+            pitch = 0;
++        if (Float.isNaN(xRot)) {
++            xRot = 0;
 +        }
 +
-+        if (pitch == Float.POSITIVE_INFINITY || pitch == Float.NEGATIVE_INFINITY) {
++        if (xRot == Float.POSITIVE_INFINITY || xRot == Float.NEGATIVE_INFINITY) {
 +            if (this instanceof ServerPlayer) {
 +                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch");
 +                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
 +            }
-+            pitch = 0;
++            xRot = 0;
 +        }
 +        // CraftBukkit end
-+
-         this.setYRot(yaw % 360.0F);
-         this.setXRot(pitch % 360.0F);
+         this.setYRot(yRot % 360.0F);
+         this.setXRot(xRot % 360.0F);
      }
-@@ -426,8 +732,8 @@
+@@ -399,8 +_,8 @@
      }
  
      public void setPos(double x, double y, double z) {
@@ -463,7 +437,7 @@
      }
  
      protected final AABB makeBoundingBox() {
-@@ -459,13 +765,29 @@
+@@ -430,12 +_,28 @@
      }
  
      public void tick() {
@@ -486,15 +460,14 @@
 +    // CraftBukkit end
 +
      public void baseTick() {
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("entityBaseTick");
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("entityBaseTick");
 +        if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups
          this.inBlockState = null;
          if (this.isPassenger() && this.getVehicle().isRemoved()) {
              this.stopRiding();
-@@ -475,7 +797,7 @@
-             --this.boardingCooldown;
+@@ -445,7 +_,7 @@
+             this.boardingCooldown--;
          }
  
 -        this.handlePortal();
@@ -502,16 +475,16 @@
          if (this.canSpawnSprintParticle()) {
              this.spawnSprintParticle();
          }
-@@ -502,7 +824,7 @@
+@@ -470,7 +_,7 @@
                      this.setRemainingFireTicks(this.remainingFireTicks - 1);
                  }
  
 -                if (this.getTicksFrozen() > 0) {
-+                if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API
++                if (this.getTicksFrozen() > 0 && !this.freezeLocked) { // Paper - Freeze Tick Lock API
                      this.setTicksFrozen(0);
-                     this.level().levelEvent((Player) null, 1009, this.blockPosition, 1);
+                     this.level().levelEvent(null, 1009, this.blockPosition, 1);
                  }
-@@ -514,6 +836,10 @@
+@@ -482,6 +_,10 @@
          if (this.isInLava()) {
              this.lavaHurt();
              this.fallDistance *= 0.5F;
@@ -522,30 +495,21 @@
          }
  
          this.checkBelowWorld();
-@@ -525,7 +851,7 @@
-         world = this.level();
-         if (world instanceof ServerLevel worldserver) {
-             if (this instanceof Leashable) {
--                Leashable.tickLeash(worldserver, (Entity) ((Leashable) this));
-+                Leashable.tickLeash(worldserver, (Entity & Leashable) this); // CraftBukkit - decompile error
-             }
-         }
- 
-@@ -537,7 +863,12 @@
+@@ -502,7 +_,12 @@
      }
  
      public void checkBelowWorld() {
--        if (this.getY() < (double) (this.level().getMinY() - 64)) {
+-        if (this.getY() < this.level().getMinY() - 64) {
 +        if (!this.level.getWorld().isVoidDamageEnabled()) return; // Paper - check if void damage is enabled on the world
 +        // Paper start - Configurable nether ceiling damage
-+        if (this.getY() < (double) (this.level.getMinY() + this.level.getWorld().getVoidDamageMinBuildHeightOffset()) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Paper - use configured min build height offset
++        if (this.getY() < (this.level.getMinY() + this.level.getWorld().getVoidDamageMinBuildHeightOffset()) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Paper - use configured min build height offset
 +            && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
 +            && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
 +            // Paper end - Configurable nether ceiling damage
              this.onBelowWorld();
          }
- 
-@@ -568,15 +899,32 @@
+     }
+@@ -531,9 +_,24 @@
  
      public void lavaHurt() {
          if (!this.fireImmune()) {
@@ -566,29 +530,21 @@
 +                this.igniteForSeconds(15.0F, false);
 +            }
 +            // CraftBukkit end
-             Level world = this.level();
- 
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                if (this.hurtServer(worldserver, this.damageSources().lava(), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) {
-+                // CraftBukkit start
-+                if (this.hurtServer(worldserver, this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) {
-                     worldserver.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.GENERIC_BURN, this.getSoundSource(), 0.4F, 2.0F + this.random.nextFloat() * 0.4F);
-                 }
-+                // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls
-             }
- 
-         }
-@@ -587,9 +935,25 @@
+             if (this.level() instanceof ServerLevel serverLevel
+-                && this.hurtServer(serverLevel, this.damageSources().lava(), 4.0F)
++                && this.hurtServer(serverLevel, this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F) // CraftBukkit - we also don't throw an event unless the object in lava is living, to save on some event calls
+                 && this.shouldPlayLavaHurtSound()
+                 && !this.isSilent()) {
+                 serverLevel.playSound(
+@@ -548,7 +_,23 @@
      }
  
      public final void igniteForSeconds(float seconds) {
 -        this.igniteForTicks(Mth.floor(seconds * 20.0F));
 +        // CraftBukkit start
 +        this.igniteForSeconds(seconds, true);
-     }
- 
++    }
++
 +    public final void igniteForSeconds(float f, boolean callEvent) {
 +        if (callEvent) {
 +            EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), f);
@@ -602,12 +558,10 @@
 +        }
 +        // CraftBukkit end
 +        this.igniteForTicks(Mth.floor(f * 20.0F));
-+    }
-+
+     }
+ 
      public void igniteForTicks(int ticks) {
-         if (this.remainingFireTicks < ticks) {
-             this.setRemainingFireTicks(ticks);
-@@ -610,7 +974,7 @@
+@@ -570,7 +_,7 @@
      }
  
      protected void onBelowWorld() {
@@ -615,8 +569,8 @@
 +        this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
      }
  
-     public boolean isFree(double offsetX, double offsetY, double offsetZ) {
-@@ -672,6 +1036,7 @@
+     public boolean isFree(double x, double y, double z) {
+@@ -627,6 +_,7 @@
      }
  
      public void move(MoverType type, Vec3 movement) {
@@ -624,47 +578,45 @@
          if (this.noPhysics) {
              this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
          } else {
-@@ -747,8 +1112,30 @@
+@@ -701,6 +_,28 @@
+                     }
+                 }
  
-                     if (movement.y != vec3d1.y) {
-                         block.updateEntityMovementAfterFallOn(this.level(), this);
-+                    }
-+                }
-+
 +                // CraftBukkit start
 +                if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) {
 +                    Vehicle vehicle = (Vehicle) this.getBukkitEntity();
 +                    org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
 +
-+                    if (movement.x > vec3d1.x) {
++                    if (movement.x > vec3.x) {
 +                        bl = bl.getRelative(BlockFace.EAST);
-+                    } else if (movement.x < vec3d1.x) {
++                    } else if (movement.x < vec3.x) {
 +                        bl = bl.getRelative(BlockFace.WEST);
-+                    } else if (movement.z > vec3d1.z) {
++                    } else if (movement.z > vec3.z) {
 +                        bl = bl.getRelative(BlockFace.SOUTH);
-+                    } else if (movement.z < vec3d1.z) {
++                    } else if (movement.z < vec3.z) {
 +                        bl = bl.getRelative(BlockFace.NORTH);
 +                    }
 +
 +                    if (!bl.getType().isAir()) {
 +                        VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity
 +                        this.level.getCraftServer().getPluginManager().callEvent(event);
-                     }
-                 }
++                    }
++                }
 +                // CraftBukkit end
- 
++
                  if (!this.level().isClientSide() || this.isControlledByLocalInstance()) {
-                     Entity.MovementEmission entity_movementemission = this.getMovementEmission();
-@@ -913,7 +1300,7 @@
+                     Entity.MovementEmission movementEmission = this.getMovementEmission();
+                     if (movementEmission.emitsAnything() && !this.isPassenger()) {
+@@ -850,7 +_,7 @@
      }
  
-     protected BlockPos getOnPos(float offset) {
+     protected BlockPos getOnPos(float yOffset) {
 -        if (this.mainSupportingBlockPos.isPresent()) {
 +        if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads
-             BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get();
- 
-             if (offset <= 1.0E-5F) {
-@@ -1133,6 +1520,20 @@
+             BlockPos blockPos = this.mainSupportingBlockPos.get();
+             if (!(yOffset > 1.0E-5F)) {
+                 return blockPos;
+@@ -1049,6 +_,20 @@
          return SoundEvents.GENERIC_SPLASH;
      }
  
@@ -682,29 +634,29 @@
 +    }
 +    // CraftBukkit end
 +
-     public void recordMovementThroughBlocks(Vec3 oldPos, Vec3 newPos) {
-         this.movementThisTick.add(new Entity.Movement(oldPos, newPos));
+     public void recordMovementThroughBlocks(Vec3 oldPosition, Vec3 position) {
+         this.movementThisTick.add(new Entity.Movement(oldPosition, position));
      }
-@@ -1599,6 +2000,7 @@
-         this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F);
+@@ -1485,6 +_,7 @@
+         this.setXRot(Mth.clamp(xRot, -90.0F, 90.0F) % 360.0F);
          this.yRotO = this.getYRot();
          this.xRotO = this.getXRot();
-+        this.setYHeadRot(yaw); // Paper - Update head rotation
++        this.setYHeadRot(yRot); // Paper - Update head rotation
      }
  
      public void absMoveTo(double x, double y, double z) {
-@@ -1609,6 +2011,7 @@
+@@ -1494,6 +_,7 @@
          this.yo = y;
-         this.zo = d4;
-         this.setPos(d3, y, d4);
+         this.zo = d1;
+         this.setPos(d, y, d1);
 +        if (this.valid) this.level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit
      }
  
-     public void moveTo(Vec3 pos) {
-@@ -1628,11 +2031,19 @@
+     public void moveTo(Vec3 vec) {
+@@ -1513,11 +_,19 @@
      }
  
-     public void moveTo(double x, double y, double z, float yaw, float pitch) {
+     public void moveTo(double x, double y, double z, float yRot, float xRot) {
 +        // Paper start - Fix Entity Teleportation and cancel velocity if teleported
 +        if (!preserveMotion) {
 +            this.deltaMovement = Vec3.ZERO;
@@ -713,33 +665,33 @@
 +        }
 +        // Paper end - Fix Entity Teleportation and cancel velocity if teleported
          this.setPosRaw(x, y, z);
-         this.setYRot(yaw);
-         this.setXRot(pitch);
+         this.setYRot(yRot);
+         this.setXRot(xRot);
          this.setOldPosAndRot();
          this.reapplyPosition();
-+        this.setYHeadRot(yaw); // Paper - Update head rotation
++        this.setYHeadRot(yRot); // Paper - Update head rotation
      }
  
      public final void setOldPosAndRot() {
-@@ -1701,6 +2112,7 @@
+@@ -1584,6 +_,7 @@
      public void push(Entity entity) {
          if (!this.isPassengerOfSameVehicle(entity)) {
              if (!entity.noPhysics && !this.noPhysics) {
 +                if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant
-                 double d0 = entity.getX() - this.getX();
+                 double d = entity.getX() - this.getX();
                  double d1 = entity.getZ() - this.getZ();
-                 double d2 = Mth.absMax(d0, d1);
-@@ -1737,7 +2149,21 @@
+                 double max = Mth.absMax(d, d1);
+@@ -1617,7 +_,21 @@
      }
  
-     public void push(double deltaX, double deltaY, double deltaZ) {
--        this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ));
+     public void push(double x, double y, double z) {
+-        this.setDeltaMovement(this.getDeltaMovement().add(x, y, z));
 +        // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-+        this.push(deltaX, deltaY, deltaZ, null);
++        this.push(x, y, z, null);
 +    }
 +
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) {
-+        org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ);
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) {
++        org.bukkit.util.Vector delta = new org.bukkit.util.Vector(x, y, z);
 +        if (pushingEntity != null) {
 +            io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta);
 +            if (!event.callEvent()) {
@@ -752,7 +704,7 @@
          this.hasImpulse = true;
      }
  
-@@ -1858,9 +2284,21 @@
+@@ -1724,8 +_,20 @@
      }
  
      public boolean isPushable() {
@@ -764,44 +716,41 @@
 +        // Paper end - Climbing should not bypass cramming gamerule
          return false;
      }
- 
++
 +    // CraftBukkit start - collidable API
 +    public boolean canCollideWithBukkit(Entity entity) {
 +        return this.isPushable();
 +    }
 +    // CraftBukkit end
-+
-     public void awardKillScore(Entity entityKilled, DamageSource damageSource) {
-         if (entityKilled instanceof ServerPlayer) {
-             CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource);
-@@ -1889,74 +2327,133 @@
+ 
+     public void awardKillScore(Entity entity, DamageSource damageSource) {
+         if (entity instanceof ServerPlayer) {
+@@ -1752,34 +_,70 @@
      }
  
-     public boolean saveAsPassenger(CompoundTag nbt) {
+     public boolean saveAsPassenger(CompoundTag compound) {
 +        // CraftBukkit start - allow excluding certain data when saving
-+        return this.saveAsPassenger(nbt, true);
++        return this.saveAsPassenger(compound, true);
 +    }
 +
-+    public boolean saveAsPassenger(CompoundTag nbttagcompound, boolean includeAll) {
++    public boolean saveAsPassenger(CompoundTag compound, boolean includeAll) {
 +        // CraftBukkit end
          if (this.removalReason != null && !this.removalReason.shouldSave()) {
              return false;
          } else {
-             String s = this.getEncodeId();
- 
--            if (s == null) {
-+            if (!this.persist || s == null) { // CraftBukkit - persist flag
+             String encodeId = this.getEncodeId();
+-            if (encodeId == null) {
++            if (!this.persist || encodeId == null) { // CraftBukkit - persist flag
                  return false;
              } else {
--                nbt.putString("id", s);
--                this.saveWithoutId(nbt);
-+                nbttagcompound.putString("id", s);
-+                this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+                 compound.putString("id", encodeId);
+-                this.saveWithoutId(compound);
++                this.saveWithoutId(compound, includeAll); // CraftBukkit - pass on includeAll
                  return true;
              }
          }
      }
- 
++
 +    // Paper start - Entity serialization api
 +    public boolean serializeEntity(CompoundTag compound) {
 +        List<Entity> pass = new java.util.ArrayList<>(this.getPassengers());
@@ -811,45 +760,36 @@
 +        return result;
 +    }
 +    // Paper end - Entity serialization api
-     public boolean save(CompoundTag nbt) {
-         return this.isPassenger() ? false : this.saveAsPassenger(nbt);
+ 
+     public boolean save(CompoundTag compound) {
+         return !this.isPassenger() && this.saveAsPassenger(compound);
      }
  
-     public CompoundTag saveWithoutId(CompoundTag nbt) {
+     public CompoundTag saveWithoutId(CompoundTag compound) {
 +        // CraftBukkit start - allow excluding certain data when saving
-+        return this.saveWithoutId(nbt, true);
++        return this.saveWithoutId(compound, true);
 +    }
 +
-+    public CompoundTag saveWithoutId(CompoundTag nbttagcompound, boolean includeAll) {
++    public CompoundTag saveWithoutId(CompoundTag compound, boolean includeAll) {
 +        // CraftBukkit end
          try {
 -            if (this.vehicle != null) {
--                nbt.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
+-                compound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
 -            } else {
--                nbt.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
+-                compound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
+-            }
 +            // CraftBukkit start - selectively save position
 +            if (includeAll) {
 +                if (this.vehicle != null) {
-+                    nbttagcompound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
++                    compound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
 +                } else {
-+                    nbttagcompound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
++                    compound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
 +                }
-             }
++             }
 +            // CraftBukkit end
  
-             Vec3 vec3d = this.getDeltaMovement();
- 
--            nbt.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z));
--            nbt.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
--            nbt.putFloat("FallDistance", this.fallDistance);
--            nbt.putShort("Fire", (short) this.remainingFireTicks);
--            nbt.putShort("Air", (short) this.getAirSupply());
--            nbt.putBoolean("OnGround", this.onGround());
--            nbt.putBoolean("Invulnerable", this.invulnerable);
--            nbt.putInt("PortalCooldown", this.portalCooldown);
--            nbt.putUUID("UUID", this.getUUID());
-+            nbttagcompound.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z));
-+
+             Vec3 deltaMovement = this.getDeltaMovement();
+             compound.put("Motion", this.newDoubleList(deltaMovement.x, deltaMovement.y, deltaMovement.z));
 +            // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero
 +            // TODO: make sure this is the best way to address this.
 +            if (Float.isNaN(this.yRot)) {
@@ -860,173 +800,124 @@
 +                this.xRot = 0;
 +            }
 +            // CraftBukkit end
-+
-+            nbttagcompound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
-+            nbttagcompound.putFloat("FallDistance", this.fallDistance);
-+            nbttagcompound.putShort("Fire", (short) this.remainingFireTicks);
-+            nbttagcompound.putShort("Air", (short) this.getAirSupply());
-+            nbttagcompound.putBoolean("OnGround", this.onGround());
-+            nbttagcompound.putBoolean("Invulnerable", this.invulnerable);
-+            nbttagcompound.putInt("PortalCooldown", this.portalCooldown);
+             compound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
+             compound.putFloat("FallDistance", this.fallDistance);
+             compound.putShort("Fire", (short)this.remainingFireTicks);
+@@ -1787,7 +_,29 @@
+             compound.putBoolean("OnGround", this.onGround());
+             compound.putBoolean("Invulnerable", this.invulnerable);
+             compound.putInt("PortalCooldown", this.portalCooldown);
+-            compound.putUUID("UUID", this.getUUID());
 +            // CraftBukkit start - selectively save uuid and world
 +            if (includeAll) {
-+                nbttagcompound.putUUID("UUID", this.getUUID());
++                compound.putUUID("UUID", this.getUUID());
 +                // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast
-+                nbttagcompound.putLong("WorldUUIDLeast", ((ServerLevel) this.level).getWorld().getUID().getLeastSignificantBits());
-+                nbttagcompound.putLong("WorldUUIDMost", ((ServerLevel) this.level).getWorld().getUID().getMostSignificantBits());
++                compound.putLong("WorldUUIDLeast", this.level.getWorld().getUID().getLeastSignificantBits());
++                compound.putLong("WorldUUIDMost", this.level.getWorld().getUID().getMostSignificantBits());
 +            }
-+            nbttagcompound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL);
++            compound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL);
 +            if (!this.persist) {
-+                nbttagcompound.putBoolean("Bukkit.persist", this.persist);
++                compound.putBoolean("Bukkit.persist", this.persist);
 +            }
 +            if (!this.visibleByDefault) {
-+                nbttagcompound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault);
++                compound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault);
 +            }
 +            if (this.persistentInvisibility) {
-+                nbttagcompound.putBoolean("Bukkit.invisible", this.persistentInvisibility);
++                compound.putBoolean("Bukkit.invisible", this.persistentInvisibility);
 +            }
 +            // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
 +            if (this.maxAirTicks != this.getDefaultMaxAirSupply()) {
-+                nbttagcompound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply());
++                compound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply());
 +            }
-+            nbttagcompound.putInt("Spigot.ticksLived", this.tickCount);
++            compound.putInt("Spigot.ticksLived", this.tickCount);
 +            // CraftBukkit end
-             Component ichatbasecomponent = this.getCustomName();
- 
-             if (ichatbasecomponent != null) {
--                nbt.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess()));
-+                nbttagcompound.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess()));
+             Component customName = this.getCustomName();
+             if (customName != null) {
+                 compound.putString("CustomName", Component.Serializer.toJson(customName, this.registryAccess()));
+@@ -1828,13 +_,13 @@
+                 compound.put("Tags", listTag);
              }
  
-             if (this.isCustomNameVisible()) {
--                nbt.putBoolean("CustomNameVisible", this.isCustomNameVisible());
-+                nbttagcompound.putBoolean("CustomNameVisible", this.isCustomNameVisible());
-             }
- 
-             if (this.isSilent()) {
--                nbt.putBoolean("Silent", this.isSilent());
-+                nbttagcompound.putBoolean("Silent", this.isSilent());
-             }
- 
-             if (this.isNoGravity()) {
--                nbt.putBoolean("NoGravity", this.isNoGravity());
-+                nbttagcompound.putBoolean("NoGravity", this.isNoGravity());
-             }
- 
-             if (this.hasGlowingTag) {
--                nbt.putBoolean("Glowing", true);
-+                nbttagcompound.putBoolean("Glowing", true);
-             }
- 
-             int i = this.getTicksFrozen();
- 
-             if (i > 0) {
--                nbt.putInt("TicksFrozen", this.getTicksFrozen());
-+                nbttagcompound.putInt("TicksFrozen", this.getTicksFrozen());
-             }
- 
-             if (this.hasVisualFire) {
--                nbt.putBoolean("HasVisualFire", this.hasVisualFire);
-+                nbttagcompound.putBoolean("HasVisualFire", this.hasVisualFire);
-             }
- 
-             ListTag nbttaglist;
-@@ -1972,10 +2469,10 @@
-                     nbttaglist.add(StringTag.valueOf(s));
-                 }
- 
--                nbt.put("Tags", nbttaglist);
-+                nbttagcompound.put("Tags", nbttaglist);
-             }
- 
--            this.addAdditionalSaveData(nbt);
-+            this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+-            this.addAdditionalSaveData(compound);
++            this.addAdditionalSaveData(compound, includeAll); // CraftBukkit - pass on includeAll
              if (this.isVehicle()) {
-                 nbttaglist = new ListTag();
-                 iterator = this.getPassengers().iterator();
-@@ -1984,17 +2481,44 @@
-                     Entity entity = (Entity) iterator.next();
-                     CompoundTag nbttagcompound1 = new CompoundTag();
+                 ListTag listTag = new ListTag();
  
--                    if (entity.saveAsPassenger(nbttagcompound1)) {
-+                    if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll
-                         nbttaglist.add(nbttagcompound1);
+                 for (Entity entity : this.getPassengers()) {
+                     CompoundTag compoundTag = new CompoundTag();
+-                    if (entity.saveAsPassenger(compoundTag)) {
++                    if (entity.saveAsPassenger(compoundTag, includeAll)) { // CraftBukkit - pass on includeAll
+                         listTag.add(compoundTag);
                      }
                  }
- 
-                 if (!nbttaglist.isEmpty()) {
--                    nbt.put("Passengers", nbttaglist);
-+                    nbttagcompound.put("Passengers", nbttaglist);
+@@ -1844,6 +_,33 @@
                  }
              }
  
--            return nbt;
 +            // CraftBukkit start - stores eventually existing bukkit values
 +            if (this.bukkitEntity != null) {
-+                this.bukkitEntity.storeBukkitValues(nbttagcompound);
++                this.bukkitEntity.storeBukkitValues(compound);
 +            }
 +            // CraftBukkit end
 +            // Paper start
 +            if (this.origin != null) {
 +                UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null;
 +                if (originWorld != null) {
-+                    nbttagcompound.putUUID("Paper.OriginWorld", originWorld);
++                    compound.putUUID("Paper.OriginWorld", originWorld);
 +                }
-+                nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ()));
++                compound.put("Paper.Origin", this.newDoubleList(this.origin.getX(), this.origin.getY(), this.origin.getZ()));
 +            }
-+            if (spawnReason != null) {
-+                nbttagcompound.putString("Paper.SpawnReason", spawnReason.name());
++            if (this.spawnReason != null) {
++                compound.putString("Paper.SpawnReason", this.spawnReason.name());
 +            }
 +            // Save entity's from mob spawner status
-+            if (spawnedViaMobSpawner) {
-+                nbttagcompound.putBoolean("Paper.FromMobSpawner", true);
++            if (this.spawnedViaMobSpawner) {
++                compound.putBoolean("Paper.FromMobSpawner", true);
 +            }
-+            if (fromNetherPortal) {
-+                nbttagcompound.putBoolean("Paper.FromNetherPortal", true);
++            if (this.fromNetherPortal) {
++                compound.putBoolean("Paper.FromNetherPortal", true);
 +            }
-+            if (freezeLocked) {
-+                nbttagcompound.putBoolean("Paper.FreezeLock", true);
++            if (this.freezeLocked) {
++                compound.putBoolean("Paper.FreezeLock", true);
 +            }
 +            // Paper end
-+            return nbttagcompound;
-         } catch (Throwable throwable) {
-             CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
-             CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being saved");
-@@ -2080,6 +2604,71 @@
+             return compound;
+         } catch (Throwable var9) {
+             CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
+@@ -1930,6 +_,69 @@
              } else {
-                 throw new IllegalStateException("Entity has invalid position");
+                 throw new IllegalStateException("Entity has invalid rotation");
              }
-+
 +            // CraftBukkit start
 +            // Spigot start
 +            if (this instanceof net.minecraft.world.entity.LivingEntity) {
-+                this.tickCount = nbt.getInt("Spigot.ticksLived");
++                this.tickCount = compound.getInt("Spigot.ticksLived");
 +            }
 +            // Spigot end
-+            this.persist = !nbt.contains("Bukkit.persist") || nbt.getBoolean("Bukkit.persist");
-+            this.visibleByDefault = !nbt.contains("Bukkit.visibleByDefault") || nbt.getBoolean("Bukkit.visibleByDefault");
++            this.persist = !compound.contains("Bukkit.persist") || compound.getBoolean("Bukkit.persist");
++            this.visibleByDefault = !compound.contains("Bukkit.visibleByDefault") || compound.getBoolean("Bukkit.visibleByDefault");
 +            // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-+            if (nbt.contains("Bukkit.MaxAirSupply")) {
-+                this.maxAirTicks = nbt.getInt("Bukkit.MaxAirSupply");
++            if (compound.contains("Bukkit.MaxAirSupply")) {
++                this.maxAirTicks = compound.getInt("Bukkit.MaxAirSupply");
 +            }
 +            // CraftBukkit end
 +
 +            // CraftBukkit start
 +            // Paper - move world parsing/loading to PlayerList#placeNewPlayer
-+            this.getBukkitEntity().readBukkitValues(nbt);
-+            if (nbt.contains("Bukkit.invisible")) {
-+                boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible");
++            this.getBukkitEntity().readBukkitValues(compound);
++            if (compound.contains("Bukkit.invisible")) {
++                boolean bukkitInvisible = compound.getBoolean("Bukkit.invisible");
 +                this.setInvisible(bukkitInvisible);
 +                this.persistentInvisibility = bukkitInvisible;
 +            }
 +            // CraftBukkit end
 +
 +            // Paper start
-+            ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE);
++            ListTag originTag = compound.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE);
 +            if (!originTag.isEmpty()) {
 +                UUID originWorld = null;
-+                if (nbt.contains("Paper.OriginWorld")) {
-+                    originWorld = nbt.getUUID("Paper.OriginWorld");
++                if (compound.contains("Paper.OriginWorld")) {
++                    originWorld = compound.getUUID("Paper.OriginWorld");
 +                } else if (this.level != null) {
 +                    originWorld = this.level.getWorld().getUID();
 +                }
@@ -1034,10 +925,10 @@
 +                origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2));
 +            }
 +
-+            spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
-+            fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal");
-+            if (nbt.contains("Paper.SpawnReason")) {
-+                String spawnReasonName = nbt.getString("Paper.SpawnReason");
++            spawnedViaMobSpawner = compound.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
++            fromNetherPortal = compound.getBoolean("Paper.FromNetherPortal");
++            if (compound.contains("Paper.SpawnReason")) {
++                String spawnReasonName = compound.getString("Paper.SpawnReason");
 +                try {
 +                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName);
 +                } catch (Exception ignored) {
@@ -1048,7 +939,7 @@
 +                if (spawnedViaMobSpawner) {
 +                    spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
 +                } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) {
-+                    if (!nbt.getBoolean("PersistenceRequired")) {
++                    if (!compound.getBoolean("PersistenceRequired")) {
 +                        spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL;
 +                    }
 +                }
@@ -1056,34 +947,32 @@
 +            if (spawnReason == null) {
 +                spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT;
 +            }
-+            if (nbt.contains("Paper.FreezeLock")) {
-+                freezeLocked = nbt.getBoolean("Paper.FreezeLock");
++            if (compound.contains("Paper.FreezeLock")) {
++                freezeLocked = compound.getBoolean("Paper.FreezeLock");
 +            }
 +            // Paper end
-+
-         } catch (Throwable throwable) {
-             CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
-             CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded");
-@@ -2099,7 +2688,13 @@
-         ResourceLocation minecraftkey = EntityType.getKey(entitytypes);
- 
-         return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null;
-+    }
-+
-+    // CraftBukkit start - allow excluding certain data when saving
-+    protected void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
-+        this.addAdditionalSaveData(nbttagcompound);
+         } catch (Throwable var17) {
+             CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
+             CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
+@@ -1949,6 +_,12 @@
+         return type.canSerialize() && key != null ? key.toString() : null;
      }
+ 
++    // CraftBukkit start - allow excluding certain data when saving
++    protected void addAdditionalSaveData(CompoundTag tag, boolean includeAll) {
++        this.addAdditionalSaveData(tag);
++    }
 +    // CraftBukkit end
++
+     protected abstract void readAdditionalSaveData(CompoundTag tag);
  
-     protected abstract void readAdditionalSaveData(CompoundTag nbt);
- 
-@@ -2150,12 +2745,60 @@
+     protected abstract void addAdditionalSaveData(CompoundTag tag);
+@@ -1990,11 +_,61 @@
  
      @Nullable
-     public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset) {
+     public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack, float yOffset) {
 +        // Paper start - Restore vanilla drops behavior
-+        return this.spawnAtLocation(world, stack, yOffset, null);
++        return this.spawnAtLocation(level, stack, yOffset, null);
 +    }
 +    public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer<ItemStack> dropConsumer) {
 +        public DefaultDrop(final ItemStack stack, final java.util.function.Consumer<ItemStack> dropConsumer) {
@@ -1099,14 +988,14 @@
 +        }
 +    }
 +    @Nullable
-+    public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer<? super ItemEntity> delayedAddConsumer) {
++    public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer<? super ItemEntity> delayedAddConsumer) {
 +        // Paper end - Restore vanilla drops behavior
          if (stack.isEmpty()) {
              return null;
          } else {
--            ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack);
+-            ItemEntity itemEntity = new ItemEntity(level, this.getX(), this.getY() + yOffset, this.getZ(), stack);
 +            // CraftBukkit start - Capture drops for death event
-+            if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) {
++            if (this instanceof net.minecraft.world.entity.LivingEntity && !this.forceDrops) {
 +                // Paper start - Restore vanilla drops behavior
 +                ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> {
 +                    ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer
@@ -1118,30 +1007,30 @@
 +                return null;
 +            }
 +            // CraftBukkit end
-+            ItemEntity entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original
++            ItemEntity itemEntity = new ItemEntity(level, this.getX(), this.getY() + yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original
 +            stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe
- 
--            entityitem.setDefaultPickUpDelay();
-+            entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer)
++
+             itemEntity.setDefaultPickUpDelay();
++            itemEntity.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer)
 +            // Paper start - Call EntityDropItemEvent
-+            return this.spawnAtLocation(world, entityitem);
++            return this.spawnAtLocation(level, itemEntity);
 +        }
 +    }
 +    @Nullable
-+    public ItemEntity spawnAtLocation(ServerLevel world, ItemEntity entityitem) {
++    public ItemEntity spawnAtLocation(ServerLevel level, ItemEntity itemEntity) {
 +        {
 +            // Paper end - Call EntityDropItemEvent
 +            // CraftBukkit start
-+            EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++            EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
 +            Bukkit.getPluginManager().callEvent(event);
 +            if (event.isCancelled()) {
 +                return null;
 +            }
 +            // CraftBukkit end
-             world.addFreshEntity(entityitem);
-             return entityitem;
+             level.addFreshEntity(itemEntity);
+             return itemEntity;
          }
-@@ -2184,7 +2827,16 @@
+@@ -2028,7 +_,16 @@
          if (this.isAlive() && this instanceof Leashable leashable) {
              if (leashable.getLeashHolder() == player) {
                  if (!this.level().isClientSide()) {
@@ -1159,9 +1048,9 @@
                          leashable.removeLeash();
                      } else {
                          leashable.dropLeash();
-@@ -2200,6 +2852,14 @@
- 
-             if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
+@@ -2043,6 +_,14 @@
+             ItemStack itemInHand = player.getItemInHand(hand);
+             if (itemInHand.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
                  if (!this.level().isClientSide()) {
 +                    // CraftBukkit start - fire PlayerLeashEntityEvent
 +                    if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
@@ -1174,32 +1063,27 @@
                      leashable.setLeashedTo(player, true);
                  }
  
-@@ -2265,15 +2925,15 @@
+@@ -2116,11 +_,11 @@
      }
  
-     public boolean showVehicleHealth() {
--        return this instanceof LivingEntity;
-+        return this instanceof net.minecraft.world.entity.LivingEntity;
-     }
- 
-     public boolean startRiding(Entity entity, boolean force) {
--        if (entity == this.vehicle) {
-+        if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins)
+     public boolean startRiding(Entity vehicle, boolean force) {
+-        if (vehicle == this.vehicle) {
++        if (vehicle == this.vehicle || vehicle.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins)
              return false;
-         } else if (!entity.couldAcceptPassenger()) {
+         } else if (!vehicle.couldAcceptPassenger()) {
              return false;
--        } else if (!this.level().isClientSide() && !entity.type.canSerialize()) {
-+        } else if (!force && !this.level().isClientSide() && !entity.type.canSerialize()) { // SPIGOT-7947: Allow force riding all entities
+-        } else if (!this.level().isClientSide() && !vehicle.type.canSerialize()) {
++        } else if (!force && !this.level().isClientSide() && !vehicle.type.canSerialize()) { // SPIGOT-7947: Allow force riding all entities
              return false;
          } else {
-             for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) {
-@@ -2285,11 +2945,32 @@
-             if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) {
-                 return false;
-             } else {
+             for (Entity entity = vehicle; entity.vehicle != null; entity = entity.vehicle) {
+@@ -2130,6 +_,27 @@
+             }
+ 
+             if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
 +                // CraftBukkit start
-+                if (entity.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof LivingEntity) {
-+                    VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) entity.getBukkitEntity(), this.getBukkitEntity());
++                if (vehicle.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
++                    VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
 +                    // Suppress during worldgen
 +                    if (this.valid) {
 +                        Bukkit.getPluginManager().callEvent(event);
@@ -1209,7 +1093,7 @@
 +                    }
 +                }
 +
-+                EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), entity.getBukkitEntity());
++                EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), vehicle.getBukkitEntity());
 +                // Suppress during worldgen
 +                if (this.valid) {
 +                    Bukkit.getPluginManager().callEvent(event);
@@ -1221,13 +1105,7 @@
                  if (this.isPassenger()) {
                      this.stopRiding();
                  }
- 
--                this.setPose(Pose.STANDING);
-+                this.setPose(net.minecraft.world.entity.Pose.STANDING);
-                 this.vehicle = entity;
-                 this.vehicle.addPassenger(this);
-                 entity.getIndirectPassengersStream().filter((entity2) -> {
-@@ -2314,19 +2995,30 @@
+@@ -2158,15 +_,26 @@
      }
  
      public void removeVehicle() {
@@ -1238,62 +1116,58 @@
 +        // Paper end - Force entity dismount during teleportation
          if (this.vehicle != null) {
              Entity entity = this.vehicle;
- 
              this.vehicle = null;
 -            entity.removePassenger(this);
 +            if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation
          }
- 
      }
  
      public void stopRiding() {
 -        this.removeVehicle();
 +        // Paper start - Force entity dismount during teleportation
 +        this.stopRiding(false);
-     }
- 
++    }
++
 +    public void stopRiding(boolean suppressCancellation) {
 +        this.removeVehicle(suppressCancellation);
 +        // Paper end - Force entity dismount during teleportation
-+    }
-+
+     }
+ 
      protected void addPassenger(Entity passenger) {
-         if (passenger.getVehicle() != this) {
-             throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)");
-@@ -2349,21 +3041,53 @@
+@@ -2190,10 +_,43 @@
          }
      }
  
 -    protected void removePassenger(Entity passenger) {
--        if (passenger.getVehicle() == this) {
 +    // Paper start - Force entity dismount during teleportation
-+    protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);}
-+    protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit
++    protected boolean removePassenger(Entity passenger) {
++        return removePassenger(passenger, false);
++    }
++    protected boolean removePassenger(Entity passenger, boolean suppressCancellation) { // CraftBukkit
 +        // Paper end - Force entity dismount during teleportation
-+        if (entity.getVehicle() == this) {
+         if (passenger.getVehicle() == this) {
              throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
          } else {
--            if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
 +            // CraftBukkit start
-+            CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle();
++            CraftEntity craft = (CraftEntity) passenger.getBukkitEntity().getVehicle();
 +            Entity orig = craft == null ? null : craft.getHandle();
-+            if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) {
++            if (this.getBukkitEntity() instanceof Vehicle && passenger.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
 +                VehicleExitEvent event = new VehicleExitEvent(
 +                        (Vehicle) this.getBukkitEntity(),
-+                        (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation
++                        (org.bukkit.entity.LivingEntity) passenger.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation
 +                );
 +                // Suppress during worldgen
 +                if (this.valid) {
 +                    Bukkit.getPluginManager().callEvent(event);
 +                }
-+                CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle();
++                CraftEntity craftn = (CraftEntity) passenger.getBukkitEntity().getVehicle();
 +                Entity n = craftn == null ? null : craftn.getHandle();
 +                if (event.isCancelled() || n != orig) {
 +                    return false;
 +                }
 +            }
 +
-+            EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
++            EntityDismountEvent event = new EntityDismountEvent(passenger.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
 +            // Suppress during worldgen
 +            if (this.valid) {
 +                Bukkit.getPluginManager().callEvent(event);
@@ -1302,34 +1176,27 @@
 +                return false;
 +            }
 +            // CraftBukkit end
-+            if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
+             if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
                  this.passengers = ImmutableList.of();
              } else {
-                 this.passengers = (ImmutableList) this.passengers.stream().filter((entity1) -> {
--                    return entity1 != passenger;
-+                    return entity1 != entity;
-                 }).collect(ImmutableList.toImmutableList());
-             }
- 
--            passenger.boardingCooldown = 60;
--            this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
-+            entity.boardingCooldown = 60;
-+            this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity);
+@@ -2203,6 +_,7 @@
+             passenger.boardingCooldown = 60;
+             this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
          }
 +        return true; // CraftBukkit
      }
  
      protected boolean canAddPassenger(Entity passenger) {
-@@ -2464,7 +3188,7 @@
-                     if (teleporttransition != null) {
-                         ServerLevel worldserver1 = teleporttransition.newLevel();
- 
--                        if (worldserver.getServer().isLevelEnabled(worldserver1) && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1))) {
-+                        if (this instanceof ServerPlayer || (worldserver1 != null && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1)))) { // CraftBukkit - always call event for players
-                             this.teleport(teleporttransition);
+@@ -2295,7 +_,7 @@
+                     TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this);
+                     if (portalDestination != null) {
+                         ServerLevel level = portalDestination.newLevel();
+-                        if (serverLevel.getServer().isLevelEnabled(level)
++                        if (this instanceof ServerPlayer // CraftBukkit - always call event for players
+                             && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level))) {
+                             this.teleport(portalDestination);
                          }
-                     }
-@@ -2547,7 +3271,7 @@
+@@ -2377,7 +_,7 @@
      }
  
      public boolean isCrouching() {
@@ -1338,7 +1205,7 @@
      }
  
      public boolean isSprinting() {
-@@ -2563,7 +3287,7 @@
+@@ -2393,7 +_,7 @@
      }
  
      public boolean isVisuallySwimming() {
@@ -1347,7 +1214,7 @@
      }
  
      public boolean isVisuallyCrawling() {
-@@ -2571,6 +3295,13 @@
+@@ -2401,6 +_,13 @@
      }
  
      public void setSwimming(boolean swimming) {
@@ -1361,7 +1228,7 @@
          this.setSharedFlag(4, swimming);
      }
  
-@@ -2609,6 +3340,7 @@
+@@ -2439,6 +_,7 @@
  
      @Nullable
      public PlayerTeam getTeam() {
@@ -1369,21 +1236,20 @@
          return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName());
      }
  
-@@ -2624,8 +3356,12 @@
-         return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false;
+@@ -2455,7 +_,11 @@
      }
  
-+    // CraftBukkit - start
      public void setInvisible(boolean invisible) {
 -        this.setSharedFlag(5, invisible);
++        // CraftBukkit - start
 +        if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag
 +            this.setSharedFlag(5, invisible);
 +        }
 +        // CraftBukkit - end
      }
  
-     public boolean getSharedFlag(int index) {
-@@ -2644,7 +3380,7 @@
+     protected boolean getSharedFlag(int flag) {
+@@ -2472,7 +_,7 @@
      }
  
      public int getMaxAirSupply() {
@@ -1392,11 +1258,11 @@
      }
  
      public int getAirSupply() {
-@@ -2652,7 +3388,18 @@
+@@ -2480,7 +_,18 @@
      }
  
      public void setAirSupply(int air) {
--        this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, air);
+-        this.entityData.set(DATA_AIR_SUPPLY_ID, air);
 +        // CraftBukkit start
 +        EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air);
 +        // Suppress during worldgen
@@ -1412,18 +1278,20 @@
      }
  
      public int getTicksFrozen() {
-@@ -2679,11 +3426,44 @@
+@@ -2506,11 +_,43 @@
  
-     public void thunderHit(ServerLevel world, LightningBolt lightning) {
+     public void thunderHit(ServerLevel level, LightningBolt lightning) {
          this.setRemainingFireTicks(this.remainingFireTicks + 1);
 +        // CraftBukkit start
 +        final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
 +        final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity();
 +        final PluginManager pluginManager = Bukkit.getPluginManager();
 +        // CraftBukkit end
-+
          if (this.remainingFireTicks == 0) {
 -            this.igniteForSeconds(8.0F);
+-        }
+-
+-        this.hurtServer(level, this.damageSources().lightningBolt(), 5.0F);
 +            // CraftBukkit start - Call a combust event when lightning strikes
 +            EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F);
 +            pluginManager.callEvent(entityCombustEvent);
@@ -1435,9 +1303,8 @@
 +            // Paper end - fix EntityCombustEvent cancellation
 +            }
 +            // CraftBukkit end
-         }
- 
--        this.hurtServer(world, this.damageSources().lightningBolt(), 5.0F);
++        }
++
 +        // CraftBukkit start
 +        if (thisBukkitEntity instanceof Hanging) {
 +            HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity);
@@ -1452,105 +1319,120 @@
 +            return;
 +        }
 +
-+        if (!this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API
++        if (!this.hurtServer(level, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API
 +            return;
 +        }
 +        // CraftBukkit end
      }
  
-     public void onAboveBubbleCol(boolean drag) {
-@@ -2713,7 +3493,7 @@
-         this.resetFallDistance();
+     public void onAboveBubbleCol(boolean downwards) {
+@@ -2636,26 +_,30 @@
+         return this.removalReason != null
+             ? String.format(
+                 Locale.ROOT,
+-                "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]",
++                "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", // Paper - add more info
+                 this.getClass().getSimpleName(),
+                 this.getName().getString(),
+                 this.id,
++                this.uuid, // Paper - add more info
+                 string,
+                 this.getX(),
+                 this.getY(),
+                 this.getZ(),
++                this.chunkPosition(), this.tickCount, this.valid, // Paper - add more info
+                 this.removalReason
+             )
+             : String.format(
+                 Locale.ROOT,
+-                "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]",
++                "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", // Paper - add more info
+                 this.getClass().getSimpleName(),
+                 this.getName().getString(),
+                 this.id,
++                this.uuid, // Paper - add more info
+                 string,
+                 this.getX(),
+                 this.getY(),
+-                this.getZ()
++                this.getZ(),
++                this.chunkPosition(), this.tickCount, this.valid // Paper - add more info
+             );
      }
  
--    public boolean killedEntity(ServerLevel world, LivingEntity other) {
-+    public boolean killedEntity(ServerLevel world, net.minecraft.world.entity.LivingEntity other) {
-         return true;
+@@ -2679,6 +_,13 @@
      }
  
-@@ -2818,7 +3598,7 @@
-     public String toString() {
-         String s = this.level() == null ? "~NULL~" : this.level().toString();
- 
--        return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ());
-+        return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info
-     }
- 
-     public final boolean isInvulnerableToBase(DamageSource damageSource) {
-@@ -2838,6 +3618,13 @@
-     }
- 
-     public void restoreFrom(Entity original) {
+     public void restoreFrom(Entity entity) {
 +        // Paper start - Forward CraftEntity in teleport command
-+        CraftEntity bukkitEntity = original.bukkitEntity;
++        CraftEntity bukkitEntity = entity.bukkitEntity;
 +        if (bukkitEntity != null) {
 +            bukkitEntity.setHandle(this);
 +            this.bukkitEntity = bukkitEntity;
 +        }
 +        // Paper end - Forward CraftEntity in teleport command
-         CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag());
- 
-         nbttagcompound.remove("Dimension");
-@@ -2850,8 +3637,57 @@
-     public Entity teleport(TeleportTransition teleportTarget) {
-         Level world = this.level();
+         CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
+         compoundTag.remove("Dimension");
+         this.load(compoundTag);
+@@ -2688,7 +_,56 @@
  
+     @Nullable
+     public Entity teleport(TeleportTransition teleportTransition) {
 +        // Paper start - Fix item duplication and teleport issues
-+        if ((!this.isAlive() || !this.valid) && (teleportTarget.newLevel() != world)) {
-+            LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.position(), new Throwable());
++        if ((!this.isAlive() || !this.valid) && (teleportTransition.newLevel() != this.level)) {
++            LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTransition.newLevel() + ":" + teleportTransition.position(), new Throwable());
 +            return null;
 +        }
 +        // Paper end - Fix item duplication and teleport issues
-         if (world instanceof ServerLevel worldserver) {
-             if (!this.isRemoved()) {
-+                // CraftBukkit start
-+                PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
-+                Vec3 velocity = absolutePosition.deltaMovement(); // Paper
-+                Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
-+                // Paper start - gateway-specific teleport event
-+                final EntityTeleportEvent teleEvent;
-+                if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
-+                    teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity));
-+                    teleEvent.callEvent();
-+                } else {
-+                    teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to);
-+                }
-+                // Paper end - gateway-specific teleport event
-+                if (teleEvent.isCancelled() || teleEvent.getTo() == null) {
-+                    return null;
-+                }
-+                if (!to.equals(teleEvent.getTo())) {
-+                    to = teleEvent.getTo();
-+                    teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause());
-+                    // Paper start - Call EntityPortalExitEvent
-+                    velocity = Vec3.ZERO;
-+                }
-+                if (this.portalProcess != null) { // if in a portal
-+                    CraftEntity bukkitEntity = this.getBukkitEntity();
-+                    org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
-+                        bukkitEntity,
-+                        bukkitEntity.getLocation(), to.clone(),
-+                        bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity)
-+                    );
-+                    event.callEvent();
+         if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) {
++            // CraftBukkit start
++            PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
++            Vec3 velocity = absolutePosition.deltaMovement(); // Paper
++            Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTransition.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
++            // Paper start - gateway-specific teleport event
++            final EntityTeleportEvent teleEvent;
++            if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
++                teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity));
++                teleEvent.callEvent();
++            } else {
++                teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to);
++            }
++            // Paper end - gateway-specific teleport event
++            if (teleEvent.isCancelled() || teleEvent.getTo() == null) {
++                return null;
++            }
++            if (!to.equals(teleEvent.getTo())) {
++                to = teleEvent.getTo();
++                teleportTransition = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
++                // Paper start - Call EntityPortalExitEvent
++                velocity = Vec3.ZERO;
++            }
++            if (this.portalProcess != null) { // if in a portal
++                CraftEntity bukkitEntity = this.getBukkitEntity();
++                org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
++                    bukkitEntity,
++                    bukkitEntity.getLocation(), to.clone(),
++                    bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity)
++                );
++                event.callEvent();
 +
-+                    // Only change the target if actually needed, since we reset relative flags
-+                    if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) {
-+                        to = event.getTo().clone();
-+                        velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
-+                        teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause());
-+                    }
++                // Only change the target if actually needed, since we reset relative flags
++                if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) {
++                    to = event.getTo().clone();
++                    velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
++                    teleportTransition = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
 +                }
-+                if (this.isRemoved()) {
-+                    return null;
-+                }
-+                // Paper end - Call EntityPortalExitEvent
-+                // CraftBukkit end
-                 ServerLevel worldserver1 = teleportTarget.newLevel();
-                 boolean flag = worldserver1.dimension() != worldserver.dimension();
- 
-@@ -2918,10 +3754,19 @@
-             gameprofilerfiller.pop();
++            }
++            if (this.isRemoved()) {
++                return null;
++            }
++            // Paper end - Call EntityPortalExitEvent
++            // CraftBukkit end
+             ServerLevel level = teleportTransition.newLevel();
+             boolean flag = level.dimension() != serverLevel.dimension();
+             if (!teleportTransition.asPassenger()) {
+@@ -2737,10 +_,19 @@
+             profilerFiller.pop();
              return null;
          } else {
 +            // Paper start - Fix item duplication and teleport issues
@@ -1558,28 +1440,19 @@
 +                leashable.dropLeash(); // Paper drop lead
 +            }
 +            // Paper end - Fix item duplication and teleport issues
-             entity.restoreFrom(this);
+             entityx.restoreFrom(this);
              this.removeAfterChangingDimensions();
 +            // CraftBukkit start - Forward the CraftEntity to the new entity
 +            //this.getBukkitEntity().setHandle(entity);
 +            //entity.bukkitEntity = this.getBukkitEntity(); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom
 +            // CraftBukkit end
-             entity.teleportSetPosition(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
--            world.addDuringTeleport(entity);
-+            if (this.inWorld) world.addDuringTeleport(entity); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned
-             Iterator iterator1 = list1.iterator();
+             entityx.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
+-            level.addDuringTeleport(entityx);
++            if (this.inWorld) level.addDuringTeleport(entityx); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned
  
-             while (iterator1.hasNext()) {
-@@ -2947,7 +3792,7 @@
-     }
- 
-     private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTarget) {
--        LivingEntity entityliving = this.getControllingPassenger();
-+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
-         Iterator iterator = this.getIndirectPassengers().iterator();
- 
-         while (iterator.hasNext()) {
-@@ -2995,9 +3840,17 @@
+             for (Entity entity2 : list) {
+                 entity2.startRiding(entityx, true);
+@@ -2814,9 +_,17 @@
      }
  
      protected void removeAfterChangingDimensions() {
@@ -1598,13 +1471,12 @@
 +            }
 +            // Paper end - Expand EntityUnleashEvent
          }
+     }
  
+@@ -2824,11 +_,34 @@
+         return PortalShape.getRelativePosition(portal, axis, this.position(), this.getDimensions(this.getPose()));
      }
-@@ -3005,12 +3858,35 @@
-     public Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) {
-         return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose()));
-     }
-+
+ 
 +    // CraftBukkit start
 +    public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
 +        org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
@@ -1626,103 +1498,75 @@
 +        return new CraftPortalEvent(event);
 +    }
 +    // CraftBukkit end
- 
-     public boolean canUsePortal(boolean allowVehicles) {
-         return (allowVehicles || !this.isPassenger()) && this.isAlive();
-     }
- 
-     public boolean canTeleport(Level from, Level to) {
-+        if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues
-         if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) {
-             Iterator iterator = this.getPassengers().iterator();
- 
-@@ -3134,10 +4010,16 @@
-         return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE);
-     }
- 
--    public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) {
--        float f2 = Mth.clamp(pitch, -90.0F, 90.0F);
--        Entity entity = this.teleport(new TeleportTransition(world, new Vec3(destX, destY, destZ), Vec3.ZERO, yaw, f2, flags, TeleportTransition.DO_NOTHING));
-+    // CraftBukkit start
-+    public final boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) {
-+        return this.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-+    }
- 
-+    public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<Relative> set, float f, float f1, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
-+        float f2 = Mth.clamp(f1, -90.0F, 90.0F);
-+        Entity entity = this.teleport(new TeleportTransition(worldserver, new Vec3(d0, d1, d2), Vec3.ZERO, f, f2, set, TeleportTransition.DO_NOTHING, cause));
-+        // CraftBukkit end
 +
+     public boolean canUsePortal(boolean allowPassengers) {
+         return (allowPassengers || !this.isPassenger()) && this.isAlive();
+     }
+ 
+     public boolean canTeleport(Level fromLevel, Level toLevel) {
++        if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues
+         if (fromLevel.dimension() == Level.END && toLevel.dimension() == Level.OVERWORLD) {
+             for (Entity entity : this.getPassengers()) {
+                 if (entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) {
+@@ -2936,9 +_,14 @@
+         return this.entityData.get(DATA_CUSTOM_NAME_VISIBLE);
+     }
+ 
+-    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
++    // CraftBukkit start
++    public final boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
++        return this.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN);
++    }
++    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++        // CraftBukkit end
+         float f = Mth.clamp(pitch, -90.0F, 90.0F);
+-        Entity entity = this.teleport(new TeleportTransition(level, new Vec3(x, y, z), Vec3.ZERO, yaw, f, relativeMovements, TeleportTransition.DO_NOTHING));
++        Entity entity = this.teleport(new TeleportTransition(level, new Vec3(x, y, z), Vec3.ZERO, yaw, f, relativeMovements, TeleportTransition.DO_NOTHING, cause)); // CraftBukkit
          return entity != null;
      }
  
-@@ -3187,7 +4069,7 @@
-     /** @deprecated */
-     @Deprecated
-     protected void fixupDimensions() {
--        Pose entitypose = this.getPose();
-+        net.minecraft.world.entity.Pose entitypose = this.getPose();
-         EntityDimensions entitysize = this.getDimensions(entitypose);
- 
-         this.dimensions = entitysize;
-@@ -3196,7 +4078,7 @@
- 
-     public void refreshDimensions() {
-         EntityDimensions entitysize = this.dimensions;
--        Pose entitypose = this.getPose();
-+        net.minecraft.world.entity.Pose entitypose = this.getPose();
-         EntityDimensions entitysize1 = this.getDimensions(entitypose);
- 
-         this.dimensions = entitysize1;
-@@ -3258,10 +4140,29 @@
+@@ -3052,7 +_,26 @@
      }
  
-     public final void setBoundingBox(AABB boundingBox) {
--        this.bb = boundingBox;
+     public final void setBoundingBox(AABB bb) {
+-        this.bb = bb;
 +        // CraftBukkit start - block invalid bounding boxes
-+        double minX = boundingBox.minX,
-+                minY = boundingBox.minY,
-+                minZ = boundingBox.minZ,
-+                maxX = boundingBox.maxX,
-+                maxY = boundingBox.maxY,
-+                maxZ = boundingBox.maxZ;
-+        double len = boundingBox.maxX - boundingBox.minX;
++        double minX = bb.minX,
++                minY = bb.minY,
++                minZ = bb.minZ,
++                maxX = bb.maxX,
++                maxY = bb.maxY,
++                maxZ = bb.maxZ;
++        double len = bb.maxX - bb.minX;
 +        if (len < 0) maxX = minX;
 +        if (len > 64) maxX = minX + 64.0;
 +
-+        len = boundingBox.maxY - boundingBox.minY;
++        len = bb.maxY - bb.minY;
 +        if (len < 0) maxY = minY;
 +        if (len > 64) maxY = minY + 64.0;
 +
-+        len = boundingBox.maxZ - boundingBox.minZ;
++        len = bb.maxZ - bb.minZ;
 +        if (len < 0) maxZ = minZ;
 +        if (len > 64) maxZ = minZ + 64.0;
 +        this.bb = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
 +        // CraftBukkit end
      }
  
--    public final float getEyeHeight(Pose pose) {
-+    public final float getEyeHeight(net.minecraft.world.entity.Pose pose) {
-         return this.getDimensions(pose).eyeHeight();
+     public final float getEyeHeight(Pose pose) {
+@@ -3096,6 +_,12 @@
      }
  
-@@ -3300,7 +4201,14 @@
- 
-     public void startSeenByPlayer(ServerPlayer player) {}
- 
--    public void stopSeenByPlayer(ServerPlayer player) {}
-+    // Paper start - entity tracking events
-+    public void stopSeenByPlayer(ServerPlayer player) {
+     public void stopSeenByPlayer(ServerPlayer serverPlayer) {
++        // Paper start - entity tracking events
 +        // Since this event cannot be cancelled, we should call it here to catch all "un-tracks"
 +        if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+            new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent();
++            new io.papermc.paper.event.player.PlayerUntrackEntityEvent(serverPlayer.getBukkitEntity(), this.getBukkitEntity()).callEvent();
 +        }
-+    }
-+    // Paper end - entity tracking events
++        // Paper end - entity tracking events
+     }
  
-     public float rotate(Rotation rotation) {
-         float f = Mth.wrapDegrees(this.getYRot());
-@@ -3335,7 +4243,7 @@
+     public float rotate(Rotation transformRotation) {
+@@ -3129,7 +_,7 @@
      }
  
      @Nullable
@@ -1731,7 +1575,7 @@
          return null;
      }
  
-@@ -3373,20 +4281,34 @@
+@@ -3161,21 +_,32 @@
      }
  
      private Stream<Entity> getIndirectPassengersStream() {
@@ -1752,6 +1596,7 @@
      }
  
      public Iterable<Entity> getIndirectPassengers() {
+-        return () -> this.getIndirectPassengersStream().iterator();
 +        // Paper start - Optimize indirect passenger iteration
 +        if (this.passengers.isEmpty()) { return ImmutableList.of(); }
 +        ImmutableList.Builder<Entity> indirectPassengers = ImmutableList.builder();
@@ -1760,13 +1605,11 @@
 +            indirectPassengers.addAll(passenger.getIndirectPassengers());
 +        }
 +        return indirectPassengers.build();
-+    }
-+    private Iterable<Entity> getIndirectPassengers_old() {
 +        // Paper end - Optimize indirect passenger iteration
-         return () -> {
-             return this.getIndirectPassengersStream().iterator();
-         };
-@@ -3399,6 +4321,7 @@
+     }
+ 
+     public int countPlayerPassengers() {
+@@ -3183,6 +_,7 @@
      }
  
      public boolean hasExactlyOnePlayerPassenger() {
@@ -1774,45 +1617,17 @@
          return this.countPlayerPassengers() == 1;
      }
  
-@@ -3435,7 +4358,7 @@
-     }
- 
-     public boolean isControlledByLocalInstance() {
--        LivingEntity entityliving = this.getControllingPassenger();
-+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
- 
-         if (entityliving instanceof Player entityhuman) {
-             return entityhuman.isLocalPlayer();
-@@ -3445,7 +4368,7 @@
-     }
- 
-     public boolean isControlledByClient() {
--        LivingEntity entityliving = this.getControllingPassenger();
-+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
- 
-         return entityliving != null && entityliving.isControlledByClient();
-     }
-@@ -3463,7 +4386,7 @@
-         return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3);
-     }
- 
--    public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
-+    public Vec3 getDismountLocationForPassenger(net.minecraft.world.entity.LivingEntity passenger) {
-         return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ());
-     }
- 
-@@ -3488,9 +4411,38 @@
-     public int getFireImmuneTicks() {
+@@ -3260,9 +_,38 @@
          return 1;
      }
-+
+ 
 +    // CraftBukkit start
 +    private final CommandSource commandSource = new CommandSource() {
 +
 +        @Override
 +        public void sendSystemMessage(Component message) {
 +        }
- 
++
 +        @Override
 +        public CommandSender getBukkitSender(CommandSourceStack wrapper) {
 +            return Entity.this.getBukkitEntity();
@@ -1835,44 +1650,35 @@
 +    };
 +    // CraftBukkit end
 +
-     public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel world) {
--        return new CommandSourceStack(CommandSource.NULL, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this);
-+        return new CommandSourceStack(this.commandSource, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this); // CraftBukkit
+     public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel level) {
+         return new CommandSourceStack(
+-            CommandSource.NULL, this.position(), this.getRotationVector(), level, 0, this.getName().getString(), this.getDisplayName(), level.getServer(), this
++            this.commandSource, this.position(), this.getRotationVector(), level, 0, this.getName().getString(), this.getDisplayName(), level.getServer(), this // CraftBukkit
+         );
      }
  
-     public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) {
-@@ -3551,6 +4503,11 @@
-                                     vec3d = vec3d.add(vec3d1);
-                                     ++k1;
+@@ -3320,6 +_,11 @@
+                                     vec3 = vec3.add(flow);
+                                     i++;
                                  }
 +                                // CraftBukkit start - store last lava contact location
-+                                if (tag == FluidTags.LAVA) {
-+                                    this.lastLavaContact = blockposition_mutableblockposition.immutable();
++                                if (fluidTag == FluidTags.LAVA) {
++                                    this.lastLavaContact = mutableBlockPos.immutable();
 +                                }
 +                                // CraftBukkit end
                              }
                          }
                      }
-@@ -3613,7 +4570,7 @@
-         return new ClientboundAddEntityPacket(this, entityTrackerEntry);
+@@ -3480,7 +_,39 @@
+         return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale);
      }
  
--    public EntityDimensions getDimensions(Pose pose) {
-+    public EntityDimensions getDimensions(net.minecraft.world.entity.Pose pose) {
-         return this.type.getDimensions();
-     }
- 
-@@ -3713,8 +4670,40 @@
-     public double getRandomZ(double widthScale) {
-         return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale);
-     }
-+
 +    // Paper start - Block invalid positions and bounding box
 +    public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) {
 +        if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) {
 +            return true;
 +        }
- 
++
 +        String entityInfo;
 +        try {
 +            entityInfo = entity.toString();
@@ -1902,11 +1708,11 @@
 +        // Paper end - Fix MC-4
          if (this.position.x != x || this.position.y != y || this.position.z != z) {
              this.position = new Vec3(x, y, z);
-             int i = Mth.floor(x);
-@@ -3732,6 +4721,12 @@
+             int floor = Mth.floor(x);
+@@ -3496,6 +_,12 @@
+ 
              this.levelCallback.onMove();
          }
- 
 +        // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB
 +        // hanging has its own special logic
 +        if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) {
@@ -1915,43 +1721,36 @@
 +        // Paper end - Block invalid positions and bounding box
      }
  
-     public void checkDespawn() {}
-@@ -3818,8 +4813,17 @@
+     public void checkDespawn() {
+@@ -3583,6 +_,15 @@
  
      @Override
-     public final void setRemoved(Entity.RemovalReason reason) {
+     public final void setRemoved(Entity.RemovalReason removalReason) {
 +        // CraftBukkit start - add Bukkit remove cause
-+        this.setRemoved(reason, null);
++        this.setRemoved(removalReason, null);
 +    }
 +
 +    @Override
-+    public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    public final void setRemoved(Entity.RemovalReason removalReason, EntityRemoveEvent.Cause cause) {
 +        CraftEventFactory.callEntityRemoveEvent(this, cause);
 +        // CraftBukkit end
 +        final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
          if (this.removalReason == null) {
--            this.removalReason = reason;
-+            this.removalReason = entity_removalreason;
+             this.removalReason = removalReason;
          }
- 
-         if (this.removalReason.shouldDestroy()) {
-@@ -3827,14 +4831,30 @@
-         }
- 
+@@ -3594,12 +_,28 @@
          this.getPassengers().forEach(Entity::stopRiding);
--        this.levelCallback.onRemove(reason);
--        this.onRemoval(reason);
-+        this.levelCallback.onRemove(entity_removalreason);
-+        this.onRemoval(entity_removalreason);
+         this.levelCallback.onRemove(removalReason);
+         this.onRemoval(removalReason);
 +        // Paper start - Folia schedulers
-+        if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
++        if (!(this instanceof ServerPlayer) && removalReason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
 +            // Players need to be special cased, because they are regularly removed from the world
 +            this.retireScheduler();
 +        }
 +        // Paper end - Folia schedulers
      }
  
-     public void unsetRemoved() {
+     protected void unsetRemoved() {
          this.removalReason = null;
      }
  
@@ -1966,20 +1765,11 @@
 +    // Paper end - Folia schedulers
 +
      @Override
-     public void setLevelCallback(EntityInLevelCallback changeListener) {
-         this.levelCallback = changeListener;
-@@ -3887,7 +4907,7 @@
-     }
- 
-     public Vec3 getKnownMovement() {
--        LivingEntity entityliving = this.getControllingPassenger();
-+        net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger();
- 
-         if (entityliving instanceof Player entityhuman) {
-             if (this.isAlive()) {
-@@ -3962,4 +4982,14 @@
- 
-         void accept(Entity entity, double x, double y, double z);
+     public void setLevelCallback(EntityInLevelCallback levelCallback) {
+         this.levelCallback = levelCallback;
+@@ -3723,4 +_,14 @@
+             return this.save;
+         }
      }
 +
 +    // Paper start - Expose entity id counter
@@ -1988,7 +1778,7 @@
 +    }
 +
 +    public boolean isTicking() {
-+        return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition());
++        return ((ServerLevel) this.level).isPositionEntityTicking(this.blockPosition());
 +    }
 +    // Paper end - Expose entity id counter
  }

From 07642b457ecccf71d22f6f2c5fd06da599bb2073 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 12:36:08 +0100
Subject: [PATCH 047/285] More entity classes

---
 .../world/entity/LightningBolt.java.patch     | 138 +++++
 .../net/minecraft/world/entity/Mob.java.patch | 428 ++++++++++++++++
 .../world/entity/NeutralMob.java.patch        |  69 +++
 .../entity/OminousItemSpawner.java.patch      |  20 +-
 .../world/entity/Shearable.java.patch         |   7 +-
 .../world/entity/TamableAnimal.java.patch     |  82 +++
 .../world/entity/LightningBolt.java.patch     | 160 ------
 .../net/minecraft/world/entity/Mob.java.patch | 472 ------------------
 .../world/entity/NeutralMob.java.patch        |  86 ----
 .../world/entity/PathfinderMob.java.patch     |  12 -
 .../world/entity/TamableAnimal.java.patch     |  85 ----
 11 files changed, 731 insertions(+), 828 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/OminousItemSpawner.java.patch (70%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/Shearable.java.patch (65%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/LightningBolt.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/Mob.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/NeutralMob.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/PathfinderMob.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/TamableAnimal.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
new file mode 100644
index 0000000000..632c00d653
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
@@ -0,0 +1,138 @@
+--- a/net/minecraft/world/entity/LightningBolt.java
++++ b/net/minecraft/world/entity/LightningBolt.java
+@@ -39,6 +_,7 @@
+     private ServerPlayer cause;
+     private final Set<Entity> hitEntities = Sets.newHashSet();
+     private int blocksSetOnFire;
++    public boolean isEffect; // Paper - Properly handle lightning effects api
+ 
+     public LightningBolt(EntityType<? extends LightningBolt> entityType, Level level) {
+         super(entityType, level);
+@@ -76,7 +_,7 @@
+     @Override
+     public void tick() {
+         super.tick();
+-        if (this.life == 2) {
++        if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api
+             if (this.level().isClientSide()) {
+                 this.level()
+                     .playLocalSound(
+@@ -107,7 +_,7 @@
+                 }
+ 
+                 this.powerLightningRod();
+-                clearCopperOnLightningStrike(this.level(), this.getStrikePosition());
++                clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent
+                 this.gameEvent(GameEvent.LIGHTNING_STRIKE);
+             }
+         }
+@@ -130,7 +_,7 @@
+                     }
+                 }
+ 
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             } else if (this.life < -this.random.nextInt(10)) {
+                 this.flashes--;
+                 this.life = 1;
+@@ -139,10 +_,10 @@
+             }
+         }
+ 
+-        if (this.life >= 0) {
++        if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api
+             if (!(this.level() instanceof ServerLevel)) {
+                 this.level().setSkyFlashTime(2);
+-            } else if (!this.visualOnly) {
++            } else if (!this.visualOnly && !this.isEffect) {  // Paper - Properly handle lightning effects api
+                 List<Entity> entities = this.level()
+                     .getEntities(
+                         this,
+@@ -172,22 +_,30 @@
+             BlockPos blockPos = this.blockPosition();
+             BlockState state = BaseFireBlock.getState(this.level(), blockPos);
+             if (this.level().getBlockState(blockPos).isAir() && state.canSurvive(this.level(), blockPos)) {
+-                this.level().setBlockAndUpdate(blockPos, state);
+-                this.blocksSetOnFire++;
++                // CraftBukkit start - add "!visualOnly"
++                if (!this.visualOnly && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockPos, this).isCancelled()) {
++                    this.level().setBlockAndUpdate(blockPos, state);
++                    this.blocksSetOnFire++;
++                }
++                // CraftBukkit end
+             }
+ 
+             for (int i = 0; i < extraIgnitions; i++) {
+                 BlockPos blockPos1 = blockPos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
+                 state = BaseFireBlock.getState(this.level(), blockPos1);
+                 if (this.level().getBlockState(blockPos1).isAir() && state.canSurvive(this.level(), blockPos1)) {
+-                    this.level().setBlockAndUpdate(blockPos1, state);
+-                    this.blocksSetOnFire++;
++                    // CraftBukkit start - add "!visualOnly"
++                    if (!this.visualOnly && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockPos1, this).isCancelled()) {
++                        this.level().setBlockAndUpdate(blockPos1, state);
++                        this.blocksSetOnFire++;
++                    }
++                    // CraftBukkit end
+                 }
+             }
+         }
+     }
+ 
+-    private static void clearCopperOnLightningStrike(Level level, BlockPos pos) {
++    private static void clearCopperOnLightningStrike(Level level, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent
+         BlockState blockState = level.getBlockState(pos);
+         BlockPos blockPos;
+         BlockState blockState1;
+@@ -200,22 +_,27 @@
+         }
+ 
+         if (blockState1.getBlock() instanceof WeatheringCopper) {
+-            level.setBlockAndUpdate(blockPos, WeatheringCopper.getFirst(level.getBlockState(blockPos)));
++            // Paper start - Call EntityChangeBlockEvent
++            BlockState newBlockState = WeatheringCopper.getFirst(level.getBlockState(blockPos));
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(lightning, blockPos, newBlockState)) {
++                level.setBlockAndUpdate(blockPos, newBlockState);
++            }
++            // Paper end - Call EntityChangeBlockEvent
+             BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
+             int i = level.random.nextInt(3) + 3;
+ 
+             for (int i1 = 0; i1 < i; i1++) {
+                 int i2 = level.random.nextInt(8) + 1;
+-                randomWalkCleaningCopper(level, blockPos, mutableBlockPos, i2);
++                randomWalkCleaningCopper(level, blockPos, mutableBlockPos, i2, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
+             }
+         }
+     }
+ 
+-    private static void randomWalkCleaningCopper(Level level, BlockPos pos, BlockPos.MutableBlockPos mutable, int steps) {
++    private static void randomWalkCleaningCopper(Level level, BlockPos pos, BlockPos.MutableBlockPos mutable, int steps, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
+         mutable.set(pos);
+ 
+         for (int i = 0; i < steps; i++) {
+-            Optional<BlockPos> optional = randomStepCleaningCopper(level, mutable);
++            Optional<BlockPos> optional = randomStepCleaningCopper(level, mutable, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
+             if (optional.isEmpty()) {
+                 break;
+             }
+@@ -224,11 +_,17 @@
+         }
+     }
+ 
+-    private static Optional<BlockPos> randomStepCleaningCopper(Level level, BlockPos pos) {
++    private static Optional<BlockPos> randomStepCleaningCopper(Level level, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
+         for (BlockPos blockPos : BlockPos.randomInCube(level.random, 10, pos, 1)) {
+             BlockState blockState = level.getBlockState(blockPos);
+             if (blockState.getBlock() instanceof WeatheringCopper) {
+-                WeatheringCopper.getPrevious(blockState).ifPresent(blockState1 -> level.setBlockAndUpdate(blockPos, blockState1));
++                // Paper start - call EntityChangeBlockEvent
++                WeatheringCopper.getPrevious(blockState).ifPresent(blockState1 -> {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(lightning, blockPos, blockState1)) {
++                        level.setBlockAndUpdate(blockPos, blockState1);
++                    }
++                });
++                // Paper end - call EntityChangeBlockEvent
+                 level.levelEvent(3002, blockPos, -1);
+                 return Optional.of(blockPos);
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
new file mode 100644
index 0000000000..3670fec552
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
@@ -0,0 +1,428 @@
+--- a/net/minecraft/world/entity/Mob.java
++++ b/net/minecraft/world/entity/Mob.java
+@@ -84,6 +_,18 @@
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
+ import net.minecraft.world.phys.AABB;
+ 
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityRemoveEvent;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++import org.bukkit.event.entity.EntityUnleashEvent;
++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
++// CraftBukkit end
++
+ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting {
+     private static final EntityDataAccessor<Byte> DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE);
+     private static final int MOB_FLAG_NO_AI = 1;
+@@ -112,6 +_,7 @@
+     private final BodyRotationControl bodyRotationControl;
+     protected PathNavigation navigation;
+     public GoalSelector goalSelector;
++    @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float
+     public GoalSelector targetSelector;
+     @Nullable
+     private LivingEntity target;
+@@ -131,6 +_,7 @@
+     private Leashable.LeashData leashData;
+     private BlockPos restrictCenter = BlockPos.ZERO;
+     private float restrictRadius = -1.0F;
++    public boolean aware = true; // CraftBukkit
+ 
+     protected Mob(EntityType<? extends Mob> entityType, Level level) {
+         super(entityType, level);
+@@ -150,6 +_,12 @@
+         }
+     }
+ 
++    // CraftBukkit start
++    public void setPersistenceRequired(boolean persistenceRequired) {
++        this.persistenceRequired = persistenceRequired;
++    }
++    // CraftBukkit end
++
+     protected void registerGoals() {
+     }
+ 
+@@ -230,7 +_,40 @@
+     }
+ 
+     public void setTarget(@Nullable LivingEntity target) {
++        // CraftBukkit start - fire event
++        this.setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true);
++    }
++
++    public boolean setTarget(LivingEntity target, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++        if (this.getTarget() == target) {
++            return false;
++        }
++        if (fireEvent) {
++            if (reason == EntityTargetEvent.TargetReason.UNKNOWN && this.getTarget() != null && target == null) {
++                reason = this.getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED;
++            }
++            if (reason == EntityTargetEvent.TargetReason.UNKNOWN) {
++                this.level().getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception());
++            }
++            CraftLivingEntity ctarget = null;
++            if (target != null) {
++                ctarget = (CraftLivingEntity) target.getBukkitEntity();
++            }
++            EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason);
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++            if (event.isCancelled()) {
++                return false;
++            }
++
++            if (event.getTarget() != null) {
++                target = ((CraftLivingEntity) event.getTarget()).getHandle();
++            } else {
++                target = null;
++            }
++        }
+         this.target = target;
++        return true;
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -354,6 +_,12 @@
+         return null;
+     }
+ 
++    // CraftBukkit start - Add delegate method
++    public SoundEvent getAmbientSound0() {
++        return this.getAmbientSound();
++    }
++    // CraftBukkit end
++
+     @Override
+     public void addAdditionalSaveData(CompoundTag compound) {
+         super.addAdditionalSaveData(compound);
+@@ -413,13 +_,25 @@
+         if (this.isNoAi()) {
+             compound.putBoolean("NoAI", this.isNoAi());
+         }
++        compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+     }
+ 
+     @Override
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+-        this.setCanPickUpLoot(compound.getBoolean("CanPickUpLoot"));
+-        this.persistenceRequired = compound.getBoolean("PersistenceRequired");
++        // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it
++        if (compound.contains("CanPickUpLoot", 99)) {
++            boolean data = compound.getBoolean("CanPickUpLoot");
++            if (isLevelAtLeast(compound, 1) || data) {
++                this.setCanPickUpLoot(data);
++            }
++        }
++
++        boolean data = compound.getBoolean("PersistenceRequired");
++        if (isLevelAtLeast(compound, 1) || data) {
++            this.persistenceRequired = data;
++        }
++        // CraftBukkit end
+         if (compound.contains("ArmorItems", 9)) {
+             ListTag list = compound.getList("ArmorItems", 10);
+ 
+@@ -472,13 +_,18 @@
+         this.readLeashData(compound);
+         this.setLeftHanded(compound.getBoolean("LeftHanded"));
+         if (compound.contains("DeathLootTable", 8)) {
+-            this.lootTable = Optional.of(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(compound.getString("DeathLootTable"))));
++            this.lootTable = Optional.ofNullable(ResourceLocation.tryParse(compound.getString("DeathLootTable"))).map((rs) -> ResourceKey.create(Registries.LOOT_TABLE, rs)); // Paper - Validate ResourceLocation
+         } else {
+             this.lootTable = Optional.empty();
+         }
+ 
+         this.lootTableSeed = compound.getLong("DeathLootTableSeed");
+         this.setNoAi(compound.getBoolean("NoAI"));
++        // CraftBukkit start
++        if (compound.contains("Bukkit.Aware")) {
++            this.aware = compound.getBoolean("Bukkit.Aware");
++        }
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -540,6 +_,11 @@
+                     && !itemEntity.getItem().isEmpty()
+                     && !itemEntity.hasPickUpDelay()
+                     && this.wantsToPickUp(serverLevel, itemEntity.getItem())) {
++                    // Paper start - Item#canEntityPickup
++                    if (!itemEntity.canMobPickup) {
++                        continue;
++                    }
++                    // Paper end - Item#canEntityPickup
+                     this.pickUpItem(serverLevel, itemEntity);
+                 }
+             }
+@@ -554,18 +_,24 @@
+ 
+     protected void pickUpItem(ServerLevel level, ItemEntity entity) {
+         ItemStack item = entity.getItem();
+-        ItemStack itemStack = this.equipItemIfPossible(level, item.copy());
++        ItemStack itemStack = this.equipItemIfPossible(level, item.copy(), entity); // CraftBukkit - add item
+         if (!itemStack.isEmpty()) {
+             this.onItemPickup(entity);
+             this.take(entity, itemStack.getCount());
+             item.shrink(itemStack.getCount());
+             if (item.isEmpty()) {
+-                entity.discard();
++                entity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+ 
+     public ItemStack equipItemIfPossible(ServerLevel level, ItemStack stack) {
++        // CraftBukkit start - add item
++        return this.equipItemIfPossible(level, stack, null);
++    }
++
++    public ItemStack equipItemIfPossible(ServerLevel level, ItemStack stack, ItemEntity entity) {
++        // CraftBukkit end
+         EquipmentSlot equipmentSlotForItem = this.getEquipmentSlotForItem(stack);
+         ItemStack itemBySlot = this.getItemBySlot(equipmentSlotForItem);
+         boolean canReplaceCurrentItem = this.canReplaceCurrentItem(stack, itemBySlot, equipmentSlotForItem);
+@@ -575,10 +_,18 @@
+             canReplaceCurrentItem = itemBySlot.isEmpty();
+         }
+ 
+-        if (canReplaceCurrentItem && this.canHoldItem(stack)) {
++        // CraftBukkit start
++        boolean canPickup = canReplaceCurrentItem && this.canHoldItem(stack);
++        if (entity != null) {
++            canPickup = !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, !canPickup).isCancelled();
++        }
++        if (canPickup) {
++            // CraftBukkit end
+             double d = this.getEquipmentDropChance(equipmentSlotForItem);
+             if (!itemBySlot.isEmpty() && Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d) {
++                this.forceDrops = true; // CraftBukkit
+                 this.spawnAtLocation(level, itemBySlot);
++                this.forceDrops = false; // CraftBukkit
+             }
+ 
+             ItemStack itemStack = equipmentSlotForItem.limit(stack);
+@@ -703,22 +_,29 @@
+     @Override
+     public void checkDespawn() {
+         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
+-            this.discard();
++            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
+-            Entity nearestPlayer = this.level().getNearestPlayer(this, -1.0);
++            Entity nearestPlayer = this.level().getNearestPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
+             if (nearestPlayer != null) {
+-                double d = nearestPlayer.distanceToSqr(this);
+-                int despawnDistance = this.getType().getCategory().getDespawnDistance();
+-                int i = despawnDistance * despawnDistance;
+-                if (d > i && this.removeWhenFarAway(d)) {
+-                    this.discard();
+-                }
++                // Paper start - Configurable despawn distances
++                final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory());
++                final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape;
++                final double dy = Math.abs(nearestPlayer.getY() - this.getY());
++                final double dySqr = Math.pow(dy, 2);
++                final double dxSqr = Math.pow(nearestPlayer.getX() - this.getX(), 2);
++                final double dzSqr = Math.pow(nearestPlayer.getZ() - this.getZ(), 2);
++                final double distanceSquared = dxSqr + dzSqr + dySqr;
++                // Despawn if hard/soft limit is exceeded
++                if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) {
++                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++                 }
+ 
+-                int noDespawnDistance = this.getType().getCategory().getNoDespawnDistance();
+-                int i1 = noDespawnDistance * noDespawnDistance;
+-                if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d > i1 && this.removeWhenFarAway(d)) {
+-                    this.discard();
+-                } else if (d < i1) {
++                if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) {
++                    if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) {
++                        this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++                    }
++                } else {
++                    // Paper end - Configurable despawn distances
+                     this.noActionTime = 0;
+                 }
+             }
+@@ -730,6 +_,15 @@
+     @Override
+     protected final void serverAiStep() {
+         this.noActionTime++;
++        // Paper start - Allow nerfed mobs to jump and float
++        if (!this.aware) {
++            if (goalFloat != null) {
++                if (goalFloat.canUse()) goalFloat.tick();
++                this.getJumpControl().tick();
++            }
++            return;
++        }
++        // Paper end - Allow nerfed mobs to jump and float
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("sensing");
+         this.sensing.tick();
+@@ -908,26 +_,40 @@
+ 
+     @Override
+     public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
++        // Paper start - Fix silent equipment change
++        setItemSlot(slot, stack, false);
++    }
++
++    @Override
++    public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) {
++        // Paper end - Fix silent equipment change
+         this.verifyEquippedItem(stack);
+         switch (slot.getType()) {
+             case HAND:
+-                this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack);
++                this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change
+                 break;
+             case HUMANOID_ARMOR:
+-                this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack);
++                this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change
+                 break;
+             case ANIMAL_ARMOR:
+                 ItemStack itemStack = this.bodyArmorItem;
+                 this.bodyArmorItem = stack;
+-                this.onEquipItem(slot, itemStack, stack);
++                this.onEquipItem(slot, itemStack, stack, silent); // Paper - Fix silent equipment change
+         }
+     }
++
++    // Paper start
++    protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox)
++        return false;
++    }
++    // Paper end
+ 
+     @Override
+     protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
+         super.dropCustomDeathLoot(level, damageSource, recentlyHit);
+ 
+         for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
++            if (this.shouldSkipLoot(equipmentSlot)) continue; // Paper
+             ItemStack itemBySlot = this.getItemBySlot(equipmentSlot);
+             float equipmentDropChance = this.getEquipmentDropChance(equipmentSlot);
+             if (equipmentDropChance != 0.0F) {
+@@ -951,7 +_,13 @@
+                     }
+ 
+                     this.spawnAtLocation(level, itemBySlot);
++                    if (this.clearEquipmentSlots) { // Paper
+                     this.setItemSlot(equipmentSlot, ItemStack.EMPTY);
++                    // Paper start
++                    } else {
++                        this.clearedEquipmentSlots.add(enumitemslot);
++                    }
++                    // Paper end
+                 }
+             }
+         }
+@@ -1269,6 +_,22 @@
+     public <T extends Mob> T convertTo(
+         EntityType<T> entityType, ConversionParams conversionParams, EntitySpawnReason spawnReason, ConversionParams.AfterConversion<T> afterConversion
+     ) {
++        return this.convertTo(entityType, conversionParams, spawnReason, afterConversion, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++
++    @Nullable
++    public <T extends Mob> T convertTo(
++        EntityType<T> entityType, ConversionParams conversionParams, EntitySpawnReason spawnReason, ConversionParams.AfterConversion<T> afterConversion, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason creatureSpawnReason
++    ) {
++    // Paper start - entity zap event - allow cancellation of conversion post creation
++        return this.convertTo(entityType, conversionParams, spawnReason, e -> { afterConversion.finalizeConversion(e); return true; }, transformReason, spawnReason);
++    }
++    @Nullable
++    public <T extends Mob> T convertTo(
++        EntityType<T> entityType, ConversionParams conversionParams, EntitySpawnReason spawnReason, ConversionParams.CancellingAfterConversion<T> afterConversion, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason creatureSpawnReason
++    ) {
++        // Paper end - entity zap event - allow cancellation of conversion post creation
++        // CraftBukkit end
+         if (this.isRemoved()) {
+             return null;
+         } else {
+@@ -1277,13 +_,23 @@
+                 return null;
+             } else {
+                 conversionParams.type().convert(this, mob, conversionParams);
+-                afterConversion.finalizeConversion(mob);
++                if (!afterConversion.finalizeConversionOrCancel(mob)) return null; // Paper - entity zap event - return null if conversion was cancelled
++                // CraftBukkit start
++                if (transformReason == null) {
++                    // Special handling for slime split and pig lightning
++                    return mob;
++                }
++
++                if (CraftEventFactory.callEntityTransformEvent(this, mob, transformReason).isCancelled()) {
++                    return null;
++                }
++                // CraftBukkit end
+                 if (this.level() instanceof ServerLevel serverLevel) {
+-                    serverLevel.addFreshEntity(mob);
++                    serverLevel.addFreshEntity(mob, creatureSpawnReason); // CraftBukkit
+                 }
+ 
+                 if (conversionParams.type().shouldDiscardAfterConversion()) {
+-                    this.discard();
++                    this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
+                 }
+ 
+                 return mob;
+@@ -1293,7 +_,20 @@
+ 
+     @Nullable
+     public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams coversionParams, ConversionParams.AfterConversion<T> afterConversion) {
+-        return this.convertTo(entityType, coversionParams, EntitySpawnReason.CONVERSION, afterConversion);
++        // CraftBukkit start
++        return this.convertTo(entityType, coversionParams, afterConversion, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++
++    @Nullable
++    public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams coversionParams, ConversionParams.AfterConversion<T> afterConversion, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
++    // Paper start - entity zap event - allow cancellation of conversion post creation
++        return this.convertTo(entityType, coversionParams, e -> { afterConversion.finalizeConversion(e); return true; }, transformReason, creatureSpawnReason);
++    }
++    @Nullable
++    public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams coversionParams, ConversionParams.CancellingAfterConversion<T> afterConversion, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
++    // Paper start - entity zap event - allow cancellation of conversion post creation
++        return this.convertTo(entityType, coversionParams, EntitySpawnReason.CONVERSION, afterConversion, transformReason, creatureSpawnReason);
++        // CraftBukkit end
+     }
+ 
+     @Nullable
+@@ -1329,7 +_,17 @@
+     public boolean startRiding(Entity entity, boolean force) {
+         boolean flag = super.startRiding(entity, force);
+         if (flag && this.isLeashed()) {
+-            this.dropLeash();
++            // Paper start - Expand EntityUnleashEvent
++            EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true);
++            if (!event.callEvent()) {
++                return flag;
++            }
++            if (event.isDropLeash()) {
++                this.dropLeash();
++            } else {
++                this.removeLeash();
++            }
++            // Paper end - Expand EntityUnleashEvent
+         }
+ 
+         return flag;
+@@ -1412,7 +_,7 @@
+             float knockback = this.getKnockback(source, damageSource);
+             if (knockback > 0.0F && source instanceof LivingEntity livingEntity) {
+                 livingEntity.knockback(
+-                    knockback * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
++                    knockback * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK // CraftBukkit // Paper - knockback events
+                 );
+                 this.setDeltaMovement(this.getDeltaMovement().multiply(0.6, 1.0, 0.6));
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch
new file mode 100644
index 0000000000..06fd0f09c7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/NeutralMob.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/entity/NeutralMob.java
++++ b/net/minecraft/world/entity/NeutralMob.java
+@@ -39,18 +_,11 @@
+             } else {
+                 UUID uuid = tag.getUUID("AngryAt");
+                 this.setPersistentAngerTarget(uuid);
+-                Entity entity = ((ServerLevel)level).getEntity(uuid);
+-                if (entity != null) {
+-                    if (entity instanceof Mob mob) {
+-                        this.setTarget(mob);
+-                        this.setLastHurtByMob(mob);
+-                    }
+-
+-                    if (entity instanceof Player player) {
+-                        this.setTarget(player);
+-                        this.setLastHurtByPlayer(player);
+-                    }
+-                }
++                // Paper - Prevent entity loading causing async lookups; Moved diff to separate method
++                // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively
++                // tick the initial persistent anger.
++                // If not, let the first tick on the baseTick call the method later down the line.
++                if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(level);
+             }
+         }
+     }
+@@ -104,7 +_,7 @@
+     default void stopBeingAngry() {
+         this.setLastHurtByMob(null);
+         this.setPersistentAngerTarget(null);
+-        this.setTarget(null);
++        this.setTarget(null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
+         this.setRemainingPersistentAngerTime(0);
+     }
+ 
+@@ -117,8 +_,33 @@
+ 
+     void setTarget(@Nullable LivingEntity livingEntity);
+ 
++    boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent); // CraftBukkit
++
+     boolean canAttack(LivingEntity entity);
+ 
+     @Nullable
+     LivingEntity getTarget();
++
++    // Paper start - Prevent entity loading causing async lookups
++    // Update last hurt when ticking
++    default void tickInitialPersistentAnger(Level level) {
++        UUID uuid = getPersistentAngerTarget();
++        if (uuid == null) {
++            return;
++        }
++
++        Entity entity = ((ServerLevel)level).getEntity(uuid);
++        if (entity != null) {
++            if (entity instanceof Mob mob) {
++                this.setTarget(mob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit
++                this.setLastHurtByMob(mob);
++            }
++
++            if (entity instanceof Player player) {
++                this.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit
++                this.setLastHurtByPlayer(player);
++            }
++        }
++    }
++    // Paper end - Prevent entity loading causing async lookups
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/OminousItemSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/OminousItemSpawner.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch
index c390374ad2..10e7665ce7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/OminousItemSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/OminousItemSpawner.java.patch
@@ -1,25 +1,25 @@
 --- a/net/minecraft/world/entity/OminousItemSpawner.java
 +++ b/net/minecraft/world/entity/OminousItemSpawner.java
-@@ -76,7 +76,7 @@
-                     entity = this.spawnProjectile(serverLevel, projectileItem, itemStack);
+@@ -76,7 +_,7 @@
+                     entity = this.spawnProjectile(serverLevel, projectileItem, item);
                  } else {
-                     entity = new ItemEntity(serverLevel, this.getX(), this.getY(), this.getZ(), itemStack);
+                     entity = new ItemEntity(serverLevel, this.getX(), this.getY(), this.getZ(), item);
 -                    serverLevel.addFreshEntity(entity);
 +                    serverLevel.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.OMINOUS_ITEM_SPAWNER); // Paper - fixes and addition to spawn reason API
                  }
  
                  serverLevel.levelEvent(3021, this.blockPosition(), 1);
-@@ -90,7 +90,7 @@
-         ProjectileItem.DispenseConfig dispenseConfig = item.createDispenseConfig();
-         dispenseConfig.overrideDispenseEvent().ifPresent(dispenseEvent -> world.levelEvent(dispenseEvent, this.blockPosition(), 0));
+@@ -90,7 +_,7 @@
+         ProjectileItem.DispenseConfig dispenseConfig = projectileItem.createDispenseConfig();
+         dispenseConfig.overrideDispenseEvent().ifPresent(i -> level.levelEvent(i, this.blockPosition(), 0));
          Direction direction = Direction.DOWN;
 -        Projectile projectile = Projectile.spawnProjectileUsingShoot(
 +        Projectile projectile = Projectile.spawnProjectileUsingShootDelayed( // Paper - fixes and addition to spawn reason API
-             item.asProjectile(world, this.position(), stack, direction),
-             world,
+             projectileItem.asProjectile(level, this.position(), stack, direction),
+             level,
              stack,
-@@ -99,7 +99,7 @@
-             (double)direction.getStepZ(),
+@@ -99,7 +_,7 @@
+             direction.getStepZ(),
              dispenseConfig.power(),
              dispenseConfig.uncertainty()
 -        );
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Shearable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/Shearable.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch
index aa779fc65d..177b7c0bc7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Shearable.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Shearable.java.patch
@@ -1,13 +1,14 @@
 --- a/net/minecraft/world/entity/Shearable.java
 +++ b/net/minecraft/world/entity/Shearable.java
-@@ -5,7 +5,15 @@
+@@ -5,7 +_,16 @@
  import net.minecraft.world.item.ItemStack;
  
  public interface Shearable {
-+    default void shear(ServerLevel world, SoundSource soundCategory, ItemStack shears, java.util.List<net.minecraft.world.item.ItemStack> drops) { this.shear(world, soundCategory, shears); } // Paper - Add drops to shear events
-     void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears);
++    default void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<net.minecraft.world.item.ItemStack> drops) { this.shear(level, soundSource, shears); } // Paper - Add drops to shear events
+     void shear(ServerLevel level, SoundSource soundSource, ItemStack shears);
  
      boolean readyForShearing();
++
 +    net.minecraft.world.level.Level level(); // Shearable API - expose default level needed for shearing.
 +
 +    // Paper start - custom shear drops; ensure all implementing entities override this
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
new file mode 100644
index 0000000000..ca11667ad6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
@@ -0,0 +1,82 @@
+--- a/net/minecraft/world/entity/TamableAnimal.java
++++ b/net/minecraft/world/entity/TamableAnimal.java
+@@ -57,7 +_,7 @@
+             compound.putUUID("Owner", this.getOwnerUUID());
+         }
+ 
+-        compound.putBoolean("Sitting", this.orderedToSit);
++        compound.putBoolean("Sitting", this.orderedToSit);f
+     }
+ 
+     @Override
+@@ -84,7 +_,7 @@
+         }
+ 
+         this.orderedToSit = compound.getBoolean("Sitting");
+-        this.setInSittingPose(this.orderedToSit);
++        this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent
+     }
+ 
+     @Override
+@@ -95,8 +_,16 @@
+     @Override
+     public boolean handleLeashAtDistance(Entity leashHolder, float distance) {
+         if (this.isInSittingPose()) {
+-            if (distance > 10.0F) {
+-                this.dropLeash();
++            if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
++                // Paper start - Expand EntityUnleashEvent
++                org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true);
++                if (!event.callEvent()) return false;
++                if (event.isDropLeash()) {
++                    this.dropLeash();
++                } else {
++                    this.removeLeash();
++                }
++                // Paper end - Expand EntityUnleashEvent
+             }
+ 
+             return false;
+@@ -155,6 +_,12 @@
+     }
+ 
+     public void setInSittingPose(boolean sitting) {
++        // Paper start - Add EntityToggleSitEvent
++        this.setInSittingPose(sitting, true);
++    }
++    public void setInSittingPose(boolean sitting, boolean callEvent) {
++        if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return;
++        // Paper end - Add EntityToggleSitEvent
+         byte b = this.entityData.get(DATA_FLAGS_ID);
+         if (sitting) {
+             this.entityData.set(DATA_FLAGS_ID, (byte)(b | 1));
+@@ -227,7 +_,12 @@
+         if (this.level() instanceof ServerLevel serverLevel
+             && serverLevel.getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES)
+             && this.getOwner() instanceof ServerPlayer serverPlayer) {
+-            serverPlayer.sendSystemMessage(this.getCombatTracker().getDeathMessage());
++            // Paper start - Add TameableDeathMessageEvent
++            io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage()));
++            if (event.callEvent()) {
++                serverPlayer.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage()));
++            }
++            // Paper end - Add TameableDeathMessageEvent
+         }
+ 
+         super.die(cause);
+@@ -270,7 +_,14 @@
+         if (!this.canTeleportTo(new BlockPos(x, y, z))) {
+             return false;
+         } else {
+-            this.moveTo(x + 0.5, y, z + 0.5, this.getYRot(), this.getXRot());
++            // CraftBukkit start
++            org.bukkit.event.entity.EntityTeleportEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, x + 0.5D, y, z + 0.5D);
++            if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location
++                return false;
++            }
++            org.bukkit.Location to = event.getTo();
++            this.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
++            // CraftBukkit end
+             this.navigation.stop();
+             return true;
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/LightningBolt.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/LightningBolt.java.patch
deleted file mode 100644
index 1dc2b6bf90..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/LightningBolt.java.patch
+++ /dev/null
@@ -1,160 +0,0 @@
---- a/net/minecraft/world/entity/LightningBolt.java
-+++ b/net/minecraft/world/entity/LightningBolt.java
-@@ -30,6 +30,10 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class LightningBolt extends Entity {
- 
-@@ -44,6 +48,7 @@
-     private ServerPlayer cause;
-     private final Set<Entity> hitEntities = Sets.newHashSet();
-     private int blocksSetOnFire;
-+    public boolean isEffect; // Paper - Properly handle lightning effects api
- 
-     public LightningBolt(EntityType<? extends LightningBolt> type, Level world) {
-         super(type, world);
-@@ -82,7 +87,7 @@
-     @Override
-     public void tick() {
-         super.tick();
--        if (this.life == 2) {
-+        if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api
-             if (this.level().isClientSide()) {
-                 this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false);
-                 this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false);
-@@ -94,7 +99,7 @@
-                 }
- 
-                 this.powerLightningRod();
--                LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition());
-+                LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent
-                 this.gameEvent(GameEvent.LIGHTNING_STRIKE);
-             }
-         }
-@@ -120,7 +125,7 @@
-                     }
-                 }
- 
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             } else if (this.life < -this.random.nextInt(10)) {
-                 --this.flashes;
-                 this.life = 1;
-@@ -129,7 +134,7 @@
-             }
-         }
- 
--        if (this.life >= 0) {
-+        if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api
-             if (!(this.level() instanceof ServerLevel)) {
-                 this.level().setSkyFlashTime(2);
-             } else if (!this.visualOnly) {
-@@ -158,7 +163,7 @@
-     }
- 
-     private void spawnFire(int spreadAttempts) {
--        if (!this.visualOnly) {
-+        if (!this.visualOnly && !this.isEffect) {  // Paper - Properly handle lightning effects api
-             Level world = this.level();
- 
-             if (world instanceof ServerLevel) {
-@@ -169,8 +174,12 @@
-                     BlockState iblockdata = BaseFireBlock.getState(this.level(), blockposition);
- 
-                     if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
--                        this.level().setBlockAndUpdate(blockposition, iblockdata);
--                        ++this.blocksSetOnFire;
-+                        // CraftBukkit start - add "!visualOnly"
-+                        if (!this.visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) {
-+                            this.level().setBlockAndUpdate(blockposition, iblockdata);
-+                            ++this.blocksSetOnFire;
-+                        }
-+                        // CraftBukkit end
-                     }
- 
-                     for (int j = 0; j < spreadAttempts; ++j) {
-@@ -178,8 +187,12 @@
- 
-                         iblockdata = BaseFireBlock.getState(this.level(), blockposition1);
-                         if (this.level().getBlockState(blockposition1).isAir() && iblockdata.canSurvive(this.level(), blockposition1)) {
--                            this.level().setBlockAndUpdate(blockposition1, iblockdata);
--                            ++this.blocksSetOnFire;
-+                            // CraftBukkit start - add "!visualOnly"
-+                            if (!this.visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition1, this).isCancelled()) {
-+                                this.level().setBlockAndUpdate(blockposition1, iblockdata);
-+                                ++this.blocksSetOnFire;
-+                            }
-+                            // CraftBukkit end
-                         }
-                     }
- 
-@@ -190,7 +203,7 @@
- 
-     }
- 
--    private static void clearCopperOnLightningStrike(Level world, BlockPos pos) {
-+    private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent
-         BlockState iblockdata = world.getBlockState(pos);
-         BlockPos blockposition1;
-         BlockState iblockdata1;
-@@ -204,24 +217,29 @@
-         }
- 
-         if (iblockdata1.getBlock() instanceof WeatheringCopper) {
--            world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1)));
-+            // Paper start - Call EntityChangeBlockEvent
-+            BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1));
-+            if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) {
-+                world.setBlockAndUpdate(blockposition1, newBlock);
-+            }
-+            // Paper end - Call EntityChangeBlockEvent
-             BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
-             int i = world.random.nextInt(3) + 3;
- 
-             for (int j = 0; j < i; ++j) {
-                 int k = world.random.nextInt(8) + 1;
- 
--                LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k);
-+                LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
-             }
- 
-         }
-     }
- 
--    private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) {
-+    private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
-         mutablePos.set(pos);
- 
-         for (int j = 0; j < count; ++j) {
--            Optional<BlockPos> optional = LightningBolt.randomStepCleaningCopper(world, mutablePos);
-+            Optional<BlockPos> optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
- 
-             if (optional.isEmpty()) {
-                 break;
-@@ -232,7 +250,7 @@
- 
-     }
- 
--    private static Optional<BlockPos> randomStepCleaningCopper(Level world, BlockPos pos) {
-+    private static Optional<BlockPos> randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent
-         Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator();
- 
-         BlockPos blockposition1;
-@@ -247,8 +265,10 @@
-             iblockdata = world.getBlockState(blockposition1);
-         } while (!(iblockdata.getBlock() instanceof WeatheringCopper));
- 
-+        BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error
-         WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> {
--            world.setBlockAndUpdate(blockposition1, iblockdata1);
-+            if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent
-+            world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error
-         });
-         world.levelEvent(3002, blockposition1, -1);
-         return Optional.of(blockposition1);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Mob.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/Mob.java.patch
deleted file mode 100644
index aeaafc3a1b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Mob.java.patch
+++ /dev/null
@@ -1,472 +0,0 @@
---- a/net/minecraft/world/entity/Mob.java
-+++ b/net/minecraft/world/entity/Mob.java
-@@ -84,6 +84,17 @@
- import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
- import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
- import net.minecraft.world.phys.AABB;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+import org.bukkit.event.entity.EntityUnleashEvent;
-+import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
-+// CraftBukkit end
- 
- public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting {
- 
-@@ -112,6 +123,7 @@
-     private final BodyRotationControl bodyRotationControl;
-     protected PathNavigation navigation;
-     public GoalSelector goalSelector;
-+    @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float
-     public GoalSelector targetSelector;
-     @Nullable
-     private LivingEntity target;
-@@ -132,6 +144,8 @@
-     private BlockPos restrictCenter;
-     private float restrictRadius;
- 
-+    public boolean aware = true; // CraftBukkit
-+
-     protected Mob(EntityType<? extends Mob> type, Level world) {
-         super(type, world);
-         this.handItems = NonNullList.withSize(2, ItemStack.EMPTY);
-@@ -157,8 +171,14 @@
-         if (world instanceof ServerLevel) {
-             this.registerGoals();
-         }
-+
-+    }
- 
-+    // CraftBukkit start
-+    public void setPersistenceRequired(boolean persistenceRequired) {
-+        this.persistenceRequired = persistenceRequired;
-     }
-+    // CraftBukkit end
- 
-     protected void registerGoals() {}
- 
-@@ -264,13 +284,44 @@
- 
-     @Nullable
-     protected final LivingEntity getTargetFromBrain() {
--        return (LivingEntity) this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse((Object) null);
-+        return (LivingEntity) this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     public void setTarget(@Nullable LivingEntity target) {
--        this.target = target;
-+        // CraftBukkit start - fire event
-+        this.setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true);
-     }
- 
-+    public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
-+        if (this.getTarget() == entityliving) return false;
-+        if (fireEvent) {
-+            if (reason == EntityTargetEvent.TargetReason.UNKNOWN && this.getTarget() != null && entityliving == null) {
-+                reason = this.getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED;
-+            }
-+            if (reason == EntityTargetEvent.TargetReason.UNKNOWN) {
-+                this.level().getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception());
-+            }
-+            CraftLivingEntity ctarget = null;
-+            if (entityliving != null) {
-+                ctarget = (CraftLivingEntity) entityliving.getBukkitEntity();
-+            }
-+            EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason);
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                return false;
-+            }
-+
-+            if (event.getTarget() != null) {
-+                entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
-+            } else {
-+                entityliving = null;
-+            }
-+        }
-+        this.target = entityliving;
-+        return true;
-+        // CraftBukkit end
-+    }
-+
-     @Override
-     public boolean canAttackType(EntityType<?> type) {
-         return type != EntityType.GHAST;
-@@ -399,6 +450,12 @@
-         return null;
-     }
- 
-+    // CraftBukkit start - Add delegate method
-+    public SoundEvent getAmbientSound0() {
-+        return this.getAmbientSound();
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
-@@ -473,13 +530,25 @@
-             nbt.putBoolean("NoAI", this.isNoAi());
-         }
- 
-+        nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
-     }
- 
-     @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
--        this.setCanPickUpLoot(nbt.getBoolean("CanPickUpLoot"));
--        this.persistenceRequired = nbt.getBoolean("PersistenceRequired");
-+        // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it
-+        if (nbt.contains("CanPickUpLoot", 99)) {
-+            boolean data = nbt.getBoolean("CanPickUpLoot");
-+            if (isLevelAtLeast(nbt, 1) || data) {
-+                this.setCanPickUpLoot(data);
-+            }
-+        }
-+
-+        boolean data = nbt.getBoolean("PersistenceRequired");
-+        if (isLevelAtLeast(nbt, 1) || data) {
-+            this.persistenceRequired = data;
-+        }
-+        // CraftBukkit end
-         ListTag nbttaglist;
-         CompoundTag nbttagcompound1;
-         int i;
-@@ -540,13 +609,18 @@
-         this.readLeashData(nbt);
-         this.setLeftHanded(nbt.getBoolean("LeftHanded"));
-         if (nbt.contains("DeathLootTable", 8)) {
--            this.lootTable = Optional.of(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("DeathLootTable"))));
-+            this.lootTable = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("DeathLootTable"))).map((rs) -> ResourceKey.create(Registries.LOOT_TABLE, rs)); // Paper - Validate ResourceLocation
-         } else {
-             this.lootTable = Optional.empty();
-         }
- 
-         this.lootTableSeed = nbt.getLong("DeathLootTableSeed");
-         this.setNoAi(nbt.getBoolean("NoAI"));
-+        // CraftBukkit start
-+        if (nbt.contains("Bukkit.Aware")) {
-+            this.aware = nbt.getBoolean("Bukkit.Aware");
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -608,6 +682,11 @@
-                     ItemEntity entityitem = (ItemEntity) iterator.next();
- 
-                     if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(worldserver, entityitem.getItem())) {
-+                        // Paper start - Item#canEntityPickup
-+                        if (!entityitem.canMobPickup) {
-+                            continue;
-+                        }
-+                        // Paper end - Item#canEntityPickup
-                         this.pickUpItem(worldserver, entityitem);
-                     }
-                 }
-@@ -623,23 +702,29 @@
- 
-     protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) {
-         ItemStack itemstack = itemEntity.getItem();
--        ItemStack itemstack1 = this.equipItemIfPossible(world, itemstack.copy());
-+        ItemStack itemstack1 = this.equipItemIfPossible(world, itemstack.copy(), itemEntity); // CraftBukkit - add item
- 
-         if (!itemstack1.isEmpty()) {
-             this.onItemPickup(itemEntity);
-             this.take(itemEntity, itemstack1.getCount());
-             itemstack.shrink(itemstack1.getCount());
-             if (itemstack.isEmpty()) {
--                itemEntity.discard();
-+                itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             }
-         }
- 
-     }
- 
-     public ItemStack equipItemIfPossible(ServerLevel world, ItemStack stack) {
--        EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(stack);
-+        // CraftBukkit start - add item
-+        return this.equipItemIfPossible(world, stack, null);
-+    }
-+
-+    public ItemStack equipItemIfPossible(ServerLevel worldserver, ItemStack itemstack, ItemEntity entityitem) {
-+        // CraftBukkit end
-+        EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack);
-         ItemStack itemstack1 = this.getItemBySlot(enumitemslot);
--        boolean flag = this.canReplaceCurrentItem(stack, itemstack1, enumitemslot);
-+        boolean flag = this.canReplaceCurrentItem(itemstack, itemstack1, enumitemslot);
- 
-         if (enumitemslot.isArmor() && !flag) {
-             enumitemslot = EquipmentSlot.MAINHAND;
-@@ -647,14 +732,22 @@
-             flag = itemstack1.isEmpty();
-         }
- 
--        if (flag && this.canHoldItem(stack)) {
-+        // CraftBukkit start
-+        boolean canPickup = flag && this.canHoldItem(itemstack);
-+        if (entityitem != null) {
-+            canPickup = !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entityitem, 0, !canPickup).isCancelled();
-+        }
-+        if (canPickup) {
-+            // CraftBukkit end
-             double d0 = (double) this.getEquipmentDropChance(enumitemslot);
- 
-             if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) {
--                this.spawnAtLocation(world, itemstack1);
-+                this.forceDrops = true; // CraftBukkit
-+                this.spawnAtLocation(worldserver, itemstack1);
-+                this.forceDrops = false; // CraftBukkit
-             }
- 
--            ItemStack itemstack2 = enumitemslot.limit(stack);
-+            ItemStack itemstack2 = enumitemslot.limit(itemstack);
- 
-             this.setItemSlotAndDropWhenKilled(enumitemslot, itemstack2);
-             return itemstack2;
-@@ -768,25 +861,29 @@
-     @Override
-     public void checkDespawn() {
-         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
--            Player entityhuman = this.level().getNearestPlayer(this, -1.0D);
-+            Player entityhuman = this.level().findNearbyPlayer(this, -1.0D, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
- 
-             if (entityhuman != null) {
--                double d0 = entityhuman.distanceToSqr((Entity) this);
--                int i = this.getType().getCategory().getDespawnDistance();
--                int j = i * i;
--
--                if (d0 > (double) j && this.removeWhenFarAway(d0)) {
--                    this.discard();
-+                // Paper start - Configurable despawn distances
-+                final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory());
-+                final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape;
-+                final double dy = Math.abs(entityhuman.getY() - this.getY());
-+                final double dySqr = Math.pow(dy, 2);
-+                final double dxSqr = Math.pow(entityhuman.getX() - this.getX(), 2);
-+                final double dzSqr = Math.pow(entityhuman.getZ() - this.getZ(), 2);
-+                final double distanceSquared = dxSqr + dzSqr + dySqr;
-+                // Despawn if hard/soft limit is exceeded
-+                if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) {
-+                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                 }
--
--                int k = this.getType().getCategory().getNoDespawnDistance();
--                int l = k * k;
--
--                if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > (double) l && this.removeWhenFarAway(d0)) {
--                    this.discard();
--                } else if (d0 < (double) l) {
-+                if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) {
-+                    if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) {
-+                        this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-+                    }
-+                } else {
-+                // Paper end - Configurable despawn distances
-                     this.noActionTime = 0;
-                 }
-             }
-@@ -799,6 +896,15 @@
-     @Override
-     protected final void serverAiStep() {
-         ++this.noActionTime;
-+        // Paper start - Allow nerfed mobs to jump and float
-+        if (!this.aware) {
-+            if (goalFloat != null) {
-+                if (goalFloat.canUse()) goalFloat.tick();
-+                this.getJumpControl().tick();
-+            }
-+            return;
-+        }
-+        // Paper end - Allow nerfed mobs to jump and float
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("sensing");
-@@ -994,23 +1100,36 @@
- 
-     @Override
-     public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
-+        // Paper start - Fix silent equipment change
-+        setItemSlot(slot, stack, false);
-+    }
-+
-+    @Override
-+    public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) {
-+        // Paper end - Fix silent equipment change
-         this.verifyEquippedItem(stack);
-         switch (slot.getType()) {
-             case HAND:
--                this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack);
-+                this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change
-                 break;
-             case HUMANOID_ARMOR:
--                this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack);
-+                this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change
-                 break;
-             case ANIMAL_ARMOR:
-                 ItemStack itemstack1 = this.bodyArmorItem;
- 
-                 this.bodyArmorItem = stack;
--                this.onEquipItem(slot, itemstack1, stack);
-+                this.onEquipItem(slot, itemstack1, stack, silent); // Paper - Fix silent equipment change
-         }
- 
-     }
- 
-+    // Paper start
-+    protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox)
-+        return false;
-+    }
-+    // Paper end
-+
-     @Override
-     protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {
-         super.dropCustomDeathLoot(world, source, causedByPlayer);
-@@ -1018,6 +1137,7 @@
- 
-         while (iterator.hasNext()) {
-             EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next();
-+            if (this.shouldSkipLoot(enumitemslot)) continue; // Paper
-             ItemStack itemstack = this.getItemBySlot(enumitemslot);
-             float f = this.getEquipmentDropChance(enumitemslot);
- 
-@@ -1042,7 +1162,13 @@
-                     }
- 
-                     this.spawnAtLocation(world, itemstack);
-+                    if (this.clearEquipmentSlots) { // Paper
-                     this.setItemSlot(enumitemslot, ItemStack.EMPTY);
-+                    // Paper start
-+                    } else {
-+                        this.clearedEquipmentSlots.add(enumitemslot);
-+                    }
-+                    // Paper end
-                 }
-             }
-         }
-@@ -1338,7 +1464,7 @@
-         if (itemstack.getItem() instanceof SpawnEggItem) {
-             if (this.level() instanceof ServerLevel) {
-                 SpawnEggItem itemmonsteregg = (SpawnEggItem) itemstack.getItem();
--                Optional<Mob> optional = itemmonsteregg.spawnOffspringFromSpawnEgg(player, this, this.getType(), (ServerLevel) this.level(), this.position(), itemstack);
-+                Optional<Mob> optional = itemmonsteregg.spawnOffspringFromSpawnEgg(player, this, (EntityType<? extends Mob>) this.getType(), (ServerLevel) this.level(), this.position(), itemstack); // CraftBukkit - decompile error
- 
-                 optional.ifPresent((entityinsentient) -> {
-                     this.onOffspringSpawnedFromEgg(player, entityinsentient);
-@@ -1389,28 +1515,51 @@
-         return this.restrictRadius != -1.0F;
-     }
- 
-+    // CraftBukkit start
-     @Nullable
-     public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams context, EntitySpawnReason reason, ConversionParams.AfterConversion<T> finalizer) {
-+        return this.convertTo(entityType, context, reason, finalizer, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
-+
-+    @Nullable
-+    public <T extends Mob> T convertTo(EntityType<T> entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.AfterConversion<T> conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
-+    // Paper start - entity zap event - allow cancellation of conversion post creation
-+        return this.convertTo(entitytypes, conversionparams, entityspawnreason, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason);
-+    }
-+    @Nullable
-+    public <T extends Mob> T convertTo(EntityType<T> entitytypes, ConversionParams conversionparams, EntitySpawnReason entityspawnreason, ConversionParams.CancellingAfterConversion<T> conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
-+    // Paper end - entity zap event - allow cancellation of conversion post creation
-+        // CraftBukkit end
-         if (this.isRemoved()) {
-             return null;
-         } else {
--            T t0 = (Mob) entityType.create(this.level(), reason);
-+            T t0 = entitytypes.create(this.level(), EntitySpawnReason.CONVERSION); // CraftBukkit - decompile error
- 
-             if (t0 == null) {
-                 return null;
-             } else {
--                context.type().convert(this, t0, context);
--                finalizer.finalizeConversion(t0);
-+                conversionparams.type().convert(this, t0, conversionparams);
-+                if (!conversionparams_a.finalizeConversionOrCancel(t0)) return null; // Paper - entity zap event - return null if conversion was cancelled
-                 Level world = this.level();
- 
-+                // CraftBukkit start
-+                if (transformReason == null) {
-+                    // Special handling for slime split and pig lightning
-+                    return t0;
-+                }
-+
-+                if (CraftEventFactory.callEntityTransformEvent(this, t0, transformReason).isCancelled()) {
-+                    return null;
-+                }
-+                // CraftBukkit end
-                 if (world instanceof ServerLevel) {
-                     ServerLevel worldserver = (ServerLevel) world;
- 
--                    worldserver.addFreshEntity(t0);
-+                    worldserver.addFreshEntity(t0, spawnReason); // CraftBukkit
-                 }
- 
--                if (context.type().shouldDiscardAfterConversion()) {
--                    this.discard();
-+                if (conversionparams.type().shouldDiscardAfterConversion()) {
-+                    this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
-                 }
- 
-                 return t0;
-@@ -1420,10 +1569,22 @@
- 
-     @Nullable
-     public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams context, ConversionParams.AfterConversion<T> finalizer) {
--        return this.convertTo(entityType, context, EntitySpawnReason.CONVERSION, finalizer);
-+        // CraftBukkit start
-+        return this.convertTo(entityType, context, finalizer, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
-     }
- 
-     @Nullable
-+    public <T extends Mob> T convertTo(EntityType<T> entitytypes, ConversionParams conversionparams, ConversionParams.AfterConversion<T> conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
-+    // Paper start - entity zap event - allow cancellation of conversion post creation
-+        return this.convertTo(entitytypes, conversionparams, e -> { conversionparams_a.finalizeConversion(e); return true; }, transformReason, spawnReason);
-+    }
-+    public <T extends Mob> T convertTo(EntityType<T> entitytypes, ConversionParams conversionparams, ConversionParams.CancellingAfterConversion<T> conversionparams_a, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
-+    // Paper start - entity zap event - allow cancellation of conversion post creation
-+        return this.convertTo(entitytypes, conversionparams, EntitySpawnReason.CONVERSION, conversionparams_a, transformReason, spawnReason);
-+        // CraftBukkit end
-+    }
-+
-+    @Nullable
-     @Override
-     public Leashable.LeashData getLeashData() {
-         return this.leashData;
-@@ -1458,7 +1619,15 @@
-         boolean flag1 = super.startRiding(entity, force);
- 
-         if (flag1 && this.isLeashed()) {
--            this.dropLeash();
-+            // Paper start - Expand EntityUnleashEvent
-+            EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true);
-+            if (!event.callEvent()) { return flag1; }
-+            if (event.isDropLeash()) {
-+                this.dropLeash();
-+            } else {
-+                this.removeLeash();
-+            }
-+            // Paper end - Expand EntityUnleashEvent
-         }
- 
-         return flag1;
-@@ -1542,7 +1711,7 @@
- 
-             if (f1 > 0.0F && target instanceof LivingEntity) {
-                 entityliving = (LivingEntity) target;
--                entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)));
-+                entityliving.knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
-                 this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D));
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/NeutralMob.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/NeutralMob.java.patch
deleted file mode 100644
index 2088822118..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/NeutralMob.java.patch
+++ /dev/null
@@ -1,86 +0,0 @@
---- a/net/minecraft/world/entity/NeutralMob.java
-+++ b/net/minecraft/world/entity/NeutralMob.java
-@@ -8,6 +8,9 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public interface NeutralMob {
- 
-@@ -42,24 +45,11 @@
-                 UUID uuid = nbt.getUUID("AngryAt");
- 
-                 this.setPersistentAngerTarget(uuid);
--                Entity entity = ((ServerLevel) world).getEntity(uuid);
--
--                if (entity != null) {
--                    if (entity instanceof Mob) {
--                        Mob entityinsentient = (Mob) entity;
--
--                        this.setTarget(entityinsentient);
--                        this.setLastHurtByMob(entityinsentient);
--                    }
--
--                    if (entity instanceof Player) {
--                        Player entityhuman = (Player) entity;
--
--                        this.setTarget(entityhuman);
--                        this.setLastHurtByPlayer(entityhuman);
--                    }
--
--                }
-+                // Paper - Prevent entity loading causing async lookups; Moved diff to separate method
-+                // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively
-+                // tick the initial persistent anger.
-+                // If not, let the first tick on the baseTick call the method later down the line.
-+                if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world);
-             }
-         }
-     }
-@@ -114,7 +104,7 @@
-     default void stopBeingAngry() {
-         this.setLastHurtByMob((LivingEntity) null);
-         this.setPersistentAngerTarget((UUID) null);
--        this.setTarget((LivingEntity) null);
-+        this.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
-         this.setRemainingPersistentAngerTime(0);
-     }
- 
-@@ -127,8 +117,34 @@
- 
-     void setTarget(@Nullable LivingEntity target);
- 
-+    boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent); // CraftBukkit
-+
-     boolean canAttack(LivingEntity target);
- 
-     @Nullable
-     LivingEntity getTarget();
-+
-+    // Paper start - Prevent entity loading causing async lookups
-+    // Update last hurt when ticking
-+    default void tickInitialPersistentAnger(Level level) {
-+        UUID target = getPersistentAngerTarget();
-+        if (target == null) {
-+            return;
-+        }
-+
-+        Entity entity = ((ServerLevel) level).getEntity(target);
-+
-+        if (entity != null) {
-+            if (entity instanceof Mob mob) {
-+                this.setTarget(mob, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit
-+                this.setLastHurtByMob(mob);
-+            }
-+
-+            if (entity instanceof Player player) {
-+                this.setTarget(player, EntityTargetEvent.TargetReason.UNKNOWN, false); // CraftBukkit
-+                this.setLastHurtByPlayer(player);
-+            }
-+        }
-+    }
-+    // Paper end - Prevent entity loading causing async lookups
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/PathfinderMob.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/PathfinderMob.java.patch
deleted file mode 100644
index 24a880fc47..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/PathfinderMob.java.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- a/net/minecraft/world/entity/PathfinderMob.java
-+++ b/net/minecraft/world/entity/PathfinderMob.java
-@@ -10,6 +10,9 @@
- import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.LevelReader;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityUnleashEvent;
-+// CraftBukkit end
- 
- public abstract class PathfinderMob extends Mob {
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/TamableAnimal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/TamableAnimal.java.patch
deleted file mode 100644
index ae8be7d5d0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/TamableAnimal.java.patch
+++ /dev/null
@@ -1,85 +0,0 @@
---- a/net/minecraft/world/entity/TamableAnimal.java
-+++ b/net/minecraft/world/entity/TamableAnimal.java
-@@ -27,6 +27,11 @@
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
- import net.minecraft.world.scores.PlayerTeam;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTeleportEvent;
-+// CraftBukkit end
- 
- public abstract class TamableAnimal extends Animal implements OwnableEntity {
- 
-@@ -85,7 +90,7 @@
-         }
- 
-         this.orderedToSit = nbt.getBoolean("Sitting");
--        this.setInSittingPose(this.orderedToSit);
-+        this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent
-     }
- 
-     @Override
-@@ -96,8 +101,16 @@
-     @Override
-     public boolean handleLeashAtDistance(Entity leashHolder, float distance) {
-         if (this.isInSittingPose()) {
--            if (distance > 10.0F) {
--                this.dropLeash();
-+            if (distance > (float) this.level().paperConfig().misc.maxLeashDistance.or(Leashable.LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
-+                // Paper start - Expand EntityUnleashEvent
-+                org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true);
-+                if (!event.callEvent()) return false;
-+                if (event.isDropLeash()) {
-+                    this.dropLeash();
-+                } else {
-+                    this.removeLeash();
-+                }
-+                // Paper end - Expand EntityUnleashEvent
-             }
- 
-             return false;
-@@ -161,6 +174,12 @@
-     }
- 
-     public void setInSittingPose(boolean inSittingPose) {
-+        // Paper start - Add EntityToggleSitEvent
-+        this.setInSittingPose(inSittingPose, true);
-+    }
-+    public void setInSittingPose(boolean inSittingPose, boolean callEvent) {
-+        if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return;
-+        // Paper end - Add EntityToggleSitEvent
-         byte b0 = (Byte) this.entityData.get(TamableAnimal.DATA_FLAGS_ID);
- 
-         if (inSittingPose) {
-@@ -244,7 +263,12 @@
-                 if (entityliving instanceof ServerPlayer) {
-                     ServerPlayer entityplayer = (ServerPlayer) entityliving;
- 
--                    entityplayer.sendSystemMessage(this.getCombatTracker().getDeathMessage());
-+                    // Paper start - Add TameableDeathMessageEvent
-+                    io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage()));
-+                    if (event.callEvent()) {
-+                        entityplayer.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage()));
-+                    }
-+                    // Paper end - Add TameableDeathMessageEvent
-                 }
-             }
-         }
-@@ -295,7 +319,14 @@
-         if (!this.canTeleportTo(new BlockPos(x, y, z))) {
-             return false;
-         } else {
--            this.moveTo((double) x + 0.5D, (double) y, (double) z + 0.5D, this.getYRot(), this.getXRot());
-+            // CraftBukkit start
-+            EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this, (double) x + 0.5D, (double) y, (double) z + 0.5D);
-+            if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location
-+                return false;
-+            }
-+            Location to = event.getTo();
-+            this.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
-+            // CraftBukkit end
-             this.navigation.stop();
-             return true;
-         }

From 679c2f7c9fa186da0513a68542d2a44b25309d84 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 13:05:27 +0100
Subject: [PATCH 048/285] More more entity classes

---
 .../world/entity/AgeableMob.java.patch        |  49 +++---
 .../world/entity/AreaEffectCloud.java.patch   | 117 +++++++++++++
 .../world/entity/ConversionParams.java.patch  |   4 +-
 .../world/entity/ConversionType.java.patch    |  11 ++
 .../minecraft/world/entity/Display.java.patch |  11 ++
 .../world/entity/EntitySelector.java.patch    |  49 ++++++
 .../world/entity/Interaction.java.patch       |  20 +++
 .../world/entity/ItemBasedSteering.java.patch |   4 +-
 .../world/entity/AreaEffectCloud.java.patch   | 160 ------------------
 .../world/entity/ConversionType.java.patch    |  46 -----
 .../minecraft/world/entity/Display.java.patch |  11 --
 .../world/entity/EntitySelector.java.patch    |  49 ------
 .../world/entity/Interaction.java.patch       |  42 -----
 13 files changed, 236 insertions(+), 337 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/AgeableMob.java.patch (54%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ConversionParams.java.patch (88%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ItemBasedSteering.java.patch (84%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/AreaEffectCloud.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ConversionType.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/Display.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/EntitySelector.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/Interaction.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/AgeableMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/AgeableMob.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
index 5748ed58a8..c9a6111f89 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/AgeableMob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
@@ -1,17 +1,17 @@
 --- a/net/minecraft/world/entity/AgeableMob.java
 +++ b/net/minecraft/world/entity/AgeableMob.java
-@@ -21,12 +21,38 @@
+@@ -20,11 +_,37 @@
      protected int age;
      protected int forcedAge;
      protected int forcedAgeTimer;
 +    public boolean ageLocked; // CraftBukkit
  
-     protected AgeableMob(EntityType<? extends AgeableMob> type, Level world) {
-         super(type, world);
+     protected AgeableMob(EntityType<? extends AgeableMob> entityType, Level level) {
+         super(entityType, level);
      }
  
 +    // Spigot start
-     @Override
++    @Override
 +    public void inactiveTick()
 +    {
 +        super.inactiveTick();
@@ -35,35 +35,34 @@
 +    }
 +    // Spigot end
 +
-+    @Override
-     public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) {
-         if (entityData == null) {
-             entityData = new AgeableMob.AgeableMobGroupData(true);
-@@ -60,6 +86,7 @@
+     @Override
+     public SpawnGroupData finalizeSpawn(
+         ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
+@@ -66,6 +_,7 @@
      }
  
-     public void ageUp(int age, boolean overGrow) {
+     public void ageUp(int amount, boolean forced) {
 +        if (this.ageLocked) return; // Paper - Honor ageLock
-         int j = this.getAge();
-         int k = j;
- 
-@@ -104,6 +131,7 @@
-         super.addAdditionalSaveData(nbt);
-         nbt.putInt("Age", this.getAge());
-         nbt.putInt("ForcedAge", this.forcedAge);
-+        nbt.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
+         int age = this.getAge();
+         age += amount * 20;
+         if (age > 0) {
+@@ -104,6 +_,7 @@
+         super.addAdditionalSaveData(tag);
+         tag.putInt("Age", this.getAge());
+         tag.putInt("ForcedAge", this.forcedAge);
++        tag.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
      }
  
      @Override
-@@ -111,6 +139,7 @@
-         super.readAdditionalSaveData(nbt);
-         this.setAge(nbt.getInt("Age"));
-         this.forcedAge = nbt.getInt("ForcedAge");
-+        this.ageLocked = nbt.getBoolean("AgeLocked"); // CraftBukkit
+@@ -111,6 +_,7 @@
+         super.readAdditionalSaveData(tag);
+         this.setAge(tag.getInt("Age"));
+         this.forcedAge = tag.getInt("ForcedAge");
++        this.ageLocked = tag.getBoolean("AgeLocked"); // CraftBukkit
      }
  
      @Override
-@@ -125,7 +154,7 @@
+@@ -125,7 +_,7 @@
      @Override
      public void aiStep() {
          super.aiStep();
@@ -71,4 +70,4 @@
 +        if (this.level().isClientSide || this.ageLocked) { // CraftBukkit
              if (this.forcedAgeTimer > 0) {
                  if (this.forcedAgeTimer % 4 == 0) {
-                     this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), 0.0D, 0.0D, 0.0D);
+                     this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), 0.0, 0.0, 0.0);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
new file mode 100644
index 0000000000..1f6b41bf6d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -0,0 +1,117 @@
+--- a/net/minecraft/world/entity/AreaEffectCloud.java
++++ b/net/minecraft/world/entity/AreaEffectCloud.java
+@@ -47,7 +_,7 @@
+     public float radiusOnUse;
+     public float radiusPerTick;
+     @Nullable
+-    private LivingEntity owner;
++    private net.minecraft.world.entity.LivingEntity owner;
+     @Nullable
+     public UUID ownerUUID;
+ 
+@@ -128,6 +_,18 @@
+         this.duration = duration;
+     }
+ 
++    // Spigot start - copied from below
++    @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++
++        if (this.tickCount >= this.waitTime + this.duration) {
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++            return;
++        }
++    }
++    // Spigot end
++
+     @Override
+     public void tick() {
+         super.tick();
+@@ -177,7 +_,7 @@
+ 
+     private void serverTick(ServerLevel level) {
+         if (this.tickCount >= this.waitTime + this.duration) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else {
+             boolean isWaiting = this.isWaiting();
+             boolean flag = this.tickCount < this.waitTime;
+@@ -190,7 +_,7 @@
+                 if (this.radiusPerTick != 0.0F) {
+                     radius += this.radiusPerTick;
+                     if (radius < 0.5F) {
+-                        this.discard();
++                        this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                         return;
+                     }
+ 
+@@ -220,6 +_,7 @@
+                         list.addAll(this.potionContents.customEffects());
+                         List<LivingEntity> entitiesOfClass = this.level().getEntitiesOfClass(LivingEntity.class, this.getBoundingBox());
+                         if (!entitiesOfClass.isEmpty()) {
++                            List<org.bukkit.entity.LivingEntity> entities = new java.util.ArrayList<>(); // CraftBukkit
+                             for (LivingEntity livingEntity : entitiesOfClass) {
+                                 if (!this.victims.containsKey(livingEntity)
+                                     && livingEntity.isAffectedByPotions()
+@@ -228,6 +_,17 @@
+                                     double d1 = livingEntity.getZ() - this.getZ();
+                                     double d2 = d * d + d1 * d1;
+                                     if (d2 <= radius * radius) {
++                                        // CraftBukkit start
++                                        entities.add((org.bukkit.entity.LivingEntity) livingEntity.getBukkitEntity());
++                                    }
++                                }
++                            }
++                            org.bukkit.event.entity.AreaEffectCloudApplyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callAreaEffectCloudApplyEvent(this, entities);
++                            if (!event.isCancelled()) {
++                                for (org.bukkit.entity.LivingEntity entity : event.getAffectedEntities()) {
++                                    if (entity instanceof org.bukkit.craftbukkit.entity.CraftLivingEntity) {
++                                        net.minecraft.world.entity.LivingEntity livingEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity).getHandle();
++                                        // CraftBukkit end
+                                         this.victims.put(livingEntity, this.tickCount + this.reapplicationDelay);
+ 
+                                         for (MobEffectInstance mobEffectInstance1 : list) {
+@@ -236,14 +_,14 @@
+                                                     .value()
+                                                     .applyInstantenousEffect(level, this, this.getOwner(), livingEntity, mobEffectInstance1.getAmplifier(), 0.5);
+                                             } else {
+-                                                livingEntity.addEffect(new MobEffectInstance(mobEffectInstance1), this);
++                                                livingEntity.addEffect(new MobEffectInstance(mobEffectInstance1), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AREA_EFFECT_CLOUD); // CraftBukkit
+                                             }
+                                         }
+ 
+                                         if (this.radiusOnUse != 0.0F) {
+                                             radius += this.radiusOnUse;
+                                             if (radius < 0.5F) {
+-                                                this.discard();
++                                                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                                                 return;
+                                             }
+ 
+@@ -253,7 +_,7 @@
+                                         if (this.durationOnUse != 0) {
+                                             this.duration = this.duration + this.durationOnUse;
+                                             if (this.duration <= 0) {
+-                                                this.discard();
++                                                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                                                 return;
+                                             }
+                                         }
+@@ -299,14 +_,14 @@
+         this.waitTime = waitTime;
+     }
+ 
+-    public void setOwner(@Nullable LivingEntity owner) {
++    public void setOwner(@Nullable net.minecraft.world.entity.LivingEntity owner) {
+         this.owner = owner;
+         this.ownerUUID = owner == null ? null : owner.getUUID();
+     }
+ 
+     @Nullable
+     @Override
+-    public LivingEntity getOwner() {
++    public net.minecraft.world.entity.LivingEntity getOwner() {
+         if (this.owner != null && !this.owner.isRemoved()) {
+             return this.owner;
+         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ConversionParams.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ConversionParams.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch
index 3673cd3667..2be9008a50 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ConversionParams.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ConversionParams.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/world/entity/ConversionParams.java
 +++ b/net/minecraft/world/entity/ConversionParams.java
-@@ -12,4 +12,11 @@
+@@ -12,4 +_,11 @@
      public interface AfterConversion<T extends Mob> {
-         void finalizeConversion(T convertedEntity);
+         void finalizeConversion(T mob);
      }
 +
 +    // Paper start - entity zap event - allow conversion to be cancelled during finalization
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch
new file mode 100644
index 0000000000..1bd938bdbe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ConversionType.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ConversionType.java
++++ b/net/minecraft/world/entity/ConversionType.java
+@@ -21,7 +_,7 @@
+ 
+                 for (Entity entity : newMob.getPassengers()) {
+                     entity.stopRiding();
+-                    entity.remove(Entity.RemovalReason.DISCARDED);
++                    entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
+                 }
+ 
+                 firstPassenger.startRiding(newMob);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch
new file mode 100644
index 0000000000..467ae36f2f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Display.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/Display.java
++++ b/net/minecraft/world/entity/Display.java
+@@ -213,7 +_,7 @@
+         if (tag.contains("transformation")) {
+             Transformation.EXTENDED_CODEC
+                 .decode(NbtOps.INSTANCE, tag.get("transformation"))
+-                .resultOrPartial(Util.prefix("Display entity", LOGGER::error))
++                .result() // Paper - Hide text display error on spawn
+                 .ifPresent(pair -> this.setTransformation(pair.getFirst()));
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
new file mode 100644
index 0000000000..5ecfaf92e4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/entity/EntitySelector.java
++++ b/net/minecraft/world/entity/EntitySelector.java
+@@ -16,6 +_,23 @@
+     public static final Predicate<Entity> NO_SPECTATORS = entity -> !entity.isSpectator();
+     public static final Predicate<Entity> CAN_BE_COLLIDED_WITH = NO_SPECTATORS.and(Entity::canBeCollidedWith);
+     public static final Predicate<Entity> CAN_BE_PICKED = NO_SPECTATORS.and(Entity::isPickable);
++    // Paper start - Ability to control player's insomnia and phantoms
++    public static Predicate<Player> IS_INSOMNIAC = (player) -> {
++        net.minecraft.server.level.ServerPlayer serverPlayer = (net.minecraft.server.level.ServerPlayer) player;
++        int playerInsomniaTicks = serverPlayer.level().paperConfig().entities.behavior.playerInsomniaStartTicks;
++
++        if (playerInsomniaTicks <= 0) {
++            return false;
++        }
++
++        return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
++    };
++    // Paper end - Ability to control player's insomnia and phantoms
++    // Paper start - Affects Spawning API
++    public static final Predicate<Entity> PLAYER_AFFECTS_SPAWNING = (entity) -> {
++        return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning;
++    };
++    // Paper end - Affects Spawning API
+ 
+     private EntitySelector() {
+     }
+@@ -26,15 +_,20 @@
+     }
+ 
+     public static Predicate<Entity> pushableBy(Entity entity) {
++        // Paper start - Climbing should not bypass cramming gamerule
++        return pushable(entity, false);
++    }
++    public static Predicate<Entity> pushable(Entity entity, boolean ignoreClimbing) {
++        // Paper end - Climbing should not bypass cramming gamerule
+         Team team = entity.getTeam();
+         Team.CollisionRule collisionRule = team == null ? Team.CollisionRule.ALWAYS : team.getCollisionRule();
+         return (Predicate<Entity>)(collisionRule == Team.CollisionRule.NEVER
+             ? Predicates.alwaysFalse()
+             : NO_SPECTATORS.and(
+                 pushedEntity -> {
+-                    if (!pushedEntity.isPushable()) {
++                    if (!pushedEntity.isCollidable(ignoreClimbing) || !pushedEntity.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(pushedEntity)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule
+                         return false;
+-                    } else if (!entity.level().isClientSide || pushedEntity instanceof Player && ((Player)pushedEntity).isLocalPlayer()) {
++                    } else if (pushedEntity instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision
+                         Team team1 = pushedEntity.getTeam();
+                         Team.CollisionRule collisionRule1 = team1 == null ? Team.CollisionRule.ALWAYS : team1.getCollisionRule();
+                         if (collisionRule1 == Team.CollisionRule.NEVER) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch
new file mode 100644
index 0000000000..3d9857c808
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Interaction.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/Interaction.java
++++ b/net/minecraft/world/entity/Interaction.java
+@@ -130,9 +_,16 @@
+     @Override
+     public boolean skipAttackInteraction(Entity entity) {
+         if (entity instanceof Player player) {
++            // CraftBukkit start
++            DamageSource source = player.damageSources().playerAttack(player);
++            org.bukkit.event.entity.EntityDamageEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNonLivingEntityDamageEvent(this, source, 1.0F, false);
++            if (event.isCancelled()) {
++                return true;
++            }
++            // CraftBukkit end
+             this.attack = new Interaction.PlayerAction(player.getUUID(), this.level().getGameTime());
+             if (player instanceof ServerPlayer serverPlayer) {
+-                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(serverPlayer, this, player.damageSources().generic(), 1.0F, 1.0F, false);
++                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(serverPlayer, this, player.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order
+             }
+ 
+             return !this.getResponse();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ItemBasedSteering.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch
similarity index 84%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ItemBasedSteering.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch
index 2f4eab4bc8..cd34eb69a7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ItemBasedSteering.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ItemBasedSteering.java.patch
@@ -1,7 +1,7 @@
 --- a/net/minecraft/world/entity/ItemBasedSteering.java
 +++ b/net/minecraft/world/entity/ItemBasedSteering.java
-@@ -53,6 +53,14 @@
-         return (Integer) this.entityData.get(this.boostTimeAccessor);
+@@ -51,6 +_,14 @@
+         return this.entityData.get(this.boostTimeAccessor);
      }
  
 +    // CraftBukkit add setBoostTicks(int)
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/AreaEffectCloud.java.patch
deleted file mode 100644
index 835489e22c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/AreaEffectCloud.java.patch
+++ /dev/null
@@ -1,160 +0,0 @@
---- a/net/minecraft/world/entity/AreaEffectCloud.java
-+++ b/net/minecraft/world/entity/AreaEffectCloud.java
-@@ -33,6 +33,12 @@
- import net.minecraft.world.level.material.PushReaction;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.entity.LivingEntity;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
-+
- public class AreaEffectCloud extends Entity implements TraceableEntity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -54,7 +60,7 @@
-     public float radiusOnUse;
-     public float radiusPerTick;
-     @Nullable
--    private LivingEntity owner;
-+    private net.minecraft.world.entity.LivingEntity owner;
-     @Nullable
-     public UUID ownerUUID;
- 
-@@ -145,7 +151,19 @@
-         this.duration = duration;
-     }
- 
-+    // Spigot start - copied from below
-     @Override
-+    public void inactiveTick() {
-+        super.inactiveTick();
-+
-+        if (this.tickCount >= this.waitTime + this.duration) {
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-+            return;
-+        }
-+    }
-+    // Spigot end
-+
-+    @Override
-     public void tick() {
-         super.tick();
-         Level world = this.level();
-@@ -200,7 +218,7 @@
- 
-     private void serverTick(ServerLevel world) {
-         if (this.tickCount >= this.waitTime + this.duration) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else {
-             boolean flag = this.isWaiting();
-             boolean flag1 = this.tickCount < this.waitTime;
-@@ -215,7 +233,7 @@
-                 if (this.radiusPerTick != 0.0F) {
-                     f += this.radiusPerTick;
-                     if (f < 0.5F) {
--                        this.discard();
-+                        this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                         return;
-                     }
- 
-@@ -244,16 +262,17 @@
-                         }
- 
-                         list.addAll(this.potionContents.customEffects());
--                        List<LivingEntity> list1 = this.level().getEntitiesOfClass(LivingEntity.class, this.getBoundingBox());
-+                        List<net.minecraft.world.entity.LivingEntity> list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, this.getBoundingBox());
- 
-                         if (!list1.isEmpty()) {
-                             Iterator iterator1 = list1.iterator();
- 
-+                            List<LivingEntity> entities = new java.util.ArrayList<LivingEntity>(); // CraftBukkit
-                             while (iterator1.hasNext()) {
--                                LivingEntity entityliving = (LivingEntity) iterator1.next();
-+                                net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator1.next();
- 
-                                 if (!this.victims.containsKey(entityliving) && entityliving.isAffectedByPotions()) {
--                                    Stream stream = list.stream();
-+                                    Stream<MobEffectInstance> stream = list.stream(); // CraftBukkit - decompile error
- 
-                                     Objects.requireNonNull(entityliving);
-                                     if (!stream.noneMatch(entityliving::canBeAffected)) {
-@@ -262,6 +281,19 @@
-                                         double d2 = d0 * d0 + d1 * d1;
- 
-                                         if (d2 <= (double) (f * f)) {
-+                                            // CraftBukkit start
-+                                            entities.add((LivingEntity) entityliving.getBukkitEntity());
-+                                        }
-+                                    }
-+                                }
-+                            }
-+                            {
-+                                org.bukkit.event.entity.AreaEffectCloudApplyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callAreaEffectCloudApplyEvent(this, entities);
-+                                if (!event.isCancelled()) {
-+                                    for (LivingEntity entity : event.getAffectedEntities()) {
-+                                        if (entity instanceof CraftLivingEntity) {
-+                                            net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) entity).getHandle();
-+                                            // CraftBukkit end
-                                             this.victims.put(entityliving, this.tickCount + this.reapplicationDelay);
-                                             Iterator iterator2 = list.iterator();
- 
-@@ -271,14 +303,14 @@
-                                                 if (((MobEffect) mobeffect1.getEffect().value()).isInstantenous()) {
-                                                     ((MobEffect) mobeffect1.getEffect().value()).applyInstantenousEffect(world, this, this.getOwner(), entityliving, mobeffect1.getAmplifier(), 0.5D);
-                                                 } else {
--                                                    entityliving.addEffect(new MobEffectInstance(mobeffect1), this);
-+                                                    entityliving.addEffect(new MobEffectInstance(mobeffect1), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AREA_EFFECT_CLOUD); // CraftBukkit
-                                                 }
-                                             }
- 
-                                             if (this.radiusOnUse != 0.0F) {
-                                                 f += this.radiusOnUse;
-                                                 if (f < 0.5F) {
--                                                    this.discard();
-+                                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                                                     return;
-                                                 }
- 
-@@ -288,7 +320,7 @@
-                                             if (this.durationOnUse != 0) {
-                                                 this.duration += this.durationOnUse;
-                                                 if (this.duration <= 0) {
--                                                    this.discard();
-+                                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                                                     return;
-                                                 }
-                                             }
-@@ -336,14 +368,14 @@
-         this.waitTime = waitTime;
-     }
- 
--    public void setOwner(@Nullable LivingEntity owner) {
-+    public void setOwner(@Nullable net.minecraft.world.entity.LivingEntity owner) {
-         this.owner = owner;
-         this.ownerUUID = owner == null ? null : owner.getUUID();
-     }
- 
-     @Nullable
-     @Override
--    public LivingEntity getOwner() {
-+    public net.minecraft.world.entity.LivingEntity getOwner() {
-         if (this.owner != null && !this.owner.isRemoved()) {
-             return this.owner;
-         } else {
-@@ -353,10 +385,10 @@
-                 if (world instanceof ServerLevel) {
-                     ServerLevel worldserver = (ServerLevel) world;
-                     Entity entity = worldserver.getEntity(this.ownerUUID);
--                    LivingEntity entityliving;
-+                    net.minecraft.world.entity.LivingEntity entityliving;
- 
--                    if (entity instanceof LivingEntity) {
--                        LivingEntity entityliving1 = (LivingEntity) entity;
-+                    if (entity instanceof net.minecraft.world.entity.LivingEntity) {
-+                        net.minecraft.world.entity.LivingEntity entityliving1 = (net.minecraft.world.entity.LivingEntity) entity;
- 
-                         entityliving = entityliving1;
-                     } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ConversionType.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ConversionType.java.patch
deleted file mode 100644
index ed6daf39bf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ConversionType.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/entity/ConversionType.java
-+++ b/net/minecraft/world/entity/ConversionType.java
-@@ -4,6 +4,7 @@
- import java.util.Objects;
- import java.util.Optional;
- import java.util.Set;
-+import net.minecraft.core.BlockPos;
- import net.minecraft.world.effect.MobEffectInstance;
- import net.minecraft.world.entity.ai.Brain;
- import net.minecraft.world.entity.ai.memory.MemoryModuleType;
-@@ -11,6 +12,8 @@
- import net.minecraft.world.entity.monster.Zombie;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.scores.Scoreboard;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public enum ConversionType {
- 
-@@ -31,7 +34,7 @@
-                 while (iterator.hasNext()) {
-                     entity1 = (Entity) iterator.next();
-                     entity1.stopRiding();
--                    entity1.remove(Entity.RemovalReason.DISCARDED);
-+                    entity1.remove(Entity.RemovalReason.DISCARDED, EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
-                 }
- 
-                 entity.startRiding(newEntity);
-@@ -64,7 +67,7 @@
-             newEntity.hurtTime = oldEntity.hurtTime;
-             newEntity.yBodyRot = oldEntity.yBodyRot;
-             newEntity.setOnGround(oldEntity.onGround());
--            Optional optional = oldEntity.getSleepingPos();
-+            Optional<BlockPos> optional = oldEntity.getSleepingPos(); // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(newEntity);
-             optional.ifPresent(newEntity::setSleepingPos);
-@@ -156,7 +159,7 @@
-         newEntity.setNoGravity(oldEntity.isNoGravity());
-         newEntity.setPortalCooldown(oldEntity.getPortalCooldown());
-         newEntity.setSilent(oldEntity.isSilent());
--        Set set = oldEntity.getTags();
-+        Set<String> set = oldEntity.getTags(); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(newEntity);
-         set.forEach(newEntity::addTag);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Display.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/Display.java.patch
deleted file mode 100644
index 893b7a911c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Display.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/Display.java
-+++ b/net/minecraft/world/entity/Display.java
-@@ -903,7 +903,7 @@
-             b = loadFlag(b, nbt, "default_background", (byte)4);
-             Optional<Display.TextDisplay.Align> optional = Display.TextDisplay.Align.CODEC
-                 .decode(NbtOps.INSTANCE, nbt.get("alignment"))
--                .resultOrPartial(Util.prefix("Display entity", Display.LOGGER::error))
-+                .result() // Paper - Hide text display error on spawn
-                 .map(Pair::getFirst);
-             if (optional.isPresent()) {
-                 b = switch ((Display.TextDisplay.Align)optional.get()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/EntitySelector.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/EntitySelector.java.patch
deleted file mode 100644
index e79a8f240b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/EntitySelector.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/world/entity/EntitySelector.java
-+++ b/net/minecraft/world/entity/EntitySelector.java
-@@ -27,8 +27,25 @@
-     };
-     public static final Predicate<Entity> CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith);
-     public static final Predicate<Entity> CAN_BE_PICKED = EntitySelector.NO_SPECTATORS.and(Entity::isPickable);
-+    // Paper start - Ability to control player's insomnia and phantoms
-+    public static Predicate<Player> IS_INSOMNIAC = (player) -> {
-+        net.minecraft.server.level.ServerPlayer serverPlayer = (net.minecraft.server.level.ServerPlayer) player;
-+        int playerInsomniaTicks = serverPlayer.level().paperConfig().entities.behavior.playerInsomniaStartTicks;
- 
-+        if (playerInsomniaTicks <= 0) {
-+            return false;
-+        }
-+
-+        return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
-+    };
-+    // Paper end - Ability to control player's insomnia and phantoms
-+
-     private EntitySelector() {}
-+    // Paper start - Affects Spawning API
-+    public static final Predicate<Entity> PLAYER_AFFECTS_SPAWNING = (entity) -> {
-+        return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning;
-+    };
-+    // Paper end - Affects Spawning API
- 
-     public static Predicate<Entity> withinDistance(double x, double y, double z, double max) {
-         double d4 = max * max;
-@@ -39,13 +56,18 @@
-     }
- 
-     public static Predicate<Entity> pushableBy(Entity entity) {
-+        // Paper start - Climbing should not bypass cramming gamerule
-+        return pushable(entity, false);
-+    }
-+    public static Predicate<Entity> pushable(Entity entity, boolean ignoreClimbing) {
-+        // Paper end - Climbing should not bypass cramming gamerule
-         PlayerTeam scoreboardteam = entity.getTeam();
-         Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule();
- 
-         return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> {
--            if (!entity1.isPushable()) {
-+            if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule
-                 return false;
--            } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) {
-+            } else if (entity1 instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision
-                 return false;
-             } else {
-                 PlayerTeam scoreboardteam1 = entity1.getTeam();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Interaction.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/Interaction.java.patch
deleted file mode 100644
index 4ee0117128..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Interaction.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/entity/Interaction.java
-+++ b/net/minecraft/world/entity/Interaction.java
-@@ -27,6 +27,12 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import net.minecraft.world.damagesource.DamageSource;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityDamageEvent;
-+// CraftBukkit end
-+
- public class Interaction extends Entity implements Attackable, Targeting {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -65,7 +71,7 @@
-             this.setHeight(nbt.getFloat("height"));
-         }
- 
--        DataResult dataresult;
-+        DataResult<com.mojang.datafixers.util.Pair<Interaction.PlayerAction, net.minecraft.nbt.Tag>> dataresult; // CraftBukkit - decompile error
-         Logger logger;
- 
-         if (nbt.contains("attack")) {
-@@ -145,9 +151,16 @@
-     @Override
-     public boolean skipAttackInteraction(Entity attacker) {
-         if (attacker instanceof Player entityhuman) {
-+            // CraftBukkit start
-+            DamageSource source = entityhuman.damageSources().playerAttack(entityhuman);
-+            EntityDamageEvent event = CraftEventFactory.callNonLivingEntityDamageEvent(this, source, 1.0F, false);
-+            if (event.isCancelled()) {
-+                return true;
-+            }
-+            // CraftBukkit end
-             this.attack = new Interaction.PlayerAction(entityhuman.getUUID(), this.level().getGameTime());
-             if (entityhuman instanceof ServerPlayer entityplayer) {
--                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, 1.0F, false);
-+                CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order
-             }
- 
-             return !this.getResponse();

From 017359bc0f23080745a21d5250d70035f57f8e02 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 13:17:55 +0100
Subject: [PATCH 049/285] Update README

---
 README.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/README.md b/README.md
index 567114a550..52165c44ee 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,10 @@ How To (Pull Request)
 ------
 See [Contributing](CONTRIBUTING.md)
 
+Old Versions (1.21.3 and below)
+------
+For branches of versions 1.8-1.21.3, please see our [archive repository](https://github.com/PaperMC/Paper-archive).
+
 Support Us
 ------
 First of all, thank you for considering helping out, we really appreciate that!

From 729c6e5369e76507c9785eb16a113a812e6e8a28 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 14:31:00 +0100
Subject: [PATCH 050/285] Random small stuff

---
 .../bossevents/CustomBossEvent.java.patch     | 19 ++++++++++++
 .../ai/gossip/GossipContainer.java.patch      |  5 +--
 .../treedecorators/CocoaDecorator.java.patch  | 10 ++++++
 .../ExplorationMapFunction.java.patch         | 20 ++++++++++++
 .../bossevents/CustomBossEvent.java.patch     | 31 -------------------
 .../treedecorators/CocoaDecorator.java.patch  | 10 ------
 .../ExplorationMapFunction.java.patch         | 21 -------------
 7 files changed, 52 insertions(+), 64 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch (96%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/bossevents/CustomBossEvent.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch b/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch
new file mode 100644
index 0000000000..9aa2e7d869
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/bossevents/CustomBossEvent.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/server/bossevents/CustomBossEvent.java
++++ b/net/minecraft/server/bossevents/CustomBossEvent.java
+@@ -23,6 +_,16 @@
+     private final Set<UUID> players = Sets.newHashSet();
+     private int value;
+     private int max = 100;
++    // CraftBukkit start
++    private org.bukkit.boss.KeyedBossBar bossBar;
++
++    public org.bukkit.boss.KeyedBossBar getBukkitEntity() {
++        if (this.bossBar == null) {
++            this.bossBar = new org.bukkit.craftbukkit.boss.CraftKeyedBossbar(this);
++        }
++        return this.bossBar;
++    }
++    // CraftBukkit end
+ 
+     public CustomBossEvent(ResourceLocation id, Component name) {
+         super(name, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch
index 55d0a4951d..1a74963cdf 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/gossip/GossipContainer.java.patch
@@ -1,12 +1,13 @@
 --- a/net/minecraft/world/entity/ai/gossip/GossipContainer.java
 +++ b/net/minecraft/world/entity/ai/gossip/GossipContainer.java
-@@ -216,6 +216,43 @@
+@@ -216,6 +_,44 @@
          public void remove(GossipType gossipType) {
              this.entries.removeInt(gossipType);
          }
 +
 +        // Paper start - Add villager reputation API
 +        private static final GossipType[] TYPES = GossipType.values();
++
 +        public com.destroystokyo.paper.entity.villager.Reputation getPaperReputation() {
 +            Map<com.destroystokyo.paper.entity.villager.ReputationType, Integer> map = new java.util.EnumMap<>(com.destroystokyo.paper.entity.villager.ReputationType.class);
 +            for (Object2IntMap.Entry<GossipType> type : this.entries.object2IntEntrySet()) {
@@ -43,4 +44,4 @@
 +        // Paper end - Add villager reputation API
      }
  
-     static record GossipEntry(UUID target, GossipType type, int value) {
+     record GossipEntry(UUID target, GossipType type, int value) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
new file mode 100644
index 0000000000..f034ad2abb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
++++ b/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
+@@ -26,6 +_,7 @@
+ 
+     @Override
+     public void place(TreeDecorator.Context context) {
++        if (context.logs().isEmpty()) return; // Paper - Fix crash when trying to generate without logs
+         RandomSource randomSource = context.random();
+         if (!(randomSource.nextFloat() >= this.probability)) {
+             List<BlockPos> list = context.logs();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
new file mode 100644
index 0000000000..1d206d5719
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java
++++ b/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java
+@@ -89,7 +_,16 @@
+             Vec3 vec3 = context.getOptionalParameter(LootContextParams.ORIGIN);
+             if (vec3 != null) {
+                 ServerLevel level = context.getLevel();
+-                BlockPos blockPos = level.findNearestMapStructure(this.destination, BlockPos.containing(vec3), this.searchRadius, this.skipKnownStructures);
++                // Paper start - Configurable cartographer treasure maps
++                if (!level.paperConfig().environment.treasureMaps.enabled) {
++                    /*
++                     * NOTE: I fear users will just get a plain map as their "treasure"
++                     * This is preferable to disrespecting the config.
++                     */
++                    return stack;
++                }
++                // Paper end - Configurable cartographer treasure maps
++                BlockPos blockPos = level.findNearestMapStructure(this.destination, BlockPos.containing(vec3), this.searchRadius, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredLootTable.or(!this.skipKnownStructures)); // Paper - Configurable cartographer treasure maps
+                 if (blockPos != null) {
+                     ItemStack itemStack = MapItem.create(level, blockPos.getX(), blockPos.getZ(), this.zoom, true, true);
+                     MapItem.renderBiomePreviewMap(level, itemStack);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/bossevents/CustomBossEvent.java.patch b/paper-server/patches/unapplied/net/minecraft/server/bossevents/CustomBossEvent.java.patch
deleted file mode 100644
index 6e80d59765..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/bossevents/CustomBossEvent.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/server/bossevents/CustomBossEvent.java
-+++ b/net/minecraft/server/bossevents/CustomBossEvent.java
-@@ -18,6 +18,10 @@
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.Mth;
- import net.minecraft.world.BossEvent;
-+// CraftBukkit start
-+import org.bukkit.boss.KeyedBossBar;
-+import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
-+// CraftBukkit end
- 
- public class CustomBossEvent extends ServerBossEvent {
- 
-@@ -25,7 +29,17 @@
-     private final Set<UUID> players = Sets.newHashSet();
-     private int value;
-     private int max = 100;
-+    // CraftBukkit start
-+    private KeyedBossBar bossBar;
- 
-+    public KeyedBossBar getBukkitEntity() {
-+        if (this.bossBar == null) {
-+            this.bossBar = new CraftKeyedBossbar(this);
-+        }
-+        return this.bossBar;
-+    }
-+    // CraftBukkit end
-+
-     public CustomBossEvent(ResourceLocation id, Component displayName) {
-         super(displayName, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS);
-         this.id = id;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
deleted file mode 100644
index 4df0e3925c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
-+++ b/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
-@@ -26,6 +26,7 @@
- 
-     @Override
-     public void place(TreeDecorator.Context generator) {
-+        if (generator.logs().isEmpty()) return; // Paper - Fix crash when trying to generate without logs
-         RandomSource randomSource = generator.random();
-         if (!(randomSource.nextFloat() >= this.probability)) {
-             List<BlockPos> list = generator.logs();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
deleted file mode 100644
index 130a1b59c6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java
-+++ b/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java
-@@ -83,8 +83,17 @@
-             Vec3 vec3 = context.getOptionalParameter(LootContextParams.ORIGIN);
-             if (vec3 != null) {
-                 ServerLevel serverLevel = context.getLevel();
-+                // Paper start - Configurable cartographer treasure maps
-+                if (!serverLevel.paperConfig().environment.treasureMaps.enabled) {
-+                    /*
-+                     * NOTE: I fear users will just get a plain map as their "treasure"
-+                     * This is preferable to disrespecting the config.
-+                     */
-+                    return stack;
-+                }
-+                // Paper end - Configurable cartographer treasure maps
-                 BlockPos blockPos = serverLevel.findNearestMapStructure(
--                    this.destination, BlockPos.containing(vec3), this.searchRadius, this.skipKnownStructures
-+                    this.destination, BlockPos.containing(vec3), this.searchRadius, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredLootTable.or(!this.skipKnownStructures) // Paper - Configurable cartographer treasure maps
-                 );
-                 if (blockPos != null) {
-                     ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), this.zoom, true, true);

From 5eb4ceb6a465ae1ec1a1982d087a2050bc834f22 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 14:40:36 +0100
Subject: [PATCH 051/285] net.minecraft.world.item.enchantment

---
 .../enchantment/ItemEnchantments.java.patch   | 36 ++++++++-----------
 1 file changed, 15 insertions(+), 21 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch (59%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch
index 519f69ad9b..d49d59d33a 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/ItemEnchantments.java.patch
@@ -1,60 +1,54 @@
 --- a/net/minecraft/world/item/enchantment/ItemEnchantments.java
 +++ b/net/minecraft/world/item/enchantment/ItemEnchantments.java
-@@ -26,12 +26,25 @@
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.TooltipFlag;
+@@ -28,10 +_,19 @@
  import net.minecraft.world.item.component.TooltipProvider;
-+// Paper start
-+import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap;
-+// Paper end
  
  public class ItemEnchantments implements TooltipProvider {
 -    public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntOpenHashMap<>(), true);
 +    // Paper start
 +    private static final java.util.Comparator<Holder<Enchantment>> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName);
-+    public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true);
++    public static final ItemEnchantments EMPTY = new ItemEnchantments(new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true);
 +    // Paper end
      private static final Codec<Integer> LEVEL_CODEC = Codec.intRange(1, 255);
 -    private static final Codec<Object2IntOpenHashMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC)
 -        .xmap(Object2IntOpenHashMap::new, Function.identity());
-+    private static final Codec<Object2IntAVLTreeMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(
-+            Enchantment.CODEC, LEVEL_CODEC
-+        )// Paper start - sort enchantments
++    // Paper start - sort enchantments
++    private static final Codec<it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC)
 +        .xmap(m -> {
-+            final Object2IntAVLTreeMap<Holder<Enchantment>> map = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER);
++            final it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>> map = new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER);
 +            map.putAll(m);
 +            return map;
 +        }, Function.identity());
 +    // Paper end - sort enchantments
      private static final Codec<ItemEnchantments> FULL_CODEC = RecordCodecBuilder.create(
          instance -> instance.group(
-                     LEVELS_CODEC.fieldOf("levels").forGetter(component -> component.enchantments),
-@@ -41,16 +54,16 @@
+                 LEVELS_CODEC.fieldOf("levels").forGetter(itemEnchantments -> itemEnchantments.enchantments),
+@@ -41,16 +_,16 @@
      );
      public static final Codec<ItemEnchantments> CODEC = Codec.withAlternative(FULL_CODEC, LEVELS_CODEC, map -> new ItemEnchantments(map, true));
      public static final StreamCodec<RegistryFriendlyByteBuf, ItemEnchantments> STREAM_CODEC = StreamCodec.composite(
 -        ByteBufCodecs.map(Object2IntOpenHashMap::new, Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT),
-+        ByteBufCodecs.map((v) -> new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT),
-         component -> component.enchantments,
++        ByteBufCodecs.map((v) -> new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT), // Paper
+         itemEnchantments -> itemEnchantments.enchantments,
          ByteBufCodecs.BOOL,
-         component -> component.showInTooltip,
+         itemEnchantments -> itemEnchantments.showInTooltip,
          ItemEnchantments::new
      );
 -    final Object2IntOpenHashMap<Holder<Enchantment>> enchantments;
-+    final Object2IntAVLTreeMap<Holder<Enchantment>> enchantments; // Paper
++    final it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>> enchantments; // Paper
      public final boolean showInTooltip;
  
 -    ItemEnchantments(Object2IntOpenHashMap<Holder<Enchantment>> enchantments, boolean showInTooltip) {
-+    ItemEnchantments(Object2IntAVLTreeMap<Holder<Enchantment>> enchantments, boolean showInTooltip) { // Paper
++    ItemEnchantments(it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>> enchantments, boolean showInTooltip) { // Paper
          this.enchantments = enchantments;
          this.showInTooltip = showInTooltip;
  
-@@ -139,7 +152,7 @@
+@@ -139,7 +_,7 @@
      }
  
      public static class Mutable {
 -        private final Object2IntOpenHashMap<Holder<Enchantment>> enchantments = new Object2IntOpenHashMap<>();
-+        private final Object2IntAVLTreeMap<Holder<Enchantment>> enchantments = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER); // Paper
++        private final it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<Holder<Enchantment>> enchantments = new it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER); // Paper
          public boolean showInTooltip;
  
-         public Mutable(ItemEnchantments enchantmentsComponent) {
+         public Mutable(ItemEnchantments enchantments) {

From 7d70dbf92782e637ed168f7e14dfce41f8e0635a Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 14:47:37 +0100
Subject: [PATCH 052/285] net.minecraft.world.damagesource

---
 .../damagesource/DamageSource.java.patch      | 11 +++---
 .../damagesource/DamageSources.java.patch     | 38 +++++++------------
 2 files changed, 18 insertions(+), 31 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/damagesource/DamageSource.java.patch (97%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/damagesource/DamageSources.java.patch (63%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSource.java.patch
rename to paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch
index 65bbca9531..44ed121c0f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/damagesource/DamageSource.java
 +++ b/net/minecraft/world/damagesource/DamageSource.java
-@@ -21,7 +21,106 @@
+@@ -20,6 +_,105 @@
      private final Entity directEntity;
      @Nullable
      private final Vec3 damageSourcePosition;
@@ -14,7 +14,7 @@
 +    private boolean poison = false;
 +    @Nullable
 +    private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API
- 
++
 +    public DamageSource sweep() {
 +        this.sweep = true;
 +        return this;
@@ -103,11 +103,10 @@
 +        return damageSource;
 +    }
 +    // CraftBukkit end
-+
+ 
+     @Override
      public String toString() {
-         return "DamageSource (" + this.type().msgId() + ")";
-     }
-@@ -163,4 +262,18 @@
+@@ -134,4 +_,18 @@
      public Holder<DamageType> typeHolder() {
          return this.type;
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSources.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSources.java.patch
rename to paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
index ef590e6374..9e1a0a8d8b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/damagesource/DamageSources.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/damagesource/DamageSources.java
 +++ b/net/minecraft/world/damagesource/DamageSources.java
-@@ -43,9 +43,15 @@
+@@ -42,9 +_,15 @@
      private final DamageSource stalagmite;
      private final DamageSource outsideBorder;
      private final DamageSource genericKill;
@@ -8,20 +8,18 @@
 +    private final DamageSource melting;
 +    private final DamageSource poison;
  
-     public DamageSources(RegistryAccess registryManager) {
-         this.damageTypes = registryManager.lookupOrThrow(Registries.DAMAGE_TYPE);
+     public DamageSources(RegistryAccess registry) {
+         this.damageTypes = registry.lookupOrThrow(Registries.DAMAGE_TYPE);
 +        this.melting = this.source(DamageTypes.ON_FIRE).melting();
 +        this.poison = this.source(DamageTypes.MAGIC).poison();
 +        // CraftBukkit end
          this.inFire = this.source(DamageTypes.IN_FIRE);
          this.campfire = this.source(DamageTypes.CAMPFIRE);
          this.lightningBolt = this.source(DamageTypes.LIGHTNING_BOLT);
-@@ -83,7 +89,17 @@
+@@ -84,6 +_,16 @@
+         return new DamageSource(this.damageTypes.getOrThrow(damageTypeKey), causingEntity, directEntity);
+     }
  
-     private DamageSource source(ResourceKey<DamageType> key, @Nullable Entity source, @Nullable Entity attacker) {
-         return new DamageSource(this.damageTypes.getOrThrow(key), source, attacker);
-+    }
-+
 +    // CraftBukkit start
 +    public DamageSource melting() {
 +        return this.melting;
@@ -29,34 +27,24 @@
 +
 +    public DamageSource poison() {
 +        return this.poison;
-     }
++    }
 +    // CraftBukkit end
- 
++
      public DamageSource inFire() {
          return this.inFire;
-@@ -254,7 +270,7 @@
      }
- 
-     public DamageSource explosion(@Nullable Entity source, @Nullable Entity attacker) {
--        return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker);
-+        return this.source(attacker != null && source != null ? DamageTypes.PLAYER_EXPLOSION : DamageTypes.EXPLOSION, source, attacker); // Paper - revert to vanilla
-     }
- 
-     public DamageSource sonicBoom(Entity attacker) {
-@@ -262,9 +278,15 @@
+@@ -261,7 +_,13 @@
      }
  
      public DamageSource badRespawnPointExplosion(Vec3 position) {
 -        return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), position);
 +        // CraftBukkit start
 +        return this.badRespawnPointExplosion(position, null);
-     }
- 
++    }
++
 +    public DamageSource badRespawnPointExplosion(Vec3 vec3d, org.bukkit.block.BlockState blockState) {
 +        return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), vec3d).directBlockState(blockState);
 +        // CraftBukkit end
-+    }
-+
-     public DamageSource outOfBorder() {
-         return this.outsideBorder;
      }
+ 
+     public DamageSource outOfBorder() {

From 6fdbfef28e4089e3584efcc4e65e0940cdb8f7bb Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 15:02:14 +0100
Subject: [PATCH 053/285] net.minecraft.world.level.entity

---
 .../level/entity/EntityAccess.java.patch      |  16 +-
 .../level/entity/EntityLookup.java.patch      |  12 +-
 .../PersistentEntitySectionManager.java.patch | 208 +++++++++++++
 .../PersistentEntitySectionManager.java.patch | 276 ------------------
 4 files changed, 217 insertions(+), 295 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/entity/EntityAccess.java.patch (50%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/entity/EntityLookup.java.patch (71%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityAccess.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch
index 517de585e8..6c5e3be1cc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityAccess.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityAccess.java.patch
@@ -1,21 +1,11 @@
 --- a/net/minecraft/world/level/entity/EntityAccess.java
 +++ b/net/minecraft/world/level/entity/EntityAccess.java
-@@ -5,6 +5,9 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.phys.AABB;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
+@@ -23,6 +_,12 @@
  
- public interface EntityAccess {
- 
-@@ -24,6 +27,12 @@
- 
-     void setRemoved(Entity.RemovalReason reason);
+     void setRemoved(Entity.RemovalReason removalReason);
  
 +    // CraftBukkit start - add Bukkit remove cause
-+    default void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    default void setRemoved(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        this.setRemoved(entity_removalreason);
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityLookup.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
similarity index 71%
rename from paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityLookup.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
index b3b9c9ccb4..900da01b8e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/entity/EntityLookup.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/level/entity/EntityLookup.java
 +++ b/net/minecraft/world/level/entity/EntityLookup.java
-@@ -33,6 +33,14 @@
-         UUID uUID = entity.getUUID();
-         if (this.byUuid.containsKey(uUID)) {
-             LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity);
+@@ -33,6 +_,14 @@
+         UUID uuid = entity.getUUID();
+         if (this.byUuid.containsKey(uuid)) {
+             LOGGER.warn("Duplicate entity UUID {}: {}", uuid, entity);
 +            // Paper start - extra debug info
 +            if (entity instanceof net.minecraft.world.entity.Entity) {
 +                final T old = this.byUuid.get(entity.getUUID());
@@ -11,7 +11,7 @@
 +                    LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity);
 +                }
 +            }
-+            // Paper end - extra debug info
++            // Paper end
          } else {
-             this.byUuid.put(uUID, entity);
+             this.byUuid.put(uuid, entity);
              this.byId.put(entity.getId(), entity);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
new file mode 100644
index 0000000000..2fce5aff6c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
@@ -0,0 +1,208 @@
+--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
++++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+@@ -52,6 +_,16 @@
+         this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
+     }
+ 
++    // CraftBukkit start - add method to get all entities in chunk
++    public List<Entity> getEntities(ChunkPos chunkCoordIntPair) {
++        return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList());
++    }
++
++    public boolean isPending(long pair) {
++        return this.chunkLoadStatuses.get(pair) == ChunkLoadStatus.PENDING;
++    }
++    // CraftBukkit end
++
+     void removeSectionIfEmpty(long sectionKey, EntitySection<T> section) {
+         if (section.isEmpty()) {
+             this.sectionStorage.remove(sectionKey);
+@@ -59,6 +_,7 @@
+     }
+ 
+     private boolean addEntityUuid(T entity) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper
+         if (!this.knownUuids.add(entity.getUUID())) {
+             LOGGER.warn("UUID of added entity already exists: {}", entity);
+             return false;
+@@ -72,6 +_,17 @@
+     }
+ 
+     private boolean addEntity(T entity, boolean worldGenSpawned) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
++        // Paper start - chunk system hooks
++        // I don't want to know why this is a generic type.
++        Entity entityCasted = (Entity)entity;
++        boolean wasRemoved = entityCasted.isRemoved();
++        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true);
++        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
++            // removed by callback
++            return false;
++        }
++        // Paper end - chunk system hooks
+         if (!this.addEntityUuid(entity)) {
+             return false;
+         } else {
+@@ -109,19 +_,23 @@
+     }
+ 
+     void startTicking(T entity) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper
+         this.callbacks.onTickingStart(entity);
+     }
+ 
+     void stopTicking(T entity) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper
+         this.callbacks.onTickingEnd(entity);
+     }
+ 
+     void startTracking(T entity) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper
+         this.visibleEntityStorage.add(entity);
+         this.callbacks.onTrackingStart(entity);
+     }
+ 
+     void stopTracking(T entity) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper
+         this.callbacks.onTrackingEnd(entity);
+         this.visibleEntityStorage.remove(entity);
+     }
+@@ -132,6 +_,7 @@
+     }
+ 
+     public void updateChunkStatus(ChunkPos pos, Visibility visibility) {
++        org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper
+         long packedChunkPos = pos.toLong();
+         if (visibility == Visibility.HIDDEN) {
+             this.chunkVisibility.remove(packedChunkPos);
+@@ -165,6 +_,7 @@
+     }
+ 
+     public void ensureChunkQueuedForLoad(long chunkPosValue) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper
+         PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPosValue);
+         if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
+             this.requestChunkLoad(chunkPosValue);
+@@ -172,6 +_,11 @@
+     }
+ 
+     private boolean storeChunkSections(long chunkPosValue, Consumer<T> entityAction) {
++        // CraftBukkit start
++        return storeChunkSections(chunkPosValue, entityAction, false);
++    }
++    private boolean storeChunkSections(long chunkPosValue, Consumer<T> entityAction, boolean callEvent) {
++        // CraftBukkit end
+         PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPosValue);
+         if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
+             return false;
+@@ -182,6 +_,7 @@
+                 .collect(Collectors.toList());
+             if (list.isEmpty()) {
+                 if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
++                    if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(chunkPosValue), ImmutableList.of()); // CraftBukkit
+                     this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), ImmutableList.of()));
+                 }
+ 
+@@ -190,6 +_,7 @@
+                 this.requestChunkLoad(chunkPosValue);
+                 return false;
+             } else {
++                if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
+                 this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), list));
+                 list.forEach(entityAction);
+                 return true;
+@@ -198,6 +_,7 @@
+     }
+ 
+     private void requestChunkLoad(long chunkPosValue) {
++        org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper
+         this.chunkLoadStatuses.put(chunkPosValue, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
+         ChunkPos chunkPos = new ChunkPos(chunkPosValue);
+         this.permanentStorage.loadEntities(chunkPos).thenAccept(this.loadingInbox::add).exceptionally(throwable -> {
+@@ -207,7 +_,8 @@
+     }
+ 
+     private boolean processChunkUnload(long chunkPosValue) {
+-        boolean flag = this.storeChunkSections(chunkPosValue, entity -> entity.getPassengersAndSelf().forEach(this::unloadEntity));
++        org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper
++        boolean flag = this.storeChunkSections(chunkPosValue, entity -> entity.getPassengersAndSelf().forEach(this::unloadEntity), true); // CraftBukkit - add boolean for event call
+         if (!flag) {
+             return false;
+         } else {
+@@ -217,7 +_,7 @@
+     }
+ 
+     private void unloadEntity(EntityAccess entity) {
+-        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
++        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); // CraftBukkit - add Bukkit remove cause
+         entity.setLevelCallback(EntityInLevelCallback.NULL);
+     }
+ 
+@@ -227,14 +_,20 @@
+     }
+ 
+     private void processPendingLoads() {
++        org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper
+         ChunkEntities<T> chunkEntities;
+         while ((chunkEntities = this.loadingInbox.poll()) != null) {
+             chunkEntities.getEntities().forEach(entity -> this.addEntity((T)entity, true));
+             this.chunkLoadStatuses.put(chunkEntities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
++            // CraftBukkit start - call entity load event
++            List<Entity> entities = this.getEntities(chunkEntities.getPos());
++            org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities);
++            // CraftBukkit end
+         }
+     }
+ 
+     public void tick() {
++        org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper
+         this.processPendingLoads();
+         this.processUnloads();
+     }
+@@ -252,6 +_,7 @@
+     }
+ 
+     public void autoSave() {
++        org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper
+         this.getAllChunksToSave().forEach(packedChunkPos -> {
+             boolean flag = this.chunkVisibility.get(packedChunkPos) == Visibility.HIDDEN;
+             if (flag) {
+@@ -263,6 +_,7 @@
+     }
+ 
+     public void saveAll() {
++        org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper
+         LongSet allChunksToSave = this.getAllChunksToSave();
+ 
+         while (!allChunksToSave.isEmpty()) {
+@@ -279,7 +_,13 @@
+ 
+     @Override
+     public void close() throws IOException {
+-        this.saveAll();
++        // CraftBukkit start
++        this.close(true);
++    }
++
++    public void close(boolean save) throws IOException {
++        if (save) this.saveAll();
++        // CraftBukkit end
+         this.permanentStorage.close();
+     }
+ 
+@@ -380,6 +_,7 @@
+             BlockPos blockPos = this.entity.blockPosition();
+             long packedSectionPos = SectionPos.asLong(blockPos);
+             if (packedSectionPos != this.currentSectionKey) {
++                org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper
+                 Visibility status = this.currentSection.getStatus();
+                 if (!this.currentSection.remove(this.entity)) {
+                     PersistentEntitySectionManager.LOGGER
+@@ -427,6 +_,7 @@
+ 
+         @Override
+         public void onRemove(Entity.RemovalReason reason) {
++            org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper
+             if (!this.currentSection.remove(this.entity)) {
+                 PersistentEntitySectionManager.LOGGER
+                     .warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
deleted file mode 100644
index 68a70b169a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
+++ /dev/null
@@ -1,276 +0,0 @@
---- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-+++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-@@ -29,8 +29,13 @@
- import net.minecraft.util.CsvOutput;
- import net.minecraft.util.VisibleForDebug;
- import net.minecraft.world.entity.Entity;
--import net.minecraft.world.level.ChunkPos;
- import org.slf4j.Logger;
-+import net.minecraft.world.level.ChunkPos;
-+// CraftBukkit start
-+import net.minecraft.world.level.chunk.storage.EntityStorage;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class PersistentEntitySectionManager<T extends EntityAccess> implements AutoCloseable {
- 
-@@ -55,6 +60,16 @@
-         this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
-     }
- 
-+    // CraftBukkit start - add method to get all entities in chunk
-+    public List<Entity> getEntities(ChunkPos chunkCoordIntPair) {
-+        return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList());
-+    }
-+
-+    public boolean isPending(long pair) {
-+        return this.chunkLoadStatuses.get(pair) == ChunkLoadStatus.PENDING;
-+    }
-+    // CraftBukkit end
-+
-     void removeSectionIfEmpty(long sectionPos, EntitySection<T> section) {
-         if (section.isEmpty()) {
-             this.sectionStorage.remove(sectionPos);
-@@ -63,6 +78,7 @@
-     }
- 
-     private boolean addEntityUuid(T entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper
-         if (!this.knownUuids.add(entity.getUUID())) {
-             PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity);
-             return false;
-@@ -76,6 +92,17 @@
-     }
- 
-     private boolean addEntity(T entity, boolean existing) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
-+        // Paper start - chunk system hooks
-+        // I don't want to know why this is a generic type.
-+        Entity entityCasted = (Entity)entity;
-+        boolean wasRemoved = entityCasted.isRemoved();
-+        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true);
-+        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
-+            // removed by callback
-+            return false;
-+        }
-+        // Paper end - chunk system hooks
-         if (!this.addEntityUuid(entity)) {
-             return false;
-         } else {
-@@ -119,19 +146,23 @@
-     }
- 
-     void startTicking(T entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper
-         this.callbacks.onTickingStart(entity);
-     }
- 
-     void stopTicking(T entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper
-         this.callbacks.onTickingEnd(entity);
-     }
- 
-     void startTracking(T entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper
-         this.visibleEntityStorage.add(entity);
-         this.callbacks.onTrackingStart(entity);
-     }
- 
-     void stopTracking(T entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper
-         this.callbacks.onTrackingEnd(entity);
-         this.visibleEntityStorage.remove(entity);
-     }
-@@ -143,6 +174,7 @@
-     }
- 
-     public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) {
-+        org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper
-         long i = chunkPos.toLong();
- 
-         if (trackingStatus == Visibility.HIDDEN) {
-@@ -187,6 +219,7 @@
-     }
- 
-     public void ensureChunkQueuedForLoad(long chunkPos) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper
-         PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos);
- 
-         if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
-@@ -196,33 +229,42 @@
-     }
- 
-     private boolean storeChunkSections(long chunkPos, Consumer<T> action) {
--        PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos);
-+        // CraftBukkit start - add boolean for event call
-+        return this.storeChunkSections(chunkPos, action, false);
-+    }
- 
-+    private boolean storeChunkSections(long i, Consumer<T> consumer, boolean callEvent) {
-+        // CraftBukkit end
-+        PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i);
-+
-         if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
-             return false;
-         } else {
--            List<T> list = (List) this.sectionStorage.getExistingSectionsInChunk(chunkPos).flatMap((entitysection) -> {
-+            List<T> list = (List) this.sectionStorage.getExistingSectionsInChunk(i).flatMap((entitysection) -> {
-                 return entitysection.getEntities().filter(EntityAccess::shouldBeSaved);
-             }).collect(Collectors.toList());
- 
-             if (list.isEmpty()) {
-                 if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
--                    this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), ImmutableList.of()));
-+                    if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), ImmutableList.of()); // CraftBukkit
-+                    this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), ImmutableList.of()));
-                 }
- 
-                 return true;
-             } else if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
--                this.requestChunkLoad(chunkPos);
-+                this.requestChunkLoad(i);
-                 return false;
-             } else {
--                this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), list));
--                list.forEach(action);
-+                if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
-+                this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), list));
-+                list.forEach(consumer);
-                 return true;
-             }
-         }
-     }
- 
-     private void requestChunkLoad(long chunkPos) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper
-         this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
-         ChunkPos chunkcoordintpair = new ChunkPos(chunkPos);
-         CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair);
-@@ -236,9 +278,10 @@
-     }
- 
-     private boolean processChunkUnload(long chunkPos) {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper
-         boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> {
-             entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
--        });
-+        }, true); // CraftBukkit - add boolean for event call
- 
-         if (!flag) {
-             return false;
-@@ -249,29 +292,35 @@
-     }
- 
-     private void unloadEntity(EntityAccess entity) {
--        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
-+        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, EntityRemoveEvent.Cause.UNLOAD); // CraftBukkit - add Bukkit remove cause
-         entity.setLevelCallback(EntityInLevelCallback.NULL);
-     }
- 
-     private void processUnloads() {
--        this.chunksToUnload.removeIf((i) -> {
-+        this.chunksToUnload.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error
-             return this.chunkVisibility.get(i) != Visibility.HIDDEN ? true : this.processChunkUnload(i);
-         });
-     }
- 
-     private void processPendingLoads() {
--        ChunkEntities chunkentities;
-+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper
-+        ChunkEntities<T> chunkentities; // CraftBukkit - decompile error
- 
-         while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) {
-             chunkentities.getEntities().forEach((entityaccess) -> {
-                 this.addEntity(entityaccess, true);
-             });
-             this.chunkLoadStatuses.put(chunkentities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
-+            // CraftBukkit start - call entity load event
-+            List<Entity> entities = this.getEntities(chunkentities.getPos());
-+            CraftEventFactory.callEntitiesLoadEvent(((EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities);
-+            // CraftBukkit end
-         }
- 
-     }
- 
-     public void tick() {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper
-         this.processPendingLoads();
-         this.processUnloads();
-     }
-@@ -292,7 +341,8 @@
-     }
- 
-     public void autoSave() {
--        this.getAllChunksToSave().forEach((i) -> {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper
-+        this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
-             boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
- 
-             if (flag) {
-@@ -306,12 +356,13 @@
-     }
- 
-     public void saveAll() {
-+        org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper
-         LongSet longset = this.getAllChunksToSave();
- 
-         while (!longset.isEmpty()) {
-             this.permanentStorage.flush(false);
-             this.processPendingLoads();
--            longset.removeIf((i) -> {
-+            longset.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error
-                 boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
- 
-                 return flag ? this.processChunkUnload(i) : this.storeChunkSections(i, (entityaccess) -> {
-@@ -323,7 +374,15 @@
-     }
- 
-     public void close() throws IOException {
--        this.saveAll();
-+        // CraftBukkit start - add save boolean
-+        this.close(true);
-+    }
-+
-+    public void close(boolean save) throws IOException {
-+        if (save) {
-+            this.saveAll();
-+        }
-+        // CraftBukkit end
-         this.permanentStorage.close();
-     }
- 
-@@ -350,7 +409,7 @@
-     public void dumpSections(Writer writer) throws IOException {
-         CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("visibility").addColumn("load_status").addColumn("entity_count").build(writer);
- 
--        this.sectionStorage.getAllChunksWithExistingSections().forEach((i) -> {
-+        this.sectionStorage.getAllChunksWithExistingSections().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
-             PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i);
- 
-             this.sectionStorage.getExistingSectionPositionsInChunk(i).forEach((j) -> {
-@@ -394,7 +453,7 @@
-         private EntitySection<T> currentSection;
- 
-         Callback(final EntityAccess entityaccess, final long i, final EntitySection entitysection) {
--            this.entity = entityaccess;
-+            this.entity = (T) entityaccess; // CraftBukkit - decompile error
-             this.currentSectionKey = i;
-             this.currentSection = entitysection;
-         }
-@@ -405,6 +464,7 @@
-             long i = SectionPos.asLong(blockposition);
- 
-             if (i != this.currentSectionKey) {
-+                org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper
-                 Visibility visibility = this.currentSection.getStatus();
- 
-                 if (!this.currentSection.remove(this.entity)) {
-@@ -459,6 +519,7 @@
- 
-         @Override
-         public void onRemove(Entity.RemovalReason reason) {
-+            org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper
-             if (!this.currentSection.remove(this.entity)) {
-                 PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason});
-             }

From 649f20ca9310b101ae58f1b6f05ad8c2f94bb17b Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 15:51:03 +0100
Subject: [PATCH 054/285] net.minecraft.util.thread

---
 .../minecraft/util/thread/BlockableEventLoop.java.patch  | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/util/thread/BlockableEventLoop.java.patch (82%)

diff --git a/paper-server/patches/unapplied/net/minecraft/util/thread/BlockableEventLoop.java.patch b/paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch
similarity index 82%
rename from paper-server/patches/unapplied/net/minecraft/util/thread/BlockableEventLoop.java.patch
rename to paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch
index 9692da77b5..186d682197 100644
--- a/paper-server/patches/unapplied/net/minecraft/util/thread/BlockableEventLoop.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/thread/BlockableEventLoop.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/util/thread/BlockableEventLoop.java
 +++ b/net/minecraft/util/thread/BlockableEventLoop.java
-@@ -82,6 +82,13 @@
-             runnable.run();
+@@ -83,6 +_,14 @@
          }
      }
+ 
 +    // Paper start
 +    public void scheduleOnMain(Runnable runnable) {
 +        // postToMainThread does not work the same as older versions of mc
@@ -11,6 +11,7 @@
 +        this.schedule(this.wrapRunnable(runnable));
 +    }
 +    // Paper end
- 
++
      @Override
-     public void schedule(R runnable) {
+     public void schedule(R task) {
+         this.pendingRunnables.add(task);

From f803e7cc285e20cb89fabf8118ec64630ee64a7f Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Sat, 14 Dec 2024 16:08:25 +0100
Subject: [PATCH 055/285] update mache, rebuild patches for concurrency fix

---
 paper-server/build.gradle.kts                    |  2 +-
 .../minecraft/world/entity/AgeableMob.java.patch | 16 ++++++++--------
 .../world/entity/animal/allay/Allay.java.patch   |  6 +++---
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 22cdfb4fcc..b0b491497e 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -12,7 +12,7 @@ plugins {
 val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
 
 dependencies {
-    mache("io.papermc:mache:1.21.4+build.5")
+    mache("io.papermc:mache:1.21.4+build.6")
     paperclip("io.papermc:paperclip:3.0.3")
     remapper("net.fabricmc:tiny-remapper:0.10.3:fat")
 }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
index c9a6111f89..dcf72a4df6 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
@@ -47,18 +47,18 @@
          age += amount * 20;
          if (age > 0) {
 @@ -104,6 +_,7 @@
-         super.addAdditionalSaveData(tag);
-         tag.putInt("Age", this.getAge());
-         tag.putInt("ForcedAge", this.forcedAge);
-+        tag.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
+         super.addAdditionalSaveData(compound);
+         compound.putInt("Age", this.getAge());
+         compound.putInt("ForcedAge", this.forcedAge);
++        compound.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
      }
  
      @Override
 @@ -111,6 +_,7 @@
-         super.readAdditionalSaveData(tag);
-         this.setAge(tag.getInt("Age"));
-         this.forcedAge = tag.getInt("ForcedAge");
-+        this.ageLocked = tag.getBoolean("AgeLocked"); // CraftBukkit
+         super.readAdditionalSaveData(compound);
+         this.setAge(compound.getInt("Age"));
+         this.forcedAge = compound.getInt("ForcedAge");
++        this.ageLocked = compound.getBoolean("AgeLocked"); // CraftBukkit
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
index c30af2f229..8653998f89 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/allay/Allay.java.patch
@@ -56,9 +56,9 @@
                  .ifPresent(data -> this.vibrationData = data);
          }
  
--        this.duplicationCooldown = tag.getInt("DuplicationCooldown");
-+        this.duplicationCooldown = tag.getLong("DuplicationCooldown"); // Paper - Load as long
-         this.entityData.set(DATA_CAN_DUPLICATE, tag.getBoolean("CanDuplicate"));
+-        this.duplicationCooldown = compound.getInt("DuplicationCooldown");
++        this.duplicationCooldown = compound.getLong("DuplicationCooldown"); // Paper - Load as long
+         this.entityData.set(DATA_CAN_DUPLICATE, compound.getBoolean("CanDuplicate"));
      }
  
 @@ -508,15 +_,17 @@

From 41494c70b039dd9c6d3cee1c54c998904088383e Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Sat, 14 Dec 2024 16:42:52 +0100
Subject: [PATCH 056/285] net/minecraft/util/datafix/**

---
 .../util/datafix/DataFixers.java.patch        | 21 +++++
 .../fixes/ItemStackMapIdFix.java.patch        | 11 +++
 .../ItemStackTheFlatteningFix.java.patch      | 11 +++
 .../util/datafix/DataFixers.java.patch        | 82 -------------------
 .../fixes/ItemStackMapIdFix.java.patch        | 11 ---
 .../ItemStackTheFlatteningFix.java.patch      | 11 ---
 6 files changed, 43 insertions(+), 104 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/datafix/DataFixers.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch
new file mode 100644
index 0000000000..5c13ab7817
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/util/datafix/DataFixers.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/util/datafix/DataFixers.java
++++ b/net/minecraft/util/datafix/DataFixers.java
+@@ -505,6 +_,18 @@
+         Schema schema44 = builder.addSchema(1456, SAME_NAMESPACED);
+         builder.addFixer(new EntityItemFrameDirectionFix(schema44, false));
+         Schema schema45 = builder.addSchema(1458, SAME_NAMESPACED);
++        // CraftBukkit start
++        builder.addFixer(new com.mojang.datafixers.DataFix(schema45, false) {
++            @Override
++            protected com.mojang.datafixers.TypeRewriteRule makeRule() {
++                return this.fixTypeEverywhereTyped("Player CustomName", this.getInputSchema().getType(References.PLAYER), (typed) -> {
++                    return typed.update(DSL.remainderFinder(), (dynamic) -> {
++                        return EntityCustomNameToComponentFix.fixTagCustomName(dynamic);
++                    });
++                });
++            }
++        });
++        // CraftBukkit end
+         builder.addFixer(new EntityCustomNameToComponentFix(schema45, false));
+         builder.addFixer(new ItemCustomNameToComponentFix(schema45, false));
+         builder.addFixer(new BlockEntityCustomNameToComponentFix(schema45, false));
diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
new file mode 100644
index 0000000000..c40c8e02d2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
+@@ -29,7 +_,7 @@
+                 Dynamic<?> dynamic = typed.get(DSL.remainderFinder());
+                 Typed<?> typed1 = typed.getOrCreateTyped(opticFinder1);
+                 Dynamic<?> dynamic1 = typed1.get(DSL.remainderFinder());
+-                dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0)));
++                if (dynamic1.getElement("map").result().isEmpty()) dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0))); // CraftBukkit
+                 return typed.set(opticFinder1, typed1.set(DSL.remainderFinder(), dynamic1));
+             } else {
+                 return typed;
diff --git a/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
new file mode 100644
index 0000000000..c20365405c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
+@@ -423,7 +_,7 @@
+                 if (DAMAGE_IDS.contains(optional.get().getSecond())) {
+                     Typed<?> typed2 = typed.getOrCreateTyped(opticFinder1);
+                     Dynamic<?> dynamic1 = typed2.get(DSL.remainderFinder());
+-                    dynamic1 = dynamic1.set("Damage", dynamic1.createInt(_int));
++                    if (_int != 0) dynamic1 = dynamic1.set("Damage", dynamic1.createInt(_int)); // CraftBukkit
+                     typed1 = typed1.set(opticFinder1, typed2.set(DSL.remainderFinder(), dynamic1));
+                 }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/util/datafix/DataFixers.java.patch b/paper-server/patches/unapplied/net/minecraft/util/datafix/DataFixers.java.patch
deleted file mode 100644
index 86e8fe50b7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/datafix/DataFixers.java.patch
+++ /dev/null
@@ -1,82 +0,0 @@
---- a/net/minecraft/util/datafix/DataFixers.java
-+++ b/net/minecraft/util/datafix/DataFixers.java
-@@ -302,6 +302,18 @@
-         builder.addFixer(new EntityItemFrameDirectionFix(schema44, false));
-         Schema schema45 = builder.addSchema(1458, DataFixers.SAME_NAMESPACED);
- 
-+        // CraftBukkit start
-+        builder.addFixer(new com.mojang.datafixers.DataFix(schema45, false) {
-+            @Override
-+            protected com.mojang.datafixers.TypeRewriteRule makeRule() {
-+                return this.fixTypeEverywhereTyped("Player CustomName", this.getInputSchema().getType(References.PLAYER), (typed) -> {
-+                    return typed.update(DSL.remainderFinder(), (dynamic) -> {
-+                        return EntityCustomNameToComponentFix.fixTagCustomName(dynamic);
-+                    });
-+                });
-+            }
-+        });
-+        // CraftBukkit end
-         builder.addFixer(new EntityCustomNameToComponentFix(schema45, false));
-         builder.addFixer(new ItemCustomNameToComponentFix(schema45, false));
-         builder.addFixer(new BlockEntityCustomNameToComponentFix(schema45, false));
-@@ -560,7 +572,8 @@
-         builder.addFixer(new AddNewChoices(schema110, "Added Zoglin", References.ENTITY));
-         Schema schema111 = builder.addSchema(2523, DataFixers.SAME_NAMESPACED);
- 
--        builder.addFixer(new AttributesRenameLegacy(schema111, "Attribute renames", DataFixers.createRenamerNoNamespace(ImmutableMap.builder().put("generic.maxHealth", "minecraft:generic.max_health").put("Max Health", "minecraft:generic.max_health").put("zombie.spawnReinforcements", "minecraft:zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "minecraft:zombie.spawn_reinforcements").put("horse.jumpStrength", "minecraft:horse.jump_strength").put("Jump Strength", "minecraft:horse.jump_strength").put("generic.followRange", "minecraft:generic.follow_range").put("Follow Range", "minecraft:generic.follow_range").put("generic.knockbackResistance", "minecraft:generic.knockback_resistance").put("Knockback Resistance", "minecraft:generic.knockback_resistance").put("generic.movementSpeed", "minecraft:generic.movement_speed").put("Movement Speed", "minecraft:generic.movement_speed").put("generic.flyingSpeed", "minecraft:generic.flying_speed").put("Flying Speed", "minecraft:generic.flying_speed").put("generic.attackDamage", "minecraft:generic.attack_damage").put("generic.attackKnockback", "minecraft:generic.attack_knockback").put("generic.attackSpeed", "minecraft:generic.attack_speed").put("generic.armorToughness", "minecraft:generic.armor_toughness").build())));
-+        // CraftBukkit - decompile error
-+        builder.addFixer(new AttributesRenameLegacy(schema111, "Attribute renames", DataFixers.createRenamerNoNamespace(ImmutableMap.<String, String>builder().put("generic.maxHealth", "minecraft:generic.max_health").put("Max Health", "minecraft:generic.max_health").put("zombie.spawnReinforcements", "minecraft:zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "minecraft:zombie.spawn_reinforcements").put("horse.jumpStrength", "minecraft:horse.jump_strength").put("Jump Strength", "minecraft:horse.jump_strength").put("generic.followRange", "minecraft:generic.follow_range").put("Follow Range", "minecraft:generic.follow_range").put("generic.knockbackResistance", "minecraft:generic.knockback_resistance").put("Knockback Resistance", "minecraft:generic.knockback_resistance").put("generic.movementSpeed", "minecraft:generic.movement_speed").put("Movement Speed", "minecraft:generic.movement_speed").put("generic.flyingSpeed", "minecraft:generic.flying_speed").put("Flying Speed", "minecraft:generic.flying_speed").put("generic.attackDamage", "minecraft:generic.attack_damage").put("generic.attackKnockback", "minecraft:generic.attack_knockback").put("generic.attackSpeed", "minecraft:generic.attack_speed").put("generic.armorToughness", "minecraft:generic.armor_toughness").build())));
-         Schema schema112 = builder.addSchema(2527, DataFixers.SAME_NAMESPACED);
- 
-         builder.addFixer(new BitStorageAlignFix(schema112));
-@@ -623,12 +636,14 @@
-         builder.addFixer(new AddNewChoices(schema130, "Added Glow Squid", References.ENTITY));
-         builder.addFixer(new AddNewChoices(schema130, "Added Glow Item Frame", References.ENTITY));
-         Schema schema131 = builder.addSchema(2690, DataFixers.SAME_NAMESPACED);
--        ImmutableMap<String, String> immutablemap = ImmutableMap.builder().put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block").put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block").put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block").put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper").put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper").put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper").put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs").put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs").put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs").put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab").put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab").put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab").put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper").put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper").put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper").put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper").put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs").put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs").put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab").put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab").build();
-+        // CraftBukkit - decompile error
-+        ImmutableMap<String, String> immutablemap = ImmutableMap.<String, String>builder().put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block").put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block").put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block").put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper").put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper").put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper").put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs").put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs").put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs").put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab").put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab").put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab").put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper").put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper").put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper").put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper").put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs").put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs").put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab").put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab").build();
- 
-         builder.addFixer(ItemRenameFix.create(schema131, "Renamed copper block items to new oxidized terms", DataFixers.createRenamer(immutablemap)));
-         builder.addFixer(BlockRenameFix.create(schema131, "Renamed copper blocks to new oxidized terms", DataFixers.createRenamer(immutablemap)));
-         Schema schema132 = builder.addSchema(2691, DataFixers.SAME_NAMESPACED);
--        ImmutableMap<String, String> immutablemap1 = ImmutableMap.builder().put("minecraft:waxed_copper", "minecraft:waxed_copper_block").put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper").put("minecraft:weathered_copper_block", "minecraft:weathered_copper").put("minecraft:exposed_copper_block", "minecraft:exposed_copper").build();
-+        // CraftBukkit - decompile error
-+        ImmutableMap<String, String> immutablemap1 = ImmutableMap.<String, String>builder().put("minecraft:waxed_copper", "minecraft:waxed_copper_block").put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper").put("minecraft:weathered_copper_block", "minecraft:weathered_copper").put("minecraft:exposed_copper_block", "minecraft:exposed_copper").build();
- 
-         builder.addFixer(ItemRenameFix.create(schema132, "Rename copper item suffixes", DataFixers.createRenamer(immutablemap1)));
-         builder.addFixer(BlockRenameFix.create(schema132, "Rename copper blocks suffixes", DataFixers.createRenamer(immutablemap1)));
-@@ -636,7 +651,8 @@
- 
-         builder.addFixer(new AddFlagIfNotPresentFix(schema133, References.WORLD_GEN_SETTINGS, "has_increased_height_already", false));
-         Schema schema134 = builder.addSchema(2696, DataFixers.SAME_NAMESPACED);
--        ImmutableMap<String, String> immutablemap2 = ImmutableMap.builder().put("minecraft:grimstone", "minecraft:deepslate").put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab").put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs").put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall").put("minecraft:polished_grimstone", "minecraft:polished_deepslate").put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab").put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs").put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall").put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles").put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab").put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs").put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall").put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks").put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab").put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs").put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall").put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate").build();
-+        // CraftBukkit - decompile error
-+        ImmutableMap<String, String> immutablemap2 = ImmutableMap.<String, String>builder().put("minecraft:grimstone", "minecraft:deepslate").put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab").put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs").put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall").put("minecraft:polished_grimstone", "minecraft:polished_deepslate").put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab").put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs").put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall").put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles").put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab").put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs").put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall").put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks").put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab").put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs").put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall").put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate").build();
- 
-         builder.addFixer(ItemRenameFix.create(schema134, "Renamed grimstone block items to deepslate", DataFixers.createRenamer(immutablemap2)));
-         builder.addFixer(BlockRenameFix.create(schema134, "Renamed grimstone blocks to deepslate", DataFixers.createRenamer(immutablemap2)));
-@@ -723,10 +739,11 @@
-         builder.addFixer(new AddNewChoices(schema159, "Added Allay", References.ENTITY));
-         Schema schema160 = builder.addSchema(3084, DataFixers.SAME_NAMESPACED);
- 
--        builder.addFixer(new NamespacedTypeRenameFix(schema160, "game_event_renames_3084", References.GAME_EVENT_NAME, DataFixers.createRenamer(ImmutableMap.builder().put("minecraft:block_press", "minecraft:block_activate").put("minecraft:block_switch", "minecraft:block_activate").put("minecraft:block_unpress", "minecraft:block_deactivate").put("minecraft:block_unswitch", "minecraft:block_deactivate").put("minecraft:drinking_finish", "minecraft:drink").put("minecraft:elytra_free_fall", "minecraft:elytra_glide").put("minecraft:entity_damaged", "minecraft:entity_damage").put("minecraft:entity_dying", "minecraft:entity_die").put("minecraft:entity_killed", "minecraft:entity_die").put("minecraft:mob_interact", "minecraft:entity_interact").put("minecraft:ravager_roar", "minecraft:entity_roar").put("minecraft:ring_bell", "minecraft:block_change").put("minecraft:shulker_close", "minecraft:container_close").put("minecraft:shulker_open", "minecraft:container_open").put("minecraft:wolf_shaking", "minecraft:entity_shake").build())));
-+        // CraftBukkit - decompile error
-+        builder.addFixer(new NamespacedTypeRenameFix(schema160, "game_event_renames_3084", References.GAME_EVENT_NAME, DataFixers.createRenamer(ImmutableMap.<String, String>builder().put("minecraft:block_press", "minecraft:block_activate").put("minecraft:block_switch", "minecraft:block_activate").put("minecraft:block_unpress", "minecraft:block_deactivate").put("minecraft:block_unswitch", "minecraft:block_deactivate").put("minecraft:drinking_finish", "minecraft:drink").put("minecraft:elytra_free_fall", "minecraft:elytra_glide").put("minecraft:entity_damaged", "minecraft:entity_damage").put("minecraft:entity_dying", "minecraft:entity_die").put("minecraft:entity_killed", "minecraft:entity_die").put("minecraft:mob_interact", "minecraft:entity_interact").put("minecraft:ravager_roar", "minecraft:entity_roar").put("minecraft:ring_bell", "minecraft:block_change").put("minecraft:shulker_close", "minecraft:container_close").put("minecraft:shulker_open", "minecraft:container_open").put("minecraft:wolf_shaking", "minecraft:entity_shake").build())));
-         Schema schema161 = builder.addSchema(3086, DataFixers.SAME_NAMESPACED);
-         TypeReference typereference = References.ENTITY;
--        Int2ObjectOpenHashMap int2objectopenhashmap = (Int2ObjectOpenHashMap) Util.make(new Int2ObjectOpenHashMap(), (int2objectopenhashmap1) -> {
-+        Int2ObjectOpenHashMap<String> int2objectopenhashmap = (Int2ObjectOpenHashMap) Util.make(new Int2ObjectOpenHashMap(), (int2objectopenhashmap1) -> { // CraftBukkit - decompile error
-             int2objectopenhashmap1.defaultReturnValue("minecraft:tabby");
-             int2objectopenhashmap1.put(0, "minecraft:tabby");
-             int2objectopenhashmap1.put(1, "minecraft:black");
-@@ -743,7 +760,8 @@
- 
-         Objects.requireNonNull(int2objectopenhashmap);
-         builder.addFixer(new EntityVariantFix(schema161, "Change cat variant type", typereference, "minecraft:cat", "CatType", int2objectopenhashmap::get));
--        ImmutableMap<String, String> immutablemap3 = ImmutableMap.builder().put("textures/entity/cat/tabby.png", "minecraft:tabby").put("textures/entity/cat/black.png", "minecraft:black").put("textures/entity/cat/red.png", "minecraft:red").put("textures/entity/cat/siamese.png", "minecraft:siamese").put("textures/entity/cat/british_shorthair.png", "minecraft:british").put("textures/entity/cat/calico.png", "minecraft:calico").put("textures/entity/cat/persian.png", "minecraft:persian").put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll").put("textures/entity/cat/white.png", "minecraft:white").put("textures/entity/cat/jellie.png", "minecraft:jellie").put("textures/entity/cat/all_black.png", "minecraft:all_black").build();
-+        // CraftBukkit - decompile error
-+        ImmutableMap<String, String> immutablemap3 = ImmutableMap.<String, String>builder().put("textures/entity/cat/tabby.png", "minecraft:tabby").put("textures/entity/cat/black.png", "minecraft:black").put("textures/entity/cat/red.png", "minecraft:red").put("textures/entity/cat/siamese.png", "minecraft:siamese").put("textures/entity/cat/british_shorthair.png", "minecraft:british").put("textures/entity/cat/calico.png", "minecraft:calico").put("textures/entity/cat/persian.png", "minecraft:persian").put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll").put("textures/entity/cat/white.png", "minecraft:white").put("textures/entity/cat/jellie.png", "minecraft:jellie").put("textures/entity/cat/all_black.png", "minecraft:all_black").build();
- 
-         builder.addFixer(new CriteriaRenameFix(schema161, "Migrate cat variant advancement", "minecraft:husbandry/complete_catalogue", (s) -> {
-             return (String) immutablemap3.getOrDefault(s, s);
diff --git a/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch b/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
deleted file mode 100644
index 139090af75..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
-+++ b/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
-@@ -32,7 +32,7 @@
-                 Typed<?> typed1 = typed.getOrCreateTyped(opticfinder1);
-                 Dynamic<?> dynamic1 = (Dynamic) typed1.get(DSL.remainderFinder());
- 
--                dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0)));
-+                if (!dynamic1.getElement("map").result().isPresent()) dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0))); // CraftBukkit
-                 return typed.set(opticfinder1, typed1.set(DSL.remainderFinder(), dynamic1));
-             } else {
-                 return typed;
diff --git a/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch b/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
deleted file mode 100644
index a56a92ae47..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
-+++ b/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
-@@ -376,7 +376,7 @@
-                     Typed<?> typed2 = typed.getOrCreateTyped(opticfinder1);
-                     Dynamic<?> dynamic1 = (Dynamic) typed2.get(DSL.remainderFinder());
- 
--                    dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i));
-+                    if (i != 0) dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i)); // CraftBukkit
-                     typed1 = typed1.set(opticfinder1, typed2.set(DSL.remainderFinder(), dynamic1));
-                 }
- 

From bfcb0e71c9a74e014cd9a523baff6809982cb7f6 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sat, 14 Dec 2024 17:00:39 +0100
Subject: [PATCH 057/285] server/network

---
 .../network/LegacyQueryHandler.java.patch     |  177 +-
 .../network/PlayerChunkSender.java.patch      |   22 +-
 .../ServerCommonPacketListenerImpl.java.patch |  287 ++-
 ...ConfigurationPacketListenerImpl.java.patch |   71 +
 .../ServerConnectionListener.java.patch       |  154 ++
 .../ServerGamePacketListenerImpl.java.patch   | 1907 ++++++++---------
 ...rverHandshakePacketListenerImpl.java.patch |   97 +-
 .../ServerLoginPacketListenerImpl.java.patch  |  293 ++-
 .../ServerStatusPacketListenerImpl.java.patch |   12 +
 ...ConfigurationPacketListenerImpl.java.patch |   89 -
 .../ServerConnectionListener.java.patch       |  156 --
 .../ServerStatusPacketListenerImpl.java.patch |  137 --
 12 files changed, 1555 insertions(+), 1847 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/LegacyQueryHandler.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/PlayerChunkSender.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch (69%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch (60%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch (65%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch (68%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
index a4f704f9e9..5ba39612d8 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -1,105 +1,117 @@
 --- a/net/minecraft/server/network/LegacyQueryHandler.java
 +++ b/net/minecraft/server/network/LegacyQueryHandler.java
-@@ -15,6 +15,7 @@
- 
+@@ -14,6 +_,7 @@
+ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter {
      private static final Logger LOGGER = LogUtils.getLogger();
      private final ServerInfo server;
 +    private ByteBuf buf; // Paper
  
      public LegacyQueryHandler(ServerInfo server) {
          this.server = server;
-@@ -23,6 +24,16 @@
-     public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) {
-         ByteBuf bytebuf = (ByteBuf) object;
- 
+@@ -22,6 +_,17 @@
+     @Override
+     public void channelRead(ChannelHandlerContext context, Object message) {
+         ByteBuf byteBuf = (ByteBuf)message;
 +        // Paper start - Make legacy ping handler more reliable
 +        if (this.buf != null) {
 +            try {
-+                readLegacy1_6(channelhandlercontext, bytebuf);
++                readLegacy1_6(context, byteBuf);
 +            } finally {
-+                bytebuf.release();
++                byteBuf.release();
 +            }
 +            return;
 +        }
-+        // Paper end
-         bytebuf.markReaderIndex();
++        // Paper end - Make legacy ping handler more reliable
++
+         byteBuf.markReaderIndex();
          boolean flag = true;
  
-@@ -34,11 +45,23 @@
+@@ -33,9 +_,21 @@
  
-                 SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress();
-                 int i = bytebuf.readableBytes();
--                String s;
-+                String s = null; // Paper
-+                // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper
+                 SocketAddress socketAddress = context.channel().remoteAddress();
+                 int i = byteBuf.readableBytes();
++                String string = null; // Paper
++                // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketAddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper
 +                com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper
- 
                  if (i == 0) {
--                    LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress);
--                    s = LegacyQueryHandler.createVersion0Response(this.server);
-+                    LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: "<ip address withheld>"); // Paper - Respect logIPs option
-+
+-                    LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
+-                    String string = createVersion0Response(this.server);
++                    LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress: "<ip address withheld>"); // Paper - Respect logIPs option
 +                    // Paper start - Call PaperServerListPingEvent and use results
-+                    event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 39, null);
++                    event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null);
 +                    if (event == null) {
-+                        channelhandlercontext.close();
-+                        bytebuf.release();
++                        context.close();
++                        byteBuf.release();
 +                        flag = false;
 +                        return;
 +                    }
-+                    s = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
-+                    // Paper end
-                     LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
++                    string = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
++                    // Paper end - Call PaperServerListPingEvent and use results
+                     sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
                  } else {
-                     if (bytebuf.readUnsignedByte() != 1) {
-@@ -46,16 +69,35 @@
+                     if (byteBuf.readUnsignedByte() != 1) {
+@@ -43,16 +_,35 @@
                      }
  
-                     if (bytebuf.isReadable()) {
--                        if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) {
--                            return;
+                     if (byteBuf.isReadable()) {
+-                        if (!readCustomPayloadPacket(byteBuf)) {
 +                        // Paper start - Replace with improved version below
-+                        if (bytebuf.readUnsignedByte() != 250) {
-+                            s = this.readLegacy1_6(channelhandlercontext, bytebuf);
-+                            if (s == null) {
++                        if (byteBuf.readUnsignedByte() != LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_ID) {
++                            string = this.readLegacy1_6(context, byteBuf);
++                            if (string == null) {
 +                                return;
 +                            }
-                         }
--
--                        LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress);
-+                        // if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) {
++                        }
++                        // if (!readCustomPayloadPacket(byteBuf)) {
 +                        //     return;
 +                        // }
-+                        //
-+                        // LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress);
-+                        // Paper end
-                     } else {
--                        LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress);
-+                        LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: "<ip address withheld>"); // Paper - Respect logIPs option
-                     }
- 
--                    s = LegacyQueryHandler.createVersion1Response(this.server);
-+                    if (s == null) {
-+                        // Paper start - Call PaperServerListPingEvent and use results
-+                        event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 127, null); // Paper
-+                        if (event == null) {
-+                            channelhandlercontext.close();
-+                            bytebuf.release();
-+                            flag = false;
-+                            return;
-+                        }
-+                        s = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit
-+                        // Paper end
++
++                        // LOGGER.debug("Ping: (1.6) from {}", socketAddress);
++                        // Paper end - Replace with improved version below
++                    } else {
++                        LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress : "<ip address withheld>"); // Paper - Respect logIPs option
 +                    }
-                     LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
++
++                    if (string == null) {
++                        // Paper start - Call PaperServerListPingEvent and use results
++                        event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper
++                        if (event == null) {
++                            context.close();
++                            byteBuf.release();
++                            flag = false;
+                             return;
+                         }
+-
+-                        LOGGER.debug("Ping: (1.6) from {}", socketAddress);
+-                    } else {
+-                        LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
++                        string = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit
++                        // Paper end - Call PaperServerListPingEvent and use results
+                     }
+-
+-                    String string = createVersion1Response(this.server);
+                     sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
                  }
  
-@@ -106,14 +148,110 @@
+@@ -95,21 +_,97 @@
          }
      }
  
 -    private static String createVersion0Response(ServerInfo server) {
--        return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
+-        return String.format(Locale.ROOT, "%s§%d§%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
+-    }
+-
+-    private static String createVersion1Response(ServerInfo server) {
+-        return String.format(
+-            Locale.ROOT,
+-            "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d",
+-            127,
+-            server.getServerVersion(),
+-            server.getMotd(),
+-            server.getPlayerCount(),
+-            server.getMaxPlayers()
+-        );
+-    }
 +    // Paper start
 +    private static String readLegacyString(ByteBuf buf) {
 +        int size = buf.readShort() * Character.BYTES;
@@ -110,10 +122,8 @@
 +        String result = buf.toString(buf.readerIndex(), size, java.nio.charset.StandardCharsets.UTF_16BE);
 +        buf.skipBytes(size); // toString doesn't increase readerIndex automatically
 +        return result;
-     }
- 
--    private static String createVersion1Response(ServerInfo server) {
--        return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
++    }
++
 +    private String readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) {
 +        ByteBuf buf = this.buf;
 +
@@ -130,12 +140,12 @@
 +            return null;
 +        }
 +
-+        String s = readLegacyString(buf);
-+        if (s == null) {
++        String string = readLegacyString(buf);
++        if (string == null) {
 +            return null;
 +        }
 +
-+        if (!s.equals("MC|PingHost")) {
++        if (!string.equals(LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_PING_CHANNEL)) {
 +            removeHandler(ctx);
 +            return null;
 +        }
@@ -144,15 +154,14 @@
 +            return null;
 +        }
 +
-+        net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer();
 +        int protocolVersion = buf.readByte();
 +        String host = readLegacyString(buf);
 +        if (host == null) {
 +            removeHandler(ctx);
 +            return null;
 +        }
-+        int port = buf.readInt();
 +
++        int port = buf.readInt();
 +        if (buf.isReadable()) {
 +            removeHandler(ctx);
 +            return null;
@@ -163,9 +172,10 @@
 +
 +        LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): "<ip address withheld>"); // Paper - Respect logIPs option
 +
++        net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer();
 +        java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port);
 +        com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(
-+                server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost);
++            server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost);
 +        if (event == null) {
 +            ctx.close();
 +            return null;
@@ -174,8 +184,8 @@
 +        String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(),
 +            com.destroystokyo.paper.network.PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers());
 +        return response;
-     }
- 
++    }
++
 +    private void removeHandler(ChannelHandlerContext ctx) {
 +        ByteBuf buf = this.buf;
 +        this.buf = null;
@@ -193,19 +203,6 @@
 +        }
 +    }
 +    // Paper end
-+
-+    // CraftBukkit start
-+    private static String createVersion0Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
-+        return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
-+        // CraftBukkit end
-+    }
-+
-+    // CraftBukkit start
-+    private static String createVersion1Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
-+        return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
-+        // CraftBukkit end
-+    }
-+
-     private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buf) {
-         context.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE);
-     }
+ 
+     private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buffer) {
+         context.pipeline().firstContext().writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch
index e1ac133048..9c8b0b62af 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch
@@ -1,26 +1,26 @@
 --- a/net/minecraft/server/network/PlayerChunkSender.java
 +++ b/net/minecraft/server/network/PlayerChunkSender.java
-@@ -44,6 +44,11 @@
-     public void dropChunk(ServerPlayer player, ChunkPos pos) {
-         if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) {
-             player.connection.send(new ClientboundForgetLevelChunkPacket(pos));
+@@ -44,6 +_,11 @@
+     public void dropChunk(ServerPlayer player, ChunkPos chunkPos) {
+         if (!this.pendingChunks.remove(chunkPos.toLong()) && player.isAlive()) {
+             player.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos));
 +            // Paper start - PlayerChunkUnloadEvent
 +            if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+                new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent();
++                new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), player.getBukkitEntity()).callEvent();
 +            }
 +            // Paper end - PlayerChunkUnloadEvent
          }
      }
  
-@@ -75,6 +80,11 @@
+@@ -75,6 +_,11 @@
  
-     private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) {
-         handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null));
+     private static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
+         packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null));
 +        // Paper start - PlayerChunkLoadEvent
 +        if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+            new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent();
++            new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
 +        }
 +        // Paper end - PlayerChunkLoadEvent
-         ChunkPos chunkPos = chunk.getPos();
-         DebugPackets.sendPoiPacketsForChunk(world, chunkPos);
+         ChunkPos pos = chunk.getPos();
+         DebugPackets.sendPoiPacketsForChunk(level, pos);
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
similarity index 69%
rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
index 4ba7794d53..3d98b71ca6 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -1,46 +1,69 @@
 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
-@@ -4,11 +4,13 @@
- import com.mojang.logging.LogUtils;
- import java.util.Objects;
- import javax.annotation.Nullable;
-+import net.minecraft.ChatFormatting;
- import net.minecraft.CrashReport;
- import net.minecraft.CrashReportCategory;
- import net.minecraft.ReportedException;
- import net.minecraft.Util;
- import net.minecraft.network.Connection;
-+import net.minecraft.network.ConnectionProtocol;
- import net.minecraft.network.DisconnectionDetails;
- import net.minecraft.network.PacketSendListener;
- import net.minecraft.network.chat.Component;
-@@ -22,39 +24,88 @@
- import net.minecraft.network.protocol.common.ServerboundPongPacket;
- import net.minecraft.network.protocol.common.ServerboundResourcePackPacket;
- import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket;
-+import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
-+import net.minecraft.resources.ResourceLocation;
- import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.level.ClientInformation;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.VisibleForDebug;
+@@ -27,30 +_,82 @@
  import net.minecraft.util.profiling.Profiler;
- import net.minecraft.util.thread.BlockableEventLoop;
  import org.slf4j.Logger;
  
 -public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener {
 +// CraftBukkit start
 +import io.netty.buffer.ByteBuf;
 +import java.util.concurrent.ExecutionException;
++import net.minecraft.network.ConnectionProtocol;
 +import net.minecraft.network.protocol.common.custom.DiscardedPayload;
++import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.server.level.ServerPlayer;
 +import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
 +import org.bukkit.craftbukkit.util.CraftLocation;
 +import org.bukkit.craftbukkit.util.Waitable;
 +import org.bukkit.event.player.PlayerKickEvent;
 +import org.bukkit.event.player.PlayerResourcePackStatusEvent;
- 
++
 +public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection {
++    // CraftBukkit end
+     private static final Logger LOGGER = LogUtils.getLogger();
+     public static final int LATENCY_CHECK_INTERVAL = 15000;
+     private static final int CLOSED_LISTENER_TIMEOUT = 15000;
+     private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout");
+     static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response");
+     protected final MinecraftServer server;
+-    protected final Connection connection;
++    public final Connection connection; // Paper
+     private final boolean transferred;
+-    private long keepAliveTime;
++    private long keepAliveTime = Util.getMillis(); // Paper
+     private boolean keepAlivePending;
+     private long keepAliveChallenge;
+     private long closedListenerTime;
+     private boolean closed = false;
+     private int latency;
+     private volatile boolean suspendFlushingOnServerThread = false;
++    // CraftBukkit start
++    protected final ServerPlayer player;
++    protected final org.bukkit.craftbukkit.CraftServer cserver;
++    public boolean processedDisconnect;
++    // CraftBukkit end
++    public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
++    private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
++    protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
+ 
+-    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
++    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { // CraftBukkit
+         this.server = server;
+         this.connection = connection;
+         this.keepAliveTime = Util.getMillis();
+         this.latency = cookie.latency();
+         this.transferred = cookie.transferred();
+-    }
++        // CraftBukkit start - add fields and methods
++        this.player = player;
++        this.player.transferCookieConnection = this;
++        this.cserver = server.server;
++    }
++
++    public CraftPlayer getCraftPlayer() {
++        return this.player == null ? null : this.player.getBukkitEntity();
++    }
 +
 +    @Override
 +    public boolean isTransferred() {
@@ -62,97 +85,45 @@
 +        this.disconnect(reason, cause); // Paper - kick event causes
 +    }
 +    // CraftBukkit end
-     private static final Logger LOGGER = LogUtils.getLogger();
-     public static final int LATENCY_CHECK_INTERVAL = 15000;
-     private static final int CLOSED_LISTENER_TIMEOUT = 15000;
-     private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout");
-     static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response");
-     protected final MinecraftServer server;
--    protected final Connection connection;
-+    public final Connection connection; // Paper
-     private final boolean transferred;
--    private long keepAliveTime;
-+    private long keepAliveTime = Util.getMillis(); // Paper
-     private boolean keepAlivePending;
-     private long keepAliveChallenge;
-     private long closedListenerTime;
-     private boolean closed = false;
-     private int latency;
-     private volatile boolean suspendFlushingOnServerThread = false;
-+    public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
-+    private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
-+    protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
  
--    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) {
--        this.server = server;
--        this.connection = connection;
-+    public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
-+        this.server = minecraftserver;
-+        this.connection = networkmanager;
-         this.keepAliveTime = Util.getMillis();
--        this.latency = clientData.latency();
--        this.transferred = clientData.transferred();
-+        this.latency = commonlistenercookie.latency();
-+        this.transferred = commonlistenercookie.transferred();
-+        // CraftBukkit start - add fields and methods
-+        this.player = player;
-+        this.player.transferCookieConnection = this;
-+        this.cserver = minecraftserver.server;
-     }
-+    protected final ServerPlayer player;
-+    protected final org.bukkit.craftbukkit.CraftServer cserver;
-+    public boolean processedDisconnect;
- 
-+    public CraftPlayer getCraftPlayer() {
-+        return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
-+        // CraftBukkit end
-+    }
-+
      private void close() {
          if (!this.closed) {
-             this.closedListenerTime = Util.getMillis();
-@@ -65,6 +116,11 @@
+@@ -61,6 +_,12 @@
  
      @Override
-     public void onDisconnect(DisconnectionDetails info) {
+     public void onDisconnect(DisconnectionDetails details) {
 +        // Paper start - Fix kick event leave message not being sent
-+        this.onDisconnect(info, null);
++        this.onDisconnect(details, null);
 +    }
++
 +    public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
 +        // Paper end - Fix kick event leave message not being sent
          if (this.isSingleplayerOwner()) {
-             ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out");
+             LOGGER.info("Stopping singleplayer server as player logged out");
              this.server.halt(false);
-@@ -80,13 +136,14 @@
- 
-     @Override
-     public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
-+        //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async
-         if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) {
-             int i = (int) (Util.getMillis() - this.keepAliveTime);
- 
+@@ -80,7 +_,7 @@
              this.latency = (this.latency * 3 + i) / 4;
              this.keepAlivePending = false;
          } else if (!this.isSingleplayerOwner()) {
--            this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
-+            this.disconnectAsync(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
+-            this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
++            this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
          }
- 
      }
-@@ -94,38 +151,127 @@
-     @Override
-     public void handlePong(ServerboundPongPacket packet) {}
  
-+    // CraftBukkit start
-+    private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register");
-+    private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister");
+@@ -88,37 +_,124 @@
+     public void handlePong(ServerboundPongPacket packet) {
+     }
+ 
++    private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); // CraftBukkit
++    private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit
 +
      @Override
--    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {}
-+    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+     public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+-    }
++        // CraftBukkit start
 +        // Paper start - Brand support
-+        if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) {
-+            this.player.clientBrandName = brandPayload.brand();
++        if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) {
++            this.player.clientBrandName = brand;
 +        }
 +        // Paper end - Brand support
 +        if (!(packet.payload() instanceof DiscardedPayload)) {
@@ -161,7 +132,7 @@
 +        PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        ResourceLocation identifier = packet.payload().type().id();
 +        ByteBuf payload = ((DiscardedPayload)packet.payload()).data();
- 
++
 +        if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) {
 +            try {
 +                String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
@@ -169,7 +140,7 @@
 +                    this.getCraftPlayer().addChannel(channel);
 +                }
 +            } catch (Exception ex) {
-+                ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
++                ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex);
 +                this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
@@ -179,7 +150,7 @@
 +                    this.getCraftPlayer().removeChannel(channel);
 +                }
 +            } catch (Exception ex) {
-+                ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
++                ServerGamePacketListenerImpl.LOGGER.error("Couldn't unregister custom payload", ex);
 +                this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        } else {
@@ -197,26 +168,26 @@
 +                // Paper end - Brand support
 +                this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data);
 +            } catch (Exception ex) {
-+                ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex);
++                ServerGamePacketListenerImpl.LOGGER.error("Couldn't dispatch custom payload", ex);
 +                this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        }
-+
 +    }
 +
 +    public final boolean isDisconnected() {
 +        return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs
 +    }
 +    // CraftBukkit end
-+
+ 
      @Override
      public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
-         PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server);
+         PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
          if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
-             ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
--            this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
-+            this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
-         }
+             LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
+-            this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
+-        }
++            this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
++        }
 +        // Paper start - adventure pack callbacks
 +        // call the callbacks before the previously-existing event so the event has final say
 +        final net.kyori.adventure.resource.ResourcePackCallback callback;
@@ -231,43 +202,41 @@
 +        // Paper end
 +        // Paper start - store last pack status
 +        PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()];
-+        player.getBukkitEntity().resourcePackStatus = packStatus;
++        this.player.getBukkitEntity().resourcePackStatus = packStatus;
 +        this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit
 +        // Paper end - store last pack status
- 
      }
  
      @Override
      public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
--        this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
+-        this.disconnect(DISCONNECT_UNEXPECTED_QUERY);
 +        // CraftBukkit start
-+        PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server);
++        PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
 +        if (this.player.getBukkitEntity().handleCookieResponse(packet)) {
 +            return;
 +        }
 +        // CraftBukkit end
-+        this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
++        this.disconnect(DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
      }
  
      protected void keepConnectionAlive() {
          Profiler.get().push("keepAlive");
--        long i = Util.getMillis();
+-        long millis = Util.getMillis();
+-        if (!this.isSingleplayerOwner() && millis - this.keepAliveTime >= 15000L) {
+-            if (this.keepAlivePending) {
+-                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
+-            } else if (this.checkIfClosed(millis)) {
 +        // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
 +        // This should effectively place the keepalive handling back to "as it was" before 1.12.2
 +        long currentTime = Util.getMillis();
 +        long elapsedTime = currentTime - this.keepAliveTime;
- 
--        if (!this.isSingleplayerOwner() && i - this.keepAliveTime >= 15000L) {
--            if (this.keepAlivePending) {
--                this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
--            } else if (this.checkIfClosed(i)) {
 +        if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
 +            if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
-+                this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
++                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
 +            } else if (this.checkIfClosed(currentTime)) { // Paper
                  this.keepAlivePending = true;
--                this.keepAliveTime = i;
--                this.keepAliveChallenge = i;
+-                this.keepAliveTime = millis;
+-                this.keepAliveChallenge = millis;
 +                this.keepAliveTime = currentTime;
 +                this.keepAliveChallenge = currentTime;
                  this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge));
@@ -277,31 +246,30 @@
  
          Profiler.get().pop();
      }
-@@ -133,7 +279,7 @@
+@@ -126,7 +_,7 @@
      private boolean checkIfClosed(long time) {
          if (this.closed) {
              if (time - this.closedListenerTime >= 15000L) {
--                this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
-+                this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
+-                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
++                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
              }
  
              return false;
-@@ -156,6 +302,14 @@
+@@ -149,6 +_,13 @@
      }
  
-     public void send(Packet<?> packet, @Nullable PacketSendListener callbacks) {
+     public void send(Packet<?> packet, @Nullable PacketSendListener listener) {
 +        // CraftBukkit start
 +        if (packet == null || this.processedDisconnect) { // Spigot
 +            return;
-+        } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) {
-+            ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet;
-+            this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld());
++        } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) {
++            this.player.compassTarget = CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld());
 +        }
 +        // CraftBukkit end
          if (packet.isTerminal()) {
              this.close();
          }
-@@ -175,22 +329,109 @@
+@@ -165,19 +_,108 @@
          }
      }
  
@@ -309,27 +277,28 @@
 +    public void disconnect(final net.kyori.adventure.text.Component reason) {
 +        this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
 +    }
++
 +    public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
 +        this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
-+        // Paper end - kick event causes
 +    }
-+    // Paper end - adventure
 +
++    // Paper end - adventure
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes
      public void disconnect(Component reason) {
 -        this.disconnect(new DisconnectionDetails(reason));
+-    }
+-
+-    public void disconnect(DisconnectionDetails disconnectionDetails) {
 +        // Paper start - kick event causes
 +        this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
-     }
++    }
++
 +    public void disconnect(final Component reason, PlayerKickEvent.Cause cause) {
 +        this.disconnect(new DisconnectionDetails(reason), cause);
 +        // Paper end - kick event causes
 +    }
- 
--    public void disconnect(DisconnectionDetails disconnectionInfo) {
--        this.connection.send(new ClientboundDisconnectPacket(disconnectionInfo.reason()), PacketSendListener.thenRun(() -> {
--            this.connection.disconnect(disconnectionInfo);
-+    public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause
++
++    public void disconnect(DisconnectionDetails disconnectionDetails, PlayerKickEvent.Cause cause) { // Paper - kick event cause
 +        // CraftBukkit start - fire PlayerKickEvent
 +        if (this.processedDisconnect) {
 +            return;
@@ -338,7 +307,7 @@
 +            Waitable waitable = new Waitable() {
 +                @Override
 +                protected Object evaluate() {
-+                    ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes
++                    ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes
 +                    return null;
 +                }
 +            };
@@ -357,7 +326,7 @@
 +
 +        net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure
 +
-+        PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
++        PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
 +
 +        if (this.cserver.getServer().isRunning()) {
 +            this.cserver.getPluginManager().callEvent(event);
@@ -368,26 +337,26 @@
 +            return;
 +        }
 +        // Send the possibly modified leave message
-+        this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message
++        this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionDetails.report(), disconnectionDetails.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message
 +    }
 +
-+    private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message
++    private void disconnect0(DisconnectionDetails disconnectionDetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message
 +        // CraftBukkit end
 +        this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
-+        this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> {
-+            this.connection.disconnect(disconnectiondetails);
-         }));
-+        this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
-         this.connection.setReadOnly();
-         MinecraftServer minecraftserver = this.server;
-         Connection networkmanager = this.connection;
- 
-         Objects.requireNonNull(this.connection);
--        minecraftserver.executeBlocking(networkmanager::handleDisconnection);
+         this.connection
+             .send(
+                 new ClientboundDisconnectPacket(disconnectionDetails.reason()),
+                 PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
+             );
+-        this.connection.setReadOnly();
+-        this.server.executeBlocking(this.connection::handleDisconnection);
+-    }
++        this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
++        this.connection.setReadOnly();
 +        // CraftBukkit - Don't wait
-+        minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper
-     }
- 
++        this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper
++    }
++
 +    // Paper start - add proper async disconnect
 +    public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
 +        this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
@@ -402,6 +371,7 @@
 +            this.disconnect(disconnectionInfo, cause);
 +            return;
 +        }
++
 +        this.connection.setReadOnly();
 +        this.server.scheduleOnMain(() -> {
 +            ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause);
@@ -412,7 +382,6 @@
 +        });
 +    }
 +    // Paper end - add proper async disconnect
-+
+ 
      protected boolean isSingleplayerOwner() {
          return this.server.isSingleplayerOwner(this.playerProfile());
-     }
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..08bccdf22e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+@@ -48,8 +_,10 @@
+     @Nullable
+     private SynchronizeRegistriesTask synchronizeRegistriesTask;
+ 
+-    public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
+-        super(server, connection, cookie);
++    // CraftBukkit start
++    public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) {
++        super(server, connection, cookie, player);
++        // CraftBukkit end
+         this.gameProfile = cookie.gameProfile();
+         this.clientInformation = cookie.clientInformation();
+     }
+@@ -61,6 +_,10 @@
+ 
+     @Override
+     public void onDisconnect(DisconnectionDetails details) {
++        // Paper start - Debugging
++        if (this.server.isDebugging()) {
++            ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, details.reason().getString(), this.currentTask != null ? this.currentTask.type().id() : "null");
++        } else // Paper end
+         LOGGER.info("{} lost connection: {}", this.gameProfile, details.reason().getString());
+         super.onDisconnect(details);
+     }
+@@ -73,6 +_,12 @@
+     public void startConfiguration() {
+         this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName())));
+         ServerLinks serverLinks = this.server.serverLinks();
++        // CraftBukkit start
++        org.bukkit.craftbukkit.CraftServerLinks wrapper = new org.bukkit.craftbukkit.CraftServerLinks(serverLinks);
++        org.bukkit.event.player.PlayerLinksSendEvent event = new org.bukkit.event.player.PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper);
++        this.cserver.getPluginManager().callEvent(event);
++        serverLinks = wrapper.getServerLinks();
++        // CraftBukkit end
+         if (!serverLinks.isEmpty()) {
+             this.send(new ClientboundServerLinksPacket(serverLinks.untrust()));
+         }
+@@ -105,6 +_,7 @@
+     @Override
+     public void handleClientInformation(ServerboundClientInformationPacket packet) {
+         this.clientInformation = packet.information();
++        this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper
+     }
+ 
+     @Override
+@@ -139,16 +_,21 @@
+                 return;
+             }
+ 
+-            Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
++            Component component = null; // CraftBukkit - login checks already completed
+             if (component != null) {
+                 this.disconnect(component);
+                 return;
+             }
+ 
+-            ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation);
++            ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
+             playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
+         } catch (Exception var5) {
+             LOGGER.error("Couldn't place player in world", (Throwable)var5);
++            // Paper start - Debugging
++            if (this.server.isDebugging()) {
++                var5.printStackTrace();
++            }
++            // Paper end - Debugging
+             this.connection.send(new ClientboundDisconnectPacket(DISCONNECT_REASON_INVALID_DATA));
+             this.connection.disconnect(DISCONNECT_REASON_INVALID_DATA);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
new file mode 100644
index 0000000000..fa6e15d0d7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -0,0 +1,154 @@
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -49,10 +_,10 @@
+ public class ServerConnectionListener {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(
+-        () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).build())
++        () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
+     );
+     public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(
+-        () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build())
++        () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
+     );
+     final MinecraftServer server;
+     public volatile boolean running;
+@@ -64,12 +_,35 @@
+         this.running = true;
+     }
+ 
++    // Paper start - prevent blocking on adding a new connection while the server is ticking
++    private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
++
++    private final void addPending() {
++        Connection connection;
++        while ((connection = this.pending.poll()) != null) {
++            this.connections.add(connection);
++        }
++    }
++    // Paper end - prevent blocking on adding a new connection while the server is ticking
++
+     public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
++        // Paper start - Unix domain socket support
++        this.startTcpServerListener(new java.net.InetSocketAddress(address, port));
++    }
++
++    public void startTcpServerListener(SocketAddress address) throws IOException {
++        // Paper end - Unix domain socket support
+         synchronized (this.channels) {
+-            Class<? extends ServerSocketChannel> clazz;
++            Class<? extends io.netty.channel.ServerChannel> clazz; // Paper - Unix domain socket support
+             EventLoopGroup eventLoopGroup;
+             if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
++                // Paper start - Unix domain socket support
++                if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
++                    clazz = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
++                } else {
+                 clazz = EpollServerSocketChannel.class;
++                }
++                // Paper end - Unix domain socket support
+                 eventLoopGroup = SERVER_EPOLL_EVENT_GROUP.get();
+                 LOGGER.info("Using epoll channel type");
+             } else {
+@@ -78,6 +_,12 @@
+                 LOGGER.info("Using default channel type");
+             }
+ 
++            // Paper start - Warn people with console access that HAProxy is in use.
++            if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
++                LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
++            }
++            // Paper end - Warn people with console access that HAProxy is in use.
++
+             this.channels
+                 .add(
+                     new ServerBootstrap()
+@@ -101,22 +_,64 @@
+                                     Connection connection = (Connection)(rateLimitPacketsPerSecond > 0
+                                         ? new RateKickingConnection(rateLimitPacketsPerSecond)
+                                         : new Connection(PacketFlow.SERVERBOUND));
+-                                    ServerConnectionListener.this.connections.add(connection);
++                                    // ServerConnectionListener.this.connections.add(connection);
++                                    // Paper start - Add support for Proxy Protocol
++                                    if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
++                                        channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
++                                        channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
++                                            @Override
++                                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
++                                                if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
++                                                    if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
++                                                        String realaddress = message.sourceAddress();
++                                                        int realport = message.sourcePort();
++
++                                                        SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
++
++                                                        Connection connection = (Connection) channel.pipeline().get("packet_handler");
++                                                        connection.address = socketaddr;
++                                                        // Paper start - Add API to get player's proxy address
++                                                        final String proxyAddress = message.destinationAddress();
++                                                        final int proxyPort = message.destinationPort();
++
++                                                        connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort);
++                                                        // Paper end - Add API to get player's proxy address
++                                                    }
++                                                } else {
++                                                    super.channelRead(ctx, msg);
++                                                }
++                                            }
++                                        });
++                                    }
++                                    // Paper end - Add support for proxy protocol
++                                    pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
+                                     connection.configurePacketHandler(channelPipeline);
+                                     connection.setListenerForServerboundHandshake(
+                                         new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
+                                     );
++                                    io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
+                                 }
+                             }
+                         )
+                         .group(eventLoopGroup)
+-                        .localAddress(address, port)
++                        .localAddress(address) // Paper - Unix domain socket support
++                        .option(ChannelOption.AUTO_READ, false) // CraftBukkit
+                         .bind()
+                         .syncUninterruptibly()
+                 );
+         }
+     }
+ 
++    // CraftBukkit start
++    public void acceptConnections() {
++        synchronized (this.channels) {
++            for (ChannelFuture future : this.channels) {
++                future.channel().config().setAutoRead(true);
++            }
++        }
++    }
++    // CraftBukkit end
++
+     public SocketAddress startMemoryChannel() {
+         ChannelFuture channelFuture;
+         synchronized (this.channels) {
+@@ -161,6 +_,13 @@
+ 
+     public void tick() {
+         synchronized (this.connections) {
++            // Spigot start
++            this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
++            // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
++            if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) {
++                Collections.shuffle(this.connections);
++            }
++            // Spigot end
+             Iterator<Connection> iterator = this.connections.iterator();
+ 
+             while (iterator.hasNext()) {
+@@ -180,6 +_,7 @@
+                             connection.setReadOnly();
+                         }
+                     } else {
++                        if (connection.preparing) continue; // Spigot - Fix a race condition where a NetworkManager could be unregistered just before connection
+                         iterator.remove();
+                         connection.handleDisconnection();
+                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 6c1745ae43..80b64ccf25 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -1,93 +1,34 @@
 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -45,6 +45,7 @@
- import net.minecraft.network.Connection;
- import net.minecraft.network.DisconnectionDetails;
- import net.minecraft.network.TickablePacketListener;
-+import net.minecraft.network.chat.ChatDecorator;
- import net.minecraft.network.chat.ChatType;
- import net.minecraft.network.chat.Component;
- import net.minecraft.network.chat.LastSeenMessages;
-@@ -65,12 +66,15 @@
- import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket;
- import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
- import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
-+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
- import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket;
- import net.minecraft.network.protocol.game.ClientboundMoveVehiclePacket;
- import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket;
- import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
- import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
- import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
- import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket;
- import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket;
- import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
-@@ -148,14 +152,13 @@
- import net.minecraft.world.entity.ExperienceOrb;
- import net.minecraft.world.entity.HasCustomInventoryScreen;
- import net.minecraft.world.entity.LivingEntity;
-+import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.MoverType;
- import net.minecraft.world.entity.PlayerRideableJumping;
- import net.minecraft.world.entity.PositionMoveRotation;
- import net.minecraft.world.entity.Relative;
--import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.ChatVisiblity;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.player.PlayerModelPart;
- import net.minecraft.world.entity.player.ProfilePublicKey;
- import net.minecraft.world.entity.projectile.AbstractArrow;
-@@ -176,6 +179,7 @@
- import net.minecraft.world.item.crafting.RecipeHolder;
- import net.minecraft.world.item.crafting.RecipeManager;
- import net.minecraft.world.level.BaseCommandBlock;
-+import net.minecraft.world.level.ClipContext;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.GameType;
- import net.minecraft.world.level.Level;
-@@ -192,11 +196,72 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
-+import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.BooleanOp;
- import net.minecraft.world.phys.shapes.Shapes;
+@@ -193,6 +_,59 @@
  import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.NamespacedKey;
  import org.slf4j.Logger;
-+
+ 
 +// CraftBukkit start
 +import io.papermc.paper.adventure.ChatProcessor; // Paper
 +import io.papermc.paper.adventure.PaperAdventure; // Paper
 +import com.mojang.datafixers.util.Pair;
 +import java.util.Arrays;
 +import java.util.concurrent.ExecutionException;
-+import java.util.concurrent.atomic.AtomicInteger;
 +import java.util.function.Function;
++import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
 +import net.minecraft.network.chat.OutgoingChatMessage;
 +import net.minecraft.world.entity.animal.Bucketable;
 +import net.minecraft.world.entity.animal.allay.Allay;
-+import net.minecraft.world.entity.item.ItemEntity;
++import net.minecraft.world.inventory.AbstractContainerMenu;
 +import net.minecraft.world.inventory.Slot;
-+import net.minecraft.world.item.crafting.RecipeHolder;
++import net.minecraft.world.level.ClipContext;
++import net.minecraft.world.phys.HitResult;
 +import org.bukkit.Location;
++import org.bukkit.NamespacedKey;
 +import org.bukkit.craftbukkit.CraftInput;
 +import org.bukkit.craftbukkit.entity.CraftEntity;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.craftbukkit.inventory.CraftItemStack;
 +import org.bukkit.craftbukkit.inventory.CraftItemType;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
 +import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
 +import org.bukkit.craftbukkit.util.LazyPlayerSet;
 +import org.bukkit.craftbukkit.util.Waitable;
-+import org.bukkit.entity.Player;
 +import org.bukkit.event.Event;
 +import org.bukkit.event.block.Action;
 +import org.bukkit.event.inventory.ClickType;
@@ -98,8 +39,6 @@
 +import org.bukkit.event.inventory.InventoryType.SlotType;
 +import org.bukkit.event.inventory.SmithItemEvent;
 +import org.bukkit.event.player.AsyncPlayerChatEvent;
-+import org.bukkit.event.player.PlayerAnimationEvent;
-+import org.bukkit.event.player.PlayerAnimationType;
 +import org.bukkit.event.player.PlayerChatEvent;
 +import org.bukkit.event.player.PlayerCommandPreprocessEvent;
 +import org.bukkit.event.player.PlayerInputEvent;
@@ -114,14 +53,14 @@
 +import org.bukkit.event.player.PlayerToggleSneakEvent;
 +import org.bukkit.event.player.PlayerToggleSprintEvent;
 +import org.bukkit.inventory.CraftingInventory;
-+import org.bukkit.inventory.EquipmentSlot;
 +import org.bukkit.inventory.InventoryView;
 +import org.bukkit.inventory.SmithingInventory;
 +// CraftBukkit end
- 
- public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener {
- 
-@@ -212,7 +277,9 @@
++
+ public class ServerGamePacketListenerImpl
+     extends ServerCommonPacketListenerImpl
+     implements ServerGamePacketListener,
+@@ -210,7 +_,9 @@
      private int tickCount;
      private int ackBlockChangesUpTo = -1;
      private final TickThrottler chatSpamThrottler = new TickThrottler(20, 200);
@@ -131,33 +70,11 @@
      private double firstGoodX;
      private double firstGoodY;
      private double firstGoodZ;
-@@ -240,14 +307,16 @@
+@@ -236,22 +_,39 @@
+     private int receivedMovePacketCount;
+     private int knownMovePacketCount;
      private boolean receivedMovementThisTick;
-     @Nullable
-     private RemoteChatSession chatSession;
-+    private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins
-     private SignedMessageChain.Decoder signedMessageDecoder;
-     private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20);
-     private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault();
-     private final FutureChain chatMessageChain;
-     private boolean waitingForSwitchToConfig;
-+    private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length
- 
-     public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
--        super(server, connection, clientData);
-+        super(server, connection, clientData, player); // CraftBukkit
-         this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection());
-         this.player = player;
-         player.connection = this;
-@@ -256,9 +325,25 @@
- 
-         Objects.requireNonNull(server);
-         this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, server::enforceSecureProfile);
--        this.chatMessageChain = new FutureChain(server);
-+        this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat
-     }
- 
-+    // CraftBukkit start - add fields and methods
++    // CraftBukkit start - add fields
 +    private int lastTick = MinecraftServer.currentTick;
 +    private int allowedPlayerTicks = 1;
 +    private int lastDropTick = MinecraftServer.currentTick;
@@ -172,53 +89,72 @@
 +    private float lastYaw = Float.MAX_VALUE;
 +    private boolean justTeleported = false;
 +    // CraftBukkit end
-+
+     @Nullable
+     private RemoteChatSession chatSession;
++    private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins
+     private SignedMessageChain.Decoder signedMessageDecoder;
+     private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20);
+     private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault();
+     private final FutureChain chatMessageChain;
+     private boolean waitingForSwitchToConfig;
++    private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length
+ 
+     public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
+-        super(server, connection, cookie);
++        super(server, connection, cookie, player); // CraftBukkit
+         this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection());
+         this.player = player;
+         player.connection = this;
+         player.getTextFilter().join();
+         this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile);
+-        this.chatMessageChain = new FutureChain(server);
++        this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat
+     }
+ 
      @Override
-     public void tick() {
-         if (this.ackBlockChangesUpTo > -1) {
-@@ -277,7 +362,7 @@
+@@ -272,7 +_,7 @@
          if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) {
              if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) {
-                 ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString());
--                this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying"));
+                 LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString());
+-                this.disconnect(Component.translatable("multiplayer.disconnect.flying"));
 +                this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause
                  return;
              }
          } else {
-@@ -296,7 +381,7 @@
+@@ -291,7 +_,7 @@
              if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) {
                  if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) {
-                     ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString());
--                    this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying"));
+                     LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString());
+-                    this.disconnect(Component.translatable("multiplayer.disconnect.flying"));
 +                    this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause
                      return;
                  }
              } else {
-@@ -311,11 +396,21 @@
- 
+@@ -307,11 +_,20 @@
          this.keepConnectionAlive();
          this.chatSpamThrottler.tick();
+         this.dropSpamThrottler.tick();
 +        this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits
 +        this.recipeSpamPackets.tick(); // Paper - auto recipe limit
-         this.dropSpamThrottler.tick();
--        if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) {
--            this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"));
-+        if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
+         if (this.player.getLastActionTime() > 0L
+             && this.server.getPlayerIdleTimeout() > 0
+-            && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L) {
+-            this.disconnect(Component.translatable("multiplayer.disconnect.idling"));
+-        }
++            && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
 +            this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
-+            this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
-         }
- 
++            this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
++        }
 +        // Paper start - Prevent causing expired keys from impacting new joins
-+        if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) {
-+            LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString());
-+            hasLoggedExpiry = true;
++        if (!this.hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) {
++            LOGGER.info("Player profile key for {} has expired!", this.player.getGameProfile().getName());
++            this.hasLoggedExpiry = true;
 +        }
 +        // Paper end - Prevent causing expired keys from impacting new joins
-+
      }
  
-     private int getMaximumFlyingTicks(Entity vehicle) {
-@@ -376,6 +471,12 @@
+     private int getMaximumFlyingTicks(Entity entity) {
+@@ -371,6 +_,12 @@
      @Override
      public void handlePlayerInput(ServerboundPlayerInputPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -231,28 +167,21 @@
          this.player.setLastClientInput(packet.input());
      }
  
-@@ -395,27 +496,84 @@
+@@ -390,17 +_,29 @@
      public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
-         if (ServerGamePacketListenerImpl.containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) {
--            this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"));
-+            this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause
+         if (containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) {
+-            this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"));
++            this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause
          } else if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) {
-             Entity entity = this.player.getRootVehicle();
+             Entity rootVehicle = this.player.getRootVehicle();
 +            // Paper start - Don't allow vehicle movement from players while teleporting
-+            if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) {
++            if (this.awaitingPositionFromClient != null || this.player.isImmobile() || rootVehicle.isRemoved()) {
 +                return;
 +            }
 +            // Paper end - Don't allow vehicle movement from players while teleporting
- 
-             if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) {
-                 ServerLevel worldserver = this.player.serverLevel();
--                double d0 = entity.getX();
--                double d1 = entity.getY();
--                double d2 = entity.getZ();
--                double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x());
--                double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y());
--                double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z());
+             if (rootVehicle != this.player && rootVehicle.getControllingPassenger() == this.player && rootVehicle == this.lastVehicle) {
+                 ServerLevel serverLevel = this.player.serverLevel();
 +                // CraftBukkit - store current player position
 +                double prevX = this.player.getX();
 +                double prevY = this.player.getY();
@@ -260,31 +189,35 @@
 +                float prevYaw = this.player.getYRot();
 +                float prevPitch = this.player.getXRot();
 +                // CraftBukkit end
-+                double d0 = entity.getX(); final double fromX = d0; // Paper - OBFHELPER
-+                double d1 = entity.getY(); final double fromY = d1; // Paper - OBFHELPER
-+                double d2 = entity.getZ(); final double fromZ = d2; // Paper - OBFHELPER
-+                double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x()); final double toX = d3; // Paper - OBFHELPER
-+                double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y()); final double toY = d4; // Paper - OBFHELPER
-+                double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z()); final double toZ = d5; // Paper - OBFHELPER
+                 double x = rootVehicle.getX();
+                 double y = rootVehicle.getY();
+                 double z = rootVehicle.getZ();
+-                double d = clampHorizontal(packet.position().x());
+-                double d1 = clampVertical(packet.position().y());
+-                double d2 = clampHorizontal(packet.position().z());
++                double d = clampHorizontal(packet.position().x()); final double toX = d; // Paper - OBFHELPER
++                double d1 = clampVertical(packet.position().y()); final double toY = d1; // Paper - OBFHELPER
++                double d2 = clampHorizontal(packet.position().z()); final double toZ = d2; // Paper - OBFHELPER
                  float f = Mth.wrapDegrees(packet.yRot());
                  float f1 = Mth.wrapDegrees(packet.xRot());
-                 double d6 = d3 - this.vehicleFirstGoodX;
-                 double d7 = d4 - this.vehicleFirstGoodY;
-                 double d8 = d5 - this.vehicleFirstGoodZ;
-                 double d9 = entity.getDeltaMovement().lengthSqr();
--                double d10 = d6 * d6 + d7 * d7 + d8 * d8;
+                 double d3 = d - this.vehicleFirstGoodX;
+@@ -408,7 +_,54 @@
+                 double d5 = d2 - this.vehicleFirstGoodZ;
+                 double d6 = rootVehicle.getDeltaMovement().lengthSqr();
+                 double d7 = d3 * d3 + d4 * d4 + d5 * d5;
+-                if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
 +                // Paper start - fix large move vectors killing the server
-+                double currDeltaX = toX - fromX;
-+                double currDeltaY = toY - fromY;
-+                double currDeltaZ = toZ - fromZ;
-+                double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
++                double currDeltaX = toX - x;
++                double currDeltaY = toY - y;
++                double currDeltaZ = toZ - z;
++                double d10 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
 +                double otherFieldX = d3 - this.vehicleLastGoodX;
 +                double otherFieldY = d4 - this.vehicleLastGoodY;
 +                double otherFieldZ = d5 - this.vehicleLastGoodZ;
 +                d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
 +                // Paper end - fix large move vectors killing the server
- 
--                if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) {
++                //if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
++
 +                // CraftBukkit start - handle custom speeds and skipped ticks
 +                this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
 +                this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
@@ -312,55 +245,53 @@
 +
 +                // Paper start - Prevent moving into unloaded chunks
 +                if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (
-+                    !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) ||
-+                        !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position())))
-+                    )) {
-+                    this.connection.send(ClientboundMoveVehiclePacket.fromEntity(entity));
++                    !serverLevel.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) ||
++                        !serverLevel.areChunksLoadedForMove(rootVehicle.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(rootVehicle.position())))
++                )) {
++                    this.connection.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
 +                    return;
 +                }
 +                // Paper end - Prevent moving into unloaded chunks
-+
-+                if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
-+                // CraftBukkit end
-                     ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8});
-                     this.send(ClientboundMoveVehiclePacket.fromEntity(entity));
-                     return;
-@@ -423,9 +581,9 @@
- 
-                 boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
- 
--                d6 = d3 - this.vehicleLastGoodX;
--                d7 = d4 - this.vehicleLastGoodY;
--                d8 = d5 - this.vehicleLastGoodZ;
-+                d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
-+                d7 = d4 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
-+                d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
-                 boolean flag1 = entity.verticalCollisionBelow;
- 
-                 if (entity instanceof LivingEntity) {
-@@ -449,19 +607,72 @@
-                 d10 = d6 * d6 + d7 * d7 + d8 * d8;
-                 boolean flag2 = false;
- 
--                if (d10 > 0.0625D) {
-+                if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
-                     flag2 = true;
-                     ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)});
++                if (d7 - d6 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
++                    // CraftBukkit end
+                     LOGGER.warn(
+                         "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5
+                     );
+@@ -417,9 +_,9 @@
                  }
  
-                 entity.absMoveTo(d3, d4, d5, f, f1);
-+                this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
-                 boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
+                 boolean flag = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
+-                d3 = d - this.vehicleLastGoodX;
+-                d4 = d1 - this.vehicleLastGoodY;
+-                d5 = d2 - this.vehicleLastGoodZ;
++                d3 = d - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
++                d4 = d1 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
++                d5 = d2 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
+                 boolean flag1 = rootVehicle.verticalCollisionBelow;
+                 if (rootVehicle instanceof LivingEntity livingEntity && livingEntity.onClimbable()) {
+                     livingEntity.resetFallDistance();
+@@ -435,18 +_,71 @@
+                 d5 = d2 - rootVehicle.getZ();
+                 d7 = d3 * d3 + d4 * d4 + d5 * d5;
+                 boolean flag2 = false;
+-                if (d7 > 0.0625) {
++                if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
+                     flag2 = true;
+                     LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
+                 }
  
+                 rootVehicle.absMoveTo(d, d1, d2, f, f1);
++                this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+                 boolean flag3 = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
                  if (flag && (flag2 || !flag3)) {
-                     entity.absMoveTo(d0, d1, d2, f, f1);
-+                    this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
-                     this.send(ClientboundMoveVehiclePacket.fromEntity(entity));
+                     rootVehicle.absMoveTo(x, y, z, f, f1);
++                    this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+                     this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
                      return;
-+                }
+                 }
 +
 +                // CraftBukkit start - fire PlayerMoveEvent
-+                Player player = this.getCraftPlayer();
++                org.bukkit.entity.Player player = this.getCraftPlayer();
 +                if (!this.hasMoved) {
 +                    this.lastPosX = prevX;
 +                    this.lastPosY = prevY;
@@ -407,32 +338,35 @@
 +                        this.justTeleported = false;
 +                        return;
 +                    }
-                 }
++                }
 +                // CraftBukkit end
  
                  this.player.serverLevel().getChunkSource().move(this.player);
-                 entity.recordMovementThroughBlocks(new Vec3(d0, d1, d2), entity.position());
-@@ -489,16 +700,17 @@
+                 rootVehicle.recordMovementThroughBlocks(new Vec3(x, y, z), rootVehicle.position());
+@@ -478,12 +_,12 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (packet.getId() == this.awaitingTeleport) {
              if (this.awaitingPositionFromClient == null) {
--                this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"));
-+                this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+-                this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"));
++                this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
                  return;
              }
  
--            this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot());
-+            this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported
-             this.lastGoodX = this.awaitingPositionFromClient.x;
-             this.lastGoodY = this.awaitingPositionFromClient.y;
+             this.player
+-                .absMoveTo(
++                .moveTo( // Paper - Fix Entity Teleportation and cancel velocity if teleported
+                     this.awaitingPositionFromClient.x,
+                     this.awaitingPositionFromClient.y,
+                     this.awaitingPositionFromClient.z,
+@@ -495,6 +_,7 @@
              this.lastGoodZ = this.awaitingPositionFromClient.z;
              this.player.hasChangedDimension();
              this.awaitingPositionFromClient = null;
 +            this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit
          }
- 
      }
-@@ -528,6 +740,7 @@
+ 
+@@ -521,6 +_,7 @@
      @Override
      public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -440,18 +374,19 @@
          this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering());
      }
  
-@@ -545,21 +758,104 @@
- 
+@@ -536,14 +_,84 @@
+         }
      }
  
 +    // Paper start - AsyncTabCompleteEvent
 +    private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4,
-+        new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build());
++        new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER)).build());
 +    // Paper end - AsyncTabCompleteEvent
++
      @Override
      public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
 -        PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
-+        // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async
++        // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        // CraftBukkit start
 +        if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits
 +            this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause // Paper - add proper async disconnect
@@ -475,12 +410,13 @@
 +    }
 +
 +    private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) {
-         StringReader stringreader = new StringReader(packet.getCommand());
- 
-         if (stringreader.canRead() && stringreader.peek() == '/') {
-             stringreader.skip();
++        // Paper end - AsyncTabCompleteEvent
+         StringReader stringReader = new StringReader(packet.getCommand());
+         if (stringReader.canRead() && stringReader.peek() == '/') {
+             stringReader.skip();
          }
  
++        // Paper start - AsyncTabCompleteEvent
 +        final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), true, null);
 +        event.callEvent();
 +        final List<com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion> completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions();
@@ -491,9 +427,9 @@
 +            }
 +
 +            // This needs to be on main
-+            this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader));
++            this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringReader));
 +        } else if (!completions.isEmpty()) {
-+            final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength());
++            final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringReader.getTotalLength());
 +            final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1);
 +            for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) {
 +                final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion());
@@ -519,37 +455,35 @@
 +    }
 +    // Paper end - brig API
 +
-+    private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) {
++    private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringReader) {
 +        // Paper end - AsyncTabCompleteEvent
-         ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
-+        // Paper start - Handle non-recoverable exceptions
-+        if (!parseresults.getExceptions().isEmpty()
-+            && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) {
-+            this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM);
-+            return;
-+        }
-+        // Paper end - Handle non-recoverable exceptions
- 
-         this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
--            Suggestions suggestions1 = suggestions.getList().size() <= 1000 ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000));
--
--            this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1));
-+            // Paper start - Don't tab-complete namespaced commands if send-namespaced is false
-+            if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) {
-+                suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":"));
-+            }
-+            // Paper end - Don't tab-complete namespaced commands if send-namespaced is false
-+            // Paper start - Brigadier API
-+            com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand());
-+            suggestEvent.setCancelled(suggestions.isEmpty());
-+            if (suggestEvent.callEvent()) {
-+                this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS)));
-+            }
-+            // Paper end - Brigadier API
-         });
+         ParseResults<CommandSourceStack> parseResults = this.server.getCommands().getDispatcher().parse(stringReader, this.player.createCommandSourceStack());
+         this.server
+             .getCommands()
+@@ -551,10 +_,18 @@
+             .getCompletionSuggestions(parseResults)
+             .thenAccept(
+                 suggestions -> {
+-                    Suggestions suggestions1 = suggestions.getList().size() <= 1000
+-                        ? suggestions
+-                        : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000));
+-                    this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1));
++                    // Paper start - Don't tab-complete namespaced commands if send-namespaced is false
++                    if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) {
++                        suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":"));
++                    }
++                    // Paper end - Don't tab-complete namespaced commands if send-namespaced is false
++                    // Paper start - Brigadier API
++                    com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand());
++                    suggestEvent.setCancelled(suggestions.isEmpty());
++                    if (suggestEvent.callEvent()) {
++                        this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS)));
++                    }
++                    // Paper end - Brigadier API
+                 }
+             );
      }
- 
-@@ -568,7 +864,7 @@
+@@ -564,7 +_,7 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (!this.server.isCommandBlockEnabled()) {
              this.player.sendSystemMessage(Component.translatable("advMode.notEnabled"));
@@ -557,8 +491,8 @@
 +        } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission
              this.player.sendSystemMessage(Component.translatable("advMode.notAllowed"));
          } else {
-             BaseCommandBlock commandblocklistenerabstract = null;
-@@ -635,7 +931,7 @@
+             BaseCommandBlock baseCommandBlock = null;
+@@ -620,7 +_,7 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (!this.server.isCommandBlockEnabled()) {
              this.player.sendSystemMessage(Component.translatable("advMode.notEnabled"));
@@ -566,61 +500,60 @@
 +        } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission
              this.player.sendSystemMessage(Component.translatable("advMode.notAllowed"));
          } else {
-             BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level());
-@@ -668,7 +964,7 @@
-                 ItemStack itemstack = iblockdata.getCloneItemStack(worldserver, blockposition, flag);
- 
-                 if (!itemstack.isEmpty()) {
+             BaseCommandBlock commandBlock = packet.getCommandBlock(this.player.level());
+@@ -648,7 +_,7 @@
+                 boolean flag = this.player.hasInfiniteMaterials() && packet.includeData();
+                 ItemStack cloneItemStack = blockState.getCloneItemStack(serverLevel, blockPos, flag);
+                 if (!cloneItemStack.isEmpty()) {
 -                    if (flag) {
 +                    if (flag && this.player.getBukkitEntity().hasPermission("minecraft.nbt.copy")) { // Spigot
-                         ServerGamePacketListenerImpl.addBlockDataToItem(iblockdata, worldserver, blockposition, itemstack);
+                         addBlockDataToItem(blockState, serverLevel, blockPos, cloneItemStack);
                      }
  
-@@ -712,15 +1008,25 @@
+@@ -685,14 +_,24 @@
          if (stack.isItemEnabled(this.player.level().enabledFeatures())) {
-             Inventory playerinventory = this.player.getInventory();
-             int i = playerinventory.findSlotMatchingItem(stack);
+             Inventory inventory = this.player.getInventory();
+             int i = inventory.findSlotMatchingItem(stack);
 +            // Paper start - Add PlayerPickItemEvent
 +            final int sourceSlot = i;
-+            final int targetSlot = Inventory.isHotbarSlot(sourceSlot) ? sourceSlot : playerinventory.getSuitableHotbarSlot();
-+            final Player bukkitPlayer = this.player.getBukkitEntity();
++            final int targetSlot = Inventory.isHotbarSlot(sourceSlot) ? sourceSlot : inventory.getSuitableHotbarSlot();
++            final org.bukkit.entity.Player bukkitPlayer = this.player.getBukkitEntity();
 +            final io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot);
 +            if (!event.callEvent()) {
 +                return;
 +            }
 +            i = event.getSourceSlot();
- 
++            // Paper end - Add PlayerPickItemEvent
              if (i != -1) {
 -                if (Inventory.isHotbarSlot(i)) {
--                    playerinventory.selected = i;
-+                if (Inventory.isHotbarSlot(i) && Inventory.isHotbarSlot(event.getTargetSlot())) {
-+                    playerinventory.selected = event.getTargetSlot();
+-                    inventory.selected = i;
++                if (Inventory.isHotbarSlot(i) && Inventory.isHotbarSlot(event.getTargetSlot())) { // Paper - Add PlayerPickItemEvent
++                    inventory.selected = event.getTargetSlot(); // Paper - Add PlayerPickItemEvent
                  } else {
--                    playerinventory.pickSlot(i);
-+                    playerinventory.pickSlot(i, event.getTargetSlot());
+-                    inventory.pickSlot(i);
++                    inventory.pickSlot(i, event.getTargetSlot()); // Paper - Add PlayerPickItemEvent
                  }
              } else if (this.player.hasInfiniteMaterials()) {
--                playerinventory.addAndPickItem(stack);
-+                playerinventory.addAndPickItem(stack, event.getTargetSlot());
-+                // Paper end - Add PlayerPickItemEvent
+-                inventory.addAndPickItem(stack);
++                inventory.addAndPickItem(stack, event.getTargetSlot()); // Paper - Add PlayerPickItemEvent
              }
  
-             this.player.connection.send(new ClientboundSetHeldSlotPacket(playerinventory.selected));
-@@ -866,6 +1172,13 @@
-         AbstractContainerMenu container = this.player.containerMenu;
- 
-         if (container instanceof MerchantMenu containermerchant) {
+             this.player.connection.send(new ClientboundSetHeldSlotPacket(inventory.selected));
+@@ -814,6 +_,13 @@
+         PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+         int item = packet.getItem();
+         if (this.player.containerMenu instanceof MerchantMenu merchantMenu) {
 +            // CraftBukkit start
-+            final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant);
++            final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, item, merchantMenu);
 +            if (tradeSelectEvent.isCancelled()) {
-+                this.player.getBukkitEntity().updateInventory();
++                this.player.containerMenu.sendAllDataToRemote();
 +                return;
 +            }
 +            // CraftBukkit end
-             if (!containermerchant.stillValid(this.player)) {
-                 ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containermerchant);
+             if (!merchantMenu.stillValid(this.player)) {
+                 LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantMenu);
                  return;
-@@ -879,6 +1192,51 @@
+@@ -826,6 +_,51 @@
  
      @Override
      public void handleEditBook(ServerboundEditBookPacket packet) {
@@ -669,52 +602,43 @@
 +        }
 +        this.lastBookTick = MinecraftServer.currentTick;
 +        // CraftBukkit end
-         int i = packet.slot();
- 
-         if (Inventory.isHotbarSlot(i) || i == 40) {
-@@ -899,12 +1257,16 @@
+         int slot = packet.slot();
+         if (Inventory.isHotbarSlot(slot) || slot == 40) {
+             List<String> list = Lists.newArrayList();
+@@ -840,10 +_,14 @@
      }
  
-     private void updateBookContents(List<FilteredText> pages, int slotId) {
--        ItemStack itemstack = this.player.getInventory().getItem(slotId);
+     private void updateBookContents(List<FilteredText> pages, int index) {
+-        ItemStack item = this.player.getInventory().getItem(index);
 +        // CraftBukkit start
-+        ItemStack handItem = this.player.getInventory().getItem(slotId);
-+        ItemStack itemstack = handItem.copy();
++        ItemStack handItem = this.player.getInventory().getItem(index);
++        ItemStack item = handItem.copy();
 +        // CraftBukkit end
- 
-         if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
-             List<Filterable<String>> list1 = pages.stream().map(this::filterableFromOutgoing).toList();
- 
-             itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1));
-+            this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
+         if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
+             List<Filterable<String>> list = pages.stream().map(this::filterableFromOutgoing).toList();
+             item.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list));
++            this.player.getInventory().setItem(index, CraftEventFactory.handleEditBookEvent(this.player, index, handItem, item)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
          }
      }
  
-@@ -915,12 +1277,13 @@
-             ItemStack itemstack1 = itemstack.transmuteCopy(Items.WRITTEN_BOOK);
- 
-             itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT);
--            List<Filterable<Component>> list1 = pages.stream().map((filteredtext1) -> {
-+            List<Filterable<Component>> list1 = (List<Filterable<Component>>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error
-                 return this.filterableFromOutgoing(filteredtext1).map(Component::literal);
-             }).toList();
- 
-             itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true));
--            this.player.getInventory().setItem(slotId, itemstack1);
-+            CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit
-+            this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book
+@@ -857,7 +_,8 @@
+                 DataComponents.WRITTEN_BOOK_CONTENT,
+                 new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list, true)
+             );
+-            this.player.getInventory().setItem(index, itemStack);
++            CraftEventFactory.handleEditBookEvent(this.player, index, item, itemStack); // CraftBukkit
++            this.player.getInventory().setItem(index, item); // CraftBukkit - event factory updates the hand book
          }
      }
  
-@@ -978,26 +1341,34 @@
+@@ -901,24 +_,32 @@
      public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
-         if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
--            this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"));
-+            this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+         if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+-            this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"));
++            this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
          } else {
-             ServerLevel worldserver = this.player.serverLevel();
- 
+             ServerLevel serverLevel = this.player.serverLevel();
 -            if (!this.player.wonGame) {
 +            if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit
                  if (this.tickCount == 0) {
@@ -722,17 +646,16 @@
                  }
  
                  if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) {
--                    double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX()));
--                    double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY()));
--                    double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ()));
+-                    double d = clampHorizontal(packet.getX(this.player.getX()));
+-                    double d1 = clampVertical(packet.getY(this.player.getY()));
+-                    double d2 = clampHorizontal(packet.getZ(this.player.getZ()));
 -                    float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot()));
 -                    float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot()));
-+                    double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER
-+                    double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER
-+                    double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER
++                    double d = clampHorizontal(packet.getX(this.player.getX())); final double toX = d; // Paper - OBFHELPER
++                    double d1 = clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER
++                    double d2 = clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER
 +                    float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER
 +                    float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER
- 
                      if (this.player.isPassenger()) {
                          this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
                          this.player.serverLevel().getChunkSource().move(this.player);
@@ -745,43 +668,43 @@
 +                        float prevYaw = this.player.getYRot();
 +                        float prevPitch = this.player.getXRot();
 +                        // CraftBukkit end
-                         double d3 = this.player.getX();
-                         double d4 = this.player.getY();
-                         double d5 = this.player.getZ();
-@@ -1005,7 +1376,16 @@
-                         double d7 = d1 - this.firstGoodY;
-                         double d8 = d2 - this.firstGoodZ;
-                         double d9 = this.player.getDeltaMovement().lengthSqr();
--                        double d10 = d6 * d6 + d7 * d7 + d8 * d8;
+                         double x = this.player.getX();
+                         double y = this.player.getY();
+                         double z = this.player.getZ();
+@@ -927,6 +_,16 @@
+                         double d5 = d2 - this.firstGoodZ;
+                         double d6 = this.player.getDeltaMovement().lengthSqr();
+                         double d7 = d3 * d3 + d4 * d4 + d5 * d5;
 +                        // Paper start - fix large move vectors killing the server
 +                        double currDeltaX = toX - prevX;
 +                        double currDeltaY = toY - prevY;
 +                        double currDeltaZ = toZ - prevZ;
-+                        double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
-+                        double otherFieldX = d0 - this.lastGoodX;
++                        d7 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
++                        double otherFieldX = d - this.lastGoodX;
 +                        double otherFieldY = d1 - this.lastGoodY;
 +                        double otherFieldZ = d2 - this.lastGoodZ;
-+                        d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
++                        d7 = Math.max(d7, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
 +                        // Paper end - fix large move vectors killing the server
- 
                          if (this.player.isSleeping()) {
-                             if (d10 > 1.0D) {
-@@ -1019,36 +1399,106 @@
-                                 ++this.receivedMovePacketCount;
+                             if (d7 > 1.0) {
+                                 this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
+@@ -936,32 +_,104 @@
+                             if (serverLevel.tickRateManager().runsNormally()) {
+                                 this.receivedMovePacketCount++;
                                  int i = this.receivedMovePacketCount - this.knownMovePacketCount;
- 
 -                                if (i > 5) {
++
 +                                // CraftBukkit start - handle custom speeds and skipped ticks
 +                                this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
 +                                this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
 +                                this.lastTick = (int) (System.currentTimeMillis() / 50);
 +
 +                                if (i > Math.max(this.allowedPlayerTicks, 5)) {
-                                     ServerGamePacketListenerImpl.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
+                                     LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
                                      i = 1;
                                  }
  
-+                                if (packet.hasRot || d10 > 0) {
++                                if (packet.hasRot || d7 > 0) {
 +                                    this.allowedPlayerTicks -= 1;
 +                                } else {
 +                                    this.allowedPlayerTicks = 20;
@@ -793,7 +716,7 @@
 +                                    speed = this.player.getAbilities().walkingSpeed * 10f;
 +                                }
 +                                // Paper start - Prevent moving into unloaded chunks
-+                                if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) {
++                                if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !serverLevel.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) {
 +                                    // Paper start - Add fail move event
 +                                    io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK,
 +                                        toX, toY, toZ, toYaw, toPitch, false);
@@ -805,21 +728,21 @@
 +                                }
 +                                // Paper end - Prevent moving into unloaded chunks
 +
-                                 if (this.shouldCheckPlayerMovement(flag)) {
-                                     float f2 = flag ? 300.0F : 100.0F;
- 
--                                    if (d10 - d9 > (double) (f2 * (float) i)) {
--                                        ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8});
+                                 if (this.shouldCheckPlayerMovement(isFallFlying)) {
+                                     float f2 = isFallFlying ? 300.0F : 100.0F;
+-                                    if (d7 - d6 > f2 * i) {
+-                                        LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
 -                                        this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot());
 -                                        return;
-+                                    if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2))) {
-+                                    // CraftBukkit end
++                                    if (d7 - d6 > Math.max(f2, Mth.square((org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed)))) {
++                                        // CraftBukkit end
 +                                        // Paper start - Add fail move event
 +                                        io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY,
 +                                            toX, toY, toZ, toYaw, toPitch, true);
 +                                        if (!event.isAllowed()) {
-+                                            if (event.getLogWarning())
-+                                                ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8});
++                                            if (event.getLogWarning()) {
++                                                LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
++                                            }
 +                                            this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot());
 +                                            return;
 +                                        }
@@ -828,21 +751,19 @@
                                  }
                              }
  
-                             AABB axisalignedbb = this.player.getBoundingBox();
- 
--                            d6 = d0 - this.lastGoodX;
--                            d7 = d1 - this.lastGoodY;
--                            d8 = d2 - this.lastGoodZ;
-+                            d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
-+                            d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
-+                            d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
-                             boolean flag1 = d7 > 0.0D;
- 
-                             if (this.player.onGround() && !packet.isOnGround() && flag1) {
+                             AABB boundingBox = this.player.getBoundingBox();
+-                            d3 = d - this.lastGoodX;
+-                            d4 = d1 - this.lastGoodY;
+-                            d5 = d2 - this.lastGoodZ;
++                            d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
++                            d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
++                            d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
+                             boolean flag = d4 > 0.0;
+                             if (this.player.onGround() && !packet.isOnGround() && flag) {
 -                                this.player.jumpFromGround();
 +                                // Paper start - Add PlayerJumpEvent
-+                                Player player = this.getCraftPlayer();
-+                                Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location.
++                                org.bukkit.entity.Player player = this.getCraftPlayer();
++                                Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); // Get the Players previous Event location.
 +                                Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
 +
 +                                // If the packet contains movement information then we update the To location with the correct XYZ.
@@ -870,43 +791,50 @@
 +                                // Paper end - Add PlayerJumpEvent
                              }
  
-                             boolean flag2 = this.player.verticalCollisionBelow;
- 
-                             this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8));
+                             boolean flag1 = this.player.verticalCollisionBelow;
+                             this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
 +                            this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
 +                            // Paper start - prevent position desync
 +                            if (this.awaitingPositionFromClient != null) {
 +                                return; // ... thanks Mojang for letting move calls teleport across dimensions.
 +                            }
 +                            // Paper end - prevent position desync
-                             double d11 = d7;
+                             d3 = d - this.player.getX();
+                             d4 = d1 - this.player.getY();
+                             if (d4 > -0.5 || d4 < 0.5) {
+@@ -970,21 +_,106 @@
  
-                             d6 = d0 - this.player.getX();
-@@ -1059,17 +1509,100 @@
- 
-                             d8 = d2 - this.player.getZ();
-                             d10 = d6 * d6 + d7 * d7 + d8 * d8;
--                            boolean flag3 = false;
+                             d5 = d2 - this.player.getZ();
+                             d7 = d3 * d3 + d4 * d4 + d5 * d5;
+-                            boolean flag2 = false;
 +                            boolean movedWrongly = false; // Paper - Add fail move event; rename
- 
--                            if (!this.player.isChangingDimension() && d10 > 0.0625D && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) {
--                                flag3 = true;
-+                            if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot
+                             if (!this.player.isChangingDimension()
+-                                && d7 > 0.0625
++                                && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold // Spigot
+                                 && !this.player.isSleeping()
+                                 && !this.player.gameMode.isCreative()
+                                 && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) {
+-                                flag2 = true;
+-                                LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+-                            }
+-
+-                            if (this.player.noPhysics
+-                                || this.player.isSleeping()
+-                                || (!flag2 || !serverLevel.noCollision(this.player, boundingBox))
+-                                    && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) {
 +                                // Paper start - Add fail move event
 +                                io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY,
 +                                    toX, toY, toZ, toYaw, toPitch, true);
 +                                if (!event.isAllowed()) {
 +                                    movedWrongly = true;
 +                                    if (event.getLogWarning())
-+                                // Paper end
-                                 ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
++                                        // Paper end
++                                        LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
 +                                } // Paper
-                             }
- 
--                            if (!this.player.noPhysics && !this.player.isSleeping() && (flag3 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) {
--                                this.teleport(d3, d4, d5, f, f1);
++                            }
++
 +                            // Paper start - Add fail move event
-+                            boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2));
++                            boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && serverLevel.noCollision(this.player, boundingBox) || this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2));
 +                            if (teleportBack) {
 +                                io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
 +                                    toX, toY, toZ, toYaw, toPitch, false);
@@ -915,15 +843,15 @@
 +                                }
 +                            }
 +                            if (teleportBack) {
-+                            // Paper end - Add fail move event
-+                                this.internalTeleport(d3, d4, d5, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet.
-                                 this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround());
-                             } else {
++                                // Paper end - Add fail move event
++                                this.internalTeleport(x, y, z, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet.
++                                this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
++                            } else {
 +                                // CraftBukkit start - fire PlayerMoveEvent
 +                                // Reset to old location first
 +                                this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch);
 +
-+                                Player player = this.getCraftPlayer();
++                                org.bukkit.entity.Player player = this.getCraftPlayer();
 +                                if (!this.hasMoved) {
 +                                    this.lastPosX = prevX;
 +                                    this.lastPosY = prevY;
@@ -932,6 +860,7 @@
 +                                    this.lastPitch = prevPitch;
 +                                    this.hasMoved = true;
 +                                }
++
 +                                Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); // Get the Players previous Event location.
 +                                Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
 +
@@ -985,50 +914,71 @@
 +                                    }
 +                                }
 +                                // CraftBukkit end
-                                 this.player.absMoveTo(d0, d1, d2, f, f1);
-                                 boolean flag4 = this.player.isAutoSpinAttack();
- 
-@@ -1119,6 +1652,7 @@
-                 this.awaitingTeleportTime = this.tickCount;
-                 this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot());
+                                 this.player.absMoveTo(d, d1, d2, f, f1);
++
+                                 boolean isAutoSpinAttack = this.player.isAutoSpinAttack();
+                                 this.clientIsFloating = d4 >= -0.03125
+                                     && !flag1
+@@ -996,6 +_,7 @@
+                                     && !isAutoSpinAttack
+                                     && this.noBlocksAround(this.player);
+                                 this.player.serverLevel().getChunkSource().move(this.player);
++
+                                 Vec3 vec3 = new Vec3(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z);
+                                 this.player.setOnGroundWithMovement(packet.isOnGround(), packet.horizontalCollision(), vec3);
+                                 this.player.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.isOnGround());
+@@ -1018,9 +_,6 @@
+                                 this.lastGoodX = this.player.getX();
+                                 this.lastGoodY = this.player.getY();
+                                 this.lastGoodZ = this.player.getZ();
+-                            } else {
+-                                this.teleport(x, y, z, f, f1);
+-                                this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
+                             }
+                         }
+                     }
+@@ -1053,6 +_,7 @@
+                     this.player.getXRot()
+                 );
              }
 +            this.allowedPlayerTicks = 20; // CraftBukkit
  
              return true;
          } else {
-@@ -1147,23 +1681,98 @@
+@@ -1076,10 +_,78 @@
      }
  
      public void teleport(double x, double y, double z, float yaw, float pitch) {
 -        this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet());
++        //this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet());
 +        // CraftBukkit start - Delegate to teleport(Location)
 +        this.teleport(x, y, z, yaw, pitch, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-     }
- 
++    }
++
 +    public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {
 +        return this.teleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet(), cause);
 +        // CraftBukkit end
-+    }
-+
-     public void teleport(PositionMoveRotation pos, Set<Relative> flags) {
+     }
+ 
+     public void teleport(PositionMoveRotation posMoveRotation, Set<Relative> relatives) {
 +        // CraftBukkit start
-+        this.teleport(pos, flags, PlayerTeleportEvent.TeleportCause.UNKNOWN);
++        this.teleport(posMoveRotation, relatives, PlayerTeleportEvent.TeleportCause.UNKNOWN);
 +    }
 +
-+    public boolean teleport(PositionMoveRotation positionmoverotation, Set<Relative> set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status
-+        Player player = this.getCraftPlayer();
++    public boolean teleport(PositionMoveRotation posMoveRotation, Set<Relative> relatives, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status
++        org.bukkit.entity.Player player = this.getCraftPlayer();
 +        Location from = player.getLocation();
-+        PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this.player), positionmoverotation, set);
++        PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this.player), posMoveRotation, relatives);
 +        Location to = CraftLocation.toBukkit(absolutePosition.position(), this.getCraftPlayer().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
 +        // SPIGOT-5171: Triggered on join
 +        if (from.equals(to)) {
-+            this.internalTeleport(positionmoverotation, set);
++            this.internalTeleport(posMoveRotation, relatives);
 +            return true; // CraftBukkit - Return event status
 +        }
 +
 +        // Paper start - Teleport API
 +        final Set<io.papermc.paper.entity.TeleportFlag.Relative> relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class);
-+        for (final Relative relativeArgument : set) {
++        for (final Relative relativeArgument : relatives) {
 +            final io.papermc.paper.entity.TeleportFlag.Relative flag = org.bukkit.craftbukkit.entity.CraftPlayer.deltaRelativeToAPI(relativeArgument);
 +            if (flag != null) relativeFlags.add(flag);
 +        }
@@ -1039,10 +989,10 @@
 +        if (event.isCancelled() || !to.equals(event.getTo())) {
 +            // set = Collections.emptySet(); // Can't relative teleport // Paper - Teleport API; Now you can!
 +            to = event.isCancelled() ? event.getFrom() : event.getTo();
-+            positionmoverotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch());
++            posMoveRotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch());
 +        }
 +
-+        this.internalTeleport(positionmoverotation, set);
++        this.internalTeleport(posMoveRotation, relatives);
 +        return !event.isCancelled(); // CraftBukkit - Return event status
 +    }
 +
@@ -1054,20 +1004,20 @@
 +        this.internalTeleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet());
 +    }
 +
-+    public void internalTeleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {
++    public void internalTeleport(PositionMoveRotation posMoveRotation, Set<Relative> relatives) {
 +        org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
 +        // Paper start - Prevent teleporting dead entities
-+        if (player.isRemoved()) {
++        if (this.player.isRemoved()) {
 +            LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
-+            if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player");
++            if (this.server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player");
 +            return;
 +        }
 +        // Paper end - Prevent teleporting dead entities
-+        if (Float.isNaN(positionmoverotation.yRot())) {
-+            positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), 0, positionmoverotation.xRot());
++        if (Float.isNaN(posMoveRotation.yRot())) {
++            posMoveRotation = new PositionMoveRotation(posMoveRotation.position(), posMoveRotation.deltaMovement(), 0, posMoveRotation.xRot());
 +        }
-+        if (Float.isNaN(positionmoverotation.xRot())) {
-+            positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), positionmoverotation.yRot(), 0);
++        if (Float.isNaN(posMoveRotation.xRot())) {
++            posMoveRotation = new PositionMoveRotation(posMoveRotation.position(), posMoveRotation.deltaMovement(), posMoveRotation.yRot(), 0);
 +        }
 +
 +        this.justTeleported = true;
@@ -1075,12 +1025,10 @@
          this.awaitingTeleportTime = this.tickCount;
          if (++this.awaitingTeleport == Integer.MAX_VALUE) {
              this.awaitingTeleport = 0;
-         }
+@@ -1087,12 +_,20 @@
  
--        this.player.teleportSetPosition(pos, flags);
-+        this.player.teleportSetPosition(positionmoverotation, set);
+         this.player.teleportSetPosition(posMoveRotation, relatives);
          this.awaitingPositionFromClient = this.player.position();
--        this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, pos, flags));
 +        // CraftBukkit start - update last location
 +        this.lastPosX = this.awaitingPositionFromClient.x;
 +        this.lastPosY = this.awaitingPositionFromClient.y;
@@ -1088,7 +1036,7 @@
 +        this.lastYaw = this.player.getYRot();
 +        this.lastPitch = this.player.getXRot();
 +        // CraftBukkit end
-+        this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, positionmoverotation, set));
+         this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, posMoveRotation, relatives));
      }
  
      @Override
@@ -1096,16 +1044,16 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        if (this.player.isImmobile()) return; // CraftBukkit
          if (this.player.hasClientLoaded()) {
-             BlockPos blockposition = packet.getPos();
- 
-@@ -1175,14 +1784,46 @@
+             BlockPos pos = packet.getPos();
+             this.player.resetLastActionTime();
+@@ -1101,14 +_,46 @@
+                 case SWAP_ITEM_WITH_OFFHAND:
                      if (!this.player.isSpectator()) {
-                         ItemStack itemstack = this.player.getItemInHand(InteractionHand.OFF_HAND);
- 
+                         ItemStack itemInHand = this.player.getItemInHand(InteractionHand.OFF_HAND);
 -                        this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND));
--                        this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack);
+-                        this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand);
 +                        // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394)
-+                        CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack);
++                        CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemInHand);
 +                        CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(InteractionHand.MAIN_HAND));
 +                        PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(this.getCraftPlayer(), mainHand.clone(), offHand.clone());
 +                        this.cserver.getPluginManager().callEvent(swapItemsEvent);
@@ -1118,7 +1066,7 @@
 +                            this.player.setItemInHand(InteractionHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem()));
 +                        }
 +                        if (swapItemsEvent.getMainHandItem().equals(mainHand)) {
-+                            this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack);
++                            this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand);
 +                        } else {
 +                            this.player.setItemInHand(InteractionHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem()));
 +                        }
@@ -1147,48 +1095,45 @@
                          this.player.drop(false);
                      }
  
-@@ -1199,8 +1840,34 @@
+@@ -1125,8 +_,34 @@
                  case START_DESTROY_BLOCK:
                  case ABORT_DESTROY_BLOCK:
                  case STOP_DESTROY_BLOCK:
 +                    // Paper start - Don't allow digging into unloaded chunks
-+                    if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) {
++                    if (this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) {
 +                        this.player.connection.ackBlockChangesUpTo(packet.getSequence());
 +                        return;
 +                    }
 +                    // Paper end - Don't allow digging into unloaded chunks
-+                // Paper start - Send block entities after destroy prediction
-+                this.player.gameMode.capturedBlockEntity = false;
-+                this.player.gameMode.captureSentBlockEntities = true;
-+                // Paper end - Send block entities after destroy prediction
-                     this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence());
-                     this.player.connection.ackBlockChangesUpTo(packet.getSequence());
-+                // Paper start - Send block entities after destroy prediction
-+                this.player.gameMode.captureSentBlockEntities = false;
-+                // If a block entity was modified speedup the block change ack to avoid the block entity
-+                // being overriden.
-+                if (this.player.gameMode.capturedBlockEntity) {
-+                    // manually tick
-+                    this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo));
-+                    this.player.connection.ackBlockChangesUpTo = -1;
-+
++                    // Paper start - Send block entities after destroy prediction
 +                    this.player.gameMode.capturedBlockEntity = false;
-+                    BlockEntity tileentity = this.player.level().getBlockEntity(blockposition);
-+                    if (tileentity != null) {
-+                        this.player.connection.send(tileentity.getUpdatePacket());
++                    this.player.gameMode.captureSentBlockEntities = true;
++                    // Paper end - Send block entities after destroy prediction
+                     this.player.gameMode.handleBlockBreakAction(pos, action, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence());
+                     this.player.connection.ackBlockChangesUpTo(packet.getSequence());
++                    // Paper start - Send block entities after destroy prediction
++                    this.player.gameMode.captureSentBlockEntities = false;
++                    // If a block entity was modified speedup the block change ack to avoid the block entity
++                    // being overridden.
++                    if (this.player.gameMode.capturedBlockEntity) {
++                        // manually tick
++                        this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo));
++                        this.player.connection.ackBlockChangesUpTo = -1;
++
++                        this.player.gameMode.capturedBlockEntity = false;
++                        BlockEntity blockEntity = this.player.level().getBlockEntity(pos);
++                        if (blockEntity != null) {
++                            this.player.connection.send(blockEntity.getUpdatePacket());
++                        }
 +                    }
-+                }
-+                // Paper end - Send block entities after destroy prediction
++                    // Paper end - Send block entities after destroy prediction
                      return;
                  default:
                      throw new IllegalArgumentException("Invalid player action");
-@@ -1215,12 +1882,34 @@
-             Item item = stack.getItem();
+@@ -1143,9 +_,31 @@
+         }
+     }
  
-             return (item instanceof BlockItem || item instanceof BucketItem) && !player.getCooldowns().isOnCooldown(stack);
-+        }
-+    }
-+
 +    // Spigot start - limit place/interactions
 +    private int limitedPackets;
 +    private long lastLimitedPacket = -1;
@@ -1203,12 +1148,12 @@
 +            this.lastLimitedPacket = timestamp;
 +            this.limitedPackets = 0;
 +            return true;
-         }
++        }
 +
 +        return true;
-     }
-+    // Spigot end
- 
++    }
++    // Spigot end - limit place/interactions
++
      @Override
      public void handleUseItemOn(ServerboundUseItemOnPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1216,44 +1161,43 @@
 +        if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit
          if (this.player.hasClientLoaded()) {
              this.player.connection.ackBlockChangesUpTo(packet.getSequence());
-             ServerLevel worldserver = this.player.serverLevel();
-@@ -1230,6 +1919,11 @@
-             if (itemstack.isItemEnabled(worldserver.enabledFeatures())) {
-                 BlockHitResult movingobjectpositionblock = packet.getHitResult();
-                 Vec3 vec3d = movingobjectpositionblock.getLocation();
-+            // Paper start - improve distance check
-+            if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) {
-+                return;
-+            }
-+            // Paper end - improve distance check
-                 BlockPos blockposition = movingobjectpositionblock.getBlockPos();
- 
-                 if (this.player.canInteractWithBlock(blockposition, 1.0D)) {
-@@ -1243,7 +1937,8 @@
-                         int i = this.player.level().getMaxY();
- 
-                         if (blockposition.getY() <= i) {
--                            if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) {
-+                        if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection
+             ServerLevel serverLevel = this.player.serverLevel();
+@@ -1154,6 +_,11 @@
+             if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+                 BlockHitResult hitResult = packet.getHitResult();
+                 Vec3 location = hitResult.getLocation();
++                // Paper start - improve distance check
++                if (!Double.isFinite(location.x()) || !Double.isFinite(location.y()) || !Double.isFinite(location.z())) {
++                    return;
++                }
++                // Paper end - improve distance check
+                 BlockPos blockPos = hitResult.getBlockPos();
+                 if (this.player.canInteractWithBlock(blockPos, 1.0)) {
+                     Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos));
+@@ -1163,7 +_,8 @@
+                         this.player.resetLastActionTime();
+                         int maxY = this.player.level().getMaxY();
+                         if (blockPos.getY() <= maxY) {
+-                            if (this.awaitingPositionFromClient == null && serverLevel.mayInteract(this.player, blockPos)) {
++                            if (this.awaitingPositionFromClient == null && (serverLevel.mayInteract(this.player, blockPos) || (serverLevel.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && serverLevel.getBlockState(blockPos).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection
 +                                this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706
-                                 InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock);
- 
-                                 if (enuminteractionresult.consumesAction()) {
-@@ -1257,11 +1952,11 @@
-                                 } else if (enuminteractionresult instanceof InteractionResult.Success) {
-                                     InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult;
- 
--                                    if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) {
-+                                    if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { // Paper - Call interact event
-                                         this.player.swing(enumhand, true);
-                                     }
+                                 InteractionResult interactionResult = this.player.gameMode.useItemOn(this.player, serverLevel, itemInHand, hand, hitResult);
+                                 if (interactionResult.consumesAction()) {
+                                     CriteriaTriggers.ANY_BLOCK_USE.trigger(this.player, hitResult.getBlockPos(), itemInHand.copy());
+@@ -1176,10 +_,10 @@
+                                     Component component = Component.translatable("build.tooHigh", maxY).withStyle(ChatFormatting.RED);
+                                     this.player.sendSystemMessage(component, true);
+                                 } else if (interactionResult instanceof InteractionResult.Success success
+-                                    && success.swingSource() == InteractionResult.SwingSource.SERVER) {
++                                    && success.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { // Paper - Call interact event
+                                     this.player.swing(hand, true);
                                  }
 -                            }
 +                            } else { this.player.containerMenu.sendAllDataToRemote(); } // Paper - Fix inventory desync; MC-99075
                          } else {
-                             MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i).withStyle(ChatFormatting.RED);
- 
-@@ -1281,6 +1976,8 @@
+                             Component component1 = Component.translatable("build.tooHigh", maxY).withStyle(ChatFormatting.RED);
+                             this.player.sendSystemMessage(component1, true);
+@@ -1203,6 +_,8 @@
      @Override
      public void handleUseItem(ServerboundUseItemPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1261,17 +1205,17 @@
 +        if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit
          if (this.player.hasClientLoaded()) {
              this.ackBlockChangesUpTo(packet.getSequence());
-             ServerLevel worldserver = this.player.serverLevel();
-@@ -1296,6 +1993,48 @@
+             ServerLevel serverLevel = this.player.serverLevel();
+@@ -1216,6 +_,48 @@
                      this.player.absRotateTo(f, f1);
                  }
  
 +                // CraftBukkit start
 +                // Raytrace to look for 'rogue armswings'
-+                double d0 = this.player.getX();
-+                double d1 = this.player.getY() + (double) this.player.getEyeHeight();
-+                double d2 = this.player.getZ();
-+                Vec3 vec3d = new Vec3(d0, d1, d2);
++                double x = this.player.getX();
++                double eyeY = this.player.getEyeY();
++                double z = this.player.getZ();
++                Vec3 from = new Vec3(x, eyeY, z);
 +
 +                float f3 = Mth.cos(-f * 0.017453292F - 3.1415927F);
 +                float f4 = Mth.sin(-f * 0.017453292F - 3.1415927F);
@@ -1280,55 +1224,56 @@
 +                float f7 = f4 * f5;
 +                float f8 = f3 * f5;
 +                double d3 = this.player.blockInteractionRange();
-+                Vec3 vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3);
-+                HitResult movingobjectposition = this.player.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player));
++                Vec3 to = from.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3);
++                BlockHitResult hitResult = this.player.level().clip(new ClipContext(from, to, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player));
 +
 +                boolean cancelled;
-+                if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) {
-+                    org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand);
++                if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) {
++                    org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemInHand, hand);
 +                    cancelled = event.useItemInHand() == Event.Result.DENY;
 +                } else {
-+                    BlockHitResult movingobjectpositionblock = (BlockHitResult) movingobjectposition;
-+                    if (this.player.gameMode.firedInteract && this.player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && this.player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameComponents(this.player.gameMode.interactItemStack, itemstack)) {
++                    if (this.player.gameMode.firedInteract && this.player.gameMode.interactPosition.equals(hitResult.getBlockPos()) && this.player.gameMode.interactHand == hand && ItemStack.isSameItemSameComponents(this.player.gameMode.interactItemStack, itemInHand)) {
 +                        cancelled = this.player.gameMode.interactResult;
 +                    } else {
-+                        org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation());
++                        org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_BLOCK, hitResult.getBlockPos(), hitResult.getDirection(), itemInHand, true, hand, hitResult.getLocation());
 +                        cancelled = event.useItemInHand() == Event.Result.DENY;
 +                    }
 +                    this.player.gameMode.firedInteract = false;
 +                }
 +
 +                if (cancelled) {
-+                this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
-+                    this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524
++                    this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
++                    this.player.containerMenu.sendAllDataToRemote(); // SPIGOT-2524
 +                    return;
 +                }
-+                itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event
-+                if (itemstack.isEmpty()) {
++                itemInHand = this.player.getItemInHand(hand); // Update in case it was changed in the event
++                if (itemInHand.isEmpty()) {
 +                    return;
 +                }
 +                // CraftBukkit end
-                 InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand);
- 
-                 if (enuminteractionresult instanceof InteractionResult.Success) {
-@@ -1321,7 +2060,7 @@
-                 Entity entity = packet.getEntity(worldserver);
- 
++
+                 if (this.player.gameMode.useItem(this.player, serverLevel, itemInHand, hand) instanceof InteractionResult.Success success
+                     && success.swingSource() == InteractionResult.SwingSource.SERVER) {
+                     this.player.swing(hand, true);
+@@ -1231,7 +_,7 @@
+             for (ServerLevel serverLevel : this.server.getAllLevels()) {
+                 Entity entity = packet.getEntity(serverLevel);
                  if (entity != null) {
--                    this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true);
-+                    this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
+-                    this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true);
++                    this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
                      return;
                  }
              }
-@@ -1342,22 +2081,52 @@
+@@ -1248,24 +_,54 @@
  
      @Override
-     public void onDisconnect(DisconnectionDetails info) {
+     public void onDisconnect(DisconnectionDetails details) {
 +        // Paper start - Fix kick event leave message not being sent
-+        this.onDisconnect(info, null);
++        this.onDisconnect(details, null);
 +    }
++
 +    @Override
-+    public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
++    public void onDisconnect(DisconnectionDetails details, @Nullable net.kyori.adventure.text.Component quitMessage) {
 +        // Paper end - Fix kick event leave message not being sent
 +        // CraftBukkit start - Rarely it would send a disconnect line twice
 +        if (this.processedDisconnect) {
@@ -1337,15 +1282,15 @@
 +            this.processedDisconnect = true;
 +        }
 +        // CraftBukkit end
-         ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString());
+         LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString());
 -        this.removePlayerFromWorld();
--        super.onDisconnect(info);
+-        super.onDisconnect(details);
 +        this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent
-+        super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent
++        super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent
      }
  
-+    // Paper start - Fix kick event leave message not being sent
      private void removePlayerFromWorld() {
++        // Paper start - Fix kick event leave message not being sent
 +        this.removePlayerFromWorld(null);
 +    }
 +
@@ -1355,17 +1300,17 @@
 +        // CraftBukkit start - Replace vanilla quit message handling with our own.
 +        /*
          this.server.invalidateStatus();
--        this.server.getPlayerList().broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false);
-+        this.server.getPlayerList().broadcastSystemMessage(IChatBaseComponent.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(EnumChatFormat.YELLOW), false);
-+        */
-+
+         this.server
+             .getPlayerList()
+             .broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false);
++         */
          this.player.disconnect();
 -        this.server.getPlayerList().remove(this.player);
 +        // Paper start - Adventure
 +        quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used
 +        if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) {
 +            this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false);
-+            // Paper end
++            // Paper end - Adventure
 +        }
 +        // CraftBukkit end
          this.player.getTextFilter().leave();
@@ -1377,7 +1322,7 @@
              throw new IllegalArgumentException("Expected packet sequence nr >= 0");
          } else {
              this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo);
-@@ -1367,7 +2136,17 @@
+@@ -1275,7 +_,17 @@
      @Override
      public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1395,10 +1340,10 @@
              if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) {
                  this.player.stopUsingItem();
              }
-@@ -1376,11 +2155,18 @@
+@@ -1284,11 +_,18 @@
              this.player.resetLastActionTime();
          } else {
-             ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
+             LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
 +            this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause
          }
      }
@@ -1412,23 +1357,22 @@
 +        }
 +        // CraftBukkit end
          Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages());
- 
          if (!optional.isEmpty()) {
-@@ -1394,27 +2180,46 @@
+             this.tryHandleChat(packet.message(), () -> {
+@@ -1300,25 +_,45 @@
                      return;
                  }
  
--                CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent());
--                Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent());
-+                CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat
-+                CompletableFuture<Component> componentFuture = this.server.getChatDecorator().decorate(this.player, null, playerchatmessage.decoratedContent()); // Paper - Adventure
- 
--                this.chatMessageChain.append(completablefuture, (filteredtext) -> {
--                    PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(ichatbasecomponent).filter(filteredtext.mask());
-+                this.chatMessageChain.append(CompletableFuture.allOf(completablefuture, componentFuture), (filteredtext) -> { // Paper - Adventure
-+                    PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(componentFuture.join()).filter(completablefuture.join().mask()); // Paper - Adventure
- 
-                     this.broadcastChatMessage(playerchatmessage1);
+-                CompletableFuture<FilteredText> completableFuture = this.filterTextPacket(signedMessage.signedContent());
+-                Component component = this.server.getChatDecorator().decorate(this.player, signedMessage.decoratedContent());
+-                this.chatMessageChain.append(completableFuture, filteredText -> {
+-                    PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(component).filter(filteredText.mask());
++                CompletableFuture<FilteredText> completableFuture = this.filterTextPacket(signedMessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat
++                CompletableFuture<Component> componentFuture = this.server.getChatDecorator().decorate(this.player, null, signedMessage.decoratedContent()); // Paper - Adventure
++
++                this.chatMessageChain.append(CompletableFuture.allOf(completableFuture, componentFuture), (filteredtext) -> { // Paper - Adventure
++                    PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask()); // Paper - Adventure
+                     this.broadcastChatMessage(playerChatMessage);
                  });
 -            });
 +            }, false); // CraftBukkit - async chat
@@ -1452,12 +1396,12 @@
  
      private void performUnsignedChatCommand(String command) {
 +        // CraftBukkit start
-+        String command1 = "/" + command;
++        String prefixedCommand = "/" + command;
 +        if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check
-+        ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1);
++            LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), prefixedCommand);
 +        }
 +
-+        PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server));
++        PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), prefixedCommand, new LazyPlayerSet(this.server));
 +        this.cserver.getPluginManager().callEvent(event);
 +
 +        if (event.isCancelled()) {
@@ -1465,11 +1409,11 @@
 +        }
 +        command = event.getMessage().substring(1);
 +        // CraftBukkit end
-         ParseResults<CommandSourceStack> parseresults = this.parseCommand(command);
- 
-         if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseresults)) {
-@@ -1431,30 +2236,58 @@
- 
+         ParseResults<CommandSourceStack> parseResults = this.parseCommand(command);
+         if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseResults)) {
+             LOGGER.error(
+@@ -1335,13 +_,29 @@
+         Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages());
          if (!optional.isEmpty()) {
              this.tryHandleChat(packet.command(), () -> {
 +                // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands
@@ -1477,7 +1421,7 @@
 +                    return;
 +                }
 +                // CraftBukkit end
-                 this.performSignedChatCommand(packet, (LastSeenMessages) optional.get());
+                 this.performSignedChatCommand(packet, optional.get());
 -                this.detectRateSpam();
 -            });
 +                this.detectRateSpam("/" + packet.command()); // Spigot
@@ -1486,27 +1430,21 @@
      }
  
      private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) {
--        ParseResults<CommandSourceStack> parseresults = this.parseCommand(packet.command());
 +        // CraftBukkit start
 +        String command = "/" + packet.command();
 +        if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check
-+        ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command);
++            LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), command);
 +        } // Paper - Add missing SpigotConfig logCommands check
- 
--        Map map;
++
 +        PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server));
 +        this.cserver.getPluginManager().callEvent(event);
 +        command = event.getMessage().substring(1);
- 
-+        // Paper start - Fix cancellation and message changing
-+        ParseResults<CommandSourceStack> parseresults = this.parseCommand(packet.command());
 +
-+        Map<String, PlayerChatMessage> map;
-         try {
-+            // Always parse the original command to add to the chat chain
-             map = this.collectSignedArguments(packet, SignableCommand.of(parseresults), lastSeenMessages);
-         } catch (SignedMessageChain.DecodeException signedmessagechain_a) {
-             this.handleMessageDecodeFailure(signedmessagechain_a);
++        // Paper start - Fix cancellation and message changing
+         ParseResults<CommandSourceStack> parseResults = this.parseCommand(packet.command());
+ 
+         Map<String, PlayerChatMessage> map;
+@@ -1352,9 +_,21 @@
              return;
          }
  
@@ -1517,73 +1455,59 @@
 +
 +        // Remove signed parts if the command was changed
 +        if (!command.equals(packet.command())) {
-+            parseresults = this.parseCommand(command);
++            parseResults = this.parseCommand(command);
 +            map = Collections.emptyMap();
 +        }
 +        // Paper end - Fix cancellation and message changing
 +
-         CommandSigningContext.SignedArguments commandsigningcontext_a = new CommandSigningContext.SignedArguments(map);
- 
--        parseresults = Commands.mapSource(parseresults, (commandlistenerwrapper) -> {
-+        parseresults = Commands.<CommandSourceStack>mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error
-             return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain);
-         });
--        this.server.getCommands().performCommand(parseresults, packet.command());
-+        this.server.getCommands().performCommand(parseresults, command); // CraftBukkit
+         CommandSigningContext commandSigningContext = new CommandSigningContext.SignedArguments(map);
+         parseResults = Commands.mapSource(parseResults, source -> source.withSigningContext(commandSigningContext, this.chatMessageChain));
+-        this.server.getCommands().performCommand(parseResults, packet.command());
++        this.server.getCommands().performCommand(parseResults, command); // CraftBukkit
      }
  
      private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) {
-@@ -1530,14 +2363,20 @@
-         return com_mojang_brigadier_commanddispatcher.parse(command, this.player.createCommandSourceStack());
+@@ -1418,14 +_,20 @@
+         return dispatcher.parse(command, this.player.createCommandSourceStack());
      }
  
--    private void tryHandleChat(String message, Runnable callback) {
--        if (ServerGamePacketListenerImpl.isChatMessageIllegal(message)) {
--            this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters"));
+-    private void tryHandleChat(String message, Runnable handler) {
++    private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit
+         if (isChatMessageIllegal(message)) {
+-            this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"));
 -        } else if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
-+    private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit
-+        if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) {
-+            this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper // Paper - add proper async disconnect
++            this.disconnectAsync(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add proper async disconnect
 +        } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales
              this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false));
          } else {
              this.player.resetLastActionTime();
--            this.server.execute(callback);
+-            this.server.execute(handler);
 +            // CraftBukkit start
 +            if (sync) {
-+                this.server.execute(runnable);
++                this.server.execute(handler);
 +            } else {
-+                runnable.run();
++                handler.run();
 +            }
 +            // CraftBukkit end
          }
      }
  
-@@ -1549,7 +2388,7 @@
- 
-             if (optional.isEmpty()) {
-                 ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
--                this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED);
-+                this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect
-             }
- 
-             return optional;
-@@ -1566,6 +2405,117 @@
+@@ -1451,22 +_,155 @@
          return false;
      }
  
 +    // CraftBukkit start - add method
-+    public void chat(String s, PlayerChatMessage original, boolean async) {
-+        if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
++    public void chat(String msg, PlayerChatMessage original, boolean async) {
++        if (msg.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
 +            return;
 +        }
 +        OutgoingChatMessage outgoing = OutgoingChatMessage.create(original);
 +
-+        if (false && !async && s.startsWith("/")) { // Paper - Don't handle commands in chat logic
-+            this.handleCommand(s);
++        if (false && !async && msg.startsWith("/")) { // Paper - Don't handle commands in chat logic
++            this.handleCommand(msg);
 +        } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) {
 +            // Do nothing, this is coming from a plugin
-+        // Paper start
++            // Paper start
 +        } else if (true) {
 +            if (!async && !org.bukkit.Bukkit.isPrimaryThread()) {
 +                org.spigotmc.AsyncCatcher.catchOp("Asynchronous player chat is not allowed here");
@@ -1592,8 +1516,8 @@
 +            cp.process();
 +            // Paper end
 +        } else if (false) { // Paper
-+            Player player = this.getCraftPlayer();
-+            AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(this.server));
++            org.bukkit.entity.Player player = this.getCraftPlayer();
++            AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, msg, new LazyPlayerSet(this.server));
 +            String originalFormat = event.getFormat(), originalMessage = event.getMessage();
 +            this.cserver.getPluginManager().callEvent(event);
 +
@@ -1601,7 +1525,7 @@
 +                // Evil plugins still listening to deprecated event
 +                final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients());
 +                queueEvent.setCancelled(event.isCancelled());
-+                Waitable waitable = new Waitable() {
++                Waitable<Object> waitable = new Waitable<>() {
 +                    @Override
 +                    protected Object evaluate() {
 +                        org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent);
@@ -1621,8 +1545,8 @@
 +                                recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message);
 +                            }
 +                        } else {
-+                            for (Player player : queueEvent.getRecipients()) {
-+                                player.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message);
++                            for (org.bukkit.entity.Player recipient : queueEvent.getRecipients()) {
++                                recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message);
 +                            }
 +                        }
 +                        ServerGamePacketListenerImpl.this.server.console.sendMessage(message);
@@ -1646,22 +1570,22 @@
 +                    return;
 +                }
 +
-+                s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage());
++                msg = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage());
 +                if (((LazyPlayerSet) event.getRecipients()).isLazy()) {
 +                    if (!org.spigotmc.SpigotConfig.bungee && originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) { // Spigot
-+                        ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player));
++                        ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, ServerGamePacketListenerImpl.this.player));
 +                        return;
 +                    }
 +
 +                    for (ServerPlayer recipient : this.server.getPlayerList().players) {
-+                        recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s);
++                        recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), msg);
 +                    }
 +                } else {
-+                    for (Player recipient : event.getRecipients()) {
-+                        recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s);
++                    for (org.bukkit.entity.Player recipient : event.getRecipients()) {
++                        recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), msg);
 +                    }
 +                }
-+                this.server.console.sendMessage(s);
++                this.server.console.sendMessage(msg);
 +            }
 +        }
 +    }
@@ -1684,69 +1608,62 @@
 +    // CraftBukkit end
 +
      private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException {
-         SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages);
- 
-@@ -1573,15 +2523,44 @@
+         SignedMessageBody signedMessageBody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages);
+         return this.signedMessageDecoder.unpack(packet.signature(), signedMessageBody);
      }
  
      private void broadcastChatMessage(PlayerChatMessage message) {
--        this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, (Entity) this.player));
+-        this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player));
 -        this.detectRateSpam();
 +        // CraftBukkit start
-+        String s = message.signedContent();
-+        if (s.isEmpty()) {
-+            ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message");
++        String rawMessage = message.signedContent();
++        if (rawMessage.isEmpty()) {
++            LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName());
 +        } else if (this.getCraftPlayer().isConversing()) {
-+            final String conversationInput = s;
-+            this.server.processQueue.add(new Runnable() {
-+                @Override
-+                public void run() {
-+                    ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput);
-+                }
-+            });
++            final String conversationInput = rawMessage;
++            this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput));
 +        } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check
 +            this.send(new ClientboundSystemChatPacket(Component.translatable("chat.cannotSend").withStyle(ChatFormatting.RED), false));
 +        } else {
-+            this.chat(s, message, true);
++            this.chat(rawMessage, message, true);
 +        }
-+        // this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player));
++        // this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player));
 +        // CraftBukkit end
-+        this.detectRateSpam(s); // Spigot
++        this.detectRateSpam(rawMessage); // Spigot
      }
  
 -    private void detectRateSpam() {
 -        this.chatSpamThrottler.increment();
--        if (!this.chatSpamThrottler.isUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) {
--            this.disconnect((Component) Component.translatable("disconnect.spam"));
+-        if (!this.chatSpamThrottler.isUnderThreshold()
 +    // Spigot start - spam exclusions
-+    private void detectRateSpam(String s) {
++    private void detectRateSpam(String message) {
 +        // CraftBukkit start - replaced with thread safe throttle
-+        for ( String exclude : org.spigotmc.SpigotConfig.spamExclusions )
-+        {
-+            if ( exclude != null && s.startsWith( exclude ) )
-+            {
++        for (String exclude : org.spigotmc.SpigotConfig.spamExclusions) {
++            if (exclude != null && message.startsWith(exclude)) {
 +                return;
 +            }
-         }
++        }
 +        // Spigot end
 +        // this.chatSpamThrottler.increment();
-+        if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) {
++        if (!this.chatSpamThrottler.isIncrementAndUnderThreshold()
 +            // CraftBukkit end
-+            this.disconnectAsync((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Paper - add proper async disconnect
-+        }
- 
+             && !this.server.getPlayerList().isOp(this.player.getGameProfile())
+             && !this.server.isSingleplayerOwner(this.player.getGameProfile())) {
+-            this.disconnect(Component.translatable("disconnect.spam"));
++            this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause & add proper async disconnect
+         }
      }
  
-@@ -1592,7 +2571,7 @@
+@@ -1475,7 +_,7 @@
          synchronized (this.lastSeenMessages) {
              if (!this.lastSeenMessages.applyOffset(packet.offset())) {
-                 ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
--                this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED);
-+                this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect
+                 LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
+-                this.disconnect(CHAT_VALIDATION_FAILED);
++                this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes & add proper async disconnect
              }
- 
          }
-@@ -1601,7 +2580,40 @@
+     }
+@@ -1483,7 +_,40 @@
      @Override
      public void handleAnimate(ServerboundSwingPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1787,7 +1704,7 @@
          this.player.swing(packet.getHand());
      }
  
-@@ -1609,6 +2621,29 @@
+@@ -1491,10 +_,41 @@
      public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (this.player.hasClientLoaded()) {
@@ -1795,7 +1712,7 @@
 +            if (this.player.isRemoved()) return;
 +            switch (packet.getAction()) {
 +                case PRESS_SHIFT_KEY:
-+                case RELEASE_SHIFT_KEY:
++                case RELEASE_SHIFT_KEY: {
 +                    PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.PRESS_SHIFT_KEY);
 +                    this.cserver.getPluginManager().callEvent(event);
 +
@@ -1803,21 +1720,21 @@
 +                        return;
 +                    }
 +                    break;
++                }
 +                case START_SPRINTING:
-+                case STOP_SPRINTING:
-+                    PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.START_SPRINTING);
-+                    this.cserver.getPluginManager().callEvent(e2);
++                case STOP_SPRINTING: {
++                    PlayerToggleSprintEvent event = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.START_SPRINTING);
++                    this.cserver.getPluginManager().callEvent(event);
 +
-+                    if (e2.isCancelled()) {
++                    if (event.isCancelled()) {
 +                        return;
 +                    }
 +                    break;
++                }
 +            }
 +            // CraftBukkit end
++
              this.player.resetLastActionTime();
-             Entity entity;
-             PlayerRideableJumping ijumpable;
-@@ -1616,6 +2651,11 @@
              switch (packet.getAction()) {
                  case PRESS_SHIFT_KEY:
                      this.player.setShiftKeyDown(true);
@@ -1829,34 +1746,38 @@
                      break;
                  case RELEASE_SHIFT_KEY:
                      this.player.setShiftKeyDown(false);
-@@ -1684,15 +2724,25 @@
+@@ -1551,12 +_,20 @@
              }
  
              if (i > 4096) {
--                this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"));
-+                this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause // Paper - add proper async disconnect
+-                this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats"));
++                this.disconnectAsync(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause & add proper async disconnect
              }
- 
          }
      }
  
-     public void sendPlayerChatMessage(PlayerChatMessage message, ChatType.Bound params) {
+     public void sendPlayerChatMessage(PlayerChatMessage chatMessage, ChatType.Bound boundType) {
 +        // CraftBukkit start - SPIGOT-7262: if hidden we have to send as disguised message. Query whether we should send at all (but changing this may not be expected).
-+        if (!this.getCraftPlayer().canSeePlayer(message.link().sender())) {
-+            this.sendDisguisedChatMessage(message.decoratedContent(), params);
++        if (!this.getCraftPlayer().canSeePlayer(chatMessage.link().sender())) {
++            this.sendDisguisedChatMessage(chatMessage.decoratedContent(), boundType);
 +            return;
 +        }
 +        // CraftBukkit end
 +        // Paper start - Ensure that client receives chat packets in the same order that we add into the message signature cache
 +        synchronized (this.messageSignatureCache) {
-         this.send(new ClientboundPlayerChatPacket(message.link().sender(), message.link().index(), message.signature(), message.signedBody().pack(this.messageSignatureCache), message.unsignedContent(), message.filterMask(), params));
-         this.addPendingMessage(message);
+         this.send(
+             new ClientboundPlayerChatPacket(
+                 chatMessage.link().sender(),
+@@ -1569,6 +_,8 @@
+             )
+         );
+         this.addPendingMessage(chatMessage);
 +        }
 +        // Paper end - Ensure that client receives chat packets in the same order that we add into the message signature cache
      }
  
-     public void sendDisguisedChatMessage(Component message, ChatType.Bound params) {
-@@ -1703,6 +2753,18 @@
+     public void sendDisguisedChatMessage(Component message, ChatType.Bound boundType) {
+@@ -1579,6 +_,18 @@
          return this.connection.getRemoteAddress();
      }
  
@@ -1875,133 +1796,124 @@
      public void switchToConfig() {
          this.waitingForSwitchToConfig = true;
          this.removePlayerFromWorld();
-@@ -1718,9 +2780,17 @@
+@@ -1594,9 +_,16 @@
      @Override
      public void handleInteract(ServerboundInteractPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        if (this.player.isImmobile()) return; // CraftBukkit
          if (this.player.hasClientLoaded()) {
-             final ServerLevel worldserver = this.player.serverLevel();
-             final Entity entity = packet.getTarget(worldserver);
+             final ServerLevel serverLevel = this.player.serverLevel();
+             final Entity target = packet.getTarget(serverLevel);
 +            // Spigot Start
-+            if ( entity == this.player && !this.player.isSpectator() )
-+            {
-+                this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause
++            if (target == this.player && !this.player.isSpectator()) {
++                this.disconnect(Component.literal("Cannot interact with self!"), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION); // Paper - kick event cause
 +                return;
 +            }
 +            // Spigot End
- 
              this.player.resetLastActionTime();
              this.player.setShiftKeyDown(packet.isUsingSecondaryAction());
-@@ -1731,22 +2801,61 @@
+             if (target != null) {
+@@ -1605,16 +_,55 @@
+                 }
  
-                 AABB axisalignedbb = entity.getBoundingBox();
- 
--                if (this.player.canInteractWithEntity(axisalignedbb, 3.0D)) {
-+            if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range
-                     packet.dispatch(new ServerboundInteractPacket.Handler() {
--                        private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction action) {
--                            ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(hand);
-+                        private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit
-+                            ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
- 
-                             if (itemstack.isItemEnabled(worldserver.enabledFeatures())) {
-                                 ItemStack itemstack1 = itemstack.copy();
--                                InteractionResult enuminteractionresult = action.run(ServerGamePacketListenerImpl.this.player, entity, hand);
-+                                // CraftBukkit start
-+                                ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
-+                                boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof Mob;
-+                                Item origItem = ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null ? null : ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem();
- 
-+                                ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
+                 AABB boundingBox = target.getBoundingBox();
+-                if (this.player.canInteractWithEntity(boundingBox, 3.0)) {
++                if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range
+                     packet.dispatch(
+                         new ServerboundInteractPacket.Handler() {
+-                            private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction) {
++                            private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction, PlayerInteractEntityEvent event) { // CraftBukkit
+                                 ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(hand);
+                                 if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+                                     ItemStack itemStack = itemInHand.copy();
+-                                    if (entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand) instanceof InteractionResult.Success success
+-                                        )
+-                                     {
++                                    // CraftBukkit start
++                                    boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && target instanceof net.minecraft.world.entity.Mob;
++                                    Item origItem = ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null ? null : ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem();
 +
-+                                // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
-+                                if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
-+                                entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it
-+                                    ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote();
-+                                }
++                                    ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
 +
-+                                if (triggerLeashUpdate && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
-+                                    // Refresh the current leash state
-+                                    ServerGamePacketListenerImpl.this.send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder()));
-+                                }
-+
-+                                if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) {
-+                                    // Refresh the current entity metadata
-+                                    entity.refreshEntityData(ServerGamePacketListenerImpl.this.player);
-+                                    // SPIGOT-7136 - Allays
-+                                if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync
-+                                    ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize
++                                    // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
++                                    if ((target instanceof Bucketable && target instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
++                                        target.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it
++                                        ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote();
 +                                    }
 +
-+                                ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory
-+                                }
++                                    if (triggerLeashUpdate && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
++                                        // Refresh the current leash state
++                                        ServerGamePacketListenerImpl.this.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(target, ((net.minecraft.world.entity.Mob) target).getLeashHolder()));
++                                    }
 +
-+                                if (event.isCancelled()) {
-+                                    return;
-+                                }
-+                                // CraftBukkit end
-+                                InteractionResult enuminteractionresult = playerconnection_a.run(ServerGamePacketListenerImpl.this.player, entity, enumhand);
++                                    if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) {
++                                        // Refresh the current entity metadata
++                                        target.refreshEntityData(ServerGamePacketListenerImpl.this.player);
++                                        // SPIGOT-7136 - Allays
++                                        if (target instanceof Allay || target instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync
++                                            ServerGamePacketListenerImpl.this.send(new net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket(target.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) target).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize
++                                        }
 +
-+                                // CraftBukkit start
-+                                if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) {
-+                                    ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote();
-+                                }
-+                                // CraftBukkit end
++                                        ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory
++                                    }
++
++                                    if (event.isCancelled()) {
++                                        return;
++                                    }
++                                    // CraftBukkit end
++                                    InteractionResult result = entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand);
 +
-                                 if (enuminteractionresult instanceof InteractionResult.Success) {
-                                     InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult;
-                                     ItemStack itemstack2 = enuminteractionresult_d.wasItemInteraction() ? itemstack1 : ItemStack.EMPTY;
- 
-                                     CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemstack2, entity);
-                                     if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) {
--                                        ServerGamePacketListenerImpl.this.player.swing(hand, true);
-+                                        ServerGamePacketListenerImpl.this.player.swing(enumhand, true);
-                                     }
-                                 }
- 
-@@ -1755,19 +2864,20 @@
- 
-                         @Override
-                         public void onInteraction(InteractionHand hand) {
--                            this.performInteraction(hand, Player::interactOn);
-+                            this.performInteraction(hand, net.minecraft.world.entity.player.Player::interactOn, new PlayerInteractEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit
-                         }
- 
-                         @Override
-                         public void onInteraction(InteractionHand hand, Vec3 pos) {
-                             this.performInteraction(hand, (entityplayer, entity1, enumhand1) -> {
-                                 return entity1.interactAt(entityplayer, pos, enumhand1);
--                            });
-+                            }, new PlayerInteractAtEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(pos.x, pos.y, pos.z), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit
-                         }
- 
-                         @Override
-                         public void onAttack() {
--                            if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && entity != ServerGamePacketListenerImpl.this.player) {
-+                            // CraftBukkit
-+                            if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && (entity != ServerGamePacketListenerImpl.this.player || ServerGamePacketListenerImpl.this.player.isSpectator())) {
-                                 label23:
-                                 {
-                                     if (entity instanceof AbstractArrow) {
-@@ -1785,17 +2895,41 @@
-                                     }
- 
-                                     ServerGamePacketListenerImpl.this.player.attack(entity);
 +                                    // CraftBukkit start
-+                                    if (!itemstack.isEmpty() && itemstack.getCount() <= -1) {
++                                    if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) {
 +                                        ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote();
 +                                    }
 +                                    // CraftBukkit end
-                                     return;
-                                 }
++
++                                    if (result instanceof InteractionResult.Success success // CraftBukkit
++                                    ) {
+                                         ItemStack itemStack1 = success.wasItemInteraction() ? itemStack : ItemStack.EMPTY;
+                                         CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemStack1, target);
+                                         if (success.swingSource() == InteractionResult.SwingSource.SERVER) {
+@@ -1626,13 +_,13 @@
+ 
+                             @Override
+                             public void onInteraction(InteractionHand hand) {
+-                                this.performInteraction(hand, Player::interactOn);
++                                this.performInteraction(hand, Player::interactOn, new PlayerInteractEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), target.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand))); // CraftBukkit
                              }
  
--                            ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
-+                            ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause
-                             ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString());
-                         }
-                     });
+                             @Override
+                             public void onInteraction(InteractionHand hand, Vec3 interactionLocation) {
+                                 this.performInteraction(
+-                                    hand, (player, entity, interactionHand) -> entity.interactAt(player, interactionLocation, interactionHand)
++                                    hand, (player, entity, interactionHand) -> entity.interactAt(player, interactionLocation, interactionHand), new PlayerInteractAtEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), target.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(interactionLocation), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)) // CraftBukkit
+                                 );
+                             }
+ 
+@@ -1640,14 +_,19 @@
+                             public void onAttack() {
+                                 if (!(target instanceof ItemEntity)
+                                     && !(target instanceof ExperienceOrb)
+-                                    && target != ServerGamePacketListenerImpl.this.player
++                                    && (target != ServerGamePacketListenerImpl.this.player || ServerGamePacketListenerImpl.this.player.isSpectator()) // CraftBukkit
+                                     && !(target instanceof AbstractArrow abstractArrow && !abstractArrow.isAttackable())) {
+                                     ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND);
+                                     if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+                                         ServerGamePacketListenerImpl.this.player.attack(target);
++                                        // CraftBukkit start
++                                        if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) {
++                                            ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote();
++                                        }
++                                        // CraftBukkit end
+                                     }
+                                 } else {
+-                                    ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
++                                    ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause
+                                     ServerGamePacketListenerImpl.LOGGER
+                                         .warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString());
+                                 }
+@@ -1656,6 +_,26 @@
+                     );
                  }
              }
 +            // Paper start - PlayerUseUnknownEntityEvent
@@ -2011,7 +1923,7 @@
 +                    public void onInteraction(net.minecraft.world.InteractionHand hand) {
 +                        CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, null);
 +                    }
- 
++
 +                    @Override
 +                    public void onInteraction(net.minecraft.world.InteractionHand hand, net.minecraft.world.phys.Vec3 pos) {
 +                        CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, pos);
@@ -2027,7 +1939,7 @@
          }
      }
  
-@@ -1809,7 +2943,7 @@
+@@ -1668,7 +_,7 @@
              case PERFORM_RESPAWN:
                  if (this.player.wonGame) {
                      this.player.wonGame = false;
@@ -2036,7 +1948,7 @@
                      this.resetPosition();
                      CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD);
                  } else {
-@@ -1817,11 +2951,11 @@
+@@ -1676,11 +_,11 @@
                          return;
                      }
  
@@ -2045,19 +1957,20 @@
                      this.resetPosition();
                      if (this.server.isHardcore()) {
 -                        this.player.setGameMode(GameType.SPECTATOR);
--                        ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server);
+-                        this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS).set(false, this.server);
 +                        this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent
-+                        ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world
++                        this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS).set(false, this.player.serverLevel()); // CraftBukkit - per-world
                      }
                  }
                  break;
-@@ -1833,16 +2967,27 @@
+@@ -1691,16 +_,28 @@
  
      @Override
      public void handleContainerClose(ServerboundContainerClosePacket packet) {
 +        // Paper start - Inventory close reason
 +        this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER);
 +    }
++
 +    public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
 +        // Paper end - Inventory close reason
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -2080,26 +1993,24 @@
 +            if (false/*this.player.isSpectator()*/) { // CraftBukkit
                  this.player.containerMenu.sendAllDataToRemote();
              } else if (!this.player.containerMenu.stillValid(this.player)) {
-                 ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
-@@ -1855,7 +3000,315 @@
+                 LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+@@ -1713,7 +_,313 @@
+                 } else {
                      boolean flag = packet.getStateId() != this.player.containerMenu.getStateId();
- 
                      this.player.containerMenu.suppressRemoteUpdates();
--                    this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player);
+-                    this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player);
 +                    // CraftBukkit start - Call InventoryClickEvent
-+                    if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) {
++                    if (slotNum < -1 && slotNum != AbstractContainerMenu.SLOT_CLICKED_OUTSIDE) {
 +                        return;
 +                    }
 +
 +                    InventoryView inventory = this.player.containerMenu.getBukkitView();
-+                    SlotType type = inventory.getSlotType(packet.getSlotNum());
++                    SlotType type = inventory.getSlotType(slotNum);
 +
 +                    InventoryClickEvent event;
 +                    ClickType click = ClickType.UNKNOWN;
 +                    InventoryAction action = InventoryAction.UNKNOWN;
 +
-+                    ItemStack itemstack = ItemStack.EMPTY;
-+
 +                    switch (packet.getClickType()) {
 +                        case PICKUP:
 +                            if (packet.getButtonNum() == 0) {
@@ -2109,14 +2020,14 @@
 +                            }
 +                            if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) {
 +                                action = InventoryAction.NOTHING; // Don't want to repeat ourselves
-+                                if (packet.getSlotNum() == -999) {
++                                if (slotNum == AbstractContainerMenu.SLOT_CLICKED_OUTSIDE) {
 +                                    if (!this.player.containerMenu.getCarried().isEmpty()) {
 +                                        action = packet.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR;
 +                                    }
-+                                } else if (packet.getSlotNum() < 0)  {
++                                } else if (slotNum < 0)  {
 +                                    action = InventoryAction.NOTHING;
 +                                } else {
-+                                    Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++                                    Slot slot = this.player.containerMenu.getSlot(slotNum);
 +                                    if (slot != null) {
 +                                        ItemStack clickedItem = slot.getItem();
 +                                        ItemStack cursor = this.player.containerMenu.getCarried();
@@ -2157,7 +2068,7 @@
 +                                }
 +                            }
 +                            break;
-+                        // TODO check on updates
++                            // TODO check on updates
 +                        case QUICK_MOVE:
 +                            if (packet.getButtonNum() == 0) {
 +                                click = ClickType.SHIFT_LEFT;
@@ -2165,10 +2076,10 @@
 +                                click = ClickType.SHIFT_RIGHT;
 +                            }
 +                            if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) {
-+                                if (packet.getSlotNum() < 0) {
++                                if (slotNum < 0) {
 +                                    action = InventoryAction.NOTHING;
 +                                } else {
-+                                    Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++                                    Slot slot = this.player.containerMenu.getSlot(slotNum);
 +                                    if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) {
 +                                        action = InventoryAction.MOVE_TO_OTHER_INVENTORY;
 +                                    } else {
@@ -2177,150 +2088,150 @@
 +                                }
 +                            }
 +                            break;
-+                        case SWAP:
-+                            if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) {
-+                                // Paper start - Add slot sanity checks to container clicks
-+                                if (packet.getSlotNum() < 0) {
-+                                    action = InventoryAction.NOTHING;
-+                                    break;
-+                                }
-+                                // Paper end - Add slot sanity checks to container clicks
-+                                click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY;
-+                                Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum());
-+                                if (clickedSlot.mayPickup(this.player)) {
-+                                    ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum());
-+                                    if ((!hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) || (hotbar.isEmpty() && clickedSlot.hasItem())) { // Paper - modernify this logic (no such thing as a "hotbar move and readd"
-+                                        action = InventoryAction.HOTBAR_SWAP;
++                            case SWAP:
++                                if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == Inventory.SLOT_OFFHAND) {
++                                    // Paper start - Add slot sanity checks to container clicks
++                                    if (slotNum < 0) {
++                                        action = InventoryAction.NOTHING;
++                                        break;
++                                    }
++                                    // Paper end - Add slot sanity checks to container clicks
++                                    click = (packet.getButtonNum() == Inventory.SLOT_OFFHAND) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY;
++                                    Slot clickedSlot = this.player.containerMenu.getSlot(slotNum);
++                                    if (clickedSlot.mayPickup(this.player)) {
++                                        ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum());
++                                        if ((!hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) || (hotbar.isEmpty() && clickedSlot.hasItem())) { // Paper - modernify this logic (no such thing as a "hotbar move and readd"
++                                            action = InventoryAction.HOTBAR_SWAP;
++                                        } else {
++                                            action = InventoryAction.NOTHING;
++                                        }
 +                                    } else {
 +                                        action = InventoryAction.NOTHING;
 +                                    }
++                                }
++                                break;
++                            case CLONE:
++                                if (packet.getButtonNum() == 2) {
++                                    click = ClickType.MIDDLE;
++                                    if (slotNum < 0) {
++                                        action = InventoryAction.NOTHING;
++                                    } else {
++                                        Slot slot = this.player.containerMenu.getSlot(slotNum);
++                                        if (slot != null && slot.hasItem() && this.player.getAbilities().instabuild && this.player.containerMenu.getCarried().isEmpty()) {
++                                            action = InventoryAction.CLONE_STACK;
++                                        } else {
++                                            action = InventoryAction.NOTHING;
++                                        }
++                                    }
 +                                } else {
-+                                    action = InventoryAction.NOTHING;
++                                    click = ClickType.UNKNOWN;
++                                    action = InventoryAction.UNKNOWN;
 +                                }
-+                            }
-+                            break;
-+                        case CLONE:
-+                            if (packet.getButtonNum() == 2) {
-+                                click = ClickType.MIDDLE;
-+                                if (packet.getSlotNum() < 0) {
-+                                    action = InventoryAction.NOTHING;
++                                break;
++                            case THROW:
++                                if (slotNum >= 0) {
++                                    if (packet.getButtonNum() == 0) {
++                                        click = ClickType.DROP;
++                                        Slot slot = this.player.containerMenu.getSlot(slotNum);
++                                        if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Items.AIR) {
++                                            action = InventoryAction.DROP_ONE_SLOT;
++                                        } else {
++                                            action = InventoryAction.NOTHING;
++                                        }
++                                    } else if (packet.getButtonNum() == 1) {
++                                        click = ClickType.CONTROL_DROP;
++                                        Slot slot = this.player.containerMenu.getSlot(slotNum);
++                                        if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Items.AIR) {
++                                            action = InventoryAction.DROP_ALL_SLOT;
++                                        } else {
++                                            action = InventoryAction.NOTHING;
++                                        }
++                                    }
 +                                } else {
-+                                    Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
-+                                    if (slot != null && slot.hasItem() && this.player.getAbilities().instabuild && this.player.containerMenu.getCarried().isEmpty()) {
-+                                        action = InventoryAction.CLONE_STACK;
-+                                    } else {
-+                                        action = InventoryAction.NOTHING;
++                                    // Sane default (because this happens when they are holding nothing. Don't ask why.)
++                                    click = ClickType.LEFT;
++                                    if (packet.getButtonNum() == 1) {
++                                        click = ClickType.RIGHT;
++                                    }
++                                    action = InventoryAction.NOTHING;
++                                }
++                                break;
++                            case QUICK_CRAFT:
++                                // Paper start - Fix CraftBukkit drag system
++                                AbstractContainerMenu containerMenu = this.player.containerMenu;
++                                int currentStatus = this.player.containerMenu.quickcraftStatus;
++                                int newStatus = AbstractContainerMenu.getQuickcraftHeader(packet.getButtonNum());
++                                if ((currentStatus != 1 || newStatus != 2 && currentStatus != newStatus)) {
++                                } else if (containerMenu.getCarried().isEmpty()) {
++                                } else if (newStatus == 0) {
++                                } else if (newStatus == 1) {
++                                } else if (newStatus == 2) {
++                                    if (!this.player.containerMenu.quickcraftSlots.isEmpty()) {
++                                        if (this.player.containerMenu.quickcraftSlots.size() == 1) {
++                                            int index = containerMenu.quickcraftSlots.iterator().next().index;
++                                            containerMenu.resetQuickCraft();
++                                            this.handleContainerClick(new ServerboundContainerClickPacket(packet.getContainerId(), packet.getStateId(), index, containerMenu.quickcraftType, net.minecraft.world.inventory.ClickType.PICKUP, packet.getCarriedItem(), packet.getChangedSlots()));
++                                            return;
++                                        }
 +                                    }
 +                                }
-+                            } else {
-+                                click = ClickType.UNKNOWN;
-+                                action = InventoryAction.UNKNOWN;
-+                            }
-+                            break;
-+                        case THROW:
-+                            if (packet.getSlotNum() >= 0) {
-+                                if (packet.getButtonNum() == 0) {
-+                                    click = ClickType.DROP;
-+                                    Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
-+                                    if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) {
-+                                        action = InventoryAction.DROP_ONE_SLOT;
-+                                    } else {
-+                                        action = InventoryAction.NOTHING;
-+                                    }
-+                                } else if (packet.getButtonNum() == 1) {
-+                                    click = ClickType.CONTROL_DROP;
-+                                    Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
-+                                    if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) {
-+                                        action = InventoryAction.DROP_ALL_SLOT;
-+                                    } else {
-+                                        action = InventoryAction.NOTHING;
-+                                    }
-+                                }
-+                            } else {
-+                                // Sane default (because this happens when they are holding nothing. Don't ask why.)
-+                                click = ClickType.LEFT;
-+                                if (packet.getButtonNum() == 1) {
-+                                    click = ClickType.RIGHT;
-+                                }
++                                // Paper end - Fix CraftBukkit drag system
++                                this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player);
++                                break;
++                            case PICKUP_ALL:
++                                click = ClickType.DOUBLE_CLICK;
 +                                action = InventoryAction.NOTHING;
-+                            }
-+                            break;
-+                        case QUICK_CRAFT:
-+                            // Paper start - Fix CraftBukkit drag system
-+                            AbstractContainerMenu containerMenu = this.player.containerMenu;
-+                            int currentStatus = this.player.containerMenu.quickcraftStatus;
-+                            int newStatus = AbstractContainerMenu.getQuickcraftHeader(packet.getButtonNum());
-+                            if ((currentStatus != 1 || newStatus != 2 && currentStatus != newStatus)) {
-+                            } else if (containerMenu.getCarried().isEmpty()) {
-+                            } else if (newStatus == 0) {
-+                            } else if (newStatus == 1) {
-+                            } else if (newStatus == 2) {
-+                                if (!this.player.containerMenu.quickcraftSlots.isEmpty()) {
-+                                    if (this.player.containerMenu.quickcraftSlots.size() == 1) {
-+                                        int index = containerMenu.quickcraftSlots.iterator().next().index;
-+                                        containerMenu.resetQuickCraft();
-+                                        this.handleContainerClick(new ServerboundContainerClickPacket(packet.getContainerId(), packet.getStateId(), index, containerMenu.quickcraftType, net.minecraft.world.inventory.ClickType.PICKUP, packet.getCarriedItem(), packet.getChangedSlots()));
-+                                        return;
++                                if (slotNum >= 0 && !this.player.containerMenu.getCarried().isEmpty()) {
++                                    ItemStack cursor = this.player.containerMenu.getCarried();
++                                    action = InventoryAction.NOTHING;
++                                    // Quick check for if we have any of the item
++                                    if (inventory.getTopInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem())) || inventory.getBottomInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem()))) {
++                                        action = InventoryAction.COLLECT_TO_CURSOR;
 +                                    }
 +                                }
-+                            }
-+                            // Paper end - Fix CraftBukkit drag system
-+                            this.player.containerMenu.clicked(packet.getSlotNum(), packet.getButtonNum(), packet.getClickType(), this.player);
-+                            break;
-+                        case PICKUP_ALL:
-+                            click = ClickType.DOUBLE_CLICK;
-+                            action = InventoryAction.NOTHING;
-+                            if (packet.getSlotNum() >= 0 && !this.player.containerMenu.getCarried().isEmpty()) {
-+                                ItemStack cursor = this.player.containerMenu.getCarried();
-+                                action = InventoryAction.NOTHING;
-+                                // Quick check for if we have any of the item
-+                                if (inventory.getTopInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem())) || inventory.getBottomInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem()))) {
-+                                    action = InventoryAction.COLLECT_TO_CURSOR;
-+                                }
-+                            }
-+                            break;
-+                        default:
-+                            break;
++                                break;
++                            default:
++                                break;
 +                    }
 +
 +                    if (packet.getClickType() != net.minecraft.world.inventory.ClickType.QUICK_CRAFT) {
 +                        if (click == ClickType.NUMBER_KEY) {
-+                            event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++                            event = new InventoryClickEvent(inventory, type, slotNum, click, action, packet.getButtonNum());
 +                        } else {
-+                            event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action);
++                            event = new InventoryClickEvent(inventory, type, slotNum, click, action);
 +                        }
 +
 +                        org.bukkit.inventory.Inventory top = inventory.getTopInventory();
-+                        if (packet.getSlotNum() == 0 && top instanceof CraftingInventory) {
++                        if (slotNum == 0 && top instanceof CraftingInventory) {
 +                            org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe();
 +                            if (recipe != null) {
 +                                if (click == ClickType.NUMBER_KEY) {
-+                                    event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++                                    event = new CraftItemEvent(recipe, inventory, type, slotNum, click, action, packet.getButtonNum());
 +                                } else {
-+                                    event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action);
++                                    event = new CraftItemEvent(recipe, inventory, type, slotNum, click, action);
 +                                }
 +                            }
 +                        }
 +
-+                        if (packet.getSlotNum() == 3 && top instanceof SmithingInventory) {
++                        if (slotNum == 3 && top instanceof SmithingInventory) {
 +                            org.bukkit.inventory.ItemStack result = ((SmithingInventory) top).getResult();
 +                            if (result != null) {
 +                                if (click == ClickType.NUMBER_KEY) {
-+                                    event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++                                    event = new SmithItemEvent(inventory, type, slotNum, click, action, packet.getButtonNum());
 +                                } else {
-+                                    event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action);
++                                    event = new SmithItemEvent(inventory, type, slotNum, click, action);
 +                                }
 +                            }
 +                        }
 +
 +                        // Paper start - cartography item event
-+                        if (packet.getSlotNum() == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) {
++                        if (slotNum == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) {
 +                            org.bukkit.inventory.ItemStack result = cartographyInventory.getResult();
 +                            if (result != null && !result.isEmpty()) {
 +                                if (click == ClickType.NUMBER_KEY) {
-+                                    event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++                                    event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, slotNum, click, action, packet.getButtonNum());
 +                                } else {
-+                                    event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action);
++                                    event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, slotNum, click, action);
 +                                }
 +                            }
 +                        }
@@ -2336,7 +2247,7 @@
 +                        switch (event.getResult()) {
 +                            case ALLOW:
 +                            case DEFAULT:
-+                                this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player);
++                                this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player);
 +                                break;
 +                            case DENY:
 +                                /* Needs enum constructor in InventoryAction
@@ -2369,22 +2280,22 @@
 +                                    case PLACE_ONE:
 +                                    case SWAP_WITH_CURSOR:
 +                                        this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor
-+                                        this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem()));
++                                        this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), slotNum, this.player.containerMenu.getSlot(slotNum).getItem()));
 +                                        break;
 +                                    // Modified clicked only
 +                                    case DROP_ALL_SLOT:
 +                                    case DROP_ONE_SLOT:
-+                                        this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem()));
++                                        this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), slotNum, this.player.containerMenu.getSlot(slotNum).getItem()));
 +                                        break;
 +                                    // Modified cursor only
 +                                    case DROP_ALL_CURSOR:
-+                                    case DROP_ONE_CURSOR:
-+                                    case CLONE_STACK:
++                                        case DROP_ONE_CURSOR:
++                                        case CLONE_STACK:
 +                                        this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor
 +                                        break;
 +                                    // Nothing
 +                                    case NOTHING:
-+                                        break;
++                                    break;
 +                                }
 +                        }
 +
@@ -2394,11 +2305,11 @@
 +                            this.player.containerMenu.sendAllDataToRemote();
 +                        }
 +                    }
-+                    // CraftBukkit end
-                     ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator();
++                   // CraftBukkit end
  
-                     while (objectiterator.hasNext()) {
-@@ -1879,6 +3332,14 @@
+                     for (Entry<ItemStack> entry : Int2ObjectMaps.fastIterable(packet.getChangedSlots())) {
+                         this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), entry.getValue());
+@@ -1733,6 +_,14 @@
  
      @Override
      public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {
@@ -2413,12 +2324,12 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          this.player.resetLastActionTime();
          if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.containerId()) {
-@@ -1900,9 +3361,43 @@
-                                 ServerGamePacketListenerImpl.LOGGER.debug("Player {} tried to place impossible recipe {}", this.player, recipeholder.id().location());
+@@ -1749,9 +_,44 @@
                                  return;
                              }
+ 
 +                            // Paper start - Add PlayerRecipeBookClickEvent
-+                            NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeholder.id().location());
++                            NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeHolder.id().location());
 +                            boolean makeAll = packet.useMaxItems();
 +                            com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent(
 +                                this.player.getBukkitEntity(), recipeName, makeAll
@@ -2429,36 +2340,37 @@
 +                            recipeName = paperEvent.getRecipe();
 +                            makeAll = paperEvent.isMakeAll();
 +                            if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+                            // Paper end - Add PlayerRecipeBookClickEvent
- 
--                            RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(packet.useMaxItems(), this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory());
-+                            // CraftBukkit start - implement PlayerRecipeBookClickEvent
-+                            org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event
-+                            if (recipe == null) {
-+                                return;
-+                            }
-+                            // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event
-+                            org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll);
-+                            recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey();
-+                            makeAll = event.isShiftClick();
++                                // Paper end - Add PlayerRecipeBookClickEvent
++                                // CraftBukkit start - implement PlayerRecipeBookClickEvent
++                                org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event
++                                if (recipe == null) {
++                                    return;
++                                }
++                                // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event
++                                org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll);
++                                recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey();
++                                makeAll = event.isShiftClick();
 +                            }
 +                            if (!(this.player.containerMenu instanceof RecipeBookMenu)) {
 +                                return;
 +                            }
 +                            // Paper end - Add PlayerRecipeBookClickEvent - forward to legacy event
- 
++
 +                            // Cast to keyed should be safe as the recipe will never be a MerchantRecipe.
-+                            recipeholder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event
-+                            if (recipeholder == null) {
++                            recipeHolder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event
++                            if (recipeHolder == null) {
 +                                return;
 +                            }
-+                            RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(makeAll, this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory());
-+                            // CraftBukkit end
 +
-                             if (containerrecipebook_a == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) {
-                                 this.player.connection.send(new ClientboundPlaceGhostRecipePacket(this.player.containerMenu.containerId, craftingmanager_d.display().display()));
-                             }
-@@ -1917,6 +3412,7 @@
+                             RecipeBookMenu.PostPlaceAction postPlaceAction = recipeBookMenu.handlePlacement(
+-                                packet.useMaxItems(), this.player.isCreative(), recipeHolder, this.player.serverLevel(), this.player.getInventory()
++                                makeAll, this.player.isCreative(), recipeHolder, this.player.serverLevel(), this.player.getInventory()
+                             );
++                            // CraftBukkit end
+                             if (postPlaceAction == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) {
+                                 this.player
+                                     .connection
+@@ -1767,6 +_,7 @@
      @Override
      public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -2466,15 +2378,15 @@
          this.player.resetLastActionTime();
          if (this.player.containerMenu.containerId == packet.containerId() && !this.player.isSpectator()) {
              if (!this.player.containerMenu.stillValid(this.player)) {
-@@ -1945,7 +3441,44 @@
+@@ -1792,6 +_,43 @@
  
              boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
-             boolean flag2 = itemstack.isEmpty() || itemstack.getCount() <= itemstack.getMaxStackSize();
+             boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= itemStack.getMaxStackSize();
 +            if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.slotNum()).getItem(), packet.itemStack()))) { // Insist on valid slot
 +                // CraftBukkit start - Call click event
 +                InventoryView inventory = this.player.inventoryMenu.getBukkitView();
 +                org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet.itemStack());
- 
++
 +                SlotType type = SlotType.QUICKBAR;
 +                if (flag) {
 +                    type = SlotType.OUTSIDE;
@@ -2485,37 +2397,36 @@
 +                        type = SlotType.CONTAINER;
 +                    }
 +                }
-+                InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet.slotNum(), item);
++                InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? AbstractContainerMenu.SLOT_CLICKED_OUTSIDE : packet.slotNum(), item);
 +                this.cserver.getPluginManager().callEvent(event);
 +
-+                itemstack = CraftItemStack.asNMSCopy(event.getCursor());
++                itemStack = CraftItemStack.asNMSCopy(event.getCursor());
 +
 +                switch (event.getResult()) {
-+                case ALLOW:
-+                    // Plugin cleared the id / stacksize checks
-+                    flag2 = true;
-+                    break;
-+                case DEFAULT:
-+                    break;
-+                case DENY:
-+                    // Reset the slot
-+                    if (packet.slotNum() >= 0) {
-+                        this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.slotNum(), this.player.inventoryMenu.getSlot(packet.slotNum()).getItem()));
-+                        this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(ItemStack.EMPTY.copy())); // Paper - correctly set cursor
-+                    }
-+                    return;
++                    case ALLOW:
++                        // Plugin cleared the id / stacksize checks
++                        flag2 = true;
++                        break;
++                    case DEFAULT:
++                        break;
++                    case DENY:
++                        // Reset the slot
++                        if (packet.slotNum() >= 0) {
++                            this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.slotNum(), this.player.inventoryMenu.getSlot(packet.slotNum()).getItem()));
++                            this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(ItemStack.EMPTY.copy())); // Paper - correctly set cursor
++                        }
++                        return;
 +                }
 +            }
 +            // CraftBukkit end
-+
              if (flag1 && flag2) {
-                 this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemstack);
-                 this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemstack);
-@@ -1964,7 +3497,19 @@
+                 this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemStack);
+                 this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemStack);
+@@ -1809,11 +_,24 @@
  
      @Override
      public void handleSignUpdate(ServerboundSignUpdatePacket packet) {
--        List<String> list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList());
+-        List<String> list = Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList());
 +        // Paper start - Limit client sign length
 +        String[] lines = packet.getLines();
 +        for (int i = 0; i < lines.length; ++i) {
@@ -2527,20 +2438,17 @@
 +                }
 +            }
 +        }
-+        List<String> list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList());
++        List<String> list = Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList());
 +        // Paper end - Limit client sign length
- 
-         this.filterTextPacket(list).thenAcceptAsync((list1) -> {
-             this.updateSignText(packet, list1);
-@@ -1972,6 +3517,7 @@
+         this.filterTextPacket(list).thenAcceptAsync(list1 -> this.updateSignText(packet, (List<FilteredText>)list1), this.server);
      }
  
-     private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> signText) {
+     private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) {
 +        if (this.player.isImmobile()) return; // CraftBukkit
          this.player.resetLastActionTime();
-         ServerLevel worldserver = this.player.serverLevel();
-         BlockPos blockposition = packet.getPos();
-@@ -1993,15 +3539,33 @@
+         ServerLevel serverLevel = this.player.serverLevel();
+         BlockPos pos = packet.getPos();
+@@ -1829,14 +_,32 @@
      @Override
      public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -2563,19 +2471,18 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        // Paper start - do not accept invalid information
 +        if (packet.information().viewDistance() < 0) {
-+            LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance());
++            LOGGER.warn("Disconnecting {} for invalid view distance: {}", this.player.getScoreboardName(), packet.information().viewDistance());
 +            this.disconnect(Component.literal("Invalid client settings"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION);
 +            return;
 +        }
 +        // Paper end - do not accept invalid information
-         boolean flag = this.player.isModelPartShown(PlayerModelPart.HAT);
- 
+         boolean isModelPartShown = this.player.isModelPartShown(PlayerModelPart.HAT);
          this.player.updateOptions(packet.information());
 +        this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper
-         if (this.player.isModelPartShown(PlayerModelPart.HAT) != flag) {
+         if (this.player.isModelPartShown(PlayerModelPart.HAT) != isModelPartShown) {
              this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, this.player));
          }
-@@ -2012,7 +3576,7 @@
+@@ -1846,7 +_,7 @@
      public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) {
@@ -2584,66 +2491,74 @@
          }
      }
  
-@@ -2033,7 +3597,7 @@
- 
-         if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) {
-             if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) {
+@@ -1866,7 +_,7 @@
+         ProfilePublicKey.Data data2 = data.profilePublicKey();
+         if (!Objects.equals(data1, data2)) {
+             if (data1 != null && data2.expiresAt().isBefore(data1.expiresAt())) {
 -                this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY);
 +                this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes
              } else {
                  try {
-                     SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator();
-@@ -2045,8 +3609,8 @@
+                     SignatureValidator profileKeySignatureValidator = this.server.getProfileKeySignatureValidator();
+@@ -1877,8 +_,8 @@
  
-                     this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator));
-                 } catch (ProfilePublicKey.ValidationException profilepublickey_b) {
--                    ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage());
--                    this.disconnect(profilepublickey_b.getComponent());
-+                    // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors
-+                    this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes
+                     this.resetPlayerChatState(data.validate(this.player.getGameProfile(), profileKeySignatureValidator));
+                 } catch (ProfilePublicKey.ValidationException var6) {
+-                    LOGGER.error("Failed to validate profile key: {}", var6.getMessage());
+-                    this.disconnect(var6.getComponent());
++                    // LOGGER.error("Failed to validate profile key: {}", var6.getMessage()); // Paper - Improve logging and errors
++                    this.disconnect(var6.getComponent(), var6.kickCause); // Paper - kick event causes
                  }
- 
              }
-@@ -2058,7 +3622,7 @@
-         if (!this.waitingForSwitchToConfig) {
-             throw new IllegalStateException("Client acknowledged config, but none was requested");
-         } else {
--            this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())));
-+            this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit
+         }
+@@ -1892,7 +_,7 @@
+             this.connection
+                 .setupInboundProtocol(
+                     ConfigurationProtocols.SERVERBOUND,
+-                    new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))
++                    new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player) // CraftBukkit
+                 );
          }
      }
+@@ -1911,6 +_,7 @@
  
-@@ -2076,15 +3640,18 @@
- 
-     private void resetPlayerChatState(RemoteChatSession session) {
-         this.chatSession = session;
+     private void resetPlayerChatState(RemoteChatSession chatSession) {
+         this.chatSession = chatSession;
 +        this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins
-         this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID());
-         this.chatMessageChain.append(() -> {
-             this.player.setChatSession(session);
--            this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)));
-+            this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join
-         });
+         this.signedMessageDecoder = chatSession.createMessageDecoder(this.player.getUUID());
+         this.chatMessageChain
+             .append(
+@@ -1919,15 +_,17 @@
+                     this.server
+                         .getPlayerList()
+                         .broadcastAll(
+-                            new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))
++                            new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player // Paper - Use single player info update packet on join
+                         );
+                 }
+             );
      }
  
 -    @Override
--    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {}
+-    public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+-    }
 +    // CraftBukkit start - handled in super
 +    // @Override
-+    // public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) {}
++    // public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
++    // }
 +    // CraftBukkit end
  
      @Override
      public void handleClientTickEnd(ServerboundClientTickEndPacket packet) {
-@@ -2115,4 +3682,17 @@
- 
+@@ -1957,4 +_,17 @@
+     interface EntityInteraction {
          InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand);
      }
 +
 +    // Paper start - Add fail move event
 +    private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason,
 +                                                                           double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) {
-+        Player player = this.getCraftPlayer();
++        org.bukkit.entity.Player player = this.getCraftPlayer();
 +        Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch);
 +        Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch);
 +        io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason,
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
index bf3a6ca144..64f87203f2 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
-@@ -13,11 +13,26 @@
+@@ -12,11 +_,27 @@
  import net.minecraft.network.protocol.status.StatusProtocols;
  import net.minecraft.server.MinecraftServer;
  
@@ -10,24 +10,25 @@
 +// CraftBukkit end
 +
  public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener {
- 
+     private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
 +    // Spigot start
 +    private static final com.google.gson.Gson gson = new com.google.gson.Gson();
 +    static final java.util.regex.Pattern HOST_PATTERN = java.util.regex.Pattern.compile("[0-9a-f\\.:]{0,45}");
 +    static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}");
 +    // Spigot end
 +    // CraftBukkit start - add fields
-+    private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>();
++    private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<>();
 +    private static int throttleCounter = 0;
 +    // CraftBukkit end
-     private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
++    private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper
      private final MinecraftServer server;
      private final Connection connection;
-+    private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper
  
++
      public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) {
          this.server = server;
-@@ -26,6 +41,7 @@
+         this.connection = connection;
+@@ -24,6 +_,7 @@
  
      @Override
      public void handleIntention(ClientIntentionPacket packet) {
@@ -35,73 +36,66 @@
          switch (packet.intention()) {
              case LOGIN:
                  this.beginLogin(packet, false);
-@@ -55,23 +71,127 @@
-                 throw new UnsupportedOperationException("Invalid intention " + String.valueOf(packet.intention()));
+@@ -50,22 +_,117 @@
+             default:
+                 throw new UnsupportedOperationException("Invalid intention " + packet.intention());
          }
- 
++
 +        // Paper start - NetworkClient implementation
 +        this.connection.protocolVersion = packet.protocolVersion();
 +        this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port());
 +        // Paper end
      }
  
-     private void beginLogin(ClientIntentionPacket packet, boolean transfer) {
+     private void beginLogin(ClientIntentionPacket packet, boolean transferred) {
          this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND);
 +        // CraftBukkit start - Connection throttle
 +        try {
 +            if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket
-+            long currentTime = System.currentTimeMillis();
-+            long connectionThrottle = this.server.server.getConnectionThrottle();
-+            InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
++                long currentTime = System.currentTimeMillis();
++                long connectionThrottle = this.server.server.getConnectionThrottle();
++                InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
++
++                synchronized (ServerHandshakePacketListenerImpl.throttleTracker) {
++                    if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) {
++                        ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
++                        Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message
++                        this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
++                        this.connection.disconnect(chatmessage);
++                        return;
++                    }
 +
-+            synchronized (ServerHandshakePacketListenerImpl.throttleTracker) {
-+                if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) {
 +                    ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
-+                            Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message
-+                    this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
-+                    this.connection.disconnect(chatmessage);
-+                    return;
-+                }
++                    ServerHandshakePacketListenerImpl.throttleCounter++;
++                    if (ServerHandshakePacketListenerImpl.throttleCounter > 200) {
++                        ServerHandshakePacketListenerImpl.throttleCounter = 0;
 +
-+                ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
-+                ServerHandshakePacketListenerImpl.throttleCounter++;
-+                if (ServerHandshakePacketListenerImpl.throttleCounter > 200) {
-+                    ServerHandshakePacketListenerImpl.throttleCounter = 0;
-+
-+                    // Cleanup stale entries
-+                    java.util.Iterator iter = ServerHandshakePacketListenerImpl.throttleTracker.entrySet().iterator();
-+                    while (iter.hasNext()) {
-+                        java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next();
-+                        if (entry.getValue() > connectionThrottle) {
-+                            iter.remove();
-+                        }
++                        // Cleanup stale entries
++                        ServerHandshakePacketListenerImpl.throttleTracker.values().removeIf(time -> time > connectionThrottle);
 +                    }
 +                }
-+            }
 +            } // Paper - Unix domain socket support
 +        } catch (Throwable t) {
 +            org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
 +        }
 +        // CraftBukkit end
          if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) {
--            MutableComponent ichatmutablecomponent;
-+                    net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages
- 
+-            Component component;
 -            if (packet.protocolVersion() < 754) {
--                ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
+-                component = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
++            net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages
 +            if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message
-+                        adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
++                adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
              } else {
--                ichatmutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName());
-+                        adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
+-                component = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName());
++                adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
              }
++            Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages
  
-+                    Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages
-+
-             this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent));
-             this.connection.disconnect((Component) ichatmutablecomponent);
+             this.connection.send(new ClientboundLoginDisconnectPacket(component));
+             this.connection.disconnect(component);
          } else {
-             this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transfer));
+             this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transferred));
 +            // Paper start - PlayerHandshakeEvent
 +            boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee;
 +            boolean handledByEvent = false;
@@ -139,31 +133,28 @@
 +            String[] split = packet.hostName().split("\00");
 +            if (!handledByEvent && proxyLogicEnabled) { // Paper
 +                // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
-+                if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check
++                if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { // Paper - Add bypass host check
 +                    // Paper start - Unix domain socket support
 +                    java.net.SocketAddress socketAddress = this.connection.getRemoteAddress();
 +                    this.connection.hostname = split[0];
 +                    this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
 +                    // Paper end - Unix domain socket support
 +                    this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
-+                } else
-+                {
++                } else {
 +                    Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
 +                    this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
 +                    this.connection.disconnect(chatmessage);
 +                    return;
 +                }
-+                if ( split.length == 4 )
-+                {
++                if (split.length == 4) {
 +                    this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class);
 +                }
-+            } else if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) {
++            } else if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) {
 +                Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
 +                this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
 +                this.connection.disconnect(chatmessage);
-+                return;
 +            }
 +            // Spigot End
          }
- 
      }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
similarity index 68%
rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
rename to paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
index 2f73ee117e..579db13c3c 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -1,25 +1,10 @@
 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-@@ -20,6 +20,7 @@
- import net.minecraft.DefaultUncaughtExceptionHandler;
- import net.minecraft.core.UUIDUtil;
- import net.minecraft.network.Connection;
-+import net.minecraft.network.ConnectionProtocol;
- import net.minecraft.network.DisconnectionDetails;
- import net.minecraft.network.PacketSendListener;
- import net.minecraft.network.TickablePacketListener;
-@@ -36,6 +37,7 @@
- import net.minecraft.network.protocol.login.ServerboundKeyPacket;
- import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket;
- import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.server.players.PlayerList;
- import net.minecraft.util.Crypt;
- import net.minecraft.util.CryptException;
-@@ -43,11 +45,38 @@
+@@ -43,10 +_,19 @@
  import net.minecraft.util.StringUtil;
  import org.apache.commons.lang3.Validate;
  import org.slf4j.Logger;
++// CraftBukkit start
 +import net.minecraft.network.protocol.Packet;
 +import net.minecraft.network.protocol.PacketUtils;
 +import org.bukkit.craftbukkit.entity.CraftPlayer;
@@ -29,15 +14,40 @@
  
 -public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener {
 +public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection {
++    // CraftBukkit end
+     private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
+     static final Logger LOGGER = LogUtils.getLogger();
++    private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads
+     private static final int MAX_TICKS_BEFORE_LOGIN = 600;
+     private final byte[] challenge;
+     final MinecraftServer server;
+@@ -56,9 +_,12 @@
+     @Nullable
+     String requestedUsername;
+     @Nullable
+-    private GameProfile authenticatedProfile;
++    public GameProfile authenticatedProfile; // Paper - public
+     private final String serverId = "";
+     private final boolean transferred;
++    private net.minecraft.server.level.ServerPlayer player; // CraftBukkit
++    public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding
++    private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support
  
+     public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
+         this.server = server;
+@@ -67,11 +_,48 @@
+         this.transferred = transferred;
+     }
+ 
++    // CraftBukkit start
 +    @Override
 +    public boolean isTransferred() {
 +        return this.transferred;
 +    }
 +
 +    @Override
-+    public ConnectionProtocol getProtocol() {
-+        return ConnectionProtocol.LOGIN;
++    public net.minecraft.network.ConnectionProtocol getProtocol() {
++        return net.minecraft.network.ConnectionProtocol.LOGIN;
 +    }
 +
 +    @Override
@@ -50,81 +60,61 @@
 +        this.disconnect(reason);
 +    }
 +    // CraftBukkit end
-     private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
-     static final Logger LOGGER = LogUtils.getLogger();
-+    private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads
-     private static final int MAX_TICKS_BEFORE_LOGIN = 600;
-     private final byte[] challenge;
-     final MinecraftServer server;
-@@ -57,9 +86,12 @@
-     @Nullable
-     String requestedUsername;
-     @Nullable
--    private GameProfile authenticatedProfile;
-+    public GameProfile authenticatedProfile; // Paper - public
-     private final String serverId;
-     private final boolean transferred;
-+    private ServerPlayer player; // CraftBukkit
-+    public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding
-+    private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support
- 
-     public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
-         this.state = ServerLoginPacketListenerImpl.State.HELLO;
-@@ -72,10 +104,24 @@
- 
++
      @Override
      public void tick() {
 +        // Paper start - Do not allow logins while the server is shutting down
-+        if (!MinecraftServer.getServer().isRunning()) {
++        if (!this.server.isRunning()) {
 +            this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]);
 +            return;
 +        }
 +        // Paper end - Do not allow logins while the server is shutting down
++
          if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) {
 +            if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called
-             this.verifyLoginAndFinishConnectionSetup((GameProfile) Objects.requireNonNull(this.authenticatedProfile));
+             this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile));
+-        }
 +            } // Paper - prevent logins to be processed even though disconnect was called
-         }
- 
++        }
++
 +        // CraftBukkit start
 +        if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) {
 +            this.postCookies(this.authenticatedProfile);
 +        }
 +        // CraftBukkit end
-+
-         if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) {
-             this.finishLoginAndWaitForClient(this.authenticatedProfile);
-         }
-@@ -86,6 +132,13 @@
  
+         if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT
+             && !this.isPlayerAlreadyInWorld(Objects.requireNonNull(this.authenticatedProfile))) {
+@@ -83,6 +_,13 @@
+         }
      }
  
 +    // CraftBukkit start
 +    @Deprecated
-+    public void disconnect(String s) {
-+        this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages
++    public void disconnect(String reason) {
++        this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(reason))); // Paper - Fix hex colors not working in some kick messages
 +    }
 +    // CraftBukkit end
 +
      @Override
      public boolean isAcceptingMessages() {
          return this.connection.isConnected();
-@@ -120,7 +173,13 @@
+@@ -115,7 +_,13 @@
      @Override
      public void handleHello(ServerboundHelloPacket packet) {
-         Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]);
--        Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]);
+         Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet");
+-        Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username");
 +        // Paper start - Validate usernames
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
 +            && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation
 +            && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) {
-+            Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]);
++            Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username");
 +        }
 +        // Paper end - Validate usernames
          this.requestedUsername = packet.name();
-         GameProfile gameprofile = this.server.getSingleplayerProfile();
- 
-@@ -131,7 +190,36 @@
+         GameProfile singleplayerProfile = this.server.getSingleplayerProfile();
+         if (singleplayerProfile != null && this.requestedUsername.equalsIgnoreCase(singleplayerProfile.getName())) {
+@@ -125,7 +_,32 @@
                  this.state = ServerLoginPacketListenerImpl.State.KEY;
                  this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true));
              } else {
@@ -141,37 +131,32 @@
 +                // Paper end - Add Velocity IP Forwarding Support
 +                // CraftBukkit start
 +                // Paper start - Cache authenticator threads
-+                authenticatorPool.execute(new Runnable() {
++                authenticatorPool.execute(() -> {
++                    try {
++                        GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot
 +
-+                    @Override
-+                    public void run() {
-+                        try {
-+                            GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot
-+
-+                            gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
-+                            ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
-+                            ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
-+                        } catch (Exception ex) {
-+                            ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
-+                            ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex);
-+                        }
++                        gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
++                        ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
++                        ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
++                    } catch (Exception ex) {
++                        ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
++                        ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex);
 +                    }
 +                });
 +                // Paper end - Cache authenticator threads
 +                // CraftBukkit end
              }
- 
          }
-@@ -144,10 +232,24 @@
+     }
+@@ -137,9 +_,23 @@
  
      private void verifyLoginAndFinishConnectionSetup(GameProfile profile) {
-         PlayerList playerlist = this.server.getPlayerList();
--        Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), profile);
+         PlayerList playerList = this.server.getPlayerList();
+-        Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), profile);
+-        if (component != null) {
+-            this.disconnect(component);
 +        // CraftBukkit start - fire PlayerLoginEvent
-+        this.player = playerlist.canPlayerLogin(this, profile); // CraftBukkit
- 
--        if (ichatbasecomponent != null) {
--            this.disconnect(ichatbasecomponent);
++        this.player = playerList.canPlayerLogin(this, profile); // CraftBukkit
 +        if (this.player != null) {
 +            if (this.player.getBukkitEntity().isAwaitingCookies()) {
 +                this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES;
@@ -181,94 +166,88 @@
 +        }
 +    }
 +
-+    private void postCookies(GameProfile gameprofile) {
-+        PlayerList playerlist = this.server.getPlayerList();
++    private void postCookies(GameProfile profile) {
++        PlayerList playerList = this.server.getPlayerList();
 +
 +        if (this.player == null) {
-+            // this.disconnect(ichatbasecomponent);
++            // this.disconnect(component);
 +            // CraftBukkit end
          } else {
              if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
-                 this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> {
-@@ -155,12 +257,12 @@
-                 }));
+                 this.connection
+@@ -149,7 +_,7 @@
+                     );
              }
  
--            boolean flag = playerlist.disconnectAllPlayersWithProfile(profile);
-+            boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference
- 
+-            boolean flag = playerList.disconnectAllPlayersWithProfile(profile);
++            boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference
              if (flag) {
                  this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT;
              } else {
--                this.finishLoginAndWaitForClient(profile);
-+                this.finishLoginAndWaitForClient(gameprofile);
-             }
+@@ -184,7 +_,8 @@
+             throw new IllegalStateException("Protocol error", var7);
          }
  
-@@ -195,7 +297,8 @@
-             throw new IllegalStateException("Protocol error", cryptographyexception);
-         }
- 
--        Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) {
+-        Thread thread = new Thread("User Authenticator #" + UNIQUE_THREAD_ID.incrementAndGet()) {
 +        // Paper start - Cache authenticator threads
 +        authenticatorPool.execute(new Runnable() {
+             @Override
              public void run() {
-                 String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
- 
-@@ -205,11 +308,17 @@
-                     if (profileresult != null) {
-                         GameProfile gameprofile = profileresult.profile();
- 
+                 String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
+@@ -195,11 +_,17 @@
+                         .hasJoinedServer(string1, string, this.getAddress());
+                     if (profileResult != null) {
+                         GameProfile gameProfile = profileResult.profile();
 +                        // CraftBukkit start - fire PlayerPreLoginEvent
 +                        if (!ServerLoginPacketListenerImpl.this.connection.isConnected()) {
 +                            return;
 +                        }
-+                        gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
++                        gameProfile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameProfile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
 +                        // CraftBukkit end
-                         ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
-                         ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
+                         ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameProfile.getName(), gameProfile.getId());
+                         ServerLoginPacketListenerImpl.this.startClientVerification(gameProfile);
                      } else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
                          ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
--                        ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1));
-+                        ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot
+-                        ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
++                        ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
                      } else {
                          ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
-                         ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1);
-@@ -217,11 +326,16 @@
-                 } catch (AuthenticationUnavailableException authenticationunavailableexception) {
+                         ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", string1);
+@@ -207,11 +_,16 @@
+                 } catch (AuthenticationUnavailableException var4) {
                      if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
                          ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!");
--                        ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1));
-+                        ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot
+-                        ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
++                        ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
                      } else {
--                        ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
-+                        ServerLoginPacketListenerImpl.this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.authenticationServersDown)); // Paper - Configurable kick message
+                         ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
                          ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable");
                      }
 +                    // CraftBukkit start - catch all exceptions
-+                } catch (Exception exception) {
++                } catch (Exception ex) {
 +                    ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
-+                    ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception);
++                    ServerLoginPacketListenerImpl.LOGGER.warn("Exception verifying {}", string1, ex);
 +                    // CraftBukkit end
                  }
- 
              }
-@@ -232,23 +346,118 @@
  
-                 return ServerLoginPacketListenerImpl.this.server.getPreventProxyConnections() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null;
+@@ -222,24 +_,120 @@
+                     ? ((InetSocketAddress)remoteAddress).getAddress()
+                     : null;
              }
 -        };
+-        thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
+-        thread.start();
+-    }
 +        });
 +        // Paper end - Cache authenticator threads
 +    }
- 
--        thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(ServerLoginPacketListenerImpl.LOGGER));
--        thread.start();
++
 +    // CraftBukkit start
 +    private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent
 +        // Paper start - Add Velocity IP Forwarding Support
 +        if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) {
-+            disconnect("This server requires you to connect with Velocity.");
++            this.disconnect("This server requires you to connect with Velocity.");
 +            return gameprofile;
 +        }
 +        // Paper end - Add Velocity IP Forwarding Support
@@ -294,7 +273,7 @@
 +            if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
 +                event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure
 +            }
-+            Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() {
++            Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<>() {
 +                @Override
 +                protected PlayerPreLoginEvent.Result evaluate() {
 +                    server.getPluginManager().callEvent(event);
@@ -312,11 +291,12 @@
 +            }
 +        }
 +        return gameprofile; // Paper - Add more fields to AsyncPlayerPreLoginEvent
-     }
++    }
 +    // CraftBukkit end
  
      @Override
      public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) {
+-        this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
 +        // Paper start - Add Velocity IP Forwarding Support
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) {
 +            ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload();
@@ -326,7 +306,6 @@
 +            }
 +
 +            net.minecraft.network.FriendlyByteBuf buf = payload.buffer;
-+
 +            if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) {
 +                this.disconnect("Unable to verify player details");
 +                return;
@@ -362,21 +341,24 @@
 +            return;
 +        }
 +        // Paper end - Add Velocity IP Forwarding Support
-         this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
++        // this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
      }
  
      @Override
      public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) {
 +        PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit
-         Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]);
+         Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
          this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
-         CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile), this.transferred);
--        ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie);
-+        ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit
- 
-         this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverconfigurationpacketlistenerimpl);
-         serverconfigurationpacketlistenerimpl.startConfiguration();
-@@ -264,12 +473,44 @@
+         CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
+         ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl(
+-            this.server, this.connection, commonListenerCookie
++            this.server, this.connection, commonListenerCookie, this.player // CraftBukkit
+         );
++
+         this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl);
+         serverConfigurationPacketListenerImpl.startConfiguration();
+         this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
+@@ -252,8 +_,36 @@
  
      @Override
      public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
@@ -388,37 +370,36 @@
 +        // CraftBukkit end
          this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
      }
- 
++
 +    // Spigot start
 +    protected GameProfile createOfflineProfile(String s) {
 +        java.util.UUID uuid;
-+        if ( this.connection.spoofedUUID != null )
-+        {
++        if (this.connection.spoofedUUID != null) {
 +            uuid = this.connection.spoofedUUID;
-+        } else
-+        {
-+            uuid = UUIDUtil.createOfflinePlayerUUID( s );
++        } else {
++            uuid = UUIDUtil.createOfflinePlayerUUID(s);
 +        }
 +
-+        GameProfile gameProfile = new GameProfile( uuid, s );
++        GameProfile gameProfile = new GameProfile(uuid, s);
 +
-+        if (this.connection.spoofedProfile != null)
-+        {
-+            for ( com.mojang.authlib.properties.Property property : this.connection.spoofedProfile )
-+            {
-+                if ( !ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher( property.name()).matches() ) continue;
-+                gameProfile.getProperties().put( property.name(), property );
++        if (this.connection.spoofedProfile != null) {
++            for (com.mojang.authlib.properties.Property property : this.connection.spoofedProfile) {
++                if (!ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher(property.name()).matches()) continue;
++                gameProfile.getProperties().put(property.name(), property);
 +            }
 +        }
 +
 +        return gameProfile;
 +    }
 +    // Spigot end
-+
+ 
      public static enum State {
- 
--        HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED;
-+        HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_COOKIES, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; // CraftBukkit
- 
-         private State() {}
-     }
+         HELLO,
+@@ -261,6 +_,7 @@
+         AUTHENTICATING,
+         NEGOTIATING,
+         VERIFYING,
++        WAITING_FOR_COOKIES, // CraftBukkit
+         WAITING_FOR_DUPE_DISCONNECT,
+         PROTOCOL_SWITCHING,
+         ACCEPTED;
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..7a4ce9578f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+@@ -36,7 +_,8 @@
+             this.connection.disconnect(DISCONNECT_REASON);
+         } else {
+             this.hasRequestedStatus = true;
+-            this.connection.send(new ClientboundStatusResponsePacket(this.status));
++            // this.connection.send(new ClientboundStatusResponsePacket(this.status));
++            com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(net.minecraft.server.MinecraftServer.getServer(), this.connection); // Paper - handle status request
+         }
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
deleted file mode 100644
index 813b398d03..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
+++ /dev/null
@@ -1,89 +0,0 @@
---- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
-+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
-@@ -38,6 +38,11 @@
- import net.minecraft.world.flag.FeatureFlags;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.CraftServerLinks;
-+import org.bukkit.event.player.PlayerLinksSendEvent;
-+// CraftBukkit end
-+
- public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerConfigurationPacketListener, TickablePacketListener {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -50,10 +55,12 @@
-     @Nullable
-     private SynchronizeRegistriesTask synchronizeRegistriesTask;
- 
--    public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) {
--        super(server, connection, clientData);
--        this.gameProfile = clientData.gameProfile();
--        this.clientInformation = clientData.clientInformation();
-+    // CraftBukkit start
-+    public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) {
-+        super(minecraftserver, networkmanager, commonlistenercookie, player);
-+        // CraftBukkit end
-+        this.gameProfile = commonlistenercookie.gameProfile();
-+        this.clientInformation = commonlistenercookie.clientInformation();
-     }
- 
-     @Override
-@@ -63,6 +70,10 @@
- 
-     @Override
-     public void onDisconnect(DisconnectionDetails info) {
-+        // Paper start - Debugging
-+        if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) {
-+            ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, info.reason().getString(), currentTask != null ? currentTask.type().id() : "null");
-+        } else // Paper end
-         ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, info.reason().getString());
-         super.onDisconnect(info);
-     }
-@@ -75,6 +86,12 @@
-     public void startConfiguration() {
-         this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName())));
-         ServerLinks serverlinks = this.server.serverLinks();
-+        // CraftBukkit start
-+        CraftServerLinks wrapper = new CraftServerLinks(serverlinks);
-+        PlayerLinksSendEvent event = new PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper);
-+        this.player.getBukkitEntity().getServer().getPluginManager().callEvent(event);
-+        serverlinks = wrapper.getServerLinks();
-+        // CraftBukkit end
- 
-         if (!serverlinks.isEmpty()) {
-             this.send(new ClientboundServerLinksPacket(serverlinks.untrust()));
-@@ -107,6 +124,7 @@
-     @Override
-     public void handleClientInformation(ServerboundClientInformationPacket packet) {
-         this.clientInformation = packet.information();
-+        this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper
-     }
- 
-     @Override
-@@ -143,18 +161,23 @@
-                 return;
-             }
- 
--            Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
-+            Component ichatbasecomponent = null; // CraftBukkit - login checks already completed
- 
-             if (ichatbasecomponent != null) {
-                 this.disconnect(ichatbasecomponent);
-                 return;
-             }
- 
--            ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation);
-+            ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
- 
-             playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation));
-         } catch (Exception exception) {
-             ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception);
-+            // Paper start - Debugging
-+            if (MinecraftServer.getServer().isDebugging()) {
-+                exception.printStackTrace();
-+            }
-+            // Paper end - Debugging
-             this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA));
-             this.connection.disconnect(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA);
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch
deleted file mode 100644
index adc3cc15f4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch
+++ /dev/null
@@ -1,156 +0,0 @@
---- a/net/minecraft/server/network/ServerConnectionListener.java
-+++ b/net/minecraft/server/network/ServerConnectionListener.java
-@@ -52,22 +52,36 @@
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-     public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(() -> {
--        return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
-+        return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
-     });
-     public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> {
--        return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build());
-+        return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
-     });
-     final MinecraftServer server;
-     public volatile boolean running;
-     private final List<ChannelFuture> channels = Collections.synchronizedList(Lists.newArrayList());
-     final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
-+    // Paper start - prevent blocking on adding a new connection while the server is ticking
-+    private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
-+    private final void addPending() {
-+        Connection connection;
-+        while ((connection = pending.poll()) != null) {
-+            connections.add(connection);
-+        }
-+    }
-+    // Paper end - prevent blocking on adding a new connection while the server is ticking
- 
-     public ServerConnectionListener(MinecraftServer server) {
-         this.server = server;
-         this.running = true;
-     }
- 
-+    // Paper start - Unix domain socket support
-     public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
-+        bind(new java.net.InetSocketAddress(address, port));
-+    }
-+    public void bind(java.net.SocketAddress address) throws IOException {
-+    // Paper end - Unix domain socket support
-         List list = this.channels;
- 
-         synchronized (this.channels) {
-@@ -75,7 +89,13 @@
-             EventLoopGroup eventloopgroup;
- 
-             if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
-+                // Paper start - Unix domain socket support
-+                if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
-+                    oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
-+                } else {
-                 oclass = EpollServerSocketChannel.class;
-+                }
-+                // Paper end - Unix domain socket support
-                 eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get();
-                 ServerConnectionListener.LOGGER.info("Using epoll channel type");
-             } else {
-@@ -84,6 +104,12 @@
-                 ServerConnectionListener.LOGGER.info("Using default channel type");
-             }
- 
-+            // Paper start - Warn people with console access that HAProxy is in use.
-+            if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
-+                ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
-+            }
-+            // Paper end - Warn people with console access that HAProxy is in use.
-+
-             this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
-                 protected void initChannel(Channel channel) {
-                     try {
-@@ -100,16 +126,58 @@
- 
-                     Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null);
-                     int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
--                    Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND);
-+                    Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error
- 
--                    ServerConnectionListener.this.connections.add(object);
-+                    //ServerConnectionListener.this.connections.add(object); // Paper
-+                    // Paper start - Add support for Proxy Protocol
-+                    if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
-+                        channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
-+                        channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
-+                            @Override
-+                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
-+                                if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
-+                                    if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
-+                                        String realaddress = message.sourceAddress();
-+                                        int realport = message.sourcePort();
-+
-+                                        SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
-+
-+                                        Connection connection = (Connection) channel.pipeline().get("packet_handler");
-+                                        connection.address = socketaddr;
-+
-+                                        // Paper start - Add API to get player's proxy address
-+                                        final String proxyAddress = message.destinationAddress();
-+                                        final int proxyPort = message.destinationPort();
-+
-+                                        connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort);
-+                                        // Paper end - Add API to get player's proxy address
-+                                    }
-+                                } else {
-+                                    super.channelRead(ctx, msg);
-+                                }
-+                            }
-+                        });
-+                    }
-+                    // Paper end - Add support for proxy protocol
-+                    pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking
-                     ((Connection) object).configurePacketHandler(channelpipeline);
-                     ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
-+                    io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
-                 }
--            }).group(eventloopgroup).localAddress(address, port)).bind().syncUninterruptibly());
-+            }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support
-         }
-     }
- 
-+    // CraftBukkit start
-+    public void acceptConnections() {
-+        synchronized (this.channels) {
-+            for (ChannelFuture future : this.channels) {
-+                future.channel().config().setAutoRead(true);
-+            }
-+        }
-+    }
-+    // CraftBukkit end
-+
-     public SocketAddress startMemoryChannel() {
-         List list = this.channels;
-         ChannelFuture channelfuture;
-@@ -153,6 +221,14 @@
-         List list = this.connections;
- 
-         synchronized (this.connections) {
-+            // Spigot Start
-+            this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
-+            // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
-+            if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 )
-+            {
-+                Collections.shuffle( this.connections );
-+            }
-+            // Spigot End
-             Iterator<Connection> iterator = this.connections.iterator();
- 
-             while (iterator.hasNext()) {
-@@ -176,6 +252,10 @@
-                             networkmanager.setReadOnly();
-                         }
-                     } else {
-+                        // Spigot Start
-+                        // Fix a race condition where a NetworkManager could be unregistered just before connection.
-+                        if (networkmanager.preparing) continue;
-+                        // Spigot End
-                         iterator.remove();
-                         networkmanager.handleDisconnection();
-                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
deleted file mode 100644
index 7ea7e46622..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
+++ /dev/null
@@ -1,137 +0,0 @@
---- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
-+++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
-@@ -9,6 +9,19 @@
- import net.minecraft.network.protocol.status.ServerStatus;
- import net.minecraft.network.protocol.status.ServerStatusPacketListener;
- import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
-+// CraftBukkit start
-+import com.mojang.authlib.GameProfile;
-+import java.net.InetSocketAddress;
-+import java.util.Collections;
-+import java.util.Iterator;
-+import java.util.Optional;
-+import net.minecraft.SharedConstants;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerPlayer;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
-+import org.bukkit.craftbukkit.util.CraftIconCache;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
- 
- public class ServerStatusPacketListenerImpl implements ServerStatusPacketListener {
- 
-@@ -36,7 +49,113 @@
-             this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON);
-         } else {
-             this.hasRequestedStatus = true;
--            this.connection.send(new ClientboundStatusResponsePacket(this.status));
-+            // Paper start - Replace everything
-+            /*
-+            // CraftBukkit start
-+            // this.connection.send(new PacketStatusOutServerInfo(this.status));
-+            MinecraftServer server = MinecraftServer.getServer();
-+            final Object[] players = server.getPlayerList().players.toArray();
-+            class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent {
-+
-+                CraftIconCache icon = server.server.getServerIcon();
-+
-+                ServerListPingEvent() {
-+                    super(ServerStatusPacketListenerImpl.this.connection.hostname, ((InetSocketAddress) ServerStatusPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure
-+                }
-+
-+                @Override
-+                public void setServerIcon(org.bukkit.util.CachedServerIcon icon) {
-+                    if (!(icon instanceof CraftIconCache)) {
-+                        throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class);
-+                    }
-+                    this.icon = (CraftIconCache) icon;
-+                }
-+
-+                @Override
-+                public Iterator<Player> iterator() throws UnsupportedOperationException {
-+                    return new Iterator<Player>() {
-+                        int i;
-+                        int ret = Integer.MIN_VALUE;
-+                        ServerPlayer player;
-+
-+                        @Override
-+                        public boolean hasNext() {
-+                            if (this.player != null) {
-+                                return true;
-+                            }
-+                            final Object[] currentPlayers = players;
-+                            for (int length = currentPlayers.length, i = this.i; i < length; i++) {
-+                                final ServerPlayer player = (ServerPlayer) currentPlayers[i];
-+                                if (player != null) {
-+                                    this.i = i + 1;
-+                                    this.player = player;
-+                                    return true;
-+                                }
-+                            }
-+                            return false;
-+                        }
-+
-+                        @Override
-+                        public Player next() {
-+                            if (!this.hasNext()) {
-+                                throw new java.util.NoSuchElementException();
-+                            }
-+                            final ServerPlayer player = this.player;
-+                            this.player = null;
-+                            this.ret = this.i - 1;
-+                            return player.getBukkitEntity();
-+                        }
-+
-+                        @Override
-+                        public void remove() {
-+                            final Object[] currentPlayers = players;
-+                            final int i = this.ret;
-+                            if (i < 0 || currentPlayers[i] == null) {
-+                                throw new IllegalStateException();
-+                            }
-+                            currentPlayers[i] = null;
-+                        }
-+                    };
-+                }
-+            }
-+
-+            ServerListPingEvent event = new ServerListPingEvent();
-+            server.server.getPluginManager().callEvent(event);
-+
-+            java.util.List<GameProfile> profiles = new java.util.ArrayList<GameProfile>(players.length);
-+            for (Object player : players) {
-+                if (player != null) {
-+                    ServerPlayer entityPlayer = ((ServerPlayer) player);
-+                    if (entityPlayer.allowsListing()) {
-+                        profiles.add(entityPlayer.getGameProfile());
-+                    } else {
-+                        profiles.add(MinecraftServer.ANONYMOUS_PLAYER_PROFILE);
-+                    }
-+                }
-+            }
-+
-+            // Spigot Start
-+            if ( !server.hidesOnlinePlayers() && !profiles.isEmpty() )
-+            {
-+                java.util.Collections.shuffle( profiles ); // This sucks, its inefficient but we have no simple way of doing it differently
-+                profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour
-+            }
-+            // Spigot End
-+            ServerStatus.Players playerSample = new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), (server.hidesOnlinePlayers()) ? Collections.emptyList() : profiles);
-+
-+            ServerStatus ping = new ServerStatus(
-+                    CraftChatMessage.fromString(event.getMotd(), true)[0],
-+                    Optional.of(playerSample),
-+                    Optional.of(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), SharedConstants.getCurrentVersion().getProtocolVersion())),
-+                    (event.icon.value != null) ? Optional.of(new ServerStatus.Favicon(event.icon.value)) : Optional.empty(),
-+                    server.enforceSecureProfile()
-+            );
-+
-+            this.connection.send(new ClientboundStatusResponsePacket(ping));
-+            // CraftBukkit end
-+            */
-+            com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection);
-+            // Paper end
-         }
-     }
- 

From 216f3118aea91e8a353179c9351f6edae970751d Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sat, 14 Dec 2024 16:32:53 +0100
Subject: [PATCH 058/285] net/minecraft/world/level/block/entity/trialspawner

---
 .../trialspawner/TrialSpawner.java.patch      | 47 ++++++++++++++
 .../trialspawner/TrialSpawnerData.java.patch  | 23 +++++++
 .../trialspawner/TrialSpawnerState.java.patch | 11 ++++
 .../trialspawner/TrialSpawner.java.patch      | 63 -------------------
 .../trialspawner/TrialSpawnerData.java.patch  | 32 ----------
 .../trialspawner/TrialSpawnerState.java.patch | 11 ----
 6 files changed, 81 insertions(+), 106 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
new file mode 100644
index 0000000000..028fc739b8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
+@@ -237,7 +_,14 @@
+                                 nextSpawnData.getEquipment().ifPresent(mob::equip);
+                             }
+ 
+-                            if (!level.tryAddFreshEntityWithPassengers(entity)) {
++                            entity.spawnedViaMobSpawner = true; // Paper
++                            entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason
++                            // CraftBukkit start
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) {
++                                return Optional.empty();
++                            }
++                            if (!level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER)) {
++                                // CraftBukkit end
+                                 return Optional.empty();
+                             } else {
+                                 TrialSpawner.FlameParticle flameParticle = this.isOminous
+@@ -260,6 +_,19 @@
+         LootParams lootParams = new LootParams.Builder(level).create(LootContextParamSets.EMPTY);
+         ObjectArrayList<ItemStack> randomItems = lootTable1.getRandomItems(lootParams);
+         if (!randomItems.isEmpty()) {
++            // CraftBukkit start
++            org.bukkit.event.block.BlockDispenseLootEvent spawnerDispenseLootEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDispenseLootEvent(
++                level,
++                pos,
++                null,
++                randomItems
++            );
++            if (spawnerDispenseLootEvent.isCancelled()) {
++                return;
++            }
++
++            randomItems = new ObjectArrayList<>(spawnerDispenseLootEvent.getDispensedLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).toList());
++            // CraftBukkit end
+             for (ItemStack itemStack : randomItems) {
+                 DefaultDispenseItemBehavior.spawnItem(level, itemStack, 2, Direction.UP, Vec3.atBottomCenterOf(pos).relative(Direction.UP, 1.2));
+             }
+@@ -362,7 +_,7 @@
+     }
+ 
+     public void overrideEntityToSpawn(EntityType<?> entityType, Level level) {
+-        this.data.reset();
++        this.data.reset(this); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
+         this.normalConfig = Holder.direct(this.normalConfig.value().withSpawning(entityType));
+         this.ominousConfig = Holder.direct(this.ominousConfig.value().withSpawning(entityType));
+         this.setState(level, TrialSpawnerState.INACTIVE);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
new file mode 100644
index 0000000000..351f01f843
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java
++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java
+@@ -101,9 +_,9 @@
+         this.ejectingLootTable = ejectingLootTable;
+     }
+ 
+-    public void reset() {
++    public void reset(TrialSpawner spawner) { // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
+         this.currentMobs.clear();
+-        this.nextSpawnData = Optional.empty();
++        if (!spawner.getConfig().spawnPotentialsDefinition().isEmpty()) this.nextSpawnData = Optional.empty(); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
+         this.resetStatistics();
+     }
+ 
+@@ -206,7 +_,7 @@
+                     mob.dropPreservedEquipment(level);
+                 }
+ 
+-                entity.remove(Entity.RemovalReason.DISCARDED);
++                entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause;
+             }
+         });
+         if (!spawner.getOminousConfig().spawnPotentialsDefinition().isEmpty()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch
new file mode 100644
index 0000000000..28b53c34d3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java
++++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java
+@@ -145,7 +_,7 @@
+                     yield ACTIVE;
+                 } else if (data.isCooldownFinished(level)) {
+                     spawner.removeOminous(level, pos);
+-                    data.reset();
++                    data.reset(spawner); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
+                     yield WAITING_FOR_PLAYERS;
+                 } else {
+                     yield this;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
deleted file mode 100644
index e58a4bb1dc..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java.patch
+++ /dev/null
@@ -1,63 +0,0 @@
---- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
-+++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
-@@ -46,6 +46,11 @@
- import net.minecraft.world.phys.HitResult;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.CollisionContext;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseLootEvent;
-+// CraftBukkit end
- 
- public final class TrialSpawner {
- 
-@@ -219,14 +224,21 @@
-                                 }
- 
-                                 entityinsentient.setPersistenceRequired();
--                                Optional optional1 = mobspawnerdata.getEquipment();
-+                                Optional<net.minecraft.world.entity.EquipmentTable> optional1 = mobspawnerdata.getEquipment(); // CraftBukkit - decompile error
- 
-                                 Objects.requireNonNull(entityinsentient);
-                                 optional1.ifPresent(entityinsentient::equip);
-                             }
- 
--                            if (!world.tryAddFreshEntityWithPassengers(entity)) {
-+                            entity.spawnedViaMobSpawner = true; // Paper
-+                            entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason
-+                            // CraftBukkit start
-+                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) {
-                                 return Optional.empty();
-+                            }
-+                            if (!world.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER)) {
-+                                // CraftBukkit end
-+                                return Optional.empty();
-                             } else {
-                                 TrialSpawner.FlameParticle trialspawner_a = this.isOminous ? TrialSpawner.FlameParticle.OMINOUS : TrialSpawner.FlameParticle.NORMAL;
- 
-@@ -248,6 +260,15 @@
-         ObjectArrayList<ItemStack> objectarraylist = loottable.getRandomItems(lootparams);
- 
-         if (!objectarraylist.isEmpty()) {
-+            // CraftBukkit start
-+            BlockDispenseLootEvent spawnerDispenseLootEvent = CraftEventFactory.callBlockDispenseLootEvent(world, pos, null, objectarraylist);
-+            if (spawnerDispenseLootEvent.isCancelled()) {
-+                return;
-+            }
-+
-+            objectarraylist = new ObjectArrayList<>(spawnerDispenseLootEvent.getDispensedLoot().stream().map(CraftItemStack::asNMSCopy).toList());
-+            // CraftBukkit end
-+
-             ObjectListIterator objectlistiterator = objectarraylist.iterator();
- 
-             while (objectlistiterator.hasNext()) {
-@@ -370,7 +391,7 @@
-     }
- 
-     public void overrideEntityToSpawn(EntityType<?> entityType, Level world) {
--        this.data.reset();
-+        this.data.reset(this); // Paper
-         this.normalConfig = Holder.direct(((TrialSpawnerConfig) this.normalConfig.value()).withSpawning(entityType));
-         this.ominousConfig = Holder.direct(((TrialSpawnerConfig) this.ominousConfig.value()).withSpawning(entityType));
-         this.setState(world, TrialSpawnerState.INACTIVE);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
deleted file mode 100644
index 9f48f83f27..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java
-+++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerData.java
-@@ -100,9 +100,9 @@
-         this.ejectingLootTable = rewardLootTable;
-     }
- 
--    public void reset() {
-+    public void reset(TrialSpawner logic) { // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
-         this.currentMobs.clear();
--        this.nextSpawnData = Optional.empty();
-+        if (!logic.getConfig().spawnPotentialsDefinition().isEmpty()) this.nextSpawnData = Optional.empty(); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
-         this.resetStatistics();
-     }
- 
-@@ -210,7 +210,7 @@
-     }
- 
-     public void resetAfterBecomingOminous(TrialSpawner logic, ServerLevel world) {
--        Stream stream = this.currentMobs.stream();
-+        Stream<UUID> stream = this.currentMobs.stream(); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(world);
-         stream.map(world::getEntity).forEach((entity) -> {
-@@ -222,7 +222,7 @@
-                     entityinsentient.dropPreservedEquipment(world);
-                 }
- 
--                entity.remove(Entity.RemovalReason.DISCARDED);
-+                entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - Add bukkit remove cause;
-             }
-         });
-         if (!logic.getOminousConfig().spawnPotentialsDefinition().isEmpty()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch
deleted file mode 100644
index 293d3ea411..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java
-+++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawnerState.java
-@@ -145,7 +145,7 @@
-                     yield ACTIVE;
-                 } else if (trialSpawnerData.isCooldownFinished(world)) {
-                     logic.removeOminous(world, pos);
--                    trialSpawnerData.reset();
-+                    trialSpawnerData.reset(logic); // Paper - Fix TrialSpawner forgets assigned mob; MC-273635
-                     yield WAITING_FOR_PLAYERS;
-                 } else {
-                     yield this;

From dde17defa577ecd599bb2e12c3dbd54e109abca3 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sat, 14 Dec 2024 17:17:03 +0100
Subject: [PATCH 059/285] net/minecraft/world/entity/vehicle

---
 .../entity/vehicle/AbstractBoat.java.patch    |  67 ++++------
 .../vehicle/AbstractChestBoat.java.patch      |  63 ++++------
 .../vehicle/AbstractMinecart.java.patch       | 119 +++++++-----------
 .../AbstractMinecartContainer.java.patch      |  83 ++++++++++++
 .../entity/vehicle/ContainerEntity.java.patch |  45 ++++---
 .../vehicle/MinecartCommandBlock.java.patch   |  24 ++++
 .../entity/vehicle/MinecartTNT.java.patch     |  56 +++++++++
 .../vehicle/NewMinecartBehavior.java.patch    |  73 +++++++++++
 .../vehicle/OldMinecartBehavior.java.patch    |  58 +++++++++
 .../entity/vehicle/VehicleEntity.java.patch   |  42 +++++++
 .../AbstractMinecartContainer.java.patch      |  98 ---------------
 .../vehicle/MinecartCommandBlock.java.patch   |  23 ----
 .../entity/vehicle/MinecartTNT.java.patch     |  53 --------
 .../vehicle/NewMinecartBehavior.java.patch    |  83 ------------
 .../vehicle/OldMinecartBehavior.java.patch    |  75 -----------
 .../entity/vehicle/VehicleEntity.java.patch   |  64 ----------
 16 files changed, 452 insertions(+), 574 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch (62%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch (58%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch (61%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
index 28b20b3791..20b17c550f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
@@ -1,21 +1,6 @@
 --- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-@@ -47,6 +47,14 @@
- import net.minecraft.world.phys.shapes.BooleanOp;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
-+import org.bukkit.event.vehicle.VehicleMoveEvent;
-+// CraftBukkit end
- 
- public abstract class AbstractBoat extends VehicleEntity implements Leashable {
- 
-@@ -87,6 +95,14 @@
+@@ -83,6 +_,14 @@
      private Leashable.LeashData leashData;
      private final Supplier<Item> dropItem;
  
@@ -27,10 +12,10 @@
 +    public boolean landBoats = false;
 +    // CraftBukkit end
 +
-     public AbstractBoat(EntityType<? extends AbstractBoat> type, Level world, Supplier<Item> itemSupplier) {
-         super(type, world);
-         this.dropItem = itemSupplier;
-@@ -128,7 +144,7 @@
+     public AbstractBoat(EntityType<? extends AbstractBoat> entityType, Level level, Supplier<Item> dropItem) {
+         super(entityType, level);
+         this.dropItem = dropItem;
+@@ -124,7 +_,7 @@
      }
  
      @Override
@@ -39,7 +24,7 @@
          return true;
      }
  
-@@ -180,11 +196,32 @@
+@@ -177,11 +_,30 @@
  
      @Override
      public void push(Entity entity) {
@@ -48,12 +33,11 @@
              if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
 +                // CraftBukkit start
 +                if (!this.isPassengerOfSameVehicle(entity)) {
-+                    VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
-+                    this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (event.isCancelled()) {
-+                        return;
-+                    }
++                    org.bukkit.event.vehicle.VehicleEntityCollisionEvent event = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                        (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
++                        entity.getBukkitEntity()
++                    );
++                    if (!event.callEvent()) return;
 +                }
 +                // CraftBukkit end
                  super.push(entity);
@@ -61,26 +45,25 @@
          } else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) {
 +            // CraftBukkit start
 +            if (!this.isPassengerOfSameVehicle(entity)) {
-+                VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    return;
-+                }
++                org.bukkit.event.vehicle.VehicleEntityCollisionEvent event = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                    (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
++                    entity.getBukkitEntity()
++                );
++                if (!event.callEvent()) return;
 +            }
 +            // CraftBukkit end
              super.push(entity);
          }
- 
-@@ -247,6 +284,7 @@
+     }
+@@ -243,6 +_,7 @@
          return this.getDirection().getClockWise();
      }
  
-+    private Location lastLocation; // CraftBukkit
++    private org.bukkit.Location lastLocation; // CraftBukkit
      @Override
      public void tick() {
          this.oldStatus = this.status;
-@@ -287,6 +325,21 @@
+@@ -283,6 +_,21 @@
              this.setDeltaMovement(Vec3.ZERO);
          }
  
@@ -88,13 +71,13 @@
 +        org.bukkit.Server server = this.level().getCraftServer();
 +        org.bukkit.World bworld = this.level().getWorld();
 +
-+        Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
-+        Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++        org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
++        org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
 +
 +        server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
 +
 +        if (this.lastLocation != null && !this.lastLocation.equals(to)) {
-+            VehicleMoveEvent event = new VehicleMoveEvent(vehicle, this.lastLocation, to);
++            org.bukkit.event.vehicle.VehicleMoveEvent event = new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, this.lastLocation, to);
 +            server.getPluginManager().callEvent(event);
 +        }
 +        this.lastLocation = vehicle.getLocation();
@@ -102,7 +85,7 @@
          this.applyEffectsFromBlocks();
          this.applyEffectsFromBlocks();
          this.tickBubbleColumn();
-@@ -790,11 +843,18 @@
+@@ -762,11 +_,18 @@
  
      @Override
      public void remove(Entity.RemovalReason reason) {
@@ -112,7 +95,7 @@
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        // CraftBukkit end
 +        if (!this.level().isClientSide && entity_removalreason.shouldDestroy() && this.isLeashed()) {
              this.dropLeash();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
index f23f862b79..3dc8fe5278 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
@@ -1,69 +1,51 @@
 --- a/net/minecraft/world/entity/vehicle/AbstractChestBoat.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractChestBoat.java
-@@ -27,6 +27,15 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.LootTable;
- 
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
-+
- public abstract class AbstractChestBoat extends AbstractBoat implements HasCustomInventoryScreen, ContainerEntity {
- 
-     private static final int CONTAINER_SIZE = 27;
-@@ -70,11 +79,18 @@
+@@ -66,11 +_,18 @@
  
      @Override
      public void remove(Entity.RemovalReason reason) {
--        if (!this.level().isClientSide && reason.shouldDestroy()) {
 +        // CraftBukkit start - add Bukkit remove cause
 +        this.remove(reason, null);
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        // CraftBukkit end
-+        if (!this.level().isClientSide && entity_removalreason.shouldDestroy()) {
-             Containers.dropContents(this.level(), (Entity) this, (Container) this);
+         if (!this.level().isClientSide && reason.shouldDestroy()) {
+             Containers.dropContents(this.level(), this, this);
          }
  
 -        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++        super.remove(reason, cause); // CraftBukkit - add Bukkit remove cause
      }
  
      @Override
-@@ -109,10 +125,10 @@
+@@ -97,8 +_,8 @@
  
      @Override
      public void openCustomInventoryScreen(Player player) {
 -        player.openMenu(this);
+-        if (player.level() instanceof ServerLevel serverLevel) {
 +        // Paper - fix inventory open cancel - moved into below if
-         Level world = player.level();
- 
--        if (world instanceof ServerLevel worldserver) {
-+        if (world instanceof ServerLevel worldserver && player.openMenu(this).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (player.level() instanceof ServerLevel serverLevel && player.openMenu(this).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              this.gameEvent(GameEvent.CONTAINER_OPEN, player);
-             PiglinAi.angerNearbyPiglins(worldserver, player, true);
+             PiglinAi.angerNearbyPiglins(serverLevel, player, true);
          }
-@@ -165,7 +181,7 @@
+@@ -151,7 +_,7 @@
      @Nullable
      @Override
-     public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
+     public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
 -        if (this.lootTable != null && player.isSpectator()) {
 +        if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that)
              return null;
          } else {
              this.unpackLootTable(playerInventory.player);
-@@ -212,4 +228,59 @@
+@@ -198,4 +_,59 @@
      public void stopOpen(Player player) {
-         this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player));
+         this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of(player));
      }
 +
++
 +    // Paper start - LootTable API
 +    final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
 +
@@ -73,34 +55,33 @@
 +    }
 +    // Paper end - LootTable API
 +    // CraftBukkit start
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++    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<net.minecraft.world.item.ItemStack> getContents() {
 +        return this.itemStacks;
 +    }
 +
 +    @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 InventoryHolder getOwner() {
++    public org.bukkit.inventory.InventoryHolder getOwner() {
 +        org.bukkit.entity.Entity entity = this.getBukkitEntity();
-+        if (entity instanceof InventoryHolder) return (InventoryHolder) entity;
-+        return null;
++        return entity instanceof final org.bukkit.inventory.InventoryHolder inventoryHolder ? inventoryHolder : null;
 +    }
 +
 +    @Override
@@ -114,7 +95,7 @@
 +    }
 +
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        return this.getBukkitEntity().getLocation();
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
index e7b99cb727..ea248c3955 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
@@ -1,23 +1,9 @@
 --- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
-@@ -42,6 +42,13 @@
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
-+import org.bukkit.util.Vector;
-+// CraftBukkit end
- 
- public abstract class AbstractMinecart extends VehicleEntity {
- 
-@@ -76,6 +83,18 @@
-         enummap.put(RailShape.NORTH_EAST, Pair.of(baseblockposition2, baseblockposition1));
+@@ -74,6 +_,17 @@
+         map.put(RailShape.NORTH_WEST, Pair.of(unitVec3i2, unitVec3i));
+         map.put(RailShape.NORTH_EAST, Pair.of(unitVec3i2, unitVec3i1));
      });
- 
 +    // CraftBukkit start
 +    public boolean slowWhenEmpty = true;
 +    private double derailedX = 0.5;
@@ -27,32 +13,22 @@
 +    private double flyingY = 0.95;
 +    private double flyingZ = 0.95;
 +    public Double maxSpeed;
-+    // CraftBukkit end
 +    public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
-+
-     protected AbstractMinecart(EntityType<?> type, Level world) {
-         super(type, world);
-         this.blocksBuilding = true;
-@@ -101,7 +120,7 @@
++    // CraftBukkit end
  
-     @Nullable
-     public static <T extends AbstractMinecart> T createMinecart(Level world, double x, double y, double z, EntityType<T> type, EntitySpawnReason reason, ItemStack stack, @Nullable Player player) {
--        T t0 = (AbstractMinecart) type.create(world, reason);
-+        T t0 = (T) type.create(world, reason); // CraftBukkit - decompile error
- 
-         if (t0 != null) {
-             t0.setInitialPos(x, y, z);
-@@ -139,11 +158,19 @@
+     protected AbstractMinecart(EntityType<?> entityType, Level level) {
+         super(entityType, level);
+@@ -134,11 +_,19 @@
  
      @Override
-     public boolean canCollideWith(Entity other) {
--        return AbstractBoat.canVehicleCollide(this, other);
+     public boolean canCollideWith(Entity entity) {
+-        return AbstractBoat.canVehicleCollide(this, entity);
 +        // Paper start - fix VehicleEntityCollisionEvent not called when colliding with player
-+        boolean collides = AbstractBoat.canVehicleCollide(this, other);
++        boolean collides = AbstractBoat.canVehicleCollide(this, entity);
 +        if (!collides) {
 +            return false;
 +        }
-+        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), other.getBukkitEntity());
++        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), entity.getBukkitEntity());
 +
 +        return collisionEvent.callEvent();
 +        // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player
@@ -64,7 +40,7 @@
          return true;
      }
  
-@@ -262,6 +289,14 @@
+@@ -239,6 +_,14 @@
  
      @Override
      public void tick() {
@@ -79,7 +55,7 @@
          if (this.getHurtTime() > 0) {
              this.setHurtTime(this.getHurtTime() - 1);
          }
-@@ -271,8 +306,20 @@
+@@ -248,8 +_,20 @@
          }
  
          this.checkBelowWorld();
@@ -88,24 +64,24 @@
          this.behavior.tick();
 +        // CraftBukkit start
 +        org.bukkit.World bworld = this.level().getWorld();
-+        Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
-+        Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
-+        Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++        org.bukkit.Location from = new org.bukkit.Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
++        org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
++        org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
 +
-+        this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++        new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle).callEvent();
 +
 +        if (!from.equals(to)) {
-+            this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to));
++            new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to).callEvent();
 +        }
 +        // CraftBukkit end
          this.updateInWaterStateAndDoFluidPushing();
          if (this.isInLava()) {
              this.lavaHurt();
-@@ -385,12 +432,16 @@
- 
-         this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0));
+@@ -360,12 +_,16 @@
+         Vec3 deltaMovement = this.getDeltaMovement();
+         this.setDeltaMovement(Mth.clamp(deltaMovement.x, -maxSpeed, maxSpeed), deltaMovement.y, Mth.clamp(deltaMovement.z, -maxSpeed, maxSpeed));
          if (this.onGround()) {
--            this.setDeltaMovement(this.getDeltaMovement().scale(0.5D));
+-            this.setDeltaMovement(this.getDeltaMovement().scale(0.5));
 +            // CraftBukkit start - replace magic numbers with our variables
 +            this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ));
 +            // CraftBukkit end
@@ -113,20 +89,20 @@
  
          this.move(MoverType.SELF, this.getDeltaMovement());
          if (!this.onGround()) {
--            this.setDeltaMovement(this.getDeltaMovement().scale(0.95D));
+-            this.setDeltaMovement(this.getDeltaMovement().scale(0.95));
 +            // CraftBukkit start - replace magic numbers with our variables
 +            this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.flyingX, this.getDeltaMovement().y * this.flyingY, this.getDeltaMovement().z * this.flyingZ));
 +            // CraftBukkit end
          }
- 
      }
-@@ -502,6 +553,16 @@
  
-         this.flipped = nbt.getBoolean("FlippedRotation");
-         this.firstTick = nbt.getBoolean("HasTicked");
+@@ -469,6 +_,16 @@
+ 
+         this.flipped = compound.getBoolean("FlippedRotation");
+         this.firstTick = compound.getBoolean("HasTicked");
 +        // Paper start - Friction API
-+        if (nbt.contains("Paper.FrictionState")) {
-+            String fs = nbt.getString("Paper.FrictionState");
++        if (compound.contains("Paper.FrictionState")) {
++            String fs = compound.getString("Paper.FrictionState");
 +            try {
 +                frictionState = net.kyori.adventure.util.TriState.valueOf(fs);
 +            } catch (Exception ignored) {
@@ -137,14 +113,14 @@
      }
  
      @Override
-@@ -514,13 +575,28 @@
+@@ -481,13 +_,27 @@
  
-         nbt.putBoolean("FlippedRotation", this.flipped);
-         nbt.putBoolean("HasTicked", this.firstTick);
+         compound.putBoolean("FlippedRotation", this.flipped);
+         compound.putBoolean("HasTicked", this.firstTick);
 +
 +        // Paper start - Friction API
 +        if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
-+            nbt.putString("Paper.FrictionState", this.frictionState.toString());
++            compound.putString("Paper.FrictionState", this.frictionState.toString());
 +        }
 +        // Paper end - Friction API
      }
@@ -156,37 +132,36 @@
 +                if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant
                  if (!this.hasPassenger(entity)) {
 +                    // CraftBukkit start
-+                    VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
-+                    this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                    if (collisionEvent.isCancelled()) {
-+                        return;
-+                    }
++                    org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                        (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
++                        entity.getBukkitEntity()
++                    );
++                    if (!collisionEvent.callEvent()) return;
 +                    // CraftBukkit end
-                     double d0 = entity.getX() - this.getX();
+                     double d = entity.getX() - this.getX();
                      double d1 = entity.getZ() - this.getZ();
-                     double d2 = d0 * d0 + d1 * d1;
-@@ -645,4 +721,27 @@
+                     double d2 = d * d + d1 * d1;
+@@ -602,4 +_,27 @@
      public boolean isFurnace() {
          return false;
      }
 +
 +    // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers
-+    public Vector getFlyingVelocityMod() {
-+        return new Vector(this.flyingX, this.flyingY, this.flyingZ);
++    public org.bukkit.util.Vector getFlyingVelocityMod() {
++        return new org.bukkit.util.Vector(this.flyingX, this.flyingY, this.flyingZ);
 +    }
 +
-+    public void setFlyingVelocityMod(Vector flying) {
++    public void setFlyingVelocityMod(org.bukkit.util.Vector flying) {
 +        this.flyingX = flying.getX();
 +        this.flyingY = flying.getY();
 +        this.flyingZ = flying.getZ();
 +    }
 +
-+    public Vector getDerailedVelocityMod() {
-+        return new Vector(this.derailedX, this.derailedY, this.derailedZ);
++    public org.bukkit.util.Vector getDerailedVelocityMod() {
++        return new org.bukkit.util.Vector(this.derailedX, this.derailedY, this.derailedZ);
 +    }
 +
-+    public void setDerailedVelocityMod(Vector derailed) {
++    public void setDerailedVelocityMod(org.bukkit.util.Vector derailed) {
 +        this.derailedX = derailed.getX();
 +        this.derailedY = derailed.getY();
 +        this.derailedZ = derailed.getZ();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
new file mode 100644
index 0000000000..5a1e4c8f55
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
++++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+@@ -21,11 +_,59 @@
+ import net.minecraft.world.phys.Vec3;
+ 
+ public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
+-    private NonNullList<ItemStack> itemStacks = NonNullList.withSize(36, ItemStack.EMPTY);
++    private NonNullList<ItemStack> itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
+     @Nullable
+     public ResourceKey<LootTable> lootTable;
+     public long lootTableSeed;
+ 
++    // Paper start - LootTable API
++    final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
++
++    @Override
++    public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
++        return this.lootableData;
++    }
++    // Paper end - LootTable API
++    // CraftBukkit start
++    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.itemStacks;
++    }
++
++    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;
++    }
++
++    public org.bukkit.inventory.InventoryHolder getOwner() {
++        return this.getBukkitEntity() instanceof final org.bukkit.inventory.InventoryHolder inventoryHolder ? inventoryHolder : null;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        return this.getBukkitEntity().getLocation();
++    }
++    // CraftBukkit end
++
++
+     protected AbstractMinecartContainer(EntityType<?> entityType, Level level) {
+         super(entityType, level);
+     }
+@@ -72,11 +_,18 @@
+ 
+     @Override
+     public void remove(Entity.RemovalReason reason) {
++        // CraftBukkit start - add Bukkit remove cause
++        this.remove(reason, null);
++    }
++
++    @Override
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        // CraftBukkit end  - add Bukkit remove cause
+         if (!this.level().isClientSide && reason.shouldDestroy()) {
+             Containers.dropContents(this.level(), this, this);
+         }
+ 
+-        super.remove(reason);
++        super.remove(reason, cause);  // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch
index 3ac071ee7d..fea198069c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/ContainerEntity.java.patch
@@ -1,38 +1,38 @@
 --- a/net/minecraft/world/entity/vehicle/ContainerEntity.java
 +++ b/net/minecraft/world/entity/vehicle/ContainerEntity.java
-@@ -62,22 +62,26 @@
-     default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) {
+@@ -62,22 +_,26 @@
+     default void addChestVehicleSaveData(CompoundTag tag, HolderLookup.Provider levelRegistry) {
          if (this.getContainerLootTable() != null) {
-             nbt.putString("LootTable", this.getContainerLootTable().location().toString());
-+            this.lootableData().saveNbt(nbt); // Paper
+             tag.putString("LootTable", this.getContainerLootTable().location().toString());
++            this.lootableData().saveNbt(tag); // Paper
              if (this.getContainerLootTableSeed() != 0L) {
-                 nbt.putLong("LootTableSeed", this.getContainerLootTableSeed());
+                 tag.putLong("LootTableSeed", this.getContainerLootTableSeed());
              }
 -        } else {
--            ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries);
+-            ContainerHelper.saveAllItems(tag, this.getItemStacks(), levelRegistry);
          }
-+        ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain
++        ContainerHelper.saveAllItems(tag, this.getItemStacks(), levelRegistry); // Paper - always save the items, table may still remain
      }
  
-     default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) {
+     default void readChestVehicleSaveData(CompoundTag tag, HolderLookup.Provider levelRegistry) {
          this.clearItemStacks();
-         if (nbt.contains("LootTable", 8)) {
--            this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))));
-+            this.setContainerLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation
+         if (tag.contains("LootTable", 8)) {
+-            this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(tag.getString("LootTable"))));
++            this.setContainerLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(tag.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation
 +            // Paper start - LootTable API
 +            if (this.getContainerLootTable() != null) {
-+                this.lootableData().loadNbt(nbt);
++                this.lootableData().loadNbt(tag);
 +            }
 +            // Paper end - LootTable API
-             this.setContainerLootTableSeed(nbt.getLong("LootTableSeed"));
+             this.setContainerLootTableSeed(tag.getLong("LootTableSeed"));
 -        } else {
--            ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries);
+-            ContainerHelper.loadAllItems(tag, this.getItemStacks(), levelRegistry);
          }
-+        ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain
++        ContainerHelper.loadAllItems(tag, this.getItemStacks(), levelRegistry); // Paper - always read the items, table may still remain
      }
  
-     default void chestVehicleDestroyed(DamageSource source, ServerLevel world, Entity vehicle) {
-@@ -91,19 +95,28 @@
+     default void chestVehicleDestroyed(DamageSource damageSource, ServerLevel level, Entity entity) {
+@@ -91,19 +_,27 @@
      }
  
      default InteractionResult interactWithContainerVehicle(Player player) {
@@ -46,10 +46,10 @@
      }
  
      default void unpackChestVehicleLootTable(@Nullable Player player) {
-         MinecraftServer minecraftServer = this.level().getServer();
--        if (this.getContainerLootTable() != null && minecraftServer != null) {
-+        if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API
-             LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getContainerLootTable());
+         MinecraftServer server = this.level().getServer();
+-        if (this.getContainerLootTable() != null && server != null) {
++        if (server != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API
+             LootTable lootTable = server.reloadableRegistries().getLootTable(this.getContainerLootTable());
              if (player != null) {
                  CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getContainerLootTable());
              }
@@ -60,11 +60,10 @@
 +                this.setContainerLootTable(null);
 +            }
 +            // Paper end - LootTable API
-+
              LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position());
              if (player != null) {
                  builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
-@@ -173,4 +186,14 @@
+@@ -173,4 +_,14 @@
      default boolean isChestVehicleStillValid(Player player) {
          return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0);
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
new file mode 100644
index 0000000000..7a3a6b08c0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
++++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
+@@ -126,7 +_,7 @@
+                 MinecartCommandBlock.this.position(),
+                 MinecartCommandBlock.this.getRotationVector(),
+                 this.getLevel(),
+-                2,
++                this.getLevel().paperConfig().commandBlocks.permissionsLevel, // Paper - configurable command block perm level
+                 this.getName().getString(),
+                 MinecartCommandBlock.this.getDisplayName(),
+                 this.getLevel().getServer(),
+@@ -138,5 +_,12 @@
+         public boolean isValid() {
+             return !MinecartCommandBlock.this.isRemoved();
+         }
++
++        // CraftBukkit start
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++            return net.minecraft.world.entity.vehicle.MinecartCommandBlock.this.getBukkitEntity();
++        }
++        // CraftBukkit end
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
new file mode 100644
index 0000000000..8cfbf8de7d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
@@ -0,0 +1,56 @@
+--- a/net/minecraft/world/entity/vehicle/MinecartTNT.java
++++ b/net/minecraft/world/entity/vehicle/MinecartTNT.java
+@@ -33,6 +_,7 @@
+     public int fuse = -1;
+     public float explosionPowerBase = 4.0F;
+     public float explosionSpeedFactor = 1.0F;
++    public boolean isIncendiary = false; // CraftBukkit - add field
+ 
+     public MinecartTNT(EntityType<? extends MinecartTNT> entityType, Level level) {
+         super(entityType, level);
+@@ -47,6 +_,12 @@
+     public void tick() {
+         super.tick();
+         if (this.fuse > 0) {
++            // Paper start - Configurable TNT height nerf
++            if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.OUT_OF_WORLD);
++                return;
++            }
++            // Paper end - Configurable TNT height nerf
+             this.fuse--;
+             this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5, this.getZ(), 0.0, 0.0, 0.0);
+         } else if (this.fuse == 0) {
+@@ -101,6 +_,17 @@
+     protected void explode(@Nullable DamageSource damageSource, double radiusModifier) {
+         if (this.level() instanceof ServerLevel serverLevel) {
+             double min = Math.min(Math.sqrt(radiusModifier), 5.0);
++            // CraftBukkit start
++            org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(
++                this.getBukkitEntity(),
++                (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * min),
++                this.isIncendiary
++            );
++            if (!event.callEvent()) {
++                this.fuse = -1;
++                return;
++            }
++            // CraftBukkit end
+             serverLevel.explode(
+                 this,
+                 damageSource,
+@@ -108,11 +_,11 @@
+                 this.getX(),
+                 this.getY(),
+                 this.getZ(),
+-                (float)(this.explosionPowerBase + this.explosionSpeedFactor * this.random.nextDouble() * 1.5 * min),
+-                false,
++                event.getRadius(), // CraftBukkit - explosion prime event
++                event.getFire(), // CraftBukkit - explosion prime event
+                 Level.ExplosionInteraction.TNT
+             );
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
new file mode 100644
index 0000000000..510d8fd645
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
++++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
+@@ -479,6 +_,12 @@
+ 
+     @Override
+     public double getMaxSpeed(ServerLevel level) {
++        // CraftBukkit start
++        Double maxSpeed = this.minecart.maxSpeed;
++        if (maxSpeed != null) {
++            return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed);
++        }
++        // CraftBukkit end
+         return level.getGameRules().getInt(GameRules.RULE_MINECART_MAX_SPEED) * (this.minecart.isInWater() ? 0.5 : 1.0) / 20.0;
+     }
+ 
+@@ -494,7 +_,8 @@
+ 
+     @Override
+     public double getSlowdownFactor() {
+-        return this.minecart.isVehicle() ? 0.997 : 0.975;
++        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
++        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty
+     }
+ 
+     @Override
+@@ -518,6 +_,13 @@
+                         && !(entity instanceof AbstractMinecart)
+                         && !this.minecart.isVehicle()
+                         && !entity.isPassenger()) {
++                        // CraftBukkit start
++                        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                            (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(),
++                            entity.getBukkitEntity()
++                        );
++                        if (!collisionEvent.callEvent()) continue;
++                        // CraftBukkit end
+                         boolean flag = entity.startRiding(this.minecart);
+                         if (flag) {
+                             return true;
+@@ -541,6 +_,17 @@
+                         || entity instanceof AbstractMinecart
+                         || this.minecart.isVehicle()
+                         || entity.isPassenger()) {
++                        // CraftBukkit start
++                        if (!this.minecart.isPassengerOfSameVehicle(entity)) {
++                            org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                                (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(),
++                                entity.getBukkitEntity()
++                            );
++                            if (!collisionEvent.callEvent()) {
++                                continue;
++                            }
++                        }
++                        // CraftBukkit end
+                         entity.push(this.minecart);
+                         flag = true;
+                     }
+@@ -549,6 +_,15 @@
+         } else {
+             for (Entity entity1 : this.level().getEntities(this.minecart, box)) {
+                 if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
++                    // CraftBukkit start
++                    org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                        (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(),
++                        entity1.getBukkitEntity()
++                    );
++                    if (!collisionEvent.callEvent()) {
++                        continue;
++                    }
++                    // CraftBukkit end
+                     entity1.push(this.minecart);
+                     flag = true;
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
new file mode 100644
index 0000000000..71af93aa6c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
++++ b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
+@@ -414,8 +_,22 @@
+                         && !(entity instanceof AbstractMinecart)
+                         && !this.minecart.isVehicle()
+                         && !entity.isPassenger()) {
++                        // CraftBukkit start
++                        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                            (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()
++                        );
++                        if (!collisionEvent.callEvent()) continue;
++                        // CraftBukkit end
+                         entity.startRiding(this.minecart);
+                     } else {
++                        // CraftBukkit start
++                        if (!this.minecart.isPassengerOfSameVehicle(entity)) {
++                            org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                                (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity()
++                            );
++                            if (!collisionEvent.callEvent()) continue;
++                        }
++                        // CraftBukkit end
+                         entity.push(this.minecart);
+                     }
+                 }
+@@ -423,6 +_,12 @@
+         } else {
+             for (Entity entity1 : this.level().getEntities(this.minecart, aabb)) {
+                 if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
++                    // CraftBukkit start
++                    org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
++                        (org.bukkit.entity.Vehicle) this.minecart.getBukkitEntity(), entity1.getBukkitEntity()
++                    );
++                    if (!collisionEvent.callEvent()) continue;
++                    // CraftBukkit end
+                     entity1.push(this.minecart);
+                 }
+             }
+@@ -443,11 +_,18 @@
+ 
+     @Override
+     public double getMaxSpeed(ServerLevel level) {
++        // CraftBukkit start
++        Double maxSpeed = this.minecart.maxSpeed;
++        if (maxSpeed != null) {
++            return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed);
++        }
++        // CraftBukkit end
+         return this.minecart.isInWater() ? 0.2 : 0.4;
+     }
+ 
+     @Override
+     public double getSlowdownFactor() {
+-        return this.minecart.isVehicle() ? 0.997 : 0.96;
++        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
++        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
new file mode 100644
index 0000000000..0020b09f51
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/entity/vehicle/VehicleEntity.java
++++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java
+@@ -38,6 +_,14 @@
+         } else if (this.isInvulnerableToBase(damageSource)) {
+             return false;
+         } else {
++            // CraftBukkit start
++            org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
++            org.bukkit.entity.Entity attacker = (damageSource.getEntity() == null) ? null : damageSource.getEntity().getBukkitEntity();
++
++            org.bukkit.event.vehicle.VehicleDamageEvent event = new org.bukkit.event.vehicle.VehicleDamageEvent(vehicle, attacker, amount);
++            if (!event.callEvent()) return false;
++            amount = (float) event.getDamage();
++            // CraftBukkit end
+             this.setHurtDir(-this.getHurtDir());
+             this.setHurtTime(10);
+             this.markHurt();
+@@ -46,9 +_,23 @@
+             boolean flag = damageSource.getEntity() instanceof Player player && player.getAbilities().instabuild;
+             if ((flag || !(this.getDamage() > 40.0F)) && !this.shouldSourceDestroy(damageSource)) {
+                 if (flag) {
+-                    this.discard();
++                    // CraftBukkit start
++                    org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
++                    if (!destroyEvent.callEvent()) {
++                        this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
++                        return true;
++                    }
++                    // CraftBukkit end
++                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
+                 }
+             } else {
++                // CraftBukkit start
++                org.bukkit.event.vehicle.VehicleDestroyEvent destroyEvent = new org.bukkit.event.vehicle.VehicleDestroyEvent(vehicle, attacker);
++                if (!destroyEvent.callEvent()) {
++                    this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
++                    return true;
++                }
++                // CraftBukkit end
+                 this.destroy(level, damageSource);
+             }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
deleted file mode 100644
index b33136b346..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
+++ /dev/null
@@ -1,98 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
-+++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
-@@ -20,6 +20,14 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.storage.loot.LootTable;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
- 
- public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
- 
-@@ -28,9 +36,58 @@
-     public ResourceKey<LootTable> lootTable;
-     public long lootTableSeed;
- 
-+    // Paper start - LootTable API
-+    final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
-+
-+    @Override
-+    public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
-+        return this.lootableData;
-+    }
-+    // Paper end - LootTable API
-+    // CraftBukkit start
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
-+    private int maxStack = MAX_STACK;
-+
-+    public List<ItemStack> getContents() {
-+        return this.itemStacks;
-+    }
-+
-+    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;
-+    }
-+
-+    public InventoryHolder getOwner() {
-+        org.bukkit.entity.Entity cart = this.getBukkitEntity();
-+        if(cart instanceof InventoryHolder) return (InventoryHolder) cart;
-+        return null;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+    }
-+
-+    @Override
-+    public Location getLocation() {
-+        return this.getBukkitEntity().getLocation();
-+    }
-+    // CraftBukkit end
-+
-     protected AbstractMinecartContainer(EntityType<?> type, Level world) {
-         super(type, world);
--        this.itemStacks = NonNullList.withSize(36, ItemStack.EMPTY);
-+        this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
-     }
- 
-     @Override
-@@ -74,11 +131,18 @@
- 
-     @Override
-     public void remove(Entity.RemovalReason reason) {
--        if (!this.level().isClientSide && reason.shouldDestroy()) {
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.remove(reason, null);
-+    }
-+
-+    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
-+        // CraftBukkit end
-+        if (!this.level().isClientSide && entity_removalreason.shouldDestroy()) {
-             Containers.dropContents(this.level(), (Entity) this, (Container) this);
-         }
- 
--        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
deleted file mode 100644
index 7c789e80ad..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
-+++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
-@@ -128,12 +128,19 @@
- 
-         @Override
-         public CommandSourceStack createCommandSourceStack() {
--            return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), 2, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this);
-+            return new CommandSourceStack(this, MinecartCommandBlock.this.position(), MinecartCommandBlock.this.getRotationVector(), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), MinecartCommandBlock.this.getDisplayName(), this.getLevel().getServer(), MinecartCommandBlock.this); // Paper - configurable command block perm level
-         }
- 
-         @Override
-         public boolean isValid() {
-             return !MinecartCommandBlock.this.isRemoved();
-         }
-+
-+        // CraftBukkit start
-+        @Override
-+        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+            return (org.bukkit.craftbukkit.entity.CraftMinecartCommand) MinecartCommandBlock.this.getBukkitEntity();
-+        }
-+        // CraftBukkit end
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
deleted file mode 100644
index 7fdf216d8a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
+++ /dev/null
@@ -1,53 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/MinecartTNT.java
-+++ b/net/minecraft/world/entity/vehicle/MinecartTNT.java
-@@ -25,6 +25,10 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.material.FluidState;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
- 
- public class MinecartTNT extends AbstractMinecart {
- 
-@@ -37,6 +41,7 @@
-     public int fuse = -1;
-     public float explosionPowerBase = 4.0F;
-     public float explosionSpeedFactor = 1.0F;
-+    public boolean isIncendiary = false; // CraftBukkit - add field
- 
-     public MinecartTNT(EntityType<? extends MinecartTNT> type, Level world) {
-         super(type, world);
-@@ -51,6 +56,12 @@
-     public void tick() {
-         super.tick();
-         if (this.fuse > 0) {
-+            // Paper start - Configurable TNT height nerf
-+            if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) {
-+                this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
-+                return;
-+            }
-+            // Paper end - Configurable TNT height nerf
-             --this.fuse;
-             this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D);
-         } else if (this.fuse == 0) {
-@@ -117,8 +128,16 @@
-         if (world instanceof ServerLevel worldserver) {
-             double d1 = Math.min(Math.sqrt(power), 5.0D);
- 
--            worldserver.explode(this, damageSource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * d1), false, Level.ExplosionInteraction.TNT);
--            this.discard();
-+            // CraftBukkit start
-+            ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * d1), this.isIncendiary);
-+            worldserver.getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                this.fuse = -1;
-+                return;
-+            }
-+            worldserver.explode(this, damageSource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.TNT);
-+            // CraftBukkit end
-+            this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
deleted file mode 100644
index 8bed619b55..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
+++ /dev/null
@@ -1,83 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
-+++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java
-@@ -27,6 +27,10 @@
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
-+// CraftBukkit end
- 
- public class NewMinecartBehavior extends MinecartBehavior {
- 
-@@ -516,6 +520,12 @@
- 
-     @Override
-     public double getMaxSpeed(ServerLevel world) {
-+        // CraftBukkit start
-+        Double maxSpeed = this.minecart.maxSpeed;
-+        if (maxSpeed != null) {
-+            return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed);
-+        }
-+        // CraftBukkit end
-         return (double) world.getGameRules().getInt(GameRules.RULE_MINECART_MAX_SPEED) * (this.minecart.isInWater() ? 0.5D : 1.0D) / 20.0D;
-     }
- 
-@@ -544,7 +554,8 @@
- 
-     @Override
-     public double getSlowdownFactor() {
--        return this.minecart.isVehicle() ? 0.997D : 0.975D;
-+        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
-+        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty
-     }
- 
-     @Override
-@@ -571,6 +582,14 @@
-                     Entity entity = (Entity) iterator.next();
- 
-                     if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.minecart.isVehicle() && !entity.isPassenger()) {
-+                        // CraftBukkit start
-+                        VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity());
-+                        this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                        if (collisionEvent.isCancelled()) {
-+                            continue;
-+                        }
-+                        // CraftBukkit end
-                         boolean flag = entity.startRiding(this.minecart);
- 
-                         if (flag) {
-@@ -597,6 +616,16 @@
-                     Entity entity = (Entity) iterator.next();
- 
-                     if (entity instanceof Player || entity instanceof IronGolem || entity instanceof AbstractMinecart || this.minecart.isVehicle() || entity.isPassenger()) {
-+                        // CraftBukkit start
-+                        if (!this.minecart.isPassengerOfSameVehicle(entity)) {
-+                            VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity());
-+                            this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                            if (collisionEvent.isCancelled()) {
-+                                continue;
-+                            }
-+                        }
-+                        // CraftBukkit end
-                         entity.push((Entity) this.minecart);
-                         flag = true;
-                     }
-@@ -609,6 +638,14 @@
-                 Entity entity1 = (Entity) iterator1.next();
- 
-                 if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
-+                    // CraftBukkit start
-+                    VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity1.getBukkitEntity());
-+                    this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                    if (collisionEvent.isCancelled()) {
-+                        continue;
-+                    }
-+                    // CraftBukkit end
-                     entity1.push((Entity) this.minecart);
-                     flag = true;
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
deleted file mode 100644
index 5f791f5901..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
+++ /dev/null
@@ -1,75 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
-+++ b/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java
-@@ -24,6 +24,10 @@
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
-+// CraftBukkit end
- 
- public class OldMinecartBehavior extends MinecartBehavior {
- 
-@@ -454,8 +458,26 @@
-                     Entity entity = (Entity) iterator.next();
- 
-                     if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.minecart.isVehicle() && !entity.isPassenger()) {
-+                        // CraftBukkit start
-+                        VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity());
-+                        this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                        if (collisionEvent.isCancelled()) {
-+                            continue;
-+                        }
-+                        // CraftBukkit end
-                         entity.startRiding(this.minecart);
-                     } else {
-+                        // CraftBukkit start
-+                        if (!this.minecart.isPassengerOfSameVehicle(entity)) {
-+                            VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity.getBukkitEntity());
-+                            this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                            if (collisionEvent.isCancelled()) {
-+                                continue;
-+                            }
-+                        }
-+                        // CraftBukkit end
-                         entity.push((Entity) this.minecart);
-                     }
-                 }
-@@ -467,6 +489,14 @@
-                 Entity entity1 = (Entity) iterator1.next();
- 
-                 if (!this.minecart.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
-+                    // CraftBukkit start
-+                    VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.minecart.getBukkitEntity(), entity1.getBukkitEntity());
-+                    this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
-+
-+                    if (collisionEvent.isCancelled()) {
-+                        continue;
-+                    }
-+                    // CraftBukkit end
-                     entity1.push((Entity) this.minecart);
-                 }
-             }
-@@ -487,11 +517,18 @@
- 
-     @Override
-     public double getMaxSpeed(ServerLevel world) {
-+        // CraftBukkit start
-+        Double maxSpeed = this.minecart.maxSpeed;
-+        if (maxSpeed != null) {
-+            return (this.minecart.isInWater() ? maxSpeed / 2.0D : maxSpeed);
-+        }
-+        // CraftBukkit end
-         return this.minecart.isInWater() ? 0.2D : 0.4D;
-     }
- 
-     @Override
-     public double getSlowdownFactor() {
--        return this.minecart.isVehicle() ? 0.997D : 0.96D;
-+        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
-+        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
deleted file mode 100644
index 10fce15f79..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
+++ /dev/null
@@ -1,64 +0,0 @@
---- a/net/minecraft/world/entity/vehicle/VehicleEntity.java
-+++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java
-@@ -17,6 +17,13 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.vehicle.VehicleDamageEvent;
-+import org.bukkit.event.vehicle.VehicleDestroyEvent;
-+// CraftBukkit end
-+
- public abstract class VehicleEntity extends Entity {
- 
-     protected static final EntityDataAccessor<Integer> DATA_ID_HURT = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.INT);
-@@ -40,6 +47,18 @@
-             return false;
-         } else {
-             boolean flag;
-+            // CraftBukkit start
-+            Vehicle vehicle = (Vehicle) this.getBukkitEntity();
-+            org.bukkit.entity.Entity attacker = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity();
-+
-+            VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, (double) amount);
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                return false;
-+            }
-+            amount = (float) event.getDamage();
-+            // CraftBukkit end
-             label32:
-             {
-                 this.setHurtDir(-this.getHurtDir());
-@@ -65,9 +84,27 @@
- 
-             if ((flag1 || this.getDamage() <= 40.0F) && !this.shouldSourceDestroy(source)) {
-                 if (flag1) {
--                    this.discard();
-+                    // CraftBukkit start
-+                    VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker);
-+                    this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
-+
-+                    if (destroyEvent.isCancelled()) {
-+                        this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
-+                        return true;
-+                    }
-+                    // CraftBukkit end
-+                    this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-                 }
-             } else {
-+                // CraftBukkit start
-+                VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker);
-+                this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
-+
-+                if (destroyEvent.isCancelled()) {
-+                    this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
-+                    return true;
-+                }
-+                // CraftBukkit end
-                 this.destroy(world, source);
-             }
- 

From 2a9bf402959bdbaaf4baab9ec0775eb20655693d Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 17:27:01 +0100
Subject: [PATCH 060/285] net.minecraft.world.entity.animal (thank you wheel of
 pain)

---
 .../animal/AbstractSchoolingFish.java.patch   |   2 +-
 .../world/entity/animal/Animal.java.patch     | 118 ++++++++++
 .../world/entity/animal/Bee.java.patch        | 153 +++++++++++++
 .../world/entity/animal/Bucketable.java.patch |  32 +++
 .../world/entity/animal/Cat.java.patch        |  75 +++++++
 .../world/entity/animal/Chicken.java.patch    |  15 ++
 .../world/entity/animal/Cow.java.patch        |  19 ++
 .../world/entity/animal/Dolphin.java.patch    |  77 +++++++
 .../world/entity/animal/Fox.java.patch        | 143 ++++++++++++
 .../world/entity/animal/IronGolem.java.patch  |  20 ++
 .../entity/animal/MushroomCow.java.patch      |  59 +++++
 .../world/entity/animal/Ocelot.java.patch     |  20 ++
 .../world/entity/animal/Panda.java.patch      |  81 +++++++
 .../world/entity/animal/Parrot.java.patch     |  27 +--
 .../world/entity/animal/Pig.java.patch        |  18 ++
 .../world/entity/animal/Pufferfish.java.patch |  41 ++--
 .../world/entity/animal/Rabbit.java.patch     |  23 ++
 .../world/entity/animal/Sheep.java.patch      |  75 +++++++
 .../animal/ShoulderRidingEntity.java.patch    |  11 +
 .../world/entity/animal/SnowGolem.java.patch  |  74 +++++++
 .../world/entity/animal/Squid.java.patch      |  11 +
 .../world/entity/animal/Turtle.java.patch     |  75 +++++++
 .../entity/animal/WaterAnimal.java.patch      |  13 ++
 .../world/entity/animal/Wolf.java.patch       |  95 ++++++++
 .../world/entity/animal/Animal.java.patch     | 141 ------------
 .../world/entity/animal/Bee.java.patch        | 205 ------------------
 .../world/entity/animal/Bucketable.java.patch |  46 ----
 .../world/entity/animal/Cat.java.patch        |  96 --------
 .../world/entity/animal/Chicken.java.patch    |  15 --
 .../world/entity/animal/Cow.java.patch        |  33 ---
 .../world/entity/animal/Dolphin.java.patch    |  87 --------
 .../world/entity/animal/Fox.java.patch        | 168 --------------
 .../world/entity/animal/IronGolem.java.patch  |  20 --
 .../entity/animal/MushroomCow.java.patch      |  85 --------
 .../world/entity/animal/Ocelot.java.patch     |  34 ---
 .../world/entity/animal/Panda.java.patch      | 122 -----------
 .../world/entity/animal/Pig.java.patch        |  29 ---
 .../world/entity/animal/Rabbit.java.patch     |  40 ----
 .../world/entity/animal/Sheep.java.patch      |  90 --------
 .../animal/ShoulderRidingEntity.java.patch    |  21 --
 .../world/entity/animal/SnowGolem.java.patch  |  86 --------
 .../world/entity/animal/Squid.java.patch      |  11 -
 .../world/entity/animal/Turtle.java.patch     |  74 -------
 .../entity/animal/WaterAnimal.java.patch      |  13 --
 .../world/entity/animal/Wolf.java.patch       | 126 -----------
 45 files changed, 1239 insertions(+), 1580 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch (95%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/Parrot.java.patch (73%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/Pufferfish.java.patch (67%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Animal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bee.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bucketable.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cat.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Chicken.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cow.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Dolphin.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Fox.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/IronGolem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/MushroomCow.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Ocelot.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Panda.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pig.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Rabbit.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Sheep.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/SnowGolem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Squid.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Turtle.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/WaterAnimal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/Wolf.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
index 69bd0b2aef..450e26f925 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/animal/AbstractSchoolingFish.java
 +++ b/net/minecraft/world/entity/animal/AbstractSchoolingFish.java
-@@ -51,6 +51,7 @@
+@@ -51,6 +_,7 @@
      }
  
      public void stopFollowing() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
new file mode 100644
index 0000000000..a8935bd393
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
@@ -0,0 +1,118 @@
+--- a/net/minecraft/world/entity/animal/Animal.java
++++ b/net/minecraft/world/entity/animal/Animal.java
+@@ -39,6 +_,7 @@
+     public int inLove;
+     @Nullable
+     public UUID loveCause;
++    public ItemStack breedItem; // CraftBukkit - Add breedItem variable
+ 
+     protected Animal(EntityType<? extends Animal> entityType, Level level) {
+         super(entityType, level);
+@@ -78,9 +_,13 @@
+     }
+ 
+     @Override
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
++    // CraftBukkit start - void -> boolean
++    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, org.bukkit.event.entity.EntityDamageEvent event) {
++        boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event);
++        if (!damageResult) return false;
+         this.resetLove();
+-        super.actuallyHurt(level, damageSource, amount);
++        return true;
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -138,8 +_,9 @@
+         if (this.isFood(itemInHand)) {
+             int age = this.getAge();
+             if (!this.level().isClientSide && age == 0 && this.canFallInLove()) {
++                final ItemStack breedCopy = itemInHand.copy(); // Paper - Fix EntityBreedEvent copying
+                 this.usePlayerItem(player, hand, itemInHand);
+-                this.setInLove(player);
++                this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
+                 this.playEatingSound();
+                 return InteractionResult.SUCCESS_SERVER;
+             }
+@@ -176,11 +_,26 @@
+         return this.inLove <= 0;
+     }
+ 
++    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying
+     public void setInLove(@Nullable Player player) {
+-        this.inLove = 600;
++        // Paper start - Fix EntityBreedEvent copying
++        this.setInLove(player, null);
++    }
++    public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) {
++        if (breedItemCopy != null) this.breedItem = breedItemCopy;
++        // Paper end - Fix EntityBreedEvent copying
++        // CraftBukkit start
++        org.bukkit.event.entity.EntityEnterLoveModeEvent entityEnterLoveModeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600);
++        if (entityEnterLoveModeEvent.isCancelled()) {
++            this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled
++            return;
++        }
++        this.inLove = entityEnterLoveModeEvent.getTicksInLove();
++        // CraftBukkit end
+         if (player != null) {
+             this.loveCause = player.getUUID();
+         }
++        // Paper - Fix EntityBreedEvent copying; set breed item in better place
+ 
+         this.level().broadcastEntityEvent(this, (byte)18);
+     }
+@@ -220,23 +_,45 @@
+         if (breedOffspring != null) {
+             breedOffspring.setBaby(true);
+             breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+-            this.finalizeSpawnChildFromBreeding(level, mate, breedOffspring);
+-            level.addFreshEntityWithPassengers(breedOffspring);
++            // CraftBukkit start - call EntityBreedEvent
++            ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(mate.getLoveCause())).orElse(null);
++            int experience = this.getRandom().nextInt(7) + 1;
++            org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(breedOffspring, this, mate, breeder, this.breedItem, experience);
++            if (entityBreedEvent.isCancelled()) {
++                return;
++            }
++            experience = entityBreedEvent.getExperience();
++            this.finalizeSpawnChildFromBreeding(level, mate, breedOffspring, experience);
++            level.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
++            // CraftBukkit end
+         }
+     }
+ 
+     public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby) {
+-        Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(animal.getLoveCause())).ifPresent(player -> {
+-            player.awardStat(Stats.ANIMALS_BRED);
+-            CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, baby);
+-        });
++        // CraftBukkit start
++        this.finalizeSpawnChildFromBreeding(level, animal, baby, this.getRandom().nextInt(7) + 1);
++    }
++    public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby, int experience) {
++        // CraftBukkit end
++        // Paper start
++        ServerPlayer entityplayer = this.getLoveCause();
++        if (entityplayer == null) entityplayer = animal.getLoveCause();
++        if (entityplayer != null) {
++            // Paper end
++            entityplayer.awardStat(Stats.ANIMALS_BRED);
++            CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, animal, baby);
++        } // Paper
+         this.setAge(6000);
+         animal.setAge(6000);
+         this.resetLove();
+         animal.resetLove();
+         level.broadcastEntityEvent(this, (byte)18);
+         if (level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+-            level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
++            // CraftBukkit start - use event experience
++            if (experience > 0) {
++                level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, baby)); // Paper
++            }
++            // CraftBukkit end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
new file mode 100644
index 0000000000..3891fbabb2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
@@ -0,0 +1,153 @@
+--- a/net/minecraft/world/entity/animal/Bee.java
++++ b/net/minecraft/world/entity/animal/Bee.java
+@@ -144,7 +_,22 @@
+ 
+     public Bee(EntityType<? extends Bee> entityType, Level level) {
+         super(entityType, level);
+-        this.moveControl = new FlyingMoveControl(this, 20, true);
++        // Paper start - Fix MC-167279
++        class BeeFlyingMoveControl extends FlyingMoveControl {
++            public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
++                super(entity, maxPitchChange, noGravity);
++            }
++
++            @Override
++            public void tick() {
++                if (this.mob.getY() <= Bee.this.level().getMinY()) {
++                    this.mob.setNoGravity(false);
++                }
++                super.tick();
++            }
++        }
++        this.moveControl = new BeeFlyingMoveControl(this, 20, true);
++        // Paper end - Fix MC-167279
+         this.lookControl = new Bee.BeeLookControl(this);
+         this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F);
+         this.setPathfindingMalus(PathType.WATER, -1.0F);
+@@ -191,12 +_,19 @@
+ 
+     @Override
+     public void addAdditionalSaveData(CompoundTag compound) {
++        // CraftBukkit start - selectively save data
++        this.addAdditionalSaveData(compound, true);
++    }
++
++    @Override
++    public void addAdditionalSaveData(CompoundTag compound, boolean includeAll) {
++        // CraftBukkit end
+         super.addAdditionalSaveData(compound);
+-        if (this.hasHive()) {
++        if (includeAll && this.hasHive()) { // CraftBukkit - selectively save hive
+             compound.put("hive_pos", NbtUtils.writeBlockPos(this.getHivePos()));
+         }
+ 
+-        if (this.hasSavedFlowerPos()) {
++        if (includeAll && this.hasSavedFlowerPos()) { // CraftBukkit - selectively save hive
+             compound.put("flower_pos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
+         }
+ 
+@@ -237,7 +_,7 @@
+                 }
+ 
+                 if (i > 0) {
+-                    livingEntity.addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this);
++                    livingEntity.addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+ 
+@@ -489,11 +_,12 @@
+ 
+     @Nullable
+     BeehiveBlockEntity getBeehiveBlockEntity() {
+-        if (this.hivePos == null) {
+-            return null;
+-        } else {
+-            return this.isTooFarAway(this.hivePos) ? null : this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
++        // Paper start - move over logic to accommodate isTooFarAway with chunk load check
++        if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) {
++            return (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
+         }
++        return null;
++        // Paper end
+     }
+ 
+     boolean isHiveValid() {
+@@ -520,11 +_,13 @@
+         this.setFlag(4, hasStung);
+     }
+ 
++    public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override
+     public boolean isRolling() {
+         return this.getFlag(2);
+     }
+ 
+     public void setRolling(boolean isRolling) {
++        isRolling = rollingOverride.toBooleanOrElse(isRolling); // Paper - Rolling override
+         this.setFlag(2, isRolling);
+     }
+ 
+@@ -581,7 +_,7 @@
+             if (beeInteractionEffect != null) {
+                 this.usePlayerItem(player, hand, itemInHand);
+                 if (!this.level().isClientSide) {
+-                    this.addEffect(beeInteractionEffect);
++                    this.addEffect(beeInteractionEffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // Paper - Add missing effect cause
+                 }
+ 
+                 return InteractionResult.SUCCESS;
+@@ -650,8 +_,9 @@
+         if (this.isInvulnerableTo(level, damageSource)) {
+             return false;
+         } else {
++            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit - Only stop pollinating if entity was damaged
+             this.beePollinateGoal.stopPollinating();
+-            return super.hurtServer(level, damageSource, amount);
++            return true; //  CraftBukkit - Only stop pollinating if entity was damaged
+         }
+     }
+ 
+@@ -772,7 +_,7 @@
+     @VisibleForDebug
+     public class BeeGoToHiveGoal extends Bee.BaseBeeGoal {
+         public static final int MAX_TRAVELLING_TICKS = 2400;
+-        int travellingTicks = Bee.this.level().random.nextInt(10);
++        int travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
+         private static final int MAX_BLACKLISTED_TARGETS = 3;
+         final List<BlockPos> blacklistedTargets = Lists.newArrayList();
+         @Nullable
+@@ -888,7 +_,7 @@
+ 
+     public class BeeGoToKnownFlowerGoal extends Bee.BaseBeeGoal {
+         private static final int MAX_TRAVELLING_TICKS = 2400;
+-        int travellingTicks = Bee.this.level().random.nextInt(10);
++        int travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
+ 
+         BeeGoToKnownFlowerGoal() {
+             this.setFlags(EnumSet.of(Goal.Flag.MOVE));
+@@ -986,7 +_,7 @@
+                             }
+                         }
+ 
+-                        if (blockState1 != null) {
++                        if (blockState1 != null && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(Bee.this, blockPos, blockState1)) { // CraftBukkit
+                             Bee.this.level().levelEvent(2011, blockPos, 15);
+                             Bee.this.level().setBlockAndUpdate(blockPos, blockState1);
+                             Bee.this.incrementNumCropsGrownSincePollination();
+@@ -1010,7 +_,7 @@
+         @Override
+         protected void alertOther(Mob mob, LivingEntity target) {
+             if (mob instanceof Bee && this.mob.hasLineOfSight(target)) {
+-                mob.setTarget(target);
++                mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit - reason
+             }
+         }
+     }
+@@ -1168,7 +_,7 @@
+                     Bee.this.dropFlower();
+                     this.pollinating = false;
+                     Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
+-                } else {
++                } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this
+                     Vec3 vec3 = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0, 0.6F, 0.0);
+                     if (vec3.distanceTo(Bee.this.position()) > 1.0) {
+                         this.hoverPos = vec3;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch
new file mode 100644
index 0000000000..7d0041cf64
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bucketable.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/entity/animal/Bucketable.java
++++ b/net/minecraft/world/entity/animal/Bucketable.java
+@@ -88,9 +_,19 @@
+     static <T extends LivingEntity & Bucketable> Optional<InteractionResult> bucketMobPickup(Player player, InteractionHand hand, T entity) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.getItem() == Items.WATER_BUCKET && entity.isAlive()) {
+-            entity.playSound(entity.getPickupSound(), 1.0F, 1.0F);
++            // CraftBukkit start
++            // entity.playSound(entity.getPickupSound(), 1.0F, 1.0F); // CraftBukkit - moved down
+             ItemStack bucketItemStack = entity.getBucketItemStack();
+             entity.saveToBucketTag(bucketItemStack);
++            org.bukkit.event.player.PlayerBucketEntityEvent playerBucketFishEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerFishBucketEvent(entity, player, itemInHand, bucketItemStack, hand);
++            bucketItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket());
++            if (playerBucketFishEvent.isCancelled()) {
++                player.containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket
++                entity.resendPossiblyDesyncedEntityData((ServerPlayer) player); // Paper
++                return Optional.of(InteractionResult.FAIL);
++            }
++            entity.playSound(entity.getPickupSound(), 1.0F, 1.0F);
++            // CraftBukkit end
+             ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, bucketItemStack, false);
+             player.setItemInHand(hand, itemStack);
+             Level level = entity.level();
+@@ -98,7 +_,7 @@
+                 CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, bucketItemStack);
+             }
+ 
+-            entity.discard();
++            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             return Optional.of(InteractionResult.SUCCESS);
+         } else {
+             return Optional.empty();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
new file mode 100644
index 0000000000..01deda0a89
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/animal/Cat.java
++++ b/net/minecraft/world/entity/animal/Cat.java
+@@ -342,7 +_,7 @@
+         TagKey<CatVariant> tagKey = flag ? CatVariantTags.FULL_MOON_SPAWNS : CatVariantTags.DEFAULT_SPAWNS;
+         BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagKey, level.getRandom()).ifPresent(this::setVariant);
+         ServerLevel level1 = level.getLevel();
+-        if (level1.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) {
++        if (level1.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, level).isValid()) { // Paper - Fix swamp hut cat generation deadlock
+             this.setVariant(BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK));
+             this.setPersistenceRequired();
+         }
+@@ -359,6 +_,11 @@
+                 if (item instanceof DyeItem dyeItem) {
+                     DyeColor dyeColor = dyeItem.getDyeColor();
+                     if (dyeColor != this.getCollarColor()) {
++                        // Paper start - Add EntityDyeEvent and CollarColorable interface
++                        final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
++                        if (!event.callEvent()) return InteractionResult.FAIL;
++                        dyeColor = DyeColor.byId(event.getColor().getWoolData());
++                        // Paper end - Add EntityDyeEvent and CollarColorable interface
+                         if (!this.level().isClientSide()) {
+                             this.setCollarColor(dyeColor);
+                             itemInHand.consume(1, player);
+@@ -371,7 +_,7 @@
+                     if (!this.level().isClientSide()) {
+                         this.usePlayerItem(player, hand, itemInHand);
+                         FoodProperties foodProperties = itemInHand.get(DataComponents.FOOD);
+-                        this.heal(foodProperties != null ? foodProperties.nutrition() : 1.0F);
++                        this.heal(foodProperties != null ? foodProperties.nutrition() : 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason
+                         this.playEatingSound();
+                     }
+ 
+@@ -433,7 +_,7 @@
+     }
+ 
+     private void tryToTame(Player player) {
+-        if (this.random.nextInt(3) == 0) {
++        if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+             this.tame(player);
+             this.setOrderedToSit(true);
+             this.level().broadcastEntityEvent(this, (byte)7);
+@@ -567,15 +_,20 @@
+                 .dropFromGiftLootTable(
+                     getServerLevel(this.cat),
+                     BuiltInLootTables.CAT_MORNING_GIFT,
+-                    (serverLevel, itemStack) -> serverLevel.addFreshEntity(
+-                        new ItemEntity(
++                    (serverLevel, itemStack) -> {
++                        // CraftBukkit start
++                        ItemEntity item = new ItemEntity(
+                             serverLevel,
+                             (double)mutableBlockPos.getX() - Mth.sin(this.cat.yBodyRot * (float) (Math.PI / 180.0)),
+                             mutableBlockPos.getY(),
+                             (double)mutableBlockPos.getZ() + Mth.cos(this.cat.yBodyRot * (float) (Math.PI / 180.0)),
+                             itemStack
+-                        )
+-                    )
++                        );
++                        org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.cat.getBukkitEntity(), (org.bukkit.entity.Item) item.getBukkitEntity());
++                        if (!event.callEvent()) return;
++                        serverLevel.addFreshEntity(item);
++                        // CraftBukkit end
++                    }
+                 );
+         }
+ 
+@@ -602,7 +_,7 @@
+ 
+     static class CatTemptGoal extends TemptGoal {
+         @Nullable
+-        private Player selectedPlayer;
++        private LivingEntity selectedPlayer; // CraftBukkit
+         private final Cat cat;
+ 
+         public CatTemptGoal(Cat cat, double speedModifier, Predicate<ItemStack> items, boolean canScare) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch
new file mode 100644
index 0000000000..a95bdb6673
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Chicken.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/animal/Chicken.java
++++ b/net/minecraft/world/entity/animal/Chicken.java
+@@ -91,10 +_,12 @@
+ 
+         this.flap = this.flap + this.flapping * 2.0F;
+         if (this.level() instanceof ServerLevel serverLevel && this.isAlive() && !this.isBaby() && !this.isChickenJockey() && --this.eggTime <= 0) {
++            this.forceDrops = true; // CraftBukkit
+             if (this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.CHICKEN_LAY, this::spawnAtLocation)) {
+                 this.playSound(SoundEvents.CHICKEN_EGG, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+                 this.gameEvent(GameEvent.ENTITY_PLACE);
+             }
++            this.forceDrops = false; // CraftBukkit
+ 
+             this.eggTime = this.random.nextInt(6000) + 6000;
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
new file mode 100644
index 0000000000..c8bd9cb88e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/animal/Cow.java
++++ b/net/minecraft/world/entity/animal/Cow.java
+@@ -88,8 +_,15 @@
+     public InteractionResult mobInteract(Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
++            // CraftBukkit start - Got milk?
++            org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
++            if (event.isCancelled()) {
++                player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                return InteractionResult.PASS;
++            }
++            // CraftBukkit end
+             player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
+-            ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, Items.MILK_BUCKET.getDefaultInstance());
++            ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+             player.setItemInHand(hand, itemStack);
+             return InteractionResult.SUCCESS;
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
new file mode 100644
index 0000000000..281bb05b75
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
@@ -0,0 +1,77 @@
+--- a/net/minecraft/world/entity/animal/Dolphin.java
++++ b/net/minecraft/world/entity/animal/Dolphin.java
+@@ -63,6 +_,12 @@
+ import net.minecraft.world.phys.Vec3;
+ 
+ public class Dolphin extends AgeableWaterCreature {
++    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++    @Override
++    public int getDefaultMaxAirSupply() {
++        return TOTAL_AIR_SUPPLY;
++    }
++    // CraftBukkit end
+     private static final EntityDataAccessor<BlockPos> TREASURE_POS = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BLOCK_POS);
+     private static final EntityDataAccessor<Boolean> GOT_FISH = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BOOLEAN);
+     private static final EntityDataAccessor<Integer> MOISTNESS_LEVEL = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.INT);
+@@ -196,7 +_,7 @@
+ 
+     @Override
+     public int getMaxAirSupply() {
+-        return 4800;
++        return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+     }
+ 
+     @Override
+@@ -229,11 +_,15 @@
+         if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) {
+             ItemStack item = entity.getItem();
+             if (this.canHoldItem(item)) {
++                // CraftBukkit start - call EntityPickupItemEvent
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, false).isCancelled()) return;
++                item = entity.getItem(); // CraftBukkit- update ItemStack from event
++                // CraftBukkit end
+                 this.onItemPickup(entity);
+                 this.setItemSlot(EquipmentSlot.MAINHAND, item);
+                 this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+                 this.take(entity, item.getCount());
+-                entity.discard();
++                entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
+@@ -341,7 +_,7 @@
+ 
+     @Nullable
+     @Override
+-    protected SoundEvent getDeathSound() {
++    public SoundEvent getDeathSound() { // Paper - decompile error
+         return SoundEvents.DOLPHIN_DEATH;
+     }
+ 
+@@ -497,7 +_,7 @@
+ 
+         @Override
+         public void start() {
+-            this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
++            this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+         }
+ 
+         @Override
+@@ -516,7 +_,7 @@
+             }
+ 
+             if (this.player.isSwimming() && this.player.level().random.nextInt(6) == 0) {
+-                this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
++                this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+             }
+         }
+     }
+@@ -586,7 +_,7 @@
+                     0.3F * Mth.cos(Dolphin.this.getYRot() * (float) (Math.PI / 180.0)) * Mth.cos(Dolphin.this.getXRot() * (float) (Math.PI / 180.0))
+                         + Mth.sin(f1) * f2
+                 );
+-                Dolphin.this.level().addFreshEntity(itemEntity);
++                Dolphin.this.spawnAtLocation(getServerLevel(Dolphin.this), itemEntity); // Paper - Call EntityDropItemEvent
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
new file mode 100644
index 0000000000..2db8810362
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
@@ -0,0 +1,143 @@
+--- a/net/minecraft/world/entity/animal/Fox.java
++++ b/net/minecraft/world/entity/animal/Fox.java
+@@ -413,7 +_,7 @@
+ 
+         this.setSleeping(compound.getBoolean("Sleeping"));
+         this.setVariant(Fox.Variant.byName(compound.getString("Type")));
+-        this.setSitting(compound.getBoolean("Sitting"));
++        this.setSitting(compound.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent
+         this.setIsCrouching(compound.getBoolean("Crouching"));
+         if (this.level() instanceof ServerLevel) {
+             this.setTargetGoals();
+@@ -425,6 +_,12 @@
+     }
+ 
+     public void setSitting(boolean sitting) {
++        // Paper start - Add EntityToggleSitEvent
++        this.setSitting(sitting, true);
++    }
++    public void setSitting(boolean sitting, boolean fireEvent) {
++        if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return;
++        // Paper end - Add EntityToggleSitEvent
+         this.setFlag(1, sitting);
+     }
+ 
+@@ -484,19 +_,20 @@
+             itemEntity.setPickUpDelay(40);
+             itemEntity.setThrower(this);
+             this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F);
+-            this.level().addFreshEntity(itemEntity);
++            this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), itemEntity); // Paper - Call EntityDropItemEvent
+         }
+     }
+ 
+     private void dropItemStack(ItemStack stack) {
+         ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack);
+-        this.level().addFreshEntity(itemEntity);
++        this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), itemEntity); // Paper - Call EntityDropItemEvent
+     }
+ 
+     @Override
+     protected void pickUpItem(ServerLevel level, ItemEntity entity) {
+         ItemStack item = entity.getItem();
+-        if (this.canHoldItem(item)) {
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, item.getCount() - 1, !this.canHoldItem(item)).isCancelled()) { // CraftBukkit - call EntityPickupItemEvent
++            item = entity.getItem(); // CraftBukkit - update item after event
+             int count = item.getCount();
+             if (count > 1) {
+                 this.dropItemStack(item.split(count - 1));
+@@ -507,7 +_,7 @@
+             this.setItemSlot(EquipmentSlot.MAINHAND, item.split(1));
+             this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+             this.take(entity, item.getCount());
+-            entity.discard();
++            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             this.ticksSinceEaten = 0;
+         }
+     }
+@@ -671,15 +_,33 @@
+         return this.getTrustedUUIDs().contains(uuid);
+     }
+ 
+-    @Override
+-    protected void dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
++    // Paper start - handle the bitten item separately like vanilla
++    @Override
++    protected boolean shouldSkipLoot(EquipmentSlot slot) {
++        return slot == EquipmentSlot.MAINHAND;
++    }
++    // Paper end
++
++    @Override
++    // Paper start - Cancellable death event
++    protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
+         ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.MAINHAND);
+-        if (!itemBySlot.isEmpty()) {
++        boolean releaseMouth = false;
++        if (!itemBySlot.isEmpty() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010
+             this.spawnAtLocation(level, itemBySlot);
++            releaseMouth = true;
++        }
++
++        org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(level, damageSource);
++        // Below is code to drop
++        if (deathEvent == null || deathEvent.isCancelled()) return deathEvent;
++
++        if (releaseMouth) {
++            // Paper end
+             this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+         }
+ 
+-        super.dropAllDeathLoot(level, damageSource);
++        return deathEvent; // Paper - Cancellable death event
+     }
+ 
+     public static boolean isPathClear(Fox fox, LivingEntity livingEntity) {
+@@ -853,6 +_,14 @@
+                 if (loveCause1 != null && loveCause != loveCause1) {
+                     fox.addTrustedUUID(loveCause1.getUUID());
+                 }
++                // CraftBukkit start - call EntityBreedEvent
++                fox.setAge(-24000);
++                fox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
++                int experience = this.animal.getRandom().nextInt(7) + 1;
++                org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(fox, this.animal, this.partner, loveCause, this.animal.breedItem, experience);
++                if (entityBreedEvent.isCancelled()) return;
++                experience = entityBreedEvent.getExperience();
++                // CraftBukkit end
+ 
+                 if (serverPlayer != null) {
+                     serverPlayer.awardStat(Stats.ANIMALS_BRED);
+@@ -863,15 +_,17 @@
+                 this.partner.setAge(6000);
+                 this.animal.resetLove();
+                 this.partner.resetLove();
+-                fox.setAge(-24000);
+-                fox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
+-                serverLevel.addFreshEntityWithPassengers(fox);
++                level.addFreshEntityWithPassengers(fox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
+                 this.level.broadcastEntityEvent(this.animal, (byte)18);
+                 if (serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+-                    this.level
+-                        .addFreshEntity(
+-                            new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), this.animal.getRandom().nextInt(7) + 1)
+-                        );
++                    // CraftBukkit start - use event experience
++                    if (experience > 0) {
++                        this.level
++                            .addFreshEntity(
++                                new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, loveCause, fox) // Paper
++                            );
++                    }
++                    // CraftBukkit end
+                 }
+             }
+         }
+@@ -934,6 +_,7 @@
+         private void pickSweetBerries(BlockState state) {
+             int ageValue = state.getValue(SweetBerryBushBlock.AGE);
+             state.setValue(SweetBerryBushBlock.AGE, Integer.valueOf(1));
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(Fox.this, this.blockPos, state.setValue(SweetBerryBushBlock.AGE, 1))) return; // CraftBukkit - call EntityChangeBlockEvent
+             int i = 1 + Fox.this.level().random.nextInt(2) + (ageValue == 3 ? 1 : 0);
+             ItemStack itemBySlot = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND);
+             if (itemBySlot.isEmpty()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch
new file mode 100644
index 0000000000..3af59e7fb7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/IronGolem.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/animal/IronGolem.java
++++ b/net/minecraft/world/entity/animal/IronGolem.java
+@@ -104,7 +_,7 @@
+     @Override
+     protected void doPush(Entity entity) {
+         if (entity instanceof Enemy && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) {
+-            this.setTarget((LivingEntity)entity);
++            this.setTarget((LivingEntity)entity, org.bukkit.event.entity.EntityTargetLivingEntityEvent.TargetReason.COLLISION, true); // CraftBukkit - set reason
+         }
+ 
+         super.doPush(entity);
+@@ -303,7 +_,7 @@
+         BlockPos blockPos = this.blockPosition();
+         BlockPos blockPos1 = blockPos.below();
+         BlockState blockState = level.getBlockState(blockPos1);
+-        if (!blockState.entityCanStandOn(level, blockPos1, this)) {
++        if (!blockState.entityCanStandOn(level, blockPos1, this) && !this.level().paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - Add option to allow iron golems to spawn in air
+             return false;
+         } else {
+             for (int i = 1; i < 3; i++) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
new file mode 100644
index 0000000000..3b9b4c2f06
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/entity/animal/MushroomCow.java
++++ b/net/minecraft/world/entity/animal/MushroomCow.java
+@@ -110,7 +_,17 @@
+             return InteractionResult.SUCCESS;
+         } else if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
+             if (this.level() instanceof ServerLevel serverLevel) {
+-                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand);
++                // CraftBukkit start
++                // Paper start - custom shear drops
++                java.util.List<ItemStack> drops = this.generateDefaultDrops(serverLevel, itemInHand);
++                org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
++                if (event != null) {
++                    if (event.isCancelled()) return InteractionResult.PASS;
++                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
++                    // Paper end - custom shear drops
++                }
++                // CraftBukkit end
++                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand, drops); // Paper - custom shear drops
+                 this.gameEvent(GameEvent.SHEAR, player);
+                 itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
+             }
+@@ -163,15 +_,32 @@
+ 
+     @Override
+     public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
++        // Paper start - custom shear drops
++        this.shear(level, soundSource, shears, this.generateDefaultDrops(level, shears));
++    }
++
++    @Override
++    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
++        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
++        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (ignored, stack) -> {
++            for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1));
++        });
++        return drops;
++    }
++
++    @Override
++    public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<ItemStack> drops) {
++        // Paper end
+         level.playSound(null, this, SoundEvents.MOOSHROOM_SHEAR, soundSource, 1.0F, 1.0F);
+         this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), mob -> {
+             level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5), this.getZ(), 1, 0.0, 0.0, 0.0, 0.0);
+-            this.dropFromShearingLootTable(level, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (serverLevel, itemStack) -> {
+-                for (int i = 0; i < itemStack.getCount(); i++) {
+-                    serverLevel.addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(1.0), this.getZ(), itemStack.copyWithCount(1)));
+-                }
++            // Paper start - custom shear drops; moved drop generation to separate method
++            drops.forEach(drop -> {
++                ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop);
++                this.spawnAtLocation(level, entityitem);
+             });
+-        });
++            // Paper end - custom shear drops
++        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.SHEARED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHEARED); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch
new file mode 100644
index 0000000000..0d5245ce20
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Ocelot.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/animal/Ocelot.java
++++ b/net/minecraft/world/entity/animal/Ocelot.java
+@@ -125,7 +_,7 @@
+ 
+     @Override
+     public boolean removeWhenFarAway(double distanceToClosestPlayer) {
+-        return !this.isTrusting() && this.tickCount > 2400;
++        return !this.isTrusting() && this.tickCount > 2400 && !this.hasCustomName() && !this.isLeashed(); // Paper - honor name and leash
+     }
+ 
+     public static AttributeSupplier.Builder createAttributes() {
+@@ -159,7 +_,7 @@
+         if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemInHand) && player.distanceToSqr(this) < 9.0) {
+             this.usePlayerItem(player, hand, itemInHand);
+             if (!this.level().isClientSide) {
+-                if (this.random.nextInt(3) == 0) {
++                if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check
+                     this.setTrusting(true);
+                     this.spawnTrustingParticles(true);
+                     this.level().broadcastEntityEvent(this, (byte)41);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch
new file mode 100644
index 0000000000..3e9a850886
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Panda.java.patch
@@ -0,0 +1,81 @@
+--- a/net/minecraft/world/entity/animal/Panda.java
++++ b/net/minecraft/world/entity/animal/Panda.java
+@@ -127,6 +_,7 @@
+     }
+ 
+     public void sit(boolean sitting) {
++        if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent
+         this.setFlag(8, sitting);
+     }
+ 
+@@ -516,24 +_,28 @@
+ 
+         for (Panda panda : level.getEntitiesOfClass(Panda.class, this.getBoundingBox().inflate(10.0))) {
+             if (!panda.isBaby() && panda.onGround() && !panda.isInWater() && panda.canPerformAction()) {
++                if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
+                 panda.jumpFromGround();
++                } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
+             }
+         }
+ 
+         if (this.level() instanceof ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
++            this.forceDrops = true; // Paper - Add missing forceDrop toggles
+             this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation);
++            this.forceDrops = false; // Paper - Add missing forceDrop toggles
+         }
+     }
+ 
+     @Override
+     protected void pickUpItem(ServerLevel level, ItemEntity entity) {
+-        if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && canPickUpAndEat(entity)) {
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, !(this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(entity))).isCancelled()) { // CraftBukkit
+             this.onItemPickup(entity);
+             ItemStack item = entity.getItem();
+             this.setItemSlot(EquipmentSlot.MAINHAND, item);
+             this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+             this.take(entity, item.getCount());
+-            entity.discard();
++            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
+@@ -624,8 +_,9 @@
+                 this.usePlayerItem(player, hand, itemInHand);
+                 this.ageUp((int)(-this.getAge() / 20 * 0.1F), true);
+             } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) {
++                final ItemStack breedCopy = itemInHand.copy(); // Paper - Fix EntityBreedEvent copying
+                 this.usePlayerItem(player, hand, itemInHand);
+-                this.setInLove(player);
++                this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
+             } else {
+                 if (!(this.level() instanceof ServerLevel serverLevel) || this.isSitting() || this.isInWater()) {
+                     return InteractionResult.PASS;
+@@ -635,7 +_,9 @@
+                 this.eat(true);
+                 ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.MAINHAND);
+                 if (!itemBySlot.isEmpty() && !player.hasInfiniteMaterials()) {
++                    this.forceDrops = true; // Paper - Add missing forceDrop toggles
+                     this.spawnAtLocation(serverLevel, itemBySlot);
++                    this.forceDrops = false; // Paper - Add missing forceDrop toggles
+                 }
+ 
+                 this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemInHand.getItem(), 1));
+@@ -861,7 +_,7 @@
+         @Override
+         protected void alertOther(Mob mob, LivingEntity target) {
+             if (mob instanceof Panda && mob.isAggressive()) {
+-                mob.setTarget(target);
++                mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit
+             }
+         }
+     }
+@@ -1090,7 +_,9 @@
+         public void stop() {
+             ItemStack itemBySlot = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND);
+             if (!itemBySlot.isEmpty()) {
++                Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles
+                 Panda.this.spawnAtLocation(getServerLevel(Panda.this.level()), itemBySlot);
++                Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles
+                 Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+                 int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10;
+                 this.cooldown = Panda.this.tickCount + i * 20;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Parrot.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/Parrot.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
index e42863c2b1..554ab3d969 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Parrot.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
@@ -1,24 +1,24 @@
 --- a/net/minecraft/world/entity/animal/Parrot.java
 +++ b/net/minecraft/world/entity/animal/Parrot.java
-@@ -248,7 +248,7 @@
+@@ -257,7 +_,7 @@
              }
  
              if (!this.level().isClientSide) {
 -                if (this.random.nextInt(10) == 0) {
 +                if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
                      this.tame(player);
-                     this.level().broadcastEntityEvent(this, (byte) 7);
+                     this.level().broadcastEntityEvent(this, (byte)7);
                  } else {
-@@ -269,7 +269,7 @@
+@@ -278,7 +_,7 @@
              }
          } else {
-             this.usePlayerItem(player, hand, itemstack);
+             this.usePlayerItem(player, hand, itemInHand);
 -            this.addEffect(new MobEffectInstance(MobEffects.POISON, 900));
 +            this.addEffect(new MobEffectInstance(MobEffects.POISON, 900), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
              if (player.isCreative() || !this.isInvulnerable()) {
                  this.hurt(this.damageSources().playerAttack(player), Float.MAX_VALUE);
              }
-@@ -362,8 +362,8 @@
+@@ -373,8 +_,8 @@
      }
  
      @Override
@@ -29,19 +29,16 @@
      }
  
      @Override
-@@ -378,8 +378,14 @@
-         if (this.isInvulnerableTo(world, source)) {
+@@ -387,10 +_,11 @@
+     @Override
+     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
+         if (this.isInvulnerableTo(level, damageSource)) {
++            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit
              return false;
          } else {
-+            // CraftBukkit start
-+            boolean result = super.hurtServer(world, source, amount);
-+            if (!result) {
-+                return result;
-+            }
-+            // CraftBukkit end
              this.setOrderedToSit(false);
--            return super.hurtServer(world, source, amount);
-+            return result; // CraftBukkit
+-            return super.hurtServer(level, damageSource, amount);
++            return true; // CraftBukkit
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch
new file mode 100644
index 0000000000..31833a8584
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pig.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/animal/Pig.java
++++ b/net/minecraft/world/entity/animal/Pig.java
+@@ -214,7 +_,14 @@
+                 }
+ 
+                 mob.setPersistenceRequired();
+-            });
++                // CraftBukkit start
++            }, null, null);
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callPigZapEvent(this, lightning, zombifiedPiglin).isCancelled()) {
++                return;
++            }
++            level.addFreshEntity(zombifiedPiglin, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING);
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
++            // CraftBukkit end
+             if (zombifiedPiglin == null) {
+                 super.thunderHit(level, lightning);
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pufferfish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch
similarity index 67%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pufferfish.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch
index 32042a8ba6..bd25f044bc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pufferfish.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Pufferfish.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/animal/Pufferfish.java
 +++ b/net/minecraft/world/entity/animal/Pufferfish.java
-@@ -102,25 +102,39 @@
+@@ -95,24 +_,36 @@
      public void tick() {
          if (!this.level().isClientSide && this.isAlive() && this.isEffectiveAi()) {
              if (this.inflateCounter > 0) {
@@ -17,9 +17,8 @@
 +                    } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
                  }
  
-+                if (increase) { // Paper - Add PufferFishStateChangeEvent
-                 ++this.inflateCounter;
-+                } // Paper - Add PufferFishStateChangeEvent
++                if (increase) // Paper - Add PufferFishStateChangeEvent
+                 this.inflateCounter++;
              } else if (this.getPuffState() != 0) {
 +                boolean increase = true; // Paper - Add PufferFishStateChangeEvent
                  if (this.deflateTimer > 60 && this.getPuffState() == 2) {
@@ -34,27 +33,25 @@
 +                    } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
                  }
  
-+                if (increase) { // Paper - Add PufferFishStateChangeEvent
-                 ++this.deflateTimer;
-+                } // Paper - Add PufferFishStateChangeEvent
++                if (increase) // Paper - Add PufferFishStateChangeEvent
+                 this.deflateTimer++;
              }
          }
- 
-@@ -155,7 +169,7 @@
-         int i = this.getPuffState();
- 
-         if (target.hurtServer(world, this.damageSources().mobAttack(this), (float) (1 + i))) {
--            target.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this);
-+            target.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+@@ -136,7 +_,7 @@
+     private void touch(ServerLevel level, Mob mob) {
+         int puffState = this.getPuffState();
+         if (mob.hurtServer(level, this.damageSources().mobAttack(this), 1 + puffState)) {
+-            mob.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 0), this);
++            mob.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
              this.playSound(SoundEvents.PUFFER_FISH_STING, 1.0F, 1.0F);
          }
- 
-@@ -171,7 +185,7 @@
-                     entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PUFFER_FISH_STING, 0.0F));
-                 }
- 
--                player.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this);
-+                player.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+     }
+@@ -151,7 +_,7 @@
+                 serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PUFFER_FISH_STING, 0.0F));
              }
-         }
+ 
+-            entity.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 0), this);
++            entity.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+         }
+     }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch
new file mode 100644
index 0000000000..d5d3f133b3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Rabbit.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/animal/Rabbit.java
++++ b/net/minecraft/world/entity/animal/Rabbit.java
+@@ -88,7 +_,7 @@
+         super(entityType, level);
+         this.jumpControl = new Rabbit.RabbitJumpControl(this);
+         this.moveControl = new Rabbit.RabbitMoveControl(this);
+-        this.setSpeedModifier(0.0);
++        //this.setSpeedModifier(0.0); // CraftBukkit
+     }
+ 
+     @Override
+@@ -561,9 +_,11 @@
+                 if (this.canRaid && block instanceof CarrotBlock) {
+                     int ageValue = blockState.getValue(CarrotBlock.AGE);
+                     if (ageValue == 0) {
++                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockPos, blockState.getFluidState().createLegacyBlock())) return; // CraftBukkit // Paper - fix wrong block state
+                         level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 2);
+                         level.destroyBlock(blockPos, true, this.rabbit);
+                     } else {
++                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockPos, blockState.setValue(CarrotBlock.AGE, ageValue - 1))) return; // CraftBukkit // Paper - fix wrong block state
+                         level.setBlock(blockPos, blockState.setValue(CarrotBlock.AGE, Integer.valueOf(ageValue - 1)), 2);
+                         level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(this.rabbit));
+                         level.levelEvent(2001, blockPos, Block.getId(blockState));
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
new file mode 100644
index 0000000000..07a19159a6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/animal/Sheep.java
++++ b/net/minecraft/world/entity/animal/Sheep.java
+@@ -40,7 +_,6 @@
+ import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
+ import net.minecraft.world.entity.item.ItemEntity;
+ import net.minecraft.world.entity.player.Player;
+-import net.minecraft.world.item.DyeColor;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.Items;
+ import net.minecraft.world.level.Level;
+@@ -158,7 +_,19 @@
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.is(Items.SHEARS)) {
+             if (this.level() instanceof ServerLevel serverLevel && this.readyForShearing()) {
+-                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand);
++                // CraftBukkit start
++                // Paper start - custom shear drops
++                java.util.List<ItemStack> drops = this.generateDefaultDrops(serverLevel, itemInHand);
++                org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
++                if (event != null) {
++                    if (event.isCancelled()) {
++                        return InteractionResult.PASS;
++                    }
++                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
++                    // Paper end - custom shear drops
++                }
++                // CraftBukkit end
++                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand, drops); // Paper - custom shear drops
+                 this.gameEvent(GameEvent.SHEAR, player);
+                 itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
+                 return InteractionResult.SUCCESS_SERVER;
+@@ -172,14 +_,28 @@
+ 
+     @Override
+     public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
++        // Paper start - custom shear drops
++        this.shear(level, soundSource, shears, this.generateDefaultDrops(level, shears));
++    }
++
++    @Override
++    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
++        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
++        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SHEEP, shears, (ignored, stack) -> {
++            for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1));
++        });
++        return drops;
++    }
++
++    @Override
++    public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<ItemStack> drops) {
++        // Paper end - custom shear drops
+         level.playSound(null, this, SoundEvents.SHEEP_SHEAR, soundSource, 1.0F, 1.0F);
+-        this.dropFromShearingLootTable(
+-            level,
+-            BuiltInLootTables.SHEAR_SHEEP,
+-            shears,
+-            (serverLevel, itemStack) -> {
+-                for (int i = 0; i < itemStack.getCount(); i++) {
+-                    ItemEntity itemEntity = this.spawnAtLocation(serverLevel, itemStack.copyWithCount(1), 1.0F);
++        drops.forEach(itemstack1 -> { // Paper - custom drops - loop in generated default drops
++            if (true) { // Paper - custom drops - loop in generated default drops
++                this.forceDrops = true; // CraftBukkit
++                ItemEntity itemEntity = this.spawnAtLocation(level, itemstack1, 1.0F); // Paper - custom drops - copy already done above
++                this.forceDrops = false; // CraftBukkit
+                     if (itemEntity != null) {
+                         itemEntity.setDeltaMovement(
+                             itemEntity.getDeltaMovement()
+@@ -287,6 +_,7 @@
+ 
+     @Override
+     public void ate() {
++        if (!new org.bukkit.event.entity.SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity()).callEvent()) return; // CraftBukkit
+         super.ate();
+         this.setSheared(false);
+         if (this.isBaby()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
new file mode 100644
index 0000000000..367ba239ff
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/ShoulderRidingEntity.java
++++ b/net/minecraft/world/entity/animal/ShoulderRidingEntity.java
+@@ -19,7 +_,7 @@
+         compoundTag.putString("id", this.getEncodeId());
+         this.saveWithoutId(compoundTag);
+         if (player.setEntityOnShoulder(compoundTag)) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             return true;
+         } else {
+             return false;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
new file mode 100644
index 0000000000..c168577210
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/entity/animal/SnowGolem.java
++++ b/net/minecraft/world/entity/animal/SnowGolem.java
+@@ -92,7 +_,7 @@
+         super.aiStep();
+         if (this.level() instanceof ServerLevel serverLevel) {
+             if (this.level().getBiome(this.blockPosition()).is(BiomeTags.SNOW_GOLEM_MELTS)) {
+-                this.hurtServer(serverLevel, this.damageSources().onFire(), 1.0F);
++                this.hurtServer(serverLevel, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING
+             }
+ 
+             if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+@@ -107,7 +_,7 @@
+                 int floor2 = Mth.floor(this.getZ() + (i / 2 % 2 * 2 - 1) * 0.25F);
+                 BlockPos blockPos = new BlockPos(floor, floor1, floor2);
+                 if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
+-                    this.level().setBlockAndUpdate(blockPos, blockState);
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockPos, blockState, this)) continue; // CraftBukkit
+                     this.level().gameEvent(GameEvent.BLOCK_PLACE, blockPos, GameEvent.Context.of(this, blockState));
+                 }
+             }
+@@ -135,7 +_,19 @@
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
+             if (this.level() instanceof ServerLevel serverLevel) {
+-                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand);
++                // CraftBukkit start
++                // Paper start - custom shear drops
++                java.util.List<ItemStack> drops = this.generateDefaultDrops(serverLevel, itemInHand);
++                org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
++                if (event != null) {
++                    if (event.isCancelled()) {
++                        return InteractionResult.PASS;
++                    }
++                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
++                    // Paper end - custom shear drops
++                }
++                // CraftBukkit end
++                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand, drops); // Paper - custom shear drops
+                 this.gameEvent(GameEvent.SHEAR, player);
+                 itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
+             }
+@@ -148,11 +_,29 @@
+ 
+     @Override
+     public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
++        // Paper start - custom shear drops
++        this.shear(level, soundSource, shears, this.generateDefaultDrops(level, shears));
++    }
++
++    @Override
++    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
++        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
++        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (ignored, stack) -> {
++            drops.add(stack);
++        });
++        return drops;
++    }
++
++    @Override
++    public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<ItemStack> drops) {
++        // Paper end - custom shear drops
+         level.playSound(null, this, SoundEvents.SNOW_GOLEM_SHEAR, soundSource, 1.0F, 1.0F);
+         this.setPumpkin(false);
+-        this.dropFromShearingLootTable(
+-            level, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (serverLevel, itemStack) -> this.spawnAtLocation(serverLevel, itemStack, this.getEyeHeight())
+-        );
++        drops.forEach(itemstack1 -> { // Paper - custom shear drops
++            this.forceDrops = true; // CraftBukkit
++            this.spawnAtLocation(level, itemstack1, this.getEyeHeight());
++            this.forceDrops = false; // CraftBukkit
++        });
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch
new file mode 100644
index 0000000000..e2fffe7466
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Squid.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/Squid.java
++++ b/net/minecraft/world/entity/animal/Squid.java
+@@ -46,7 +_,7 @@
+ 
+     public Squid(EntityType<? extends Squid> entityType, Level level) {
+         super(entityType, level);
+-        this.random.setSeed(this.getId());
++        //this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random
+         this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
new file mode 100644
index 0000000000..e9232e5c94
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/animal/Turtle.java
++++ b/net/minecraft/world/entity/animal/Turtle.java
+@@ -303,7 +_,9 @@
+     protected void ageBoundaryReached() {
+         super.ageBoundaryReached();
+         if (!this.isBaby() && this.level() instanceof ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
++            this.forceDrops = true; // CraftBukkit
+             this.spawnAtLocation(serverLevel, Items.TURTLE_SCUTE, 1);
++            this.forceDrops = false; // CraftBukkit
+         }
+     }
+ 
+@@ -328,7 +_,7 @@
+ 
+     @Override
+     public void thunderHit(ServerLevel level, LightningBolt lightning) {
+-        this.hurtServer(level, this.damageSources().lightningBolt(), Float.MAX_VALUE);
++        this.hurtServer(level, this.damageSources().lightningBolt().customEventDamager(lightning), Float.MAX_VALUE); // CraftBukkit // Paper - fix DamageSource API
+     }
+ 
+     @Override
+@@ -355,6 +_,10 @@
+             if (loveCause == null && this.partner.getLoveCause() != null) {
+                 loveCause = this.partner.getLoveCause();
+             }
++            // Paper start - Add EntityFertilizeEggEvent event
++            io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner);
++            if (event.isCancelled()) return;
++            // Paper end - Add EntityFertilizeEggEvent event
+ 
+             if (loveCause != null) {
+                 loveCause.awardStat(Stats.ANIMALS_BRED);
+@@ -368,7 +_,7 @@
+             this.partner.resetLove();
+             RandomSource random = this.animal.getRandom();
+             if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+-                this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), random.nextInt(7) + 1));
++                if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), , event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, loveCause)); // Paper - Add EntityFertilizeEggEvent event
+             }
+         }
+     }
+@@ -392,7 +_,7 @@
+                     this.turtle.hasEgg()
+                         || this.turtle.getRandom().nextInt(reducedTickDelay(700)) == 0
+                             && !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0)
+-                );
++                ) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API
+         }
+ 
+         @Override
+@@ -500,16 +_,22 @@
+             BlockPos blockPos = this.turtle.blockPosition();
+             if (!this.turtle.isInWater() && this.isReachedTarget()) {
+                 if (this.turtle.layEggCounter < 1) {
+-                    this.turtle.setLayingEgg(true);
++                    this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API
+                 } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) {
++                    // Paper start - Turtle API
++                    int eggCount = this.turtle.random.nextInt(4) + 1;
++                    com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount);
++                    if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) {
++                    // Paper end
+                     Level level = this.turtle.level();
+                     level.playSound(null, blockPos, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + level.random.nextFloat() * 0.2F);
+                     BlockPos blockPos1 = this.blockPos.above();
+                     BlockState blockState = Blocks.TURTLE_EGG
+                         .defaultBlockState()
+-                        .setValue(TurtleEggBlock.EGGS, Integer.valueOf(this.turtle.random.nextInt(4) + 1));
++                        .setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper
+                     level.setBlock(blockPos1, blockState, 3);
+                     level.gameEvent(GameEvent.BLOCK_PLACE, blockPos1, GameEvent.Context.of(this.turtle, blockState));
++                    } // Paper
+                     this.turtle.setHasEgg(false);
+                     this.turtle.setLayingEgg(false);
+                     this.turtle.setInLoveTime(600);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch
new file mode 100644
index 0000000000..f871cf2552
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/WaterAnimal.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/animal/WaterAnimal.java
++++ b/net/minecraft/world/entity/animal/WaterAnimal.java
+@@ -70,6 +_,10 @@
+     ) {
+         int seaLevel = level.getSeaLevel();
+         int i = seaLevel - 13;
++        // Paper start - Make water animal spawn height configurable
++        seaLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(seaLevel);
++        i = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(i);
++        // Paper end - Make water animal spawn height configurable
+         return pos.getY() >= i
+             && pos.getY() <= seaLevel
+             && level.getFluidState(pos.below()).is(FluidTags.WATER)
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
new file mode 100644
index 0000000000..f162736896
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
@@ -0,0 +1,95 @@
+--- a/net/minecraft/world/entity/animal/Wolf.java
++++ b/net/minecraft/world/entity/animal/Wolf.java
+@@ -344,8 +_,9 @@
+         if (this.isInvulnerableTo(level, damageSource)) {
+             return false;
+         } else {
++            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit
+             this.setOrderedToSit(false);
+-            return super.hurtServer(level, damageSource, amount);
++            return true; // CraftBUkkit
+         }
+     }
+ 
+@@ -355,10 +_,11 @@
+     }
+ 
+     @Override
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
++    public boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) { // CraftBukkit - void -> boolean
+         if (!this.canArmorAbsorb(damageSource)) {
+-            super.actuallyHurt(level, damageSource, amount);
++            super.actuallyHurt(level, damageSource, amount, event); // CraftBukkit
+         } else {
++            if (event.isCancelled()) return false; // CraftBukkit - SPIGOT-7815: if the damage was cancelled, no need to run the wolf armor behaviour
+             ItemStack bodyArmorItem = this.getBodyArmorItem();
+             int damageValue = bodyArmorItem.getDamageValue();
+             int maxDamage = bodyArmorItem.getMaxDamage();
+@@ -378,6 +_,7 @@
+                 );
+             }
+         }
++        return true; // CraftBukkit // Paper - return false ONLY if event was cancelled
+     }
+ 
+     private boolean canArmorAbsorb(DamageSource damageSource) {
+@@ -388,7 +_,7 @@
+     protected void applyTamingSideEffects() {
+         if (this.isTame()) {
+             this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(40.0);
+-            this.setHealth(40.0F);
++            this.setHealth(this.getMaxHealth()); // CraftBukkit - 40.0 -> getMaxHealth()
+         } else {
+             this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0);
+         }
+@@ -408,7 +_,7 @@
+                 this.usePlayerItem(player, hand, itemInHand);
+                 FoodProperties foodProperties = itemInHand.get(DataComponents.FOOD);
+                 float f = foodProperties != null ? foodProperties.nutrition() : 1.0F;
+-                this.heal(2.0F * f);
++                this.heal(2.0F * f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
+                 return InteractionResult.SUCCESS;
+             }
+ 
+@@ -441,7 +_,7 @@
+                         this.setOrderedToSit(!this.isOrderedToSit());
+                         this.jumping = false;
+                         this.navigation.stop();
+-                        this.setTarget(null);
++                        this.setTarget(null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason
+                         return InteractionResult.SUCCESS.withoutItem();
+                     }
+ 
+@@ -453,7 +_,9 @@
+                 ItemStack bodyArmorItem = this.getBodyArmorItem();
+                 this.setBodyArmorItem(ItemStack.EMPTY);
+                 if (this.level() instanceof ServerLevel serverLevel) {
++                    this.forceDrops = true; // CraftBukkit
+                     this.spawnAtLocation(serverLevel, bodyArmorItem);
++                    this.forceDrops = false; // CraftBukkit
+                 }
+ 
+                 return InteractionResult.SUCCESS;
+@@ -461,6 +_,13 @@
+ 
+             DyeColor dyeColor = dyeItem.getDyeColor();
+             if (dyeColor != this.getCollarColor()) {
++                // Paper start - Add EntityDyeEvent and CollarColorable interface
++                final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) dyeColor.getId()), player.getBukkitEntity());
++                if (!event.callEvent()) {
++                    return InteractionResult.FAIL;
++                }
++                dyeColor = DyeColor.byId(event.getColor().getWoolData());
++                // Paper end - Add EntityDyeEvent and CollarColorable interface
+                 this.setCollarColor(dyeColor);
+                 itemInHand.consume(1, player);
+                 return InteractionResult.SUCCESS;
+@@ -475,7 +_,7 @@
+     }
+ 
+     private void tryToTame(Player player) {
+-        if (this.random.nextInt(3) == 0) {
++        if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check.
+             this.tame(player);
+             this.navigation.stop();
+             this.setTarget(null);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Animal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Animal.java.patch
deleted file mode 100644
index f8c533216c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Animal.java.patch
+++ /dev/null
@@ -1,141 +0,0 @@
---- a/net/minecraft/world/entity/animal/Animal.java
-+++ b/net/minecraft/world/entity/animal/Animal.java
-@@ -35,12 +35,20 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.pathfinder.PathType;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityBreedEvent;
-+import org.bukkit.event.entity.EntityDamageEvent;
-+import org.bukkit.event.entity.EntityEnterLoveModeEvent;
-+// CraftBukkit end
-+
- public abstract class Animal extends AgeableMob {
- 
-     protected static final int PARENT_AGE_AFTER_BREEDING = 6000;
-     public int inLove;
-     @Nullable
-     public UUID loveCause;
-+    public ItemStack breedItem; // CraftBukkit - Add breedItem variable
- 
-     protected Animal(EntityType<? extends Animal> type, Level world) {
-         super(type, world);
-@@ -82,9 +90,15 @@
-     }
- 
-     @Override
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
-+    // CraftBukkit start - void -> boolean
-+    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) {
-+        boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event);
-+        if (!damageResult) {
-+            return false;
-+        }
-         this.resetLove();
--        super.actuallyHurt(world, source, amount);
-+        return true;
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -144,8 +158,9 @@
-             int i = this.getAge();
- 
-             if (!this.level().isClientSide && i == 0 && this.canFallInLove()) {
-+                final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying
-                 this.usePlayerItem(player, hand, itemstack);
--                this.setInLove(player);
-+                this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
-                 this.playEatingSound();
-                 return InteractionResult.SUCCESS_SERVER;
-             }
-@@ -187,11 +202,26 @@
-         return this.inLove <= 0;
-     }
- 
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying
-     public void setInLove(@Nullable Player player) {
--        this.inLove = 600;
-+        // Paper start - Fix EntityBreedEvent copying
-+        this.setInLove(player, null);
-+    }
-+    public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) {
-+        if (breedItemCopy != null) this.breedItem = breedItemCopy;
-+        // Paper end - Fix EntityBreedEvent copying
-+        // CraftBukkit start
-+        EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600);
-+        if (entityEnterLoveModeEvent.isCancelled()) {
-+            this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled
-+            return;
-+        }
-+        this.inLove = entityEnterLoveModeEvent.getTicksInLove();
-+        // CraftBukkit end
-         if (player != null) {
-             this.loveCause = player.getUUID();
-         }
-+        // Paper - Fix EntityBreedEvent copying; set breed item in better place
- 
-         this.level().broadcastEntityEvent(this, (byte) 18);
-     }
-@@ -233,25 +263,48 @@
-         if (entityageable != null) {
-             entityageable.setBaby(true);
-             entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
--            this.finalizeSpawnChildFromBreeding(world, other, entityageable);
--            world.addFreshEntityWithPassengers(entityageable);
-+            // CraftBukkit start - call EntityBreedEvent
-+            ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> {
-+                return Optional.ofNullable(other.getLoveCause());
-+            }).orElse(null);
-+            int experience = this.getRandom().nextInt(7) + 1;
-+            EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience);
-+            if (entityBreedEvent.isCancelled()) {
-+                return;
-+            }
-+            experience = entityBreedEvent.getExperience();
-+            this.finalizeSpawnChildFromBreeding(world, other, entityageable, experience);
-+            world.addFreshEntityWithPassengers(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
-+            // CraftBukkit end
-         }
-     }
- 
-     public void finalizeSpawnChildFromBreeding(ServerLevel world, Animal other, @Nullable AgeableMob baby) {
--        Optional.ofNullable(this.getLoveCause()).or(() -> {
--            return Optional.ofNullable(other.getLoveCause());
--        }).ifPresent((entityplayer) -> {
-+        // CraftBukkit start
-+        this.finalizeSpawnChildFromBreeding(world, other, baby, this.getRandom().nextInt(7) + 1);
-+    }
-+
-+    public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) {
-+        // CraftBukkit end
-+        // Paper start
-+        ServerPlayer entityplayer = this.getLoveCause();
-+        if (entityplayer == null) entityplayer = entityanimal.getLoveCause();
-+        if (entityplayer != null) {
-+            // Paper end
-             entityplayer.awardStat(Stats.ANIMALS_BRED);
--            CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, other, baby);
--        });
-+            CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable);
-+        } // Paper
-         this.setAge(6000);
--        other.setAge(6000);
-+        entityanimal.setAge(6000);
-         this.resetLove();
--        other.resetLove();
--        world.broadcastEntityEvent(this, (byte) 18);
--        if (world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
--            world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
-+        entityanimal.resetLove();
-+        worldserver.broadcastEntityEvent(this, (byte) 18);
-+        if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-+            // CraftBukkit start - use event experience
-+            if (experience > 0) {
-+                worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bee.java.patch
deleted file mode 100644
index 748c94de8e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bee.java.patch
+++ /dev/null
@@ -1,205 +0,0 @@
---- a/net/minecraft/world/entity/animal/Bee.java
-+++ b/net/minecraft/world/entity/animal/Bee.java
-@@ -92,6 +92,11 @@
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- 
-@@ -149,7 +154,22 @@
-     public Bee(EntityType<? extends Bee> type, Level world) {
-         super(type, world);
-         this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60);
--        this.moveControl = new FlyingMoveControl(this, 20, true);
-+        // Paper start - Fix MC-167279
-+        class BeeFlyingMoveControl extends FlyingMoveControl {
-+            public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
-+                super(entity, maxPitchChange, noGravity);
-+            }
-+
-+            @Override
-+            public void tick() {
-+                if (this.mob.getY() <= Bee.this.level().getMinY()) {
-+                    this.mob.setNoGravity(false);
-+                }
-+                super.tick();
-+            }
-+        }
-+        this.moveControl = new BeeFlyingMoveControl(this, 20, true);
-+        // Paper end - Fix MC-167279
-         this.lookControl = new Bee.BeeLookControl(this);
-         this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F);
-         this.setPathfindingMalus(PathType.WATER, -1.0F);
-@@ -198,21 +218,28 @@
- 
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
--        super.addAdditionalSaveData(nbt);
--        if (this.hasHive()) {
--            nbt.put("hive_pos", NbtUtils.writeBlockPos(this.getHivePos()));
-+        // CraftBukkit start - selectively save data
-+        this.addAdditionalSaveData(nbt, true);
-+    }
-+
-+    @Override
-+    public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
-+        // CraftBukkit end
-+        super.addAdditionalSaveData(nbttagcompound);
-+        if (includeAll && this.hasHive()) { // CraftBukkit - selectively save hive
-+            nbttagcompound.put("hive_pos", NbtUtils.writeBlockPos(this.getHivePos()));
-         }
- 
--        if (this.hasSavedFlowerPos()) {
--            nbt.put("flower_pos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
-+        if (includeAll && this.hasSavedFlowerPos()) { // CraftBukkit - selectively save flower
-+            nbttagcompound.put("flower_pos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
-         }
- 
--        nbt.putBoolean("HasNectar", this.hasNectar());
--        nbt.putBoolean("HasStung", this.hasStung());
--        nbt.putInt("TicksSincePollination", this.ticksWithoutNectarSinceExitingHive);
--        nbt.putInt("CannotEnterHiveTicks", this.stayOutOfHiveCountdown);
--        nbt.putInt("CropsGrownSincePollination", this.numCropsGrownSincePollination);
--        this.addPersistentAngerSaveData(nbt);
-+        nbttagcompound.putBoolean("HasNectar", this.hasNectar());
-+        nbttagcompound.putBoolean("HasStung", this.hasStung());
-+        nbttagcompound.putInt("TicksSincePollination", this.ticksWithoutNectarSinceExitingHive);
-+        nbttagcompound.putInt("CannotEnterHiveTicks", this.stayOutOfHiveCountdown);
-+        nbttagcompound.putInt("CropsGrownSincePollination", this.numCropsGrownSincePollination);
-+        this.addPersistentAngerSaveData(nbttagcompound);
-     }
- 
-     @Override
-@@ -223,8 +250,8 @@
-         this.ticksWithoutNectarSinceExitingHive = nbt.getInt("TicksSincePollination");
-         this.stayOutOfHiveCountdown = nbt.getInt("CannotEnterHiveTicks");
-         this.numCropsGrownSincePollination = nbt.getInt("CropsGrownSincePollination");
--        this.hivePos = (BlockPos) NbtUtils.readBlockPos(nbt, "hive_pos").orElse((Object) null);
--        this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse((Object) null);
-+        this.hivePos = (BlockPos) NbtUtils.readBlockPos(nbt, "hive_pos").orElse(null); // CraftBukkit - decompile error
-+        this.savedFlowerPos = (BlockPos) NbtUtils.readBlockPos(nbt, "flower_pos").orElse(null); // CraftBukkit - decompile error
-         this.readPersistentAngerSaveData(this.level(), nbt);
-     }
- 
-@@ -248,7 +275,7 @@
-                 }
- 
-                 if (b0 > 0) {
--                    entityliving.addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this);
-+                    entityliving.addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-                 }
-             }
- 
-@@ -506,7 +533,12 @@
- 
-     @Nullable
-     BeehiveBlockEntity getBeehiveBlockEntity() {
--        return this.hivePos == null ? null : (this.isTooFarAway(this.hivePos) ? null : (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse((Object) null));
-+        // Paper start - move over logic to accommodate isTooFarAway with chunk load check
-+        if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) {
-+            return (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
-+        }
-+        return null;
-+        // Paper end
-     }
- 
-     boolean isHiveValid() {
-@@ -533,11 +565,13 @@
-         this.setFlag(4, hasStung);
-     }
- 
-+    public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override
-     public boolean isRolling() {
-         return this.getFlag(2);
-     }
- 
-     public void setRolling(boolean nearTarget) {
-+        nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override
-         this.setFlag(2, nearTarget);
-     }
- 
-@@ -602,7 +636,7 @@
-                     if (mobeffect != null) {
-                         this.usePlayerItem(player, hand, itemstack);
-                         if (!this.level().isClientSide) {
--                            this.addEffect(mobeffect);
-+                            this.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // Paper - Add missing effect cause
-                         }
- 
-                         return InteractionResult.SUCCESS;
-@@ -671,8 +705,14 @@
-         if (this.isInvulnerableTo(world, source)) {
-             return false;
-         } else {
-+            // CraftBukkit start - Only stop pollinating if entity was damaged
-+            boolean result = super.hurtServer(world, source, amount);
-+            if (!result) {
-+                return result;
-+            }
-+            // CraftBukkit end
-             this.beePollinateGoal.stopPollinating();
--            return super.hurtServer(world, source, amount);
-+            return result; // CraftBukkit
-         }
-     }
- 
-@@ -934,7 +974,7 @@
-                     Bee.this.dropFlower();
-                     this.pollinating = false;
-                     Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
--                } else {
-+                } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this
-                     Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D);
- 
-                     if (vec3d.distanceTo(Bee.this.position()) > 1.0D) {
-@@ -1082,7 +1122,7 @@
- 
-         BeeGoToHiveGoal() {
-             super();
--            this.travellingTicks = Bee.this.level().random.nextInt(10);
-+            this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
-             this.blacklistedTargets = Lists.newArrayList();
-             this.setFlags(EnumSet.of(Goal.Flag.MOVE));
-         }
-@@ -1196,7 +1236,7 @@
- 
-         BeeGoToKnownFlowerGoal() {
-             super();
--            this.travellingTicks = Bee.this.level().random.nextInt(10);
-+            this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
-             this.setFlags(EnumSet.of(Goal.Flag.MOVE));
-         }
- 
-@@ -1301,7 +1341,7 @@
-                             }
-                         }
- 
--                        if (iblockdata1 != null) {
-+                        if (iblockdata1 != null && CraftEventFactory.callEntityChangeBlockEvent(Bee.this, blockposition, iblockdata1)) { // CraftBukkit
-                             Bee.this.level().levelEvent(2011, blockposition, 15);
-                             Bee.this.level().setBlockAndUpdate(blockposition, iblockdata1);
-                             Bee.this.incrementNumCropsGrownSincePollination();
-@@ -1378,7 +1418,7 @@
-         @Override
-         protected void alertOther(Mob mob, LivingEntity target) {
-             if (mob instanceof Bee && this.mob.hasLineOfSight(target)) {
--                mob.setTarget(target);
-+                mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit - reason
-             }
- 
-         }
-@@ -1387,7 +1427,7 @@
-     private static class BeeBecomeAngryTargetGoal extends NearestAttackableTargetGoal<Player> {
- 
-         BeeBecomeAngryTargetGoal(Bee bee) {
--            Objects.requireNonNull(bee);
-+            // Objects.requireNonNull(entitybee); // CraftBukkit - decompile error
-             super(bee, Player.class, 10, true, false, bee::isAngryAt);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bucketable.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bucketable.java.patch
deleted file mode 100644
index 804bc3e693..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Bucketable.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/entity/animal/Bucketable.java
-+++ b/net/minecraft/world/entity/animal/Bucketable.java
-@@ -16,6 +16,11 @@
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.component.CustomData;
- import net.minecraft.world.level.Level;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerBucketEntityEvent;
-+// CraftBukkit end
- 
- public interface Bucketable {
- 
-@@ -93,10 +98,21 @@
-         ItemStack itemstack = player.getItemInHand(hand);
- 
-         if (itemstack.getItem() == Items.WATER_BUCKET && entity.isAlive()) {
--            entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F);
-+            // CraftBukkit start
-+            // t0.playSound(((Bucketable) t0).getPickupSound(), 1.0F, 1.0F); // CraftBukkit - moved down
-             ItemStack itemstack1 = ((Bucketable) entity).getBucketItemStack();
- 
-             ((Bucketable) entity).saveToBucketTag(itemstack1);
-+
-+            PlayerBucketEntityEvent playerBucketFishEvent = CraftEventFactory.callPlayerFishBucketEvent(entity, player, itemstack, itemstack1, hand);
-+            itemstack1 = CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket());
-+            if (playerBucketFishEvent.isCancelled()) {
-+                ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket
-+                entity.resendPossiblyDesyncedEntityData((ServerPlayer) player); // Paper
-+                return Optional.of(InteractionResult.FAIL);
-+            }
-+            entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F);
-+            // CraftBukkit end
-             ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, itemstack1, false);
- 
-             player.setItemInHand(hand, itemstack2);
-@@ -106,7 +122,7 @@
-                 CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer) player, itemstack1);
-             }
- 
--            entity.discard();
-+            entity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             return Optional.of(InteractionResult.SUCCESS);
-         } else {
-             return Optional.empty();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cat.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cat.java.patch
deleted file mode 100644
index d829874cb7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cat.java.patch
+++ /dev/null
@@ -1,96 +0,0 @@
---- a/net/minecraft/world/entity/animal/Cat.java
-+++ b/net/minecraft/world/entity/animal/Cat.java
-@@ -174,10 +174,10 @@
-     @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
--        Optional optional = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("variant"))).map((minecraftkey) -> {
-+        Optional<ResourceKey<CatVariant>> optional = Optional.ofNullable(ResourceLocation.tryParse(nbt.getString("variant"))).map((minecraftkey) -> { // CraftBukkit - decompile error
-             return ResourceKey.create(Registries.CAT_VARIANT, minecraftkey);
-         });
--        Registry iregistry = BuiltInRegistries.CAT_VARIANT;
-+        Registry<CatVariant> iregistry = BuiltInRegistries.CAT_VARIANT; // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(iregistry);
-         optional.flatMap(iregistry::get).ifPresent(this::setVariant);
-@@ -365,7 +365,7 @@
-         BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagkey, world.getRandom()).ifPresent(this::setVariant);
-         ServerLevel worldserver = world.getLevel();
- 
--        if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) {
-+        if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, world).isValid()) { // Paper - Fix swamp hut cat generation deadlock
-             this.setVariant((Holder) BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK));
-             this.setPersistenceRequired();
-         }
-@@ -386,6 +386,13 @@
-                     DyeColor enumcolor = itemdye.getDyeColor();
- 
-                     if (enumcolor != this.getCollarColor()) {
-+                        // Paper start - Add EntityDyeEvent and CollarColorable interface
-+                        final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
-+                        if (!event.callEvent()) {
-+                            return InteractionResult.FAIL;
-+                        }
-+                        enumcolor = DyeColor.byId(event.getColor().getWoolData());
-+                        // Paper end - Add EntityDyeEvent and CollarColorable interface
-                         if (!this.level().isClientSide()) {
-                             this.setCollarColor(enumcolor);
-                             itemstack.consume(1, player);
-@@ -399,7 +406,7 @@
-                         this.usePlayerItem(player, hand, itemstack);
-                         FoodProperties foodinfo = (FoodProperties) itemstack.get(DataComponents.FOOD);
- 
--                        this.heal(foodinfo != null ? (float) foodinfo.nutrition() : 1.0F);
-+                        this.heal(foodinfo != null ? (float) foodinfo.nutrition() : 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason
-                         this.playEatingSound();
-                     }
- 
-@@ -462,7 +469,7 @@
-     }
- 
-     private void tryToTame(Player player) {
--        if (this.random.nextInt(3) == 0) {
-+        if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
-             this.tame(player);
-             this.setOrderedToSit(true);
-             this.level().broadcastEntityEvent(this, (byte) 7);
-@@ -480,7 +487,7 @@
-     private static class CatTemptGoal extends TemptGoal {
- 
-         @Nullable
--        private Player selectedPlayer;
-+        private LivingEntity selectedPlayer; // CraftBukkit
-         private final Cat cat;
- 
-         public CatTemptGoal(Cat cat, double speed, Predicate<ItemStack> foodPredicate, boolean canBeScared) {
-@@ -614,7 +621,15 @@
-             this.cat.randomTeleport((double) (blockposition_mutableblockposition.getX() + randomsource.nextInt(11) - 5), (double) (blockposition_mutableblockposition.getY() + randomsource.nextInt(5) - 2), (double) (blockposition_mutableblockposition.getZ() + randomsource.nextInt(11) - 5), false);
-             blockposition_mutableblockposition.set(this.cat.blockPosition());
-             this.cat.dropFromGiftLootTable(getServerLevel((Entity) this.cat), BuiltInLootTables.CAT_MORNING_GIFT, (worldserver, itemstack) -> {
--                worldserver.addFreshEntity(new ItemEntity(worldserver, (double) blockposition_mutableblockposition.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack));
-+                // CraftBukkit start
-+                ItemEntity entityitem = new ItemEntity(worldserver, (double) blockposition_mutableblockposition.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack);
-+                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.cat.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
-+                entityitem.level().getCraftServer().getPluginManager().callEvent(event);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+                worldserver.addFreshEntity(entityitem);
-+                // CraftBukkit end
-             });
-         }
- 
-@@ -645,10 +660,10 @@
-         private final Cat cat;
- 
-         public CatAvoidEntityGoal(Cat cat, Class<T> fleeFromType, float distance, double slowSpeed, double fastSpeed) {
--            Predicate predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR;
-+            // Predicate predicate = IEntitySelector.NO_CREATIVE_OR_SPECTATOR; // CraftBukkit - decompile error
- 
--            Objects.requireNonNull(predicate);
--            super(cat, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test);
-+            // Objects.requireNonNull(predicate); // CraftBukkit - decompile error
-+            super(cat, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_CREATIVE_OR_SPECTATOR::test); // CraftBukkit - decompile error
-             this.cat = cat;
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Chicken.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Chicken.java.patch
deleted file mode 100644
index 78c5696f2a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Chicken.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/entity/animal/Chicken.java
-+++ b/net/minecraft/world/entity/animal/Chicken.java
-@@ -99,10 +99,12 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (this.isAlive() && !this.isBaby() && !this.isChickenJockey() && --this.eggTime <= 0) {
-+                this.forceDrops = true; // CraftBukkit
-                 if (this.dropFromGiftLootTable(worldserver, BuiltInLootTables.CHICKEN_LAY, this::spawnAtLocation)) {
-                     this.playSound(SoundEvents.CHICKEN_EGG, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
-                     this.gameEvent(GameEvent.ENTITY_PLACE);
-                 }
-+                this.forceDrops = false; // CraftBukkit
- 
-                 this.eggTime = this.random.nextInt(6000) + 6000;
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cow.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cow.java.patch
deleted file mode 100644
index 4e23f7179b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Cow.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/world/entity/animal/Cow.java
-+++ b/net/minecraft/world/entity/animal/Cow.java
-@@ -30,6 +30,11 @@
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.player.PlayerBucketFillEvent;
-+// CraftBukkit end
- 
- public class Cow extends Animal {
- 
-@@ -92,8 +97,17 @@
-         ItemStack itemstack = player.getItemInHand(hand);
- 
-         if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
-+            // CraftBukkit start - Got milk?
-+            PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
-+
-+            if (event.isCancelled()) {
-+                player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-+                return InteractionResult.PASS;
-+            }
-+            // CraftBukkit end
-+
-             player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
--            ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance());
-+            ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
- 
-             player.setItemInHand(hand, itemstack1);
-             return InteractionResult.SUCCESS;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Dolphin.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Dolphin.java.patch
deleted file mode 100644
index c16913343e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Dolphin.java.patch
+++ /dev/null
@@ -1,87 +0,0 @@
---- a/net/minecraft/world/entity/animal/Dolphin.java
-+++ b/net/minecraft/world/entity/animal/Dolphin.java
-@@ -61,9 +61,20 @@
- import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.pathfinder.PathComputationType;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Dolphin extends AgeableWaterCreature {
- 
-+    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-+    @Override
-+    public int getDefaultMaxAirSupply() {
-+        return Dolphin.TOTAL_AIR_SUPPLY;
-+    }
-+    // CraftBukkit end
-     private static final EntityDataAccessor<BlockPos> TREASURE_POS = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BLOCK_POS);
-     private static final EntityDataAccessor<Boolean> GOT_FISH = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BOOLEAN);
-     private static final EntityDataAccessor<Integer> MOISTNESS_LEVEL = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.INT);
-@@ -200,7 +211,7 @@
- 
-     @Override
-     public int getMaxAirSupply() {
--        return 4800;
-+        return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-     }
- 
-     @Override
-@@ -234,11 +245,17 @@
-             ItemStack itemstack = itemEntity.getItem();
- 
-             if (this.canHoldItem(itemstack)) {
-+                // CraftBukkit start - call EntityPickupItemEvent
-+                if (CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) {
-+                    return;
-+                }
-+                itemstack = itemEntity.getItem(); // CraftBukkit- update ItemStack from event
-+                // CraftBukkit start
-                 this.onItemPickup(itemEntity);
-                 this.setItemSlot(EquipmentSlot.MAINHAND, itemstack);
-                 this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
-                 this.take(itemEntity, itemstack.getCount());
--                itemEntity.discard();
-+                itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             }
-         }
- 
-@@ -332,7 +349,7 @@
- 
-     @Nullable
-     @Override
--    protected SoundEvent getDeathSound() {
-+    public SoundEvent getDeathSound() { // Paper - decompile error
-         return SoundEvents.DOLPHIN_DEATH;
-     }
- 
-@@ -495,7 +512,7 @@
- 
-         @Override
-         public void start() {
--            this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
-+            this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
-         }
- 
-         @Override
-@@ -514,7 +531,7 @@
-             }
- 
-             if (this.player.isSwimming() && this.player.level().random.nextInt(6) == 0) {
--                this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
-+                this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
-             }
- 
-         }
-@@ -587,7 +604,7 @@
-                 float f2 = 0.02F * Dolphin.this.random.nextFloat();
- 
-                 entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2));
--                Dolphin.this.level().addFreshEntity(entityitem);
-+                Dolphin.this.spawnAtLocation(getServerLevel(Dolphin.this), entityitem); // Paper - Call EntityDropItemEvent
-             }
-         }
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Fox.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Fox.java.patch
deleted file mode 100644
index 7052ecb52a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Fox.java.patch
+++ /dev/null
@@ -1,168 +0,0 @@
---- a/net/minecraft/world/entity/animal/Fox.java
-+++ b/net/minecraft/world/entity/animal/Fox.java
-@@ -90,6 +90,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Fox extends Animal implements VariantHolder<Fox.Variant> {
- 
-@@ -416,7 +419,7 @@
- 
-         this.setSleeping(nbt.getBoolean("Sleeping"));
-         this.setVariant(Fox.Variant.byName(nbt.getString("Type")));
--        this.setSitting(nbt.getBoolean("Sitting"));
-+        this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent
-         this.setIsCrouching(nbt.getBoolean("Crouching"));
-         if (this.level() instanceof ServerLevel) {
-             this.setTargetGoals();
-@@ -429,6 +432,12 @@
-     }
- 
-     public void setSitting(boolean sitting) {
-+        // Paper start - Add EntityToggleSitEvent
-+        this.setSitting(sitting, true);
-+    }
-+    public void setSitting(boolean sitting, boolean fireEvent) {
-+        if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return;
-+        // Paper end - Add EntityToggleSitEvent
-         this.setFlag(1, sitting);
-     }
- 
-@@ -489,21 +498,22 @@
-             entityitem.setPickUpDelay(40);
-             entityitem.setThrower(this);
-             this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F);
--            this.level().addFreshEntity(entityitem);
-+            this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent
-         }
-     }
- 
-     private void dropItemStack(ItemStack stack) {
-         ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack);
- 
--        this.level().addFreshEntity(entityitem);
-+        this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem); // Paper - Call EntityDropItemEvent
-     }
- 
-     @Override
-     protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) {
-         ItemStack itemstack = itemEntity.getItem();
- 
--        if (this.canHoldItem(itemstack)) {
-+        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, itemstack.getCount() - 1, !this.canHoldItem(itemstack)).isCancelled()) { // CraftBukkit - call EntityPickupItemEvent
-+            itemstack = itemEntity.getItem(); // CraftBukkit - update ItemStack from event
-             int i = itemstack.getCount();
- 
-             if (i > 1) {
-@@ -515,7 +525,7 @@
-             this.setItemSlot(EquipmentSlot.MAINHAND, itemstack.split(1));
-             this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
-             this.take(itemEntity, itemstack.getCount());
--            itemEntity.discard();
-+            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             this.ticksSinceEaten = 0;
-         }
- 
-@@ -685,16 +695,38 @@
-         return this.getTrustedUUIDs().contains(uuid);
-     }
- 
-+    // Paper start - handle the bitten item separately like vanilla
-     @Override
--    protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
-+    protected boolean shouldSkipLoot(EquipmentSlot slot) {
-+        return slot == EquipmentSlot.MAINHAND;
-+    }
-+    // Paper end
-+
-+    @Override
-+    // Paper start - Cancellable death event
-+    protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
-         ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND);
- 
--        if (!itemstack.isEmpty()) {
-+        boolean releaseMouth = false;
-+        if (!itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010
-             this.spawnAtLocation(world, itemstack);
-+            releaseMouth = true;
-+        }
-+
-+        org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(world, damageSource);
-+
-+        // Below is code to drop
-+
-+        if (deathEvent == null || deathEvent.isCancelled()) {
-+            return deathEvent;
-+        }
-+
-+        if (releaseMouth) {
-+            // Paper end - Cancellable death event
-             this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
-         }
- 
--        super.dropAllDeathLoot(world, damageSource);
-+        return deathEvent; // Paper - Cancellable death event
-     }
- 
-     public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) {
-@@ -853,6 +885,16 @@
-                 if (entityplayer1 != null && entityplayer != entityplayer1) {
-                     entityfox.addTrustedUUID(entityplayer1.getUUID());
-                 }
-+                // CraftBukkit start - call EntityBreedEvent
-+                entityfox.setAge(-24000);
-+                entityfox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
-+                int experience = this.animal.getRandom().nextInt(7) + 1;
-+                org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityfox, this.animal, this.partner, entityplayer, this.animal.breedItem, experience);
-+                if (entityBreedEvent.isCancelled()) {
-+                    return;
-+                }
-+                experience = entityBreedEvent.getExperience();
-+                // CraftBukkit end
- 
-                 if (entityplayer2 != null) {
-                     entityplayer2.awardStat(Stats.ANIMALS_BRED);
-@@ -863,12 +905,14 @@
-                 this.partner.setAge(6000);
-                 this.animal.resetLove();
-                 this.partner.resetLove();
--                entityfox.setAge(-24000);
--                entityfox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
--                worldserver.addFreshEntityWithPassengers(entityfox);
-+                worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
-                 this.level.broadcastEntityEvent(this.animal, (byte) 18);
-                 if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
--                    this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), this.animal.getRandom().nextInt(7) + 1));
-+                    // CraftBukkit start - use event experience
-+                    if (experience > 0) {
-+                        this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper
-+                    }
-+                    // CraftBukkit end
-                 }
- 
-             }
-@@ -1264,6 +1308,11 @@
-             int i = (Integer) state.getValue(SweetBerryBushBlock.AGE);
- 
-             state.setValue(SweetBerryBushBlock.AGE, 1);
-+            // CraftBukkit start - call EntityChangeBlockEvent
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(Fox.this, this.blockPos, state.setValue(SweetBerryBushBlock.AGE, 1))) {
-+                return;
-+            }
-+            // CraftBukkit end
-             int j = 1 + Fox.this.level().random.nextInt(2) + (i == 3 ? 1 : 0);
-             ItemStack itemstack = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND);
- 
-@@ -1494,7 +1543,7 @@
-         }
- 
-         public static Fox.Variant byName(String name) {
--            return (Fox.Variant) Fox.Variant.CODEC.byName(name, (Enum) Fox.Variant.RED);
-+            return (Fox.Variant) Fox.Variant.CODEC.byName(name, Fox.Variant.RED); // CraftBukkit - decompile error
-         }
- 
-         public static Fox.Variant byId(int id) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/IronGolem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/IronGolem.java.patch
deleted file mode 100644
index 4562593cc0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/IronGolem.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/entity/animal/IronGolem.java
-+++ b/net/minecraft/world/entity/animal/IronGolem.java
-@@ -98,7 +98,7 @@
-     @Override
-     protected void doPush(Entity entity) {
-         if (entity instanceof Enemy && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) {
--            this.setTarget((LivingEntity) entity);
-+            this.setTarget((LivingEntity) entity, org.bukkit.event.entity.EntityTargetLivingEntityEvent.TargetReason.COLLISION, true); // CraftBukkit - set reason
-         }
- 
-         super.doPush(entity);
-@@ -319,7 +319,7 @@
-         BlockPos blockposition1 = blockposition.below();
-         BlockState iblockdata = world.getBlockState(blockposition1);
- 
--        if (!iblockdata.entityCanStandOn(world, blockposition1, this)) {
-+        if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !this.level().paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - Add option to allow iron golems to spawn in air
-             return false;
-         } else {
-             for (int i = 1; i < 3; ++i) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/MushroomCow.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/MushroomCow.java.patch
deleted file mode 100644
index a7cff6ad18..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/MushroomCow.java.patch
+++ /dev/null
@@ -1,85 +0,0 @@
---- a/net/minecraft/world/entity/animal/MushroomCow.java
-+++ b/net/minecraft/world/entity/animal/MushroomCow.java
-@@ -42,6 +42,13 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
-+// CraftBukkit start
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.entity.EntityDropItemEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+// CraftBukkit end
- 
- public class MushroomCow extends Cow implements Shearable, VariantHolder<MushroomCow.Variant> {
- 
-@@ -120,7 +127,19 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.shear(worldserver, SoundSource.PLAYERS, itemstack);
-+                // CraftBukkit start
-+                // Paper start - custom shear drops
-+                java.util.List<ItemStack> drops = this.generateDefaultDrops(worldserver, itemstack);
-+                org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
-+                if (event != null) {
-+                    if (event.isCancelled()) {
-+                        return InteractionResult.PASS;
-+                    }
-+                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
-+                // Paper end - custom shear drops
-+                }
-+                // CraftBukkit end
-+                this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops
-                 this.gameEvent(GameEvent.SHEAR, player);
-                 itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
-             }
-@@ -158,16 +177,32 @@
- 
-     @Override
-     public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) {
-+    // Paper start - custom shear drops
-+        this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears));
-+    }
-+
-+    @Override
-+    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
-+        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
-+        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (ignored, stack) -> {
-+            for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1));
-+        });
-+        return drops;
-+    }
-+
-+    @Override
-+    public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List<ItemStack> drops) {
-+    // Paper end - custom shear drops
-         world.playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
-         this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), (entitycow) -> {
-             world.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D);
--            this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_MOOSHROOM, shears, (worldserver1, itemstack1) -> {
--                for (int i = 0; i < itemstack1.getCount(); ++i) {
--                    worldserver1.addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), itemstack1.copyWithCount(1)));
--                }
--
-+            // Paper start - custom shear drops; moved drop generation to separate method
-+            drops.forEach(drop -> {
-+                ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop);
-+                this.spawnAtLocation(world, entityitem);
-+            // Paper end - custom shear drops; moved drop generation to separate method
-             });
--        });
-+        }, EntityTransformEvent.TransformReason.SHEARED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHEARED); // CraftBukkit
-     }
- 
-     @Override
-@@ -263,7 +298,7 @@
-         }
- 
-         static MushroomCow.Variant byName(String name) {
--            return (MushroomCow.Variant) MushroomCow.Variant.CODEC.byName(name, (Enum) MushroomCow.Variant.RED);
-+            return (MushroomCow.Variant) MushroomCow.Variant.CODEC.byName(name, MushroomCow.Variant.RED); // CraftBukkit - decompile error
-         }
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Ocelot.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Ocelot.java.patch
deleted file mode 100644
index e217641cf1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Ocelot.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/world/entity/animal/Ocelot.java
-+++ b/net/minecraft/world/entity/animal/Ocelot.java
-@@ -132,7 +132,7 @@
- 
-     @Override
-     public boolean removeWhenFarAway(double distanceSquared) {
--        return !this.isTrusting() && this.tickCount > 2400;
-+        return !this.isTrusting() && this.tickCount > 2400 && !this.hasCustomName() && !this.isLeashed(); // Paper - honor name and leash
-     }
- 
-     public static AttributeSupplier.Builder createAttributes() {
-@@ -167,7 +167,7 @@
-         if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemstack) && player.distanceToSqr((Entity) this) < 9.0D) {
-             this.usePlayerItem(player, hand, itemstack);
-             if (!this.level().isClientSide) {
--                if (this.random.nextInt(3) == 0) {
-+                if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check
-                     this.setTrusting(true);
-                     this.spawnTrustingParticles(true);
-                     this.level().broadcastEntityEvent(this, (byte) 41);
-@@ -298,10 +298,10 @@
-         private final Ocelot ocelot;
- 
-         public OcelotAvoidEntityGoal(Ocelot ocelot, Class<T> fleeFromType, float distance, double slowSpeed, double fastSpeed) {
--            Predicate predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR;
-+            // Predicate predicate = IEntitySelector.NO_CREATIVE_OR_SPECTATOR; // CraftBukkit - decompile error
- 
--            Objects.requireNonNull(predicate);
--            super(ocelot, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test);
-+            // Objects.requireNonNull(predicate); // CraftBukkit - decompile error
-+            super(ocelot, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_CREATIVE_OR_SPECTATOR::test); // CraftBukkit - decompile error
-             this.ocelot = ocelot;
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Panda.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Panda.java.patch
deleted file mode 100644
index 911c02e2c0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Panda.java.patch
+++ /dev/null
@@ -1,122 +0,0 @@
---- a/net/minecraft/world/entity/animal/Panda.java
-+++ b/net/minecraft/world/entity/animal/Panda.java
-@@ -68,6 +68,11 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public class Panda extends Animal {
- 
-@@ -129,6 +134,7 @@
-     }
- 
-     public void sit(boolean sitting) {
-+        if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent
-         this.setFlag(8, sitting);
-     }
- 
-@@ -525,7 +531,9 @@
-             Panda entitypanda = (Panda) iterator.next();
- 
-             if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) {
-+                if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
-                 entitypanda.jumpFromGround();
-+                } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
-             }
-         }
- 
-@@ -533,7 +541,9 @@
- 
-         if (world1 instanceof ServerLevel worldserver) {
-             if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-+                this.forceDrops = true; // Paper - Add missing forceDrop toggles
-                 this.dropFromGiftLootTable(worldserver, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation);
-+                this.forceDrops = false; // Paper - Add missing forceDrop toggles
-             }
-         }
- 
-@@ -541,14 +551,14 @@
- 
-     @Override
-     protected void pickUpItem(ServerLevel world, ItemEntity itemEntity) {
--        if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(itemEntity)) {
-+        if (!CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, !(this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(itemEntity))).isCancelled()) { // CraftBukkit
-             this.onItemPickup(itemEntity);
-             ItemStack itemstack = itemEntity.getItem();
- 
-             this.setItemSlot(EquipmentSlot.MAINHAND, itemstack);
-             this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
-             this.take(itemEntity, itemstack.getCount());
--            itemEntity.discard();
-+            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-         }
- 
-     }
-@@ -643,8 +653,9 @@
-                 this.usePlayerItem(player, hand, itemstack);
-                 this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true);
-             } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) {
-+                final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying
-                 this.usePlayerItem(player, hand, itemstack);
--                this.setInLove(player);
-+                this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
-             } else {
-                 Level world = this.level();
- 
-@@ -657,7 +668,9 @@
-                         ItemStack itemstack1 = this.getItemBySlot(EquipmentSlot.MAINHAND);
- 
-                         if (!itemstack1.isEmpty() && !player.hasInfiniteMaterials()) {
-+                            this.forceDrops = true; // Paper - Add missing forceDrop toggles
-                             this.spawnAtLocation(worldserver, itemstack1);
-+                            this.forceDrops = false; // Paper - Add missing forceDrop toggles
-                         }
- 
-                         this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemstack.getItem(), 1));
-@@ -772,7 +785,7 @@
-         }
- 
-         public static Panda.Gene byName(String name) {
--            return (Panda.Gene) Panda.Gene.CODEC.byName(name, (Enum) Panda.Gene.NORMAL);
-+            return (Panda.Gene) Panda.Gene.CODEC.byName(name, Panda.Gene.NORMAL); // CraftBukkit - decompile error
-         }
- 
-         public static Panda.Gene getRandom(RandomSource random) {
-@@ -876,10 +889,10 @@
-         private final Panda panda;
- 
-         public PandaAvoidGoal(Panda panda, Class<T> fleeFromType, float distance, double slowSpeed, double fastSpeed) {
--            Predicate predicate = EntitySelector.NO_SPECTATORS;
-+            // Predicate predicate = IEntitySelector.NO_SPECTATORS;
- 
--            Objects.requireNonNull(predicate);
--            super(panda, fleeFromType, distance, slowSpeed, fastSpeed, predicate::test);
-+            // Objects.requireNonNull(predicate);
-+            super(panda, fleeFromType, distance, slowSpeed, fastSpeed, EntitySelector.NO_SPECTATORS::test);
-             this.panda = panda;
-         }
- 
-@@ -935,7 +948,9 @@
-             ItemStack itemstack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND);
- 
-             if (!itemstack.isEmpty()) {
-+                Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles
-                 Panda.this.spawnAtLocation(getServerLevel(Panda.this.level()), itemstack);
-+                Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles
-                 Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
-                 int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10;
- 
-@@ -1116,7 +1131,7 @@
-         @Override
-         protected void alertOther(Mob mob, LivingEntity target) {
-             if (mob instanceof Panda && mob.isAggressive()) {
--                mob.setTarget(target);
-+                mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit
-             }
- 
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pig.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pig.java.patch
deleted file mode 100644
index a5cdaa0866..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Pig.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/entity/animal/Pig.java
-+++ b/net/minecraft/world/entity/animal/Pig.java
-@@ -49,6 +49,10 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Pig extends Animal implements ItemSteerable, Saddleable {
- 
-@@ -247,7 +251,14 @@
-                 }
- 
-                 entitypigzombie1.setPersistenceRequired();
--            });
-+            // CraftBukkit start
-+            }, null, null);
-+            if (CraftEventFactory.callPigZapEvent(this, lightning, entitypigzombie).isCancelled()) {
-+                return;
-+            }
-+            world.addFreshEntity(entitypigzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING);
-+            this.discard(EntityRemoveEvent.Cause.TRANSFORMATION); // CraftBukkit - add Bukkit remove cause
-+            // CraftBukkit end
- 
-             if (entitypigzombie == null) {
-                 super.thunderHit(world, lightning);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Rabbit.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Rabbit.java.patch
deleted file mode 100644
index 1f8f84be30..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Rabbit.java.patch
+++ /dev/null
@@ -1,40 +0,0 @@
---- a/net/minecraft/world/entity/animal/Rabbit.java
-+++ b/net/minecraft/world/entity/animal/Rabbit.java
-@@ -65,6 +65,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
- 
-@@ -90,7 +93,6 @@
-         super(type, world);
-         this.jumpControl = new Rabbit.RabbitJumpControl(this);
-         this.moveControl = new Rabbit.RabbitMoveControl(this);
--        this.setSpeedModifier(0.0D);
-     }
- 
-     @Override
-@@ -577,9 +579,19 @@
-                     int i = (Integer) iblockdata.getValue(CarrotBlock.AGE);
- 
-                     if (i == 0) {
-+                        // CraftBukkit start
-+                        if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                            return;
-+                        }
-+                        // CraftBukkit end
-                         world.setBlock(blockposition, Blocks.AIR.defaultBlockState(), 2);
-                         world.destroyBlock(blockposition, true, this.rabbit);
-                     } else {
-+                        // CraftBukkit start
-+                        if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.setValue(CarrotBlock.AGE, i - 1))) {
-+                            return;
-+                        }
-+                        // CraftBukkit end
-                         world.setBlock(blockposition, (BlockState) iblockdata.setValue(CarrotBlock.AGE, i - 1), 2);
-                         world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of((Entity) this.rabbit));
-                         world.levelEvent(2001, blockposition, Block.getId(iblockdata));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Sheep.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Sheep.java.patch
deleted file mode 100644
index ce52e0ef57..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Sheep.java.patch
+++ /dev/null
@@ -1,90 +0,0 @@
---- a/net/minecraft/world/entity/animal/Sheep.java
-+++ b/net/minecraft/world/entity/animal/Sheep.java
-@@ -41,7 +41,6 @@
- import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
--import net.minecraft.world.item.DyeColor;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
-@@ -49,6 +48,12 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
-+import net.minecraft.world.item.DyeColor;
-+// CraftBukkit start
-+import net.minecraft.world.item.Item;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.SheepRegrowWoolEvent;
-+// CraftBukkit end
- 
- public class Sheep extends Animal implements Shearable {
- 
-@@ -160,7 +165,19 @@
-                 ServerLevel worldserver = (ServerLevel) world;
- 
-                 if (this.readyForShearing()) {
--                    this.shear(worldserver, SoundSource.PLAYERS, itemstack);
-+                    // CraftBukkit start
-+                    // Paper start - custom shear drops
-+                    java.util.List<ItemStack> drops = this.generateDefaultDrops(worldserver, itemstack);
-+                    org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
-+                    if (event != null) {
-+                        if (event.isCancelled()) {
-+                            return InteractionResult.PASS;
-+                        }
-+                        drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
-+                    // Paper end - custom shear drops
-+                    }
-+                    // CraftBukkit end
-+                    this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops
-                     this.gameEvent(GameEvent.SHEAR, player);
-                     itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
-                     return InteractionResult.SUCCESS_SERVER;
-@@ -175,10 +192,29 @@
- 
-     @Override
-     public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) {
-+        // Paper start - custom shear drops
-+        this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears));
-+    }
-+
-+    @Override
-+    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
-+        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
-+        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SHEEP, shears, (ignored, stack) -> {
-+            for (int i = 0; i < stack.getCount(); ++i) drops.add(stack.copyWithCount(1));
-+        });
-+        return drops;
-+    }
-+
-+    @Override
-+    public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List<ItemStack> drops) {
-+        final ServerLevel worldserver1 = world; // Named for lambda consumption
-+        // Paper end - custom shear drops
-         world.playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
--        this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SHEEP, shears, (worldserver1, itemstack1) -> {
--            for (int i = 0; i < itemstack1.getCount(); ++i) {
--                ItemEntity entityitem = this.spawnAtLocation(worldserver1, itemstack1.copyWithCount(1), 1.0F);
-+        drops.forEach(itemstack1 -> { // Paper - custom drops - loop in generated default drops
-+            if (true) { // Paper - custom drops - loop in generated default drops
-+                this.forceDrops = true; // CraftBukkit
-+                ItemEntity entityitem = this.spawnAtLocation(worldserver1, itemstack1, 1.0F); // Paper - custom drops - copy already done above
-+                this.forceDrops = false; // CraftBukkit
- 
-                 if (entityitem != null) {
-                     entityitem.setDeltaMovement(entityitem.getDeltaMovement().add((double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F), (double) (this.random.nextFloat() * 0.05F), (double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F)));
-@@ -276,6 +312,12 @@
- 
-     @Override
-     public void ate() {
-+        // CraftBukkit start
-+        SheepRegrowWoolEvent event = new SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity());
-+        this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+        if (event.isCancelled()) return;
-+        // CraftBukkit end
-         super.ate();
-         this.setSheared(false);
-         if (this.isBaby()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
deleted file mode 100644
index 40307984a0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/ShoulderRidingEntity.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/entity/animal/ShoulderRidingEntity.java
-+++ b/net/minecraft/world/entity/animal/ShoulderRidingEntity.java
-@@ -5,6 +5,9 @@
- import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.TamableAnimal;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public abstract class ShoulderRidingEntity extends TamableAnimal {
- 
-@@ -21,7 +24,7 @@
-         nbttagcompound.putString("id", this.getEncodeId());
-         this.saveWithoutId(nbttagcompound);
-         if (player.setEntityOnShoulder(nbttagcompound)) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             return true;
-         } else {
-             return false;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/SnowGolem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/SnowGolem.java.patch
deleted file mode 100644
index c07f86335e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/SnowGolem.java.patch
+++ /dev/null
@@ -1,86 +0,0 @@
---- a/net/minecraft/world/entity/animal/SnowGolem.java
-+++ b/net/minecraft/world/entity/animal/SnowGolem.java
-@@ -42,6 +42,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob {
- 
-@@ -100,7 +103,7 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (this.level().getBiome(this.blockPosition()).is(BiomeTags.SNOW_GOLEM_MELTS)) {
--                this.hurtServer(worldserver, this.damageSources().onFire(), 1.0F);
-+                this.hurtServer(worldserver, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING
-             }
- 
-             if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-@@ -116,7 +119,11 @@
-                 BlockPos blockposition = new BlockPos(j, k, l);
- 
-                 if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
--                    this.level().setBlockAndUpdate(blockposition, iblockdata);
-+                    // CraftBukkit start
-+                    if (!CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, this)) {
-+                        continue;
-+                    }
-+                    // CraftBukkit end
-                     this.level().gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this, iblockdata));
-                 }
-             }
-@@ -153,7 +160,19 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.shear(worldserver, SoundSource.PLAYERS, itemstack);
-+                // CraftBukkit start
-+                // Paper start - custom shear drops
-+                java.util.List<ItemStack> drops = this.generateDefaultDrops(worldserver, itemstack);
-+                org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
-+                if (event != null) {
-+                    if (event.isCancelled()) {
-+                        return InteractionResult.PASS;
-+                    }
-+                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
-+                // Paper end - custom shear drops
-+                }
-+                // CraftBukkit end
-+                this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops
-                 this.gameEvent(GameEvent.SHEAR, player);
-                 itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
-             }
-@@ -166,10 +185,29 @@
- 
-     @Override
-     public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) {
-+    // Paper start - custom shear drops
-+        this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears));
-+    }
-+
-+    @Override
-+    public java.util.List<ItemStack> generateDefaultDrops(final ServerLevel serverLevel, final ItemStack shears) {
-+        final java.util.List<ItemStack> drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
-+        this.dropFromShearingLootTable(serverLevel, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (ignored, stack) -> {
-+            drops.add(stack);
-+        });
-+        return drops;
-+    }
-+
-+    @Override
-+    public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List<ItemStack> drops) {
-+        final ServerLevel worldserver1 = world; // Named for lambda consumption
-+    // Paper end - custom shear drops
-         world.playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
-         this.setPumpkin(false);
--        this.dropFromShearingLootTable(world, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (worldserver1, itemstack1) -> {
-+        drops.forEach(itemstack1 -> { // Paper - custom shear drops
-+            this.forceDrops = true; // CraftBukkit
-             this.spawnAtLocation(worldserver1, itemstack1, this.getEyeHeight());
-+            this.forceDrops = false; // CraftBukkit
-         });
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Squid.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Squid.java.patch
deleted file mode 100644
index d67bfc9654..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Squid.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/animal/Squid.java
-+++ b/net/minecraft/world/entity/animal/Squid.java
-@@ -46,7 +46,7 @@
- 
-     public Squid(EntityType<? extends Squid> type, Level world) {
-         super(type, world);
--        this.random.setSeed((long)this.getId());
-+        //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random
-         this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Turtle.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Turtle.java.patch
deleted file mode 100644
index 49bba2e107..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Turtle.java.patch
+++ /dev/null
@@ -1,74 +0,0 @@
---- a/net/minecraft/world/entity/animal/Turtle.java
-+++ b/net/minecraft/world/entity/animal/Turtle.java
-@@ -310,7 +310,9 @@
-                 ServerLevel worldserver = (ServerLevel) world;
- 
-                 if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-+                    this.forceDrops = true; // CraftBukkit
-                     this.spawnAtLocation(worldserver, Items.TURTLE_SCUTE, 1);
-+                    this.forceDrops = false; // CraftBukkit
-                 }
-             }
-         }
-@@ -339,7 +341,7 @@
- 
-     @Override
-     public void thunderHit(ServerLevel world, LightningBolt lightning) {
--        this.hurtServer(world, this.damageSources().lightningBolt(), Float.MAX_VALUE);
-+        this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), Float.MAX_VALUE); // CraftBukkit // Paper - fix DamageSource API
-     }
- 
-     @Override
-@@ -446,6 +448,10 @@
-             if (entityplayer == null && this.partner.getLoveCause() != null) {
-                 entityplayer = this.partner.getLoveCause();
-             }
-+            // Paper start - Add EntityFertilizeEggEvent event
-+            io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner);
-+            if (event.isCancelled()) return;
-+            // Paper end - Add EntityFertilizeEggEvent event
- 
-             if (entityplayer != null) {
-                 entityplayer.awardStat(Stats.ANIMALS_BRED);
-@@ -460,7 +466,7 @@
-             RandomSource randomsource = this.animal.getRandom();
- 
-             if (getServerLevel((Level) this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
--                this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1));
-+                if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event
-             }
- 
-         }
-@@ -492,16 +498,21 @@
- 
-             if (!this.turtle.isInWater() && this.isReachedTarget()) {
-                 if (this.turtle.layEggCounter < 1) {
--                    this.turtle.setLayingEgg(true);
-+                    this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API
-                 } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) {
-                     Level world = this.turtle.level();
- 
-+                    // Paper start - Turtle API
-+                    int eggCount = this.turtle.random.nextInt(4) + 1;
-+                    com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount);
-+                    if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) {
-                     world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F);
-                     BlockPos blockposition1 = this.blockPos.above();
--                    BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1);
-+                    BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper
- 
-                     world.setBlock(blockposition1, iblockdata, 3);
-                     world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata));
-+                    } // CraftBukkit
-                     this.turtle.setHasEgg(false);
-                     this.turtle.setLayingEgg(false);
-                     this.turtle.setInLoveTime(600);
-@@ -567,7 +578,7 @@
- 
-         @Override
-         public boolean canUse() {
--            return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D)));
-+            return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API
-         }
- 
-         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/WaterAnimal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/WaterAnimal.java.patch
deleted file mode 100644
index 5089fd98c0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/WaterAnimal.java.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- a/net/minecraft/world/entity/animal/WaterAnimal.java
-+++ b/net/minecraft/world/entity/animal/WaterAnimal.java
-@@ -70,6 +70,10 @@
-     ) {
-         int i = world.getSeaLevel();
-         int j = i - 13;
-+        // Paper start - Make water animal spawn height configurable
-+        i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i);
-+        j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j);
-+        // Paper end - Make water animal spawn height configurable
-         return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER);
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Wolf.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Wolf.java.patch
deleted file mode 100644
index 50375e02de..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/Wolf.java.patch
+++ /dev/null
@@ -1,126 +0,0 @@
---- a/net/minecraft/world/entity/animal/Wolf.java
-+++ b/net/minecraft/world/entity/animal/Wolf.java
-@@ -85,6 +85,12 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityDamageEvent;
-+import org.bukkit.event.entity.EntityRegainHealthEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Holder<WolfVariant>> {
- 
-@@ -345,8 +351,14 @@
-         if (this.isInvulnerableTo(world, source)) {
-             return false;
-         } else {
-+            // CraftBukkit start
-+            boolean result = super.hurtServer(world, source, amount);
-+            if (!result) {
-+                return result;
-+            }
-+            // CraftBukkit end
-             this.setOrderedToSit(false);
--            return super.hurtServer(world, source, amount);
-+            return result; // CraftBukkit
-         }
-     }
- 
-@@ -356,21 +368,27 @@
-     }
- 
-     @Override
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
--        if (!this.canArmorAbsorb(source)) {
--            super.actuallyHurt(world, source, amount);
-+    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { // CraftBukkit - void -> boolean
-+        if (!this.canArmorAbsorb(damagesource)) {
-+            return super.actuallyHurt(worldserver, damagesource, f, event); // CraftBukkit
-         } else {
-+            // CraftBukkit start - SPIGOT-7815: if the damage was cancelled, no need to run the wolf armor behaviour
-+            if (event.isCancelled()) {
-+                return false;
-+            }
-+            // CraftBukkit end
-             ItemStack itemstack = this.getBodyArmorItem();
-             int i = itemstack.getDamageValue();
-             int j = itemstack.getMaxDamage();
- 
--            itemstack.hurtAndBreak(Mth.ceil(amount), this, EquipmentSlot.BODY);
-+            itemstack.hurtAndBreak(Mth.ceil(f), this, EquipmentSlot.BODY);
-             if (Crackiness.WOLF_ARMOR.byDamage(i, j) != Crackiness.WOLF_ARMOR.byDamage(this.getBodyArmorItem())) {
-                 this.playSound(SoundEvents.WOLF_ARMOR_CRACK);
--                world.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()), this.getX(), this.getY() + 1.0D, this.getZ(), 20, 0.2D, 0.1D, 0.2D, 0.1D);
-+                worldserver.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()), this.getX(), this.getY() + 1.0D, this.getZ(), 20, 0.2D, 0.1D, 0.2D, 0.1D);
-             }
- 
-         }
-+        return true; // CraftBukkit // Paper - return false ONLY if event was cancelled
-     }
- 
-     private boolean canArmorAbsorb(DamageSource source) {
-@@ -381,7 +399,7 @@
-     protected void applyTamingSideEffects() {
-         if (this.isTame()) {
-             this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(40.0D);
--            this.setHealth(40.0F);
-+            this.setHealth(this.getMaxHealth()); // CraftBukkit - 40.0 -> getMaxHealth()
-         } else {
-             this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0D);
-         }
-@@ -404,7 +422,7 @@
-                 FoodProperties foodinfo = (FoodProperties) itemstack.get(DataComponents.FOOD);
-                 float f = foodinfo != null ? (float) foodinfo.nutrition() : 1.0F;
- 
--                this.heal(2.0F * f);
-+                this.heal(2.0F * f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
-                 return InteractionResult.SUCCESS;
-             } else {
-                 if (item instanceof DyeItem) {
-@@ -414,6 +432,14 @@
-                         DyeColor enumcolor = itemdye.getDyeColor();
- 
-                         if (enumcolor != this.getCollarColor()) {
-+                            // Paper start - Add EntityDyeEvent and CollarColorable interface
-+                            final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
-+                            if (!event.callEvent()) {
-+                                return InteractionResult.FAIL;
-+                            }
-+                            enumcolor = DyeColor.byId(event.getColor().getWoolData());
-+                            // Paper end - Add EntityDyeEvent and CollarColorable interface
-+
-                             this.setCollarColor(enumcolor);
-                             itemstack.consume(1, player);
-                             return InteractionResult.SUCCESS;
-@@ -440,7 +466,9 @@
-                         if (world instanceof ServerLevel) {
-                             ServerLevel worldserver = (ServerLevel) world;
- 
-+                            this.forceDrops = true; // CraftBukkit
-                             this.spawnAtLocation(worldserver, itemstack1);
-+                            this.forceDrops = false; // CraftBukkit
-                         }
- 
-                         return InteractionResult.SUCCESS;
-@@ -459,7 +487,7 @@
-                             this.setOrderedToSit(!this.isOrderedToSit());
-                             this.jumping = false;
-                             this.navigation.stop();
--                            this.setTarget((LivingEntity) null);
-+                            this.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason
-                             return InteractionResult.SUCCESS.withoutItem();
-                         } else {
-                             return enuminteractionresult;
-@@ -477,7 +505,8 @@
-     }
- 
-     private void tryToTame(Player player) {
--        if (this.random.nextInt(3) == 0) {
-+        // CraftBukkit - added event call and isCancelled check.
-+        if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) {
-             this.tame(player);
-             this.navigation.stop();
-             this.setTarget((LivingEntity) null);

From 68961d9f5e257a352a374db6a71572092940c209 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 17:35:50 +0100
Subject: [PATCH 061/285] net.minecraft.server.gui

---
 .../server/gui/MinecraftServerGui.java.patch  | 93 ++++++++-----------
 .../server/gui/StatsComponent.java.patch      | 12 +--
 2 files changed, 44 insertions(+), 61 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/gui/MinecraftServerGui.java.patch (56%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/gui/StatsComponent.java.patch (78%)

diff --git a/paper-server/patches/unapplied/net/minecraft/server/gui/MinecraftServerGui.java.patch b/paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/server/gui/MinecraftServerGui.java.patch
rename to paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch
index 84332717d5..d78cde0d2c 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/gui/MinecraftServerGui.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch
@@ -1,44 +1,58 @@
 --- a/net/minecraft/server/gui/MinecraftServerGui.java
 +++ b/net/minecraft/server/gui/MinecraftServerGui.java
-@@ -59,6 +59,15 @@
-         jframe.pack();
-         jframe.setLocationRelativeTo((Component) null);
-         jframe.setVisible(true);
-+        jframe.setName("Minecraft server"); // Paper - Improve ServerGUI
-+
+@@ -53,6 +_,13 @@
+         jFrame.pack();
+         jFrame.setLocationRelativeTo(null);
+         jFrame.setVisible(true);
 +        // Paper start - Improve ServerGUI
++        jFrame.setName("Minecraft server");
 +        try {
-+            jframe.setIconImage(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
++            jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
 +        } catch (java.io.IOException ignore) {
 +        }
 +        // Paper end - Improve ServerGUI
-+
-         jframe.addWindowListener(new WindowAdapter() {
-             public void windowClosing(WindowEvent windowevent) {
-                 if (!servergui.isClosing.getAndSet(true)) {
-@@ -81,6 +90,7 @@
+         jFrame.addWindowListener(new WindowAdapter() {
+             @Override
+             public void windowClosing(WindowEvent event) {
+@@ -74,6 +_,7 @@
          this.setLayout(new BorderLayout());
  
          try {
 +            this.add(this.buildOnboardingPanel(), "North"); // Paper - Add onboarding message for initial server start
              this.add(this.buildChatPanel(), "Center");
              this.add(this.buildInfoPanel(), "West");
-         } catch (Exception exception) {
-@@ -95,8 +105,8 @@
+         } catch (Exception var3) {
+@@ -87,7 +_,7 @@
  
      private JComponent buildInfoPanel() {
-         JPanel jpanel = new JPanel(new BorderLayout());
--        StatsComponent guistatscomponent = new StatsComponent(this.server);
--        Collection collection = this.finalizers;
-+        com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Make GUI graph fancier
-+        Collection<Runnable> collection = this.finalizers; // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(guistatscomponent);
-         collection.add(guistatscomponent::close);
-@@ -106,6 +116,39 @@
-         return jpanel;
+         JPanel jPanel = new JPanel(new BorderLayout());
+-        StatsComponent statsComponent = new StatsComponent(this.server);
++        com.destroystokyo.paper.gui.GuiStatsComponent statsComponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Make GUI graph fancier
+         this.finalizers.add(statsComponent::close);
+         jPanel.add(statsComponent, "North");
+         jPanel.add(this.buildPlayerPanel(), "Center");
+@@ -150,6 +_,7 @@
+         this.finalizers.forEach(Runnable::run);
      }
  
++    private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
+     public void print(JTextArea textArea, JScrollPane scrollPane, String line) {
+         if (!SwingUtilities.isEventDispatchThread()) {
+             SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line));
+@@ -162,7 +_,7 @@
+             }
+ 
+             try {
+-                document.insertString(document.getLength(), line, null);
++                document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit
+             } catch (BadLocationException var8) {
+             }
+ 
+@@ -171,4 +_,37 @@
+             }
+         }
+     }
++
 +    // Paper start - Add onboarding message for initial server start
 +    private JComponent buildOnboardingPanel() {
 +        String onboardingLink = "https://docs.papermc.io/paper/next-steps";
@@ -71,33 +85,4 @@
 +        return jPanel;
 +    }
 +    // Paper end - Add onboarding message for initial server start
-+
-     private JComponent buildPlayerPanel() {
-         JList<?> jlist = new PlayerListComponent(this.server);
-         JScrollPane jscrollpane = new JScrollPane(jlist, 22, 30);
-@@ -132,7 +175,7 @@
- 
-             jtextfield.setText("");
-         });
--        jtextarea.addFocusListener(new FocusAdapter(this) {
-+        jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error
-             public void focusGained(FocusEvent focusevent) {}
-         });
-         jpanel.add(jscrollpane, "Center");
-@@ -166,6 +209,7 @@
-         this.finalizers.forEach(Runnable::run);
-     }
- 
-+    private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
-     public void print(JTextArea textArea, JScrollPane scrollPane, String message) {
-         if (!SwingUtilities.isEventDispatchThread()) {
-             SwingUtilities.invokeLater(() -> {
-@@ -181,7 +225,7 @@
-             }
- 
-             try {
--                document.insertString(document.getLength(), message, (AttributeSet) null);
-+                document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit
-             } catch (BadLocationException badlocationexception) {
-                 ;
-             }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/gui/StatsComponent.java.patch b/paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch
similarity index 78%
rename from paper-server/patches/unapplied/net/minecraft/server/gui/StatsComponent.java.patch
rename to paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch
index 3fa5e0162c..ab6d2d8cb5 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/gui/StatsComponent.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/gui/StatsComponent.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/gui/StatsComponent.java
 +++ b/net/minecraft/server/gui/StatsComponent.java
-@@ -34,10 +34,19 @@
+@@ -34,8 +_,17 @@
  
      private void tick() {
          long l = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
@@ -8,19 +8,17 @@
 +        double[] tps = org.bukkit.Bukkit.getTPS();
 +        String[] tpsAvg = new String[tps.length];
 +
-+        for ( int g = 0; g < tps.length; g++) {
-+            tpsAvg[g] = format( tps[g] );
++        for (int g = 0; g < tps.length; g++) {
++            tpsAvg[g] = format(tps[g]);
 +        }
          this.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)";
-         this.msgs[1] = "Avg tick: "
-             + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND)
-             + " ms";
+         this.msgs[1] = "Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms";
 +        this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg);
 +        // Paper end - Improve ServerGUI
          this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory());
          this.repaint();
      }
-@@ -66,4 +75,10 @@
+@@ -64,4 +_,10 @@
      public void close() {
          this.timer.stop();
      }

From 3efd1caa64119fb63046aecb957c2c0e58bf42b2 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 17:39:31 +0100
Subject: [PATCH 062/285] net.minecraft.server.packs(.repository)

---
 .../server/packs/PathPackResources.java.patch  |  8 ++++----
 .../VanillaPackResourcesBuilder.java.patch     |  2 +-
 .../repository/ServerPacksSource.java.patch    | 18 +++++++++---------
 3 files changed, 14 insertions(+), 14 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/packs/PathPackResources.java.patch (77%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch (97%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/packs/repository/ServerPacksSource.java.patch (59%)

diff --git a/paper-server/patches/unapplied/net/minecraft/server/packs/PathPackResources.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/server/packs/PathPackResources.java.patch
rename to paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch
index 4c21d9802f..7f7053b2b2 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/packs/PathPackResources.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/packs/PathPackResources.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/packs/PathPackResources.java
 +++ b/net/minecraft/server/packs/PathPackResources.java
-@@ -103,6 +103,12 @@
+@@ -103,6 +_,12 @@
          try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path)) {
-             for (Path path2 : directoryStream) {
-                 String string = path2.getFileName().toString();
+             for (Path path1 : directoryStream) {
+                 String string = path1.getFileName().toString();
 +                // Paper start - Improve logging and errors
-+                if (!Files.isDirectory(path2)) {
++                if (!Files.isDirectory(path1)) {
 +                    LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string));
 +                    continue;
 +                }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch
rename to paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch
index 5f4418300b..acd2b45a58 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/packs/VanillaPackResourcesBuilder.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
 +++ b/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
-@@ -138,6 +138,15 @@
+@@ -137,6 +_,15 @@
  
      public VanillaPackResourcesBuilder applyDevelopmentConfig() {
          developmentConfig.accept(this);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/packs/repository/ServerPacksSource.java.patch b/paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/server/packs/repository/ServerPacksSource.java.patch
rename to paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch
index a5d66bbff8..dc3a6578a8 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/packs/repository/ServerPacksSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/packs/repository/ServerPacksSource.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/packs/repository/ServerPacksSource.java
 +++ b/net/minecraft/server/packs/repository/ServerPacksSource.java
-@@ -48,7 +48,7 @@
+@@ -48,7 +_,7 @@
      public static VanillaPackResources createVanillaPackSource() {
          return new VanillaPackResourcesBuilder()
              .setMetadata(BUILT_IN_METADATA)
@@ -9,23 +9,23 @@
              .applyDevelopmentConfig()
              .pushJarResources()
              .build(VANILLA_PACK_INFO);
-@@ -68,7 +68,18 @@
+@@ -68,7 +_,18 @@
      @Nullable
      @Override
-     protected Pack createBuiltinPack(String fileName, Pack.ResourcesSupplier packFactory, Component displayName) {
--        return Pack.readMetaAndCreate(createBuiltInPackLocation(fileName, displayName), packFactory, PackType.SERVER_DATA, FEATURE_SELECTION_CONFIG);
+     protected Pack createBuiltinPack(String id, Pack.ResourcesSupplier resources, Component title) {
+-        return Pack.readMetaAndCreate(createBuiltInPackLocation(id, title), resources, PackType.SERVER_DATA, FEATURE_SELECTION_CONFIG);
 +        // Paper start - custom built-in pack
 +        final PackLocationInfo info;
 +        final PackSelectionConfig packConfig;
-+        if ("paper".equals(fileName)) {
-+            info = new PackLocationInfo(fileName, displayName, PackSource.BUILT_IN, Optional.empty());
++        if ("paper".equals(id)) {
++            info = new PackLocationInfo(id, title, PackSource.BUILT_IN, Optional.empty());
 +            packConfig = new PackSelectionConfig(true, Pack.Position.TOP, true);
 +        } else {
-+            info = createBuiltInPackLocation(fileName, displayName);
++            info = createBuiltInPackLocation(id, title);
 +            packConfig = FEATURE_SELECTION_CONFIG;
 +        }
-+        return Pack.readMetaAndCreate(info, packFactory, PackType.SERVER_DATA, packConfig);
++        return Pack.readMetaAndCreate(info, resources, PackType.SERVER_DATA, packConfig);
 +        // Paper end - custom built-in pack
      }
  
-     public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) {
+     public static PackRepository createPackRepository(Path folder, DirectoryValidator validator) {

From 14f6b329a5be0a1ece18daec308cc76cae364210 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 17:43:55 +0100
Subject: [PATCH 063/285] net.minecraft.world.level.border

---
 .../world/level/border/WorldBorder.java.patch | 46 ++++++++++---------
 1 file changed, 24 insertions(+), 22 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/border/WorldBorder.java.patch (77%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/border/WorldBorder.java.patch b/paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/level/border/WorldBorder.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch
index c2615ad43a..a7a6057099 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/border/WorldBorder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch
@@ -1,23 +1,25 @@
 --- a/net/minecraft/world/level/border/WorldBorder.java
 +++ b/net/minecraft/world/level/border/WorldBorder.java
-@@ -30,6 +30,7 @@
+@@ -28,6 +_,7 @@
      int absoluteMaxSize = 29999984;
-     private WorldBorder.BorderExtent extent = new WorldBorder.StaticBorderExtent(5.9999968E7D);
-     public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0D, 0.0D, 0.2D, 5.0D, 5, 15, 5.9999968E7D, 0L, 0.0D);
+     private WorldBorder.BorderExtent extent = new WorldBorder.StaticBorderExtent(5.999997E7F);
+     public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0, 0.0, 0.2, 5.0, 5, 15, 5.999997E7F, 0L, 0.0);
 +    public net.minecraft.server.level.ServerLevel world; // CraftBukkit
  
-     public WorldBorder() {}
- 
-@@ -45,6 +46,18 @@
-         return this.isWithinBounds((double) chunkPos.getMinBlockX(), (double) chunkPos.getMinBlockZ()) && this.isWithinBounds((double) chunkPos.getMaxBlockX(), (double) chunkPos.getMaxBlockZ());
+     public boolean isWithinBounds(BlockPos pos) {
+         return this.isWithinBounds(pos.getX(), pos.getZ());
+@@ -41,6 +_,20 @@
+         return this.isWithinBounds(chunkPos.getMinBlockX(), chunkPos.getMinBlockZ()) && this.isWithinBounds(chunkPos.getMaxBlockX(), chunkPos.getMaxBlockZ());
      }
  
 +    // Paper start - Bound treasure maps to world border
 +    private final BlockPos.MutableBlockPos mutPos = new BlockPos.MutableBlockPos();
++
 +    public boolean isBlockInBounds(int chunkX, int chunkZ) {
 +        this.mutPos.set(chunkX, 64, chunkZ);
 +        return this.isWithinBounds(this.mutPos);
 +    }
++
 +    public boolean isChunkInBounds(int chunkX, int chunkZ) {
 +        this.mutPos.set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15);
 +        return this.isWithinBounds(this.mutPos);
@@ -25,9 +27,9 @@
 +    // Paper end - Bound treasure maps to world border
 +
      public boolean isWithinBounds(AABB box) {
-         return this.isWithinBounds(box.minX, box.minZ, box.maxX - 9.999999747378752E-6D, box.maxZ - 9.999999747378752E-6D);
+         return this.isWithinBounds(box.minX, box.minZ, box.maxX - 1.0E-5F, box.maxZ - 1.0E-5F);
      }
-@@ -135,6 +148,14 @@
+@@ -129,6 +_,14 @@
      }
  
      public void setCenter(double x, double z) {
@@ -42,7 +44,7 @@
          this.centerX = x;
          this.centerZ = z;
          this.extent.onCenterChange();
-@@ -161,6 +182,17 @@
+@@ -151,6 +_,17 @@
      }
  
      public void setSize(double size) {
@@ -58,30 +60,30 @@
 +        }
 +        // Paper end - Add worldborder events
          this.extent = new WorldBorder.StaticBorderExtent(size);
-         Iterator iterator = this.getListeners().iterator();
  
-@@ -173,6 +205,20 @@
+         for (BorderChangeListener borderChangeListener : this.getListeners()) {
+@@ -159,6 +_,20 @@
      }
  
-     public void lerpSizeBetween(double fromSize, double toSize, long time) {
+     public void lerpSizeBetween(double oldSize, double newSize, long time) {
 +        // Paper start - Add worldborder events
 +        if (this.world != null) {
 +            io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type;
-+            if (fromSize == toSize) { // new size = old size
++            if (oldSize == newSize) { // new size = old size
 +                type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal.
 +            } else {
 +                type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE;
 +            }
-+            io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time);
++            io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, oldSize, newSize, time);
 +            if (!event.callEvent()) return;
-+            toSize = event.getNewSize();
++            newSize = event.getNewSize();
 +            time = event.getDuration();
 +        }
 +        // Paper end - Add worldborder events
-         this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time));
-         Iterator iterator = this.getListeners().iterator();
- 
-@@ -189,6 +235,7 @@
+         this.extent = (WorldBorder.BorderExtent)(oldSize == newSize
+             ? new WorldBorder.StaticBorderExtent(newSize)
+             : new WorldBorder.MovingBorderExtent(oldSize, newSize, time));
+@@ -173,6 +_,7 @@
      }
  
      public void addListener(BorderChangeListener listener) {
@@ -89,11 +91,11 @@
          this.listeners.add(listener);
      }
  
-@@ -483,6 +530,7 @@
+@@ -369,6 +_,7 @@
  
          @Override
          public WorldBorder.BorderExtent update() {
 +            if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - Add worldborder events
-             return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this);
+             return (WorldBorder.BorderExtent)(this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this);
          }
  

From 6002fbc152545b32fd86aa6ec924c656ea6a01cb Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 15:41:30 +0000
Subject: [PATCH 064/285] 
 /net/minecraft/world/level/levelgen/structure/templatesystem

---
 .../StructurePlaceSettings.java.patch         |  20 +-
 .../StructureTemplate.java.patch              | 168 ++++++++++++++++
 .../StructureTemplate.java.patch              | 182 ------------------
 3 files changed, 178 insertions(+), 192 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
index 60b3e8e667..dc109e1327 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
@@ -1,25 +1,25 @@
 --- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
 +++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
-@@ -22,7 +22,7 @@
-     private LiquidSettings liquidSettings;
+@@ -21,7 +_,7 @@
+     private LiquidSettings liquidSettings = LiquidSettings.APPLY_WATERLOGGING;
      @Nullable
      private RandomSource random;
 -    private int palette;
 +    public int palette = -1; // CraftBukkit - Set initial value so we know if the palette has been set forcefully
-     private final List<StructureProcessor> processors;
+     private final List<StructureProcessor> processors = Lists.newArrayList();
      private boolean knownShape;
      private boolean finalizeEntities;
-@@ -149,6 +149,13 @@
- 
-         if (i == 0) {
+@@ -142,6 +_,13 @@
+         int size = palettes.size();
+         if (size == 0) {
              throw new IllegalStateException("No palettes");
 +        // CraftBukkit start
 +        } else if (this.palette >= 0) {
-+            if (this.palette >= i) {
-+                throw new IllegalArgumentException("Palette index out of bounds. Got " + this.palette + " where there are only " + i + " palettes available.");
++            if (this.palette >= size) {
++                throw new IllegalArgumentException("Palette index out of bounds. Got " + this.palette + " where there are only " + size + " palettes available.");
 +            }
-+            return infoLists.get(this.palette);
++            return palettes.get(this.palette);
 +        // CraftBukkit end
          } else {
-             return (StructureTemplate.Palette) infoLists.get(this.getRandom(pos).nextInt(i));
+             return palettes.get(this.getRandom(pos).nextInt(size));
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
new file mode 100644
index 0000000000..7839e3322c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
@@ -0,0 +1,168 @@
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+@@ -25,6 +_,7 @@
+ import net.minecraft.nbt.IntTag;
+ import net.minecraft.nbt.ListTag;
+ import net.minecraft.nbt.NbtUtils;
++import net.minecraft.nbt.Tag;
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.world.Clearable;
+@@ -54,6 +_,10 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
+ import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
++// CraftBukkit end
+ 
+ public class StructureTemplate {
+     public static final String PALETTE_TAG = "palette";
+@@ -71,6 +_,10 @@
+     public final List<StructureTemplate.StructureEntityInfo> entityInfoList = Lists.newArrayList();
+     private Vec3i size = Vec3i.ZERO;
+     private String author = "?";
++    // CraftBukkit start - data containers
++    private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
++    public CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(StructureTemplate.DATA_TYPE_REGISTRY);
++    // CraftBukkit end
+ 
+     public Vec3i getSize() {
+         return this.size;
+@@ -245,6 +_,19 @@
+         if (this.palettes.isEmpty()) {
+             return false;
+         } else {
++            // CraftBukkit start
++            // We only want the TransformerGeneratorAccess at certain locations because in here are many "block update" calls that shouldn't be transformed
++            ServerLevelAccessor wrappedAccess = serverLevel;
++            org.bukkit.craftbukkit.util.CraftStructureTransformer structureTransformer = null;
++            if (wrappedAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++                serverLevel = transformerAccess.getHandle();
++                structureTransformer = transformerAccess.getStructureTransformer();
++                // The structureTransformer is not needed if we can not transform blocks therefore we can save a little bit of performance doing this
++                if (structureTransformer != null && !structureTransformer.canTransformBlocks()) {
++                    structureTransformer = null;
++                }
++            }
++            // CraftBukkit end
+             List<StructureTemplate.StructureBlockInfo> list = settings.getRandomPalette(this.palettes, offset).blocks();
+             if ((!list.isEmpty() || !settings.isIgnoreEntities() && !this.entityInfoList.isEmpty())
+                 && this.size.getX() >= 1
+@@ -268,10 +_,29 @@
+                         BlockState blockState = structureBlockInfo.state.mirror(settings.getMirror()).rotate(settings.getRotation());
+                         if (structureBlockInfo.nbt != null) {
+                             BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos);
+-                            Clearable.tryClear(blockEntity);
++                            // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
++                            if (!(serverLevel instanceof net.minecraft.world.level.WorldGenLevel)) {
++                                Clearable.tryClear(blockEntity);
++                            }
++                            // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
+                             serverLevel.setBlock(blockPos, Blocks.BARRIER.defaultBlockState(), 20);
+                         }
+ 
++                        // CraftBukkit start
++                        if (structureTransformer != null) {
++                            org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(serverLevel, blockPos, blockState, null);
++                            if (structureBlockInfo.nbt != null && craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> entityState) {
++                                entityState.loadData(structureBlockInfo.nbt);
++                                if (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftLootable<?> craftLootable) {
++                                    craftLootable.setSeed(random.nextLong());
++                                }
++                            }
++                            craftBlockState = structureTransformer.transformCraftState(craftBlockState);
++                            blockState = craftBlockState.getHandle();
++                            structureBlockInfo = new StructureTemplate.StructureBlockInfo(blockPos, blockState, (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState ? craftBlockEntityState.getSnapshotNBT() : null));
++                        }
++                        // CraftBukkit end
++
+                         if (serverLevel.setBlock(blockPos, blockState, flags)) {
+                             i = Math.min(i, blockPos.getX());
+                             i1 = Math.min(i1, blockPos.getY());
+@@ -283,7 +_,7 @@
+                             if (structureBlockInfo.nbt != null) {
+                                 BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos);
+                                 if (blockEntity != null) {
+-                                    if (blockEntity instanceof RandomizableContainer) {
++                                    if (structureTransformer == null && blockEntity instanceof RandomizableContainer) { // CraftBukkit - only process if don't have a transformer access (Was already set above) - SPIGOT-7520: Use structureTransformer as check, so that it is the same as above
+                                         structureBlockInfo.nbt.putLong("LootTableSeed", random.nextLong());
+                                     }
+ 
+@@ -366,7 +_,11 @@
+                         if (pair1.getSecond() != null) {
+                             BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos4);
+                             if (blockEntity != null) {
+-                                blockEntity.setChanged();
++                                // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
++                                if (!(serverLevel instanceof net.minecraft.world.level.WorldGenLevel)) {
++                                    blockEntity.setChanged();
++                                }
++                                // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
+                             }
+                         }
+                     }
+@@ -374,7 +_,7 @@
+ 
+                 if (!settings.isIgnoreEntities()) {
+                     this.placeEntities(
+-                        serverLevel,
++                        wrappedAccess,  // CraftBukkit
+                         offset,
+                         settings.getMirror(),
+                         settings.getRotation(),
+@@ -491,11 +_,13 @@
+     }
+ 
+     private static Optional<Entity> createEntityIgnoreException(ServerLevelAccessor level, CompoundTag tag) {
+-        try {
+-            return EntityType.create(tag, level.getLevel(), EntitySpawnReason.STRUCTURE);
+-        } catch (Exception var3) {
+-            return Optional.empty();
+-        }
++        // CraftBukkit start
++        // try {
++        return EntityType.create(tag, level.getLevel(), EntitySpawnReason.STRUCTURE, true); // Paper - Don't fire sync event during generation
++        // } catch (Exception var3) {
++        //     return Optional.empty();
++        // }
++        // CraftBukkit end
+     }
+ 
+     public Vec3i getSize(Rotation rotation) {
+@@ -688,6 +_,11 @@
+ 
+         tag.put("entities", listTag3);
+         tag.put("size", this.newIntegerList(this.size.getX(), this.size.getY(), this.size.getZ()));
++        // CraftBukkit start - PDC
++        if (!this.persistentDataContainer.isEmpty()) {
++            tag.put("BukkitValues", this.persistentDataContainer.toTagCompound());
++        }
++        // CraftBukkit end
+         return NbtUtils.addCurrentDataVersion(tag);
+     }
+ 
+@@ -720,6 +_,13 @@
+                 this.entityInfoList.add(new StructureTemplate.StructureEntityInfo(vec3, blockPos, compound1));
+             }
+         }
++
++        // CraftBukkit start - PDC
++        Tag base = tag.get("BukkitValues");
++        if (base instanceof CompoundTag) {
++            this.persistentDataContainer.putAll((CompoundTag) base);
++        }
++        // CraftBukkit end
+     }
+ 
+     private void loadPalette(HolderGetter<Block> blockGetter, ListTag paletteTag, ListTag blocksTag) {
+@@ -828,7 +_,7 @@
+ 
+     public static final class Palette {
+         private final List<StructureTemplate.StructureBlockInfo> blocks;
+-        private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newHashMap();
++        private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads
+         @Nullable
+         private List<StructureTemplate.JigsawBlockInfo> cachedJigsaws;
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
deleted file mode 100644
index 6bbbc67254..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
+++ /dev/null
@@ -1,182 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
-+++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
-@@ -25,6 +25,7 @@
- import net.minecraft.nbt.IntTag;
- import net.minecraft.nbt.ListTag;
- import net.minecraft.nbt.NbtUtils;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.resources.ResourceLocation;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.Clearable;
-@@ -55,6 +56,9 @@
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
- import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
-+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
-+import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
-+// CraftBukkit end
- 
- public class StructureTemplate {
- 
-@@ -74,6 +78,11 @@
-     private Vec3i size;
-     private String author;
- 
-+    // CraftBukkit start - data containers
-+    private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
-+    public CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(StructureTemplate.DATA_TYPE_REGISTRY);
-+    // CraftBukkit end
-+
-     public StructureTemplate() {
-         this.size = Vec3i.ZERO;
-         this.author = "?";
-@@ -147,7 +156,7 @@
-     }
- 
-     private static List<StructureTemplate.StructureBlockInfo> buildInfoList(List<StructureTemplate.StructureBlockInfo> fullBlocks, List<StructureTemplate.StructureBlockInfo> blocksWithNbt, List<StructureTemplate.StructureBlockInfo> otherBlocks) {
--        Comparator<StructureTemplate.StructureBlockInfo> comparator = Comparator.comparingInt((definedstructure_blockinfo) -> {
-+        Comparator<StructureTemplate.StructureBlockInfo> comparator = Comparator.<StructureTemplate.StructureBlockInfo>comparingInt((definedstructure_blockinfo) -> { // CraftBukkit - decompile error
-             return definedstructure_blockinfo.pos.getY();
-         }).thenComparingInt((definedstructure_blockinfo) -> {
-             return definedstructure_blockinfo.pos.getX();
-@@ -253,6 +262,19 @@
-         if (this.palettes.isEmpty()) {
-             return false;
-         } else {
-+            // CraftBukkit start
-+            // We only want the TransformerGeneratorAccess at certain locations because in here are many "block update" calls that shouldn't be transformed
-+            ServerLevelAccessor wrappedAccess = world;
-+            org.bukkit.craftbukkit.util.CraftStructureTransformer structureTransformer = null;
-+            if (wrappedAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
-+                world = transformerAccess.getHandle();
-+                structureTransformer = transformerAccess.getStructureTransformer();
-+                // The structureTransformer is not needed if we can not transform blocks therefore we can save a little bit of performance doing this
-+                if (structureTransformer != null && !structureTransformer.canTransformBlocks()) {
-+                    structureTransformer = null;
-+                }
-+            }
-+            // CraftBukkit end
-             List<StructureTemplate.StructureBlockInfo> list = placementData.getRandomPalette(this.palettes, pos).blocks();
- 
-             if ((!list.isEmpty() || !placementData.isIgnoreEntities() && !this.entityInfoList.isEmpty()) && this.size.getX() >= 1 && this.size.getY() >= 1 && this.size.getZ() >= 1) {
-@@ -281,9 +303,27 @@
- 
-                         if (definedstructure_blockinfo.nbt != null) {
-                             tileentity = world.getBlockEntity(blockposition2);
--                            Clearable.tryClear(tileentity);
-+                            // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
-+                            if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) {
-+                                Clearable.tryClear(tileentity);
-+                            }
-+                            // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
-                             world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20);
-                         }
-+                        // CraftBukkit start
-+                        if (structureTransformer != null) {
-+                            org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition2, iblockdata, null);
-+                            if (definedstructure_blockinfo.nbt != null && craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> entityState) {
-+                                entityState.loadData(definedstructure_blockinfo.nbt);
-+                                if (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftLootable<?> craftLootable) {
-+                                    craftLootable.setSeed(random.nextLong());
-+                                }
-+                            }
-+                            craftBlockState = structureTransformer.transformCraftState(craftBlockState);
-+                            iblockdata = craftBlockState.getHandle();
-+                            definedstructure_blockinfo = new StructureTemplate.StructureBlockInfo(blockposition2, iblockdata, (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState ? craftBlockEntityState.getSnapshotNBT() : null));
-+                        }
-+                        // CraftBukkit end
- 
-                         if (world.setBlock(blockposition2, iblockdata, flags)) {
-                             j = Math.min(j, blockposition2.getX());
-@@ -296,7 +336,7 @@
-                             if (definedstructure_blockinfo.nbt != null) {
-                                 tileentity = world.getBlockEntity(blockposition2);
-                                 if (tileentity != null) {
--                                    if (tileentity instanceof RandomizableContainer) {
-+                                    if (structureTransformer == null && tileentity instanceof RandomizableContainer) { // CraftBukkit - only process if don't have a transformer access (Was already set above) - SPIGOT-7520: Use structureTransformer as check, so that it is the same as above
-                                         definedstructure_blockinfo.nbt.putLong("LootTableSeed", random.nextLong());
-                                     }
- 
-@@ -394,14 +434,18 @@
-                         if (pair1.getSecond() != null) {
-                             tileentity = world.getBlockEntity(blockposition6);
-                             if (tileentity != null) {
--                                tileentity.setChanged();
-+                                // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
-+                                if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) {
-+                                    tileentity.setChanged();
-+                                }
-+                                // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
-                             }
-                         }
-                     }
-                 }
- 
-                 if (!placementData.isIgnoreEntities()) {
--                    this.placeEntities(world, pos, placementData.getMirror(), placementData.getRotation(), placementData.getRotationPivot(), structureboundingbox, placementData.shouldFinalizeEntities());
-+                    this.placeEntities(wrappedAccess, pos, placementData.getMirror(), placementData.getRotation(), placementData.getRotationPivot(), structureboundingbox, placementData.shouldFinalizeEntities()); // CraftBukkit
-                 }
- 
-                 return true;
-@@ -503,11 +547,13 @@
-     }
- 
-     private static Optional<Entity> createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) {
--        try {
--            return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE);
--        } catch (Exception exception) {
--            return Optional.empty();
--        }
-+        // CraftBukkit start
-+        // try {
-+            return EntityType.create(nbt, world.getLevel(), EntitySpawnReason.STRUCTURE, true); // Paper - Don't fire sync event during generation
-+        // } catch (Exception exception) {
-+            // return Optional.empty();
-+        // }
-+        // CraftBukkit end
-     }
- 
-     public Vec3i getSize(Rotation rotation) {
-@@ -721,6 +767,11 @@
- 
-         nbt.put("entities", nbttaglist3);
-         nbt.put("size", this.newIntegerList(this.size.getX(), this.size.getY(), this.size.getZ()));
-+        // CraftBukkit start - PDC
-+        if (!this.persistentDataContainer.isEmpty()) {
-+            nbt.put("BukkitValues", this.persistentDataContainer.toTagCompound());
-+        }
-+        // CraftBukkit end
-         return NbtUtils.addCurrentDataVersion(nbt);
-     }
- 
-@@ -760,6 +811,12 @@
-             }
-         }
- 
-+        // CraftBukkit start - PDC
-+        Tag base = nbt.get("BukkitValues");
-+        if (base instanceof CompoundTag) {
-+            this.persistentDataContainer.putAll((CompoundTag) base);
-+        }
-+        // CraftBukkit end
-     }
- 
-     private void loadPalette(HolderGetter<Block> blockLookup, ListTag palette, ListTag blocks) {
-@@ -840,7 +897,7 @@
-     public static final class Palette {
- 
-         private final List<StructureTemplate.StructureBlockInfo> blocks;
--        private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newHashMap();
-+        private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads
-         @Nullable
-         private List<StructureTemplate.JigsawBlockInfo> cachedJigsaws;
- 
-@@ -924,7 +981,7 @@
-         public BlockState stateFor(int id) {
-             BlockState iblockdata = (BlockState) this.ids.byId(id);
- 
--            return iblockdata == null ? StructureTemplate.SimplePalette.DEFAULT_BLOCK_STATE : iblockdata;
-+            return iblockdata == null ? SimplePalette.DEFAULT_BLOCK_STATE : iblockdata; // CraftBukkit - decompile error
-         }
- 
-         public Iterator<BlockState> iterator() {

From 3672a7d70f839dadc316057862ea61dc6afe01a0 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 16:44:13 +0000
Subject: [PATCH 065/285] net/minecraft/world/entity/npc

---
 .../entity/npc/AbstractVillager.java.patch    |  63 +++---
 .../world/entity/npc/CatSpawner.java.patch    |   6 +-
 .../entity/npc/InventoryCarrier.java.patch    |  27 ++-
 .../world/entity/npc/Villager.java.patch      | 188 ++++++++++++++++
 .../entity/npc/VillagerTrades.java.patch      |   8 +-
 .../entity/npc/WanderingTrader.java.patch     |  80 ++++---
 .../npc/WanderingTraderSpawner.java.patch     |  96 ++++++++
 .../world/entity/npc/Villager.java.patch      | 211 ------------------
 .../npc/WanderingTraderSpawner.java.patch     | 100 ---------
 9 files changed, 375 insertions(+), 404 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/npc/AbstractVillager.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/npc/CatSpawner.java.patch (62%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/npc/InventoryCarrier.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/npc/VillagerTrades.java.patch (79%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/npc/WanderingTrader.java.patch (52%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
index ba7ad87dd7..458bc1fb33 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/npc/AbstractVillager.java
 +++ b/net/minecraft/world/entity/npc/AbstractVillager.java
-@@ -40,8 +40,21 @@
+@@ -37,7 +_,20 @@
  import net.minecraft.world.phys.Vec3;
  import org.slf4j.Logger;
  
@@ -12,7 +12,6 @@
 +// CraftBukkit end
 +
  public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant {
- 
 +    // CraftBukkit start
 +    @Override
 +    public CraftMerchant getCraftMerchant() {
@@ -22,16 +21,16 @@
      private static final EntityDataAccessor<Integer> DATA_UNHAPPY_COUNTER = SynchedEntityData.defineId(AbstractVillager.class, EntityDataSerializers.INT);
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final int VILLAGER_SLOT_OFFSET = 300;
-@@ -50,7 +63,7 @@
+@@ -46,7 +_,7 @@
      private Player tradingPlayer;
      @Nullable
      protected MerchantOffers offers;
 -    private final SimpleContainer inventory = new SimpleContainer(8);
-+    private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit add argument
++    private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit - add argument
  
-     public AbstractVillager(EntityType<? extends AbstractVillager> type, Level world) {
-         super(type, world);
-@@ -101,6 +114,13 @@
+     public AbstractVillager(EntityType<? extends AbstractVillager> entityType, Level level) {
+         super(entityType, level);
+@@ -99,6 +_,13 @@
          return this.tradingPlayer != null;
      }
  
@@ -45,50 +44,40 @@
      @Override
      public MerchantOffers getOffers() {
          if (this.level().isClientSide) {
-@@ -121,11 +141,24 @@
-     @Override
-     public void overrideXp(int experience) {}
+@@ -121,11 +_,24 @@
+     public void overrideXp(int xp) {
+     }
  
 +    // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
-     @Override
--    public void notifyTrade(MerchantOffer offer) {
--        offer.increaseUses();
-+    public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent
++    @Override
++    public void processTrade(MerchantOffer offer, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent
 +        if (event == null || event.willIncreaseTradeUses()) {
-+            recipe.increaseUses();
++            offer.increaseUses();
 +        }
 +        if (event == null || event.isRewardingExp()) {
-+            this.rewardTradeXp(recipe);
++                this.rewardTradeXp(offer);
 +        }
-+        this.notifyTrade(recipe);
++        this.notifyTrade(offer);
 +    }
 +    // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
 +
-+    @Override
-+    public void notifyTrade(MerchantOffer offer) {
+     @Override
+     public void notifyTrade(MerchantOffer offer) {
+-        offer.increaseUses();
 +        // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
          this.ambientSoundTime = -this.getAmbientSoundInterval();
 -        this.rewardTradeXp(offer);
 +        // this.rewardTradeXp(offer); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
          if (this.tradingPlayer instanceof ServerPlayer) {
-             CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult());
+             CriteriaTriggers.TRADE.trigger((ServerPlayer)this.tradingPlayer, this, offer.getResult());
          }
-@@ -179,7 +212,7 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         if (nbt.contains("Offers")) {
--            DataResult dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers"));
-+            DataResult<MerchantOffers> dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); // CraftBukkit - decompile error
-             Logger logger = AbstractVillager.LOGGER;
- 
-             Objects.requireNonNull(logger);
-@@ -246,7 +279,20 @@
-             MerchantOffer merchantrecipe = ((VillagerTrades.ItemListing) arraylist.remove(this.random.nextInt(arraylist.size()))).getOffer(this, this.random);
- 
-             if (merchantrecipe != null) {
--                recipeList.add(merchantrecipe);
+@@ -236,7 +_,20 @@
+         while (i < maxNumbers && !list.isEmpty()) {
+             MerchantOffer offer = list.remove(this.random.nextInt(list.size())).getOffer(this, this.random);
+             if (offer != null) {
+-                givenMerchantOffers.add(offer);
 +                // CraftBukkit start
-+                VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit());
++                VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), offer.asBukkit());
 +                // Suppress during worldgen
 +                if (this.valid) {
 +                    Bukkit.getPluginManager().callEvent(event);
@@ -97,10 +86,10 @@
 +                    // Paper start - Fix crash from invalid ingredient list
 +                    final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe());
 +                    if (craftMerchantRecipe.getIngredients().isEmpty()) return;
-+                    recipeList.add(craftMerchantRecipe.toMinecraft());
++                    givenMerchantOffers.add(craftMerchantRecipe.toMinecraft());
 +                    // Paper end - Fix crash from invalid ingredient list
 +                }
 +                // CraftBukkit end
-                 ++j;
+                 i++;
              }
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch
index 6d93d621f8..1c1335c6b5 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/entity/npc/CatSpawner.java
 +++ b/net/minecraft/world/entity/npc/CatSpawner.java
-@@ -82,8 +82,8 @@
+@@ -82,8 +_,8 @@
          if (cat == null) {
              return 0;
          } else {
 +            cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659
-             cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null);
+             cat.finalizeSpawn(serverLevel, serverLevel.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null);
 -            cat.moveTo(pos, 0.0F, 0.0F);
-             world.addFreshEntityWithPassengers(cat);
+             serverLevel.addFreshEntityWithPassengers(cat);
              return 1;
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
index d6c445bea3..7728954a04 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/npc/InventoryCarrier.java
 +++ b/net/minecraft/world/entity/npc/InventoryCarrier.java
-@@ -8,6 +8,10 @@
+@@ -8,6 +_,10 @@
  import net.minecraft.world.entity.item.ItemEntity;
  import net.minecraft.world.item.ItemStack;
  
@@ -9,27 +9,26 @@
 +// CraftBukkit end
 +
  public interface InventoryCarrier {
- 
      String TAG_INVENTORY = "Inventory";
-@@ -25,13 +29,20 @@
+ 
+@@ -22,12 +_,19 @@
                  return;
              }
  
 +            // CraftBukkit start
-+            ItemStack remaining = new SimpleContainer(inventorysubcontainer).addItem(itemstack);
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(entity, item, remaining.getCount(), false).isCancelled()) {
++            ItemStack remaining = new SimpleContainer(inventory).addItem(item);
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(mob, itemEntity, remaining.getCount(), false).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
 +
-             entity.onItemPickup(item);
-             int i = itemstack.getCount();
-             ItemStack itemstack1 = inventorysubcontainer.addItem(itemstack);
- 
-             entity.take(item, i - itemstack1.getCount());
-             if (itemstack1.isEmpty()) {
--                item.discard();
-+                item.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             mob.onItemPickup(itemEntity);
+             int count = item.getCount();
+             ItemStack itemStack = inventory.addItem(item);
+             mob.take(itemEntity, count - itemStack.getCount());
+             if (itemStack.isEmpty()) {
+-                itemEntity.discard();
++                itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
              } else {
-                 itemstack.setCount(itemstack1.getCount());
+                 item.setCount(itemStack.getCount());
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
new file mode 100644
index 0000000000..61d20a9b99
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
@@ -0,0 +1,188 @@
+--- a/net/minecraft/world/entity/npc/Villager.java
++++ b/net/minecraft/world/entity/npc/Villager.java
+@@ -90,6 +_,14 @@
+ import net.minecraft.world.phys.AABB;
+ import org.slf4j.Logger;
+ 
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRemoveEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++import org.bukkit.event.entity.VillagerReplenishTradeEvent;
++// CraftBukkit end
++
+ public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA = SynchedEntityData.defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
+@@ -257,6 +_,17 @@
+         return this.assignProfessionWhenSpawned;
+     }
+ 
++    // Spigot Start
++    @Override
++    public void inactiveTick() {
++    // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
++        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
++            this.customServerAiStep((ServerLevel) this.level());
++        }
++        super.inactiveTick();
++    }
++    // Spigot End
++
+     @Override
+     protected void customServerAiStep(ServerLevel level) {
+         ProfilerFiller profilerFiller = Profiler.get();
+@@ -275,7 +_,7 @@
+                     this.increaseProfessionLevelOnUpdate = false;
+                 }
+ 
+-                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
++                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit
+             }
+         }
+ 
+@@ -384,7 +_,13 @@
+         this.updateDemand();
+ 
+         for (MerchantOffer merchantOffer : this.getOffers()) {
+-            merchantOffer.resetUses();
++            // CraftBukkit start
++            VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit());
++            Bukkit.getPluginManager().callEvent(event);
++            if (!event.isCancelled()) {
++                merchantOffer.resetUses();
++            }
++            // CraftBukkit end
+         }
+ 
+         this.resendOffersToTradingPlayer();
+@@ -445,7 +_,13 @@
+         int i = 2 - this.numberOfRestocksToday;
+         if (i > 0) {
+             for (MerchantOffer merchantOffer : this.getOffers()) {
+-                merchantOffer.resetUses();
++                // CraftBukkit start
++                VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit());
++                Bukkit.getPluginManager().callEvent(event);
++                if (!event.isCancelled()) {
++                    merchantOffer.resetUses();
++                }
++                // CraftBukkit end
+             }
+         }
+ 
+@@ -466,6 +_,7 @@
+         int playerReputation = this.getPlayerReputation(player);
+         if (playerReputation != 0) {
+             for (MerchantOffer merchantOffer : this.getOffers()) {
++                if (merchantOffer.ignoreDiscounts) continue; // Paper - Add ignore discounts API
+                 merchantOffer.addToSpecialPriceDiff(-Mth.floor(playerReputation * merchantOffer.getPriceMultiplier()));
+             }
+         }
+@@ -475,6 +_,7 @@
+             int amplifier = effect.getAmplifier();
+ 
+             for (MerchantOffer merchantOffer1 : this.getOffers()) {
++                if (merchantOffer1.ignoreDiscounts) continue; // Paper - Add ignore discounts API
+                 double d = 0.3 + 0.0625 * amplifier;
+                 int i = (int)Math.floor(d * merchantOffer1.getBaseCostA().getCount());
+                 merchantOffer1.addToSpecialPriceDiff(-Math.max(i, 1));
+@@ -559,7 +_,7 @@
+     }
+ 
+     @Override
+-    protected SoundEvent getDeathSound() {
++    public SoundEvent getDeathSound() {
+         return SoundEvents.VILLAGER_DEATH;
+     }
+ 
+@@ -594,7 +_,7 @@
+         }
+ 
+         if (offer.shouldRewardExp()) {
+-            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i));
++            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
+         }
+     }
+ 
+@@ -612,7 +_,7 @@
+ 
+     @Override
+     public void die(DamageSource cause) {
+-        LOGGER.info("Villager {} died, message: '{}'", this, cause.getLocalizedDeathMessage(this).getString());
++        if (org.spigotmc.SpigotConfig.logVillagerDeaths) LOGGER.info("Villager {} died, message: '{}'", this, cause.getLocalizedDeathMessage(this).getString()); // Spigot
+         Entity entity = cause.getEntity();
+         if (entity != null) {
+             this.tellWitnessesThatIWasMurdered(entity);
+@@ -782,12 +_,19 @@
+     @Override
+     public void thunderHit(ServerLevel level, LightningBolt lightning) {
+         if (level.getDifficulty() != Difficulty.PEACEFUL) {
+-            LOGGER.info("Villager {} was struck by lightning {}.", this, lightning);
++            // Paper - Add EntityZapEvent; move log down, event can cancel
+             Witch witch = this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), mob -> {
++                // Paper start - Add EntityZapEvent
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, mob).isCancelled()) {
++                    return false;
++                }
++                if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down
++                // Paper end - Add EntityZapEvent
+                 mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, null);
+                 mob.setPersistenceRequired();
+                 this.releaseAllPois();
+-            });
++                return true; // Paper start - Add EntityZapEvent
++            }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
+             if (witch == null) {
+                 super.thunderHit(level, lightning);
+             }
+@@ -827,6 +_,12 @@
+ 
+     @Override
+     protected void updateTrades() {
++        // Paper start - More vanilla friendly methods to update trades
++        updateTrades(TRADES_PER_LEVEL);
++    }
++
++    public boolean updateTrades(int amount) {
++        // Paper end - More vanilla friendly methods to update trades
+         VillagerData villagerData = this.getVillagerData();
+         Int2ObjectMap<VillagerTrades.ItemListing[]> map1;
+         if (this.level().enabledFeatures().contains(FeatureFlags.TRADE_REBALANCE)) {
+@@ -840,9 +_,11 @@
+             VillagerTrades.ItemListing[] itemListings = map1.get(villagerData.getLevel());
+             if (itemListings != null) {
+                 MerchantOffers offers = this.getOffers();
+-                this.addOffersFromItemListings(offers, itemListings, 2);
++                this.addOffersFromItemListings(offers, itemListings, amount); // Paper - More vanilla friendly methods to update trades
++                return true; // Paper - More vanilla friendly methods to update trades
+             }
+         }
++        return false; // Paper - More vanilla friendly methods to update trades
+     }
+ 
+     public void gossip(ServerLevel serverLevel, Villager target, long gameTime) {
+@@ -871,7 +_,7 @@
+             List<Villager> entitiesOfClass = serverLevel.getEntitiesOfClass(Villager.class, aabb);
+             List<Villager> list = entitiesOfClass.stream().filter(villager -> villager.wantsToSpawnGolem(gameTime)).limit(5L).toList();
+             if (list.size() >= minVillagerAmount) {
+-                if (!SpawnUtil.trySpawnMob(
++                if (SpawnUtil.trySpawnMob( // Paper - Set Golem Last Seen to stop it from spawning another one - switch to isPresent
+                         EntityType.IRON_GOLEM,
+                         EntitySpawnReason.MOB_SUMMONED,
+                         serverLevel,
+@@ -880,9 +_,11 @@
+                         8,
+                         6,
+                         SpawnUtil.Strategy.LEGACY_IRON_GOLEM,
+-                        false
++                        false,
++                        org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, // CraftBukkit,
++                        () -> {GolemSensor.golemDetected(this);} // Paper - Set Golem Last Seen to stop it from spawning another one
+                     )
+-                    .isEmpty()) {
++                    .isPresent()) { // Paper - Set Golem Last Seen to stop it from spawning another one - switch to isPresent
+                     entitiesOfClass.forEach(GolemSensor::golemDetected);
+                 }
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch
similarity index 79%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch
index a21a5e780e..9aed42eb71 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/entity/npc/VillagerTrades.java
 +++ b/net/minecraft/world/entity/npc/VillagerTrades.java
-@@ -1834,7 +1834,8 @@
+@@ -1844,7 +_,8 @@
                  return null;
              } else {
-                 ServerLevel serverLevel = (ServerLevel)entity.level();
--                BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, true);
+                 ServerLevel serverLevel = (ServerLevel)trader.level();
+-                BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, trader.blockPosition(), 100, true);
 +                if (!serverLevel.paperConfig().environment.treasureMaps.enabled) return null; // Paper - Configurable cartographer treasure maps
-+                BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredVillager); // Paper - Configurable cartographer treasure maps
++                BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, trader.blockPosition(), 100, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredVillager); // Paper - Configurable cartographer treasure maps
                  if (blockPos != null) {
                      ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), (byte)2, true, true);
                      MapItem.renderBiomePreviewMap(serverLevel, itemStack);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
index c3bde269c7..d5e6a224ee 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/npc/WanderingTrader.java
 +++ b/net/minecraft/world/entity/npc/WanderingTrader.java
-@@ -48,25 +48,38 @@
+@@ -47,11 +_,23 @@
  import net.minecraft.world.phys.Vec3;
  import org.apache.commons.lang3.tuple.Pair;
  
@@ -12,9 +12,8 @@
 +import org.bukkit.event.entity.EntityRemoveEvent;
 +import org.bukkit.event.entity.VillagerAcquireTradeEvent;
 +// CraftBukkit end
- 
-+public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager implements Consumable.OverrideConsumeSound {
 +
++public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager implements Consumable.OverrideConsumeSound {
      private static final int NUMBER_OF_TRADE_OFFERS = 5;
      @Nullable
      private BlockPos wanderTarget;
@@ -24,57 +23,68 @@
 +    public boolean canDrinkMilk = true;
 +    // Paper end - Add more WanderingTrader API
  
-     public WanderingTrader(EntityType<? extends WanderingTrader> type, Level world) {
-         super(type, world);
-+        //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set.
-     }
- 
-     @Override
-     protected void registerGoals() {
-         this.goalSelector.addGoal(0, new FloatGoal(this));
-         this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> {
--            return this.level().isNight() && !entityvillagertrader.isInvisible();
-+            return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
-         }));
-         this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> {
--            return this.level().isDay() && entityvillagertrader.isInvisible();
-+            return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
-         }));
+     public WanderingTrader(EntityType<? extends WanderingTrader> entityType, Level level) {
+         super(entityType, level);
+@@ -67,7 +_,7 @@
+                     this,
+                     PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY),
+                     SoundEvents.WANDERING_TRADER_DISAPPEARED,
+-                    wanderingTrader -> this.level().isNight() && !wanderingTrader.isInvisible()
++                    wanderingTrader -> this.canDrinkPotion && this.level().isNight() && !wanderingTrader.isInvisible()  // Paper - Add more WanderingTrader API
+                 )
+             );
+         this.goalSelector
+@@ -77,7 +_,7 @@
+                     this,
+                     new ItemStack(Items.MILK_BUCKET),
+                     SoundEvents.WANDERING_TRADER_REAPPEARED,
+-                    wanderingTrader -> this.level().isDay() && wanderingTrader.isInvisible()
++                    wanderingTrader -> this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API
+                 )
+             );
          this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
-         this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D));
-@@ -137,7 +150,16 @@
-                 MerchantOffer merchantrecipe = villagertrades_imerchantrecipeoption.getOffer(this, this.random);
- 
-                 if (merchantrecipe != null) {
--                    merchantrecipelist.add(merchantrecipe);
+@@ -145,7 +_,16 @@
+                 VillagerTrades.ItemListing itemListing = itemListings1[randomInt];
+                 MerchantOffer offer = itemListing.getOffer(this, this.random);
+                 if (offer != null) {
+-                    offers.add(offer);
 +                    // CraftBukkit start
-+                    VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit());
++                    VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), offer.asBukkit());
 +                    // Suppress during worldgen
 +                    if (this.valid) {
 +                        Bukkit.getPluginManager().callEvent(event);
 +                    }
 +                    if (!event.isCancelled()) {
-+                        merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft());
++                        offers.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft());
 +                    }
 +                    // CraftBukkit end
                  }
- 
              }
-@@ -190,7 +212,7 @@
+         }
+@@ -189,7 +_,7 @@
+     protected void rewardTradeXp(MerchantOffer offer) {
          if (offer.shouldRewardExp()) {
              int i = 3 + this.random.nextInt(4);
- 
--            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i));
-+            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
+-            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i));
++            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
          }
- 
      }
-@@ -244,7 +266,7 @@
+ 
+@@ -204,7 +_,7 @@
+     }
+ 
+     @Override
+-    protected SoundEvent getDeathSound() {
++    public SoundEvent getDeathSound() {
+         return SoundEvents.WANDERING_TRADER_DEATH;
+     }
+ 
+@@ -241,7 +_,7 @@
  
      private void maybeDespawn() {
          if (this.despawnDelay > 0 && !this.isTrading() && --this.despawnDelay == 0) {
 -            this.discard();
 +            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          }
- 
      }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
new file mode 100644
index 0000000000..8884c9e247
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
@@ -0,0 +1,96 @@
+--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+@@ -38,41 +_,50 @@
+ 
+     public WanderingTraderSpawner(ServerLevelData serverLevelData) {
+         this.serverLevelData = serverLevelData;
+-        this.tickDelay = 1200;
+-        this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay();
+-        this.spawnChance = serverLevelData.getWanderingTraderSpawnChance();
+-        if (this.spawnDelay == 0 && this.spawnChance == 0) {
+-            this.spawnDelay = 24000;
+-            serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
+-            this.spawnChance = 25;
+-            serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
+-        }
++        // Paper start - Add Wandering Trader spawn rate config options
++         this.tickDelay = Integer.MIN_VALUE;
++        // this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay();
++        // this.spawnChance = serverLevelData.getWanderingTraderSpawnChance();
++        // if (this.spawnDelay == 0 && this.spawnChance == 0) {
++        //     this.spawnDelay = 24000;
++        //     serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
++        //     this.spawnChance = 25;
++        //     serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
++        // }
++        // Paper end - Add Wandering Trader spawn rate config options
+     }
+ 
+     @Override
+     public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) {
++        // Paper start - Add Wandering Trader spawn rate config options
++        if (this.tickDelay == Integer.MIN_VALUE) {
++            this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
++            this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
++            this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
++        }
+         if (!level.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) {
+             return 0;
+-        } else if (--this.tickDelay > 0) {
++        } else if (--this.tickDelay - 1 > 0) {
++            this.tickDelay = this.tickDelay - 1;
+             return 0;
+         } else {
+-            this.tickDelay = 1200;
+-            this.spawnDelay -= 1200;
+-            this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
++            this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
++            this.spawnDelay = this.spawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
++            //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
+             if (this.spawnDelay > 0) {
+                 return 0;
+             } else {
+-                this.spawnDelay = 24000;
++                this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
+                 if (!level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
+                     return 0;
+                 } else {
+                     int i = this.spawnChance;
+-                    this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75);
+-                    this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
++                    this.spawnChance = Mth.clamp(this.spawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax);
++                    //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
+                     if (this.random.nextInt(100) > i) {
+                         return 0;
+                     } else if (this.spawn(level)) {
+-                        this.spawnChance = 25;
++                        this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
+                         return 1;
+                     } else {
+                         return 0;
+@@ -100,14 +_,14 @@
+                     return false;
+                 }
+ 
+-                WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, blockPos2, EntitySpawnReason.EVENT);
++                WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, trader -> trader.setDespawnDelay(48000), blockPos2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called
+                 if (wanderingTrader != null) {
+                     for (int i1 = 0; i1 < 2; i1++) {
+                         this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4);
+                     }
+ 
+                     this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID());
+-                    wanderingTrader.setDespawnDelay(48000);
++                    //wanderingTrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent
+                     wanderingTrader.setWanderTarget(blockPos1);
+                     wanderingTrader.restrictTo(blockPos1, 16);
+                     return true;
+@@ -121,7 +_,7 @@
+     private void tryToSpawnLlamaFor(ServerLevel serverLevel, WanderingTrader trader, int maxDistance) {
+         BlockPos blockPos = this.findSpawnPositionNear(serverLevel, trader.blockPosition(), maxDistance);
+         if (blockPos != null) {
+-            TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, EntitySpawnReason.EVENT);
++            TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
+             if (traderLlama != null) {
+                 traderLlama.setLeashedTo(trader, true);
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch
deleted file mode 100644
index cb4983a642..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch
+++ /dev/null
@@ -1,211 +0,0 @@
---- a/net/minecraft/world/entity/npc/Villager.java
-+++ b/net/minecraft/world/entity/npc/Villager.java
-@@ -93,6 +93,14 @@
- import net.minecraft.world.phys.AABB;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+import org.bukkit.event.entity.VillagerReplenishTradeEvent;
-+// CraftBukkit end
-+
- public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -150,7 +158,7 @@
- 
-     @Override
-     public Brain<Villager> getBrain() {
--        return super.getBrain();
-+        return (Brain<Villager>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -216,7 +224,18 @@
-         return this.assignProfessionWhenSpawned;
-     }
- 
-+    // Spigot Start
-     @Override
-+    public void inactiveTick() {
-+        // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
-+        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
-+            this.customServerAiStep((ServerLevel) this.level());
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
-+
-+    @Override
-     protected void customServerAiStep(ServerLevel world) {
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-@@ -235,7 +254,7 @@
-                     this.increaseProfessionLevelOnUpdate = false;
-                 }
- 
--                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
-+                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit
-             }
-         }
- 
-@@ -360,7 +379,13 @@
-         while (iterator.hasNext()) {
-             MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
- 
--            merchantrecipe.resetUses();
-+            // CraftBukkit start
-+            VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit());
-+            Bukkit.getPluginManager().callEvent(event);
-+            if (!event.isCancelled()) {
-+                merchantrecipe.resetUses();
-+            }
-+            // CraftBukkit end
-         }
- 
-         this.resendOffersToTradingPlayer();
-@@ -429,7 +454,13 @@
-             while (iterator.hasNext()) {
-                 MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
- 
--                merchantrecipe.resetUses();
-+                // CraftBukkit start
-+                VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit());
-+                Bukkit.getPluginManager().callEvent(event);
-+                if (!event.isCancelled()) {
-+                    merchantrecipe.resetUses();
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -459,6 +490,7 @@
- 
-             while (iterator.hasNext()) {
-                 MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
-+                if (merchantrecipe.ignoreDiscounts) continue; // Paper - Add ignore discounts API
- 
-                 merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier()));
-             }
-@@ -471,6 +503,7 @@
- 
-             while (iterator1.hasNext()) {
-                 MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next();
-+                if (merchantrecipe1.ignoreDiscounts) continue; // Paper - Add ignore discounts API
-                 double d0 = 0.3D + 0.0625D * (double) j;
-                 int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount());
- 
-@@ -489,7 +522,7 @@
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
--        DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData());
-+        DataResult<Tag> dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); // CraftBukkit - decompile error
-         Logger logger = Villager.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -512,7 +545,7 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         if (nbt.contains("VillagerData", 10)) {
--            DataResult dataresult = VillagerData.CODEC.parse(NbtOps.INSTANCE, nbt.get("VillagerData"));
-+            DataResult<VillagerData> dataresult = VillagerData.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("VillagerData")));
-             Logger logger = Villager.LOGGER;
- 
-             Objects.requireNonNull(logger);
-@@ -599,7 +632,7 @@
-         }
- 
-         if (offer.shouldRewardExp()) {
--            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i));
-+            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
-         }
- 
-     }
-@@ -618,7 +651,7 @@
- 
-     @Override
-     public void die(DamageSource damageSource) {
--        Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString());
-+        if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString()); // Spigot
-         Entity entity = damageSource.getEntity();
- 
-         if (entity != null) {
-@@ -803,12 +836,19 @@
-     @Override
-     public void thunderHit(ServerLevel world, LightningBolt lightning) {
-         if (world.getDifficulty() != Difficulty.PEACEFUL) {
--            Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning);
-+            // Paper - Add EntityZapEvent; move log down, event can cancel
-             Witch entitywitch = (Witch) this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), (entitywitch1) -> {
-+               // Paper start - Add EntityZapEvent
-+               if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch1).isCancelled()) {
-+                   return false;
-+               }
-+               if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down
-+               // Paper end - Add EntityZapEvent
-                 entitywitch1.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null);
-                 entitywitch1.setPersistenceRequired();
-                 this.releaseAllPois();
--            });
-+                return true; // Paper start - Add EntityZapEvent
-+            }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
- 
-             if (entitywitch == null) {
-                 super.thunderHit(world, lightning);
-@@ -855,6 +895,12 @@
- 
-     @Override
-     protected void updateTrades() {
-+        // Paper start - More vanilla friendly methods to update trades
-+        updateTrades(TRADES_PER_LEVEL);
-+    }
-+
-+    public boolean updateTrades(int amount) {
-+        // Paper end - More vanilla friendly methods to update trades
-         VillagerData villagerdata = this.getVillagerData();
-         Int2ObjectMap int2objectmap;
- 
-@@ -872,9 +918,11 @@
-             if (avillagertrades_imerchantrecipeoption != null) {
-                 MerchantOffers merchantrecipelist = this.getOffers();
- 
--                this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 2);
-+                this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, amount); // Paper - More vanilla friendly methods to update trades
-+                return true; // Paper - More vanilla friendly methods to update trades
-             }
-         }
-+        return false; // Paper - More vanilla friendly methods to update trades
-     }
- 
-     public void gossip(ServerLevel world, Villager villager, long time) {
-@@ -906,7 +954,7 @@
-             }).limit(5L).toList();
- 
-             if (list1.size() >= requiredCount) {
--                if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false).isEmpty()) {
-+                if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one
-                     list.forEach(GolemSensor::golemDetected);
-                 }
-             }
-@@ -963,7 +1011,7 @@
-     @Override
-     public void startSleeping(BlockPos pos) {
-         super.startSleeping(pos);
--        this.brain.setMemory(MemoryModuleType.LAST_SLEPT, (Object) this.level().getGameTime());
-+        this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error
-         this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);
-         this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
-     }
-@@ -971,7 +1019,7 @@
-     @Override
-     public void stopSleeping() {
-         super.stopSleeping();
--        this.brain.setMemory(MemoryModuleType.LAST_WOKEN, (Object) this.level().getGameTime());
-+        this.brain.setMemory(MemoryModuleType.LAST_WOKEN, this.level().getGameTime()); // CraftBukkit - decompile error
-     }
- 
-     private boolean golemSpawnConditionsMet(long worldTime) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
deleted file mode 100644
index 5b4f2d10a7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
+++ /dev/null
@@ -1,100 +0,0 @@
---- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
-+++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
-@@ -40,43 +40,53 @@
- 
-     public WanderingTraderSpawner(ServerLevelData properties) {
-         this.serverLevelData = properties;
--        this.tickDelay = 1200;
--        this.spawnDelay = properties.getWanderingTraderSpawnDelay();
--        this.spawnChance = properties.getWanderingTraderSpawnChance();
--        if (this.spawnDelay == 0 && this.spawnChance == 0) {
--            this.spawnDelay = 24000;
--            properties.setWanderingTraderSpawnDelay(this.spawnDelay);
--            this.spawnChance = 25;
--            properties.setWanderingTraderSpawnChance(this.spawnChance);
--        }
-+        // Paper start - Add Wandering Trader spawn rate config options
-+        this.tickDelay = Integer.MIN_VALUE;
-+        //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value
-+        //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value
-+        //if (this.spawnDelay == 0 && this.spawnChance == 0) {
-+        //    this.spawnDelay = 24000;
-+        //    properties.setWanderingTraderSpawnDelay(this.spawnDelay);
-+        //    this.spawnChance = 25;
-+        //    properties.setWanderingTraderSpawnChance(this.spawnChance);
-+        //}
-+        // Paper end - Add Wandering Trader spawn rate config options
- 
-     }
- 
-     @Override
-     public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) {
-+        // Paper start - Add Wandering Trader spawn rate config options
-+        if (this.tickDelay == Integer.MIN_VALUE) {
-+            this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
-+            this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
-+            this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
-+        }
-         if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) {
-             return 0;
--        } else if (--this.tickDelay > 0) {
-+        } else if (this.tickDelay - 1 > 0) {
-+            this.tickDelay = this.tickDelay - 1;
-             return 0;
-         } else {
--            this.tickDelay = 1200;
--            this.spawnDelay -= 1200;
--            this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
-+            this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
-+            this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength;
-+            //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
-             if (this.spawnDelay > 0) {
-                 return 0;
-             } else {
--                this.spawnDelay = 24000;
-+                this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength;
-                 if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
-                     return 0;
-                 } else {
-                     int i = this.spawnChance;
- 
--                    this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75);
--                    this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
-+                    // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways
-+                    this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax);
-                     if (this.random.nextInt(100) > i) {
-                         return 0;
-                     } else if (this.spawn(world)) {
--                        this.spawnChance = 25;
-+                        this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
-+                        // Paper end - Add Wandering Trader spawn rate config options
-                         return 1;
-                     } else {
-                         return 0;
-@@ -110,7 +120,7 @@
-                     return false;
-                 }
- 
--                WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, blockposition2, EntitySpawnReason.EVENT);
-+                WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called
- 
-                 if (entityvillagertrader != null) {
-                     for (int i = 0; i < 2; ++i) {
-@@ -118,7 +128,7 @@
-                     }
- 
-                     this.serverLevelData.setWanderingTraderId(entityvillagertrader.getUUID());
--                    entityvillagertrader.setDespawnDelay(48000);
-+                    // entityvillagertrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent
-                     entityvillagertrader.setWanderTarget(blockposition1);
-                     entityvillagertrader.restrictTo(blockposition1, 16);
-                     return true;
-@@ -133,7 +143,7 @@
-         BlockPos blockposition = this.findSpawnPositionNear(world, wanderingTrader.blockPosition(), range);
- 
-         if (blockposition != null) {
--            TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT);
-+            TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
- 
-             if (entityllamatrader != null) {
-                 entityllamatrader.setLeashedTo(wanderingTrader, true);

From 64e61681f4888d754f3ebcd65665a62a682f2b50 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 11:56:00 -0500
Subject: [PATCH 066/285] net/minecraft/world/inventory

---
 .../AbstractContainerMenu.java.patch          | 281 ++++++++++++++++
 .../inventory/AbstractCraftingMenu.java.patch |  39 +++
 .../inventory/AbstractFurnaceMenu.java.patch  |  49 +++
 .../world/inventory/AnvilMenu.java.patch      | 109 +++---
 .../world/inventory/BeaconMenu.java.patch     | 121 +++++++
 .../inventory/BrewingStandMenu.java.patch     | 139 ++++++++
 .../inventory/CartographyTableMenu.java.patch | 103 ++++++
 .../world/inventory/ChestMenu.java.patch      |  50 +++
 .../inventory/ContainerLevelAccess.java.patch |  80 ++---
 .../inventory/ContainerListener.java.patch    |  13 +
 .../ContainerSynchronizer.java.patch          |   9 +
 .../world/inventory/CrafterMenu.java.patch    |  31 ++
 .../inventory/CraftingContainer.java.patch    |  18 +
 .../world/inventory/CraftingMenu.java.patch   |  62 ++++
 .../world/inventory/DispenserMenu.java.patch  |  49 +++
 .../inventory/EnchantmentMenu.java.patch      | 205 ++++++++++++
 .../inventory/FurnaceResultSlot.java.patch    |  11 +
 .../world/inventory/GrindstoneMenu.java.patch | 108 ++++++
 .../world/inventory/HopperMenu.java.patch     |  40 +++
 .../inventory/HorseInventoryMenu.java.patch   |  26 ++
 .../world/inventory/InventoryMenu.java.patch  |  45 +++
 .../inventory/ItemCombinerMenu.java.patch     |  41 +--
 .../world/inventory/LecternMenu.java.patch    | 103 ++++++
 .../world/inventory/LoomMenu.java.patch       | 143 ++++++++
 .../world/inventory/MenuType.java.patch       |  33 ++
 .../inventory/MerchantContainer.java.patch    |  48 +++
 .../world/inventory/MerchantMenu.java.patch   |  83 +++++
 .../inventory/MerchantResultSlot.java.patch   |  28 +-
 .../PlayerEnderChestContainer.java.patch      |  27 ++
 .../inventory/ResultContainer.java.patch      |  37 +--
 .../world/inventory/ShulkerBoxMenu.java.patch |  39 +++
 .../world/inventory/SmithingMenu.java.patch   |  50 +++
 .../inventory/StonecutterMenu.java.patch      | 128 ++++++++
 .../TransientCraftingContainer.java.patch     |  71 ++++
 .../AbstractContainerMenu.java.patch          | 310 ------------------
 .../inventory/AbstractCraftingMenu.java.patch |  44 ---
 .../inventory/AbstractFurnaceMenu.java.patch  |  60 ----
 .../world/inventory/BeaconMenu.java.patch     | 109 ------
 .../inventory/BrewingStandMenu.java.patch     | 127 -------
 .../inventory/CartographyTableMenu.java.patch | 146 ---------
 .../world/inventory/ChestMenu.java.patch      |  64 ----
 .../inventory/ContainerListener.java.patch    |  14 -
 .../ContainerSynchronizer.java.patch          |  10 -
 .../world/inventory/CrafterMenu.java.patch    |  38 ---
 .../inventory/CraftingContainer.java.patch    |  29 --
 .../world/inventory/CraftingMenu.java.patch   |  74 -----
 .../world/inventory/DispenserMenu.java.patch  |  62 ----
 .../inventory/EnchantmentMenu.java.patch      | 270 ---------------
 .../inventory/FurnaceResultSlot.java.patch    |  11 -
 .../world/inventory/GrindstoneMenu.java.patch | 141 --------
 .../world/inventory/HopperMenu.java.patch     |  51 ---
 .../inventory/HorseInventoryMenu.java.patch   |  53 ---
 .../world/inventory/InventoryMenu.java.patch  |  64 ----
 .../world/inventory/LecternMenu.java.patch    | 143 --------
 .../world/inventory/LoomMenu.java.patch       | 203 ------------
 .../world/inventory/MenuType.java.patch       |  11 -
 .../inventory/MerchantContainer.java.patch    |  70 ----
 .../world/inventory/MerchantMenu.java.patch   |  92 ------
 .../PlayerEnderChestContainer.java.patch      |  36 --
 .../world/inventory/ShulkerBoxMenu.java.patch |  49 ---
 .../world/inventory/SmithingMenu.java.patch   |  66 ----
 .../inventory/StonecutterMenu.java.patch      | 188 -----------
 .../TransientCraftingContainer.java.patch     |  93 ------
 63 files changed, 2256 insertions(+), 2791 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/inventory/AnvilMenu.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/inventory/ContainerLevelAccess.java.patch (83%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/inventory/ItemCombinerMenu.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/inventory/MerchantResultSlot.java.patch (57%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/inventory/ResultContainer.java.patch (65%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/BeaconMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/BrewingStandMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/CartographyTableMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/ChestMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerListener.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/CrafterMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/DispenserMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/EnchantmentMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/GrindstoneMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/HopperMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/InventoryMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/LecternMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/LoomMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/MenuType.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/SmithingMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/StonecutterMenu.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/inventory/TransientCraftingContainer.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
new file mode 100644
index 0000000000..e1f470371c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
@@ -0,0 +1,281 @@
+--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
+@@ -19,6 +_,8 @@
+ import net.minecraft.ReportedException;
+ import net.minecraft.core.NonNullList;
+ import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.network.chat.Component;
++import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.Mth;
+ import net.minecraft.world.Container;
+@@ -63,6 +_,31 @@
+     @Nullable
+     private ContainerSynchronizer synchronizer;
+     private boolean suppressRemoteUpdates;
++    // CraftBukkit start
++    public boolean checkReachable = true;
++    public abstract org.bukkit.inventory.InventoryView getBukkitView();
++    public void transferTo(AbstractContainerMenu other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        org.bukkit.inventory.InventoryView source = this.getBukkitView(), destination = other.getBukkitView();
++        ((org.bukkit.craftbukkit.inventory.CraftInventory) source.getTopInventory()).getInventory().onClose(player);
++        ((org.bukkit.craftbukkit.inventory.CraftInventory) source.getBottomInventory()).getInventory().onClose(player);
++        ((org.bukkit.craftbukkit.inventory.CraftInventory) destination.getTopInventory()).getInventory().onOpen(player);
++        ((org.bukkit.craftbukkit.inventory.CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player);
++    }
++    private Component title;
++    public final Component getTitle() {
++        // Paper start - return chat component with empty text instead of throwing error
++        // Preconditions.checkState(this.title != null, "Title not set");
++        if (this.title == null){
++            return Component.literal("");
++        }
++        // Paper end - return chat component with empty text instead of throwing error
++        return this.title;
++    }
++    public final void setTitle(Component title) {
++        com.google.common.base.Preconditions.checkState(this.title == null, "Title already set");
++        this.title = title;
++    }
++    // CraftBukkit end
+ 
+     protected AbstractContainerMenu(@Nullable MenuType<?> menuType, int containerId) {
+         this.menuType = menuType;
+@@ -168,8 +_,18 @@
+ 
+         if (this.synchronizer != null) {
+             this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray());
+-        }
+-    }
++            this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot
++        }
++    }
++
++    // CraftBukkit start
++    public void broadcastCarriedItem() {
++        this.remoteCarried = this.getCarried().copy();
++        if (this.synchronizer != null) {
++            this.synchronizer.sendCarriedChange(this, this.remoteCarried);
++        }
++    }
++    // CraftBukkit end
+ 
+     public void removeSlotListener(ContainerListener listener) {
+         this.containerListeners.remove(listener);
+@@ -235,7 +_,7 @@
+             this.lastSlots.set(slotIndex, itemStack1);
+ 
+             for (ContainerListener containerListener : this.containerListeners) {
+-                containerListener.slotChanged(this, slotIndex, itemStack1);
++                containerListener.slotChanged(this, slotIndex, itemStack, itemStack1); // Paper - Add PlayerInventorySlotChangeEvent
+             }
+         }
+     }
+@@ -343,6 +_,7 @@
+                     this.resetQuickCraft();
+                 }
+             } else if (this.quickcraftStatus == 1) {
++                if (slotId < 0) return; // Paper - Add slot sanity checks to container clicks
+                 Slot slot = this.slots.get(slotId);
+                 ItemStack carried = this.getCarried();
+                 if (canItemQuickReplace(slot, carried, true)
+@@ -367,6 +_,7 @@
+                     }
+ 
+                     int count = this.getCarried().getCount();
++                    java.util.Map<Integer, ItemStack> draggedSlots = new java.util.HashMap<>(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack)
+ 
+                     for (Slot slot1 : this.quickcraftSlots) {
+                         ItemStack carried1 = this.getCarried();
+@@ -379,12 +_,48 @@
+                             int min = Math.min(itemStack.getMaxStackSize(), slot1.getMaxStackSize(itemStack));
+                             int min1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemStack) + i2, min);
+                             count -= min1 - i2;
+-                            slot1.setByPlayer(itemStack.copyWithCount(min1));
+-                        }
+-                    }
+-
+-                    itemStack.setCount(count);
+-                    this.setCarried(itemStack);
++                            // slot1.setByPlayer(itemStack.copyWithCount(min1));
++                            draggedSlots.put(slot1.index, itemStack.copyWithCount(min1)); // CraftBukkit - Put in map instead of setting
++                        }
++                    }
++
++                    // CraftBukkit start - InventoryDragEvent
++                    org.bukkit.inventory.InventoryView view = this.getBukkitView();
++                    org.bukkit.inventory.ItemStack newcursor = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++                    newcursor.setAmount(count);
++                    java.util.Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new java.util.HashMap<Integer, org.bukkit.inventory.ItemStack>();
++                    for (java.util.Map.Entry<Integer, ItemStack> ditem : draggedSlots.entrySet()) {
++                        eventmap.put(ditem.getKey(), org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(ditem.getValue()));
++                    }
++
++                    // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory.
++                    ItemStack oldCursor = this.getCarried();
++                    this.setCarried(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(newcursor));
++
++                    org.bukkit.event.inventory.InventoryDragEvent event = new org.bukkit.event.inventory.InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(oldCursor), this.quickcraftType == 1, eventmap);
++                    player.level().getCraftServer().getPluginManager().callEvent(event);
++
++                    // Whether or not a change was made to the inventory that requires an update.
++                    boolean needsUpdate = event.getResult() != org.bukkit.event.Event.Result.DEFAULT;
++
++                    if (event.getResult() != org.bukkit.event.Event.Result.DENY) {
++                        for (java.util.Map.Entry<Integer, ItemStack> dslot : draggedSlots.entrySet()) {
++                            view.setItem(dslot.getKey(), org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(dslot.getValue()));
++                        }
++                        // The only time the carried item will be set to null is if the inventory is closed by the server.
++                        // If the inventory is closed by the server, then the cursor items are dropped.  This is why we change the cursor early.
++                        if (this.getCarried() != null) {
++                            this.setCarried(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getCursor()));
++                            needsUpdate = true;
++                        }
++                    } else {
++                        this.setCarried(oldCursor);
++                    }
++
++                    if (needsUpdate && player instanceof ServerPlayer) {
++                        this.sendAllDataToRemote();
++                    }
++                    // CraftBukkit end
+                 }
+ 
+                 this.resetQuickCraft();
+@@ -398,8 +_,11 @@
+             if (slotId == -999) {
+                 if (!this.getCarried().isEmpty()) {
+                     if (clickAction == ClickAction.PRIMARY) {
+-                        player.drop(this.getCarried(), true);
+-                        this.setCarried(ItemStack.EMPTY);
++                            // CraftBukkit start
++                            ItemStack carried = this.getCarried();
++                            this.setCarried(ItemStack.EMPTY);
++                            player.drop(carried, true);
++                            // CraftBukkit start
+                     } else {
+                         player.drop(this.getCarried().split(1), true);
+                     }
+@@ -461,8 +_,18 @@
+                 }
+ 
+                 slot.setChanged();
++                // CraftBukkit start - Make sure the client has the right slot contents
++                if (player instanceof ServerPlayer && slot.getMaxStackSize() != 64) {
++                    ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem()));
++                    // Updating a crafting inventory makes the client reset the result slot, have to send it again
++                    if (this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.WORKBENCH || this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.CRAFTING) {
++                        ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem()));
++                    }
++                }
++                // CraftBukkit end
+             }
+         } else if (clickType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) {
++            if (slotId < 0) return; // Paper - Add slot sanity checks to container clicks
+             ItemStack item = inventory.getItem(button);
+             Slot slot = this.slots.get(slotId);
+             ItemStack carried = slot.getItem();
+@@ -582,8 +_,9 @@
+         if (player instanceof ServerPlayer) {
+             ItemStack carried = this.getCarried();
+             if (!carried.isEmpty()) {
++                this.setCarried(ItemStack.EMPTY); // CraftBukkit - SPIGOT-4556 - from below
+                 dropOrPlaceInInventory(player, carried);
+-                this.setCarried(ItemStack.EMPTY);
++                // this.setCarried(ItemStack.EMPTY); // CraftBukkit - moved up
+             }
+         }
+     }
+@@ -629,6 +_,14 @@
+     public abstract boolean stillValid(Player player);
+ 
+     protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean reverseDirection) {
++        // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
++        return this.moveItemStackTo(stack, startIndex, endIndex, reverseDirection, false);
++    }
++    protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean reverseDirection, boolean isCheck) {
++        if (isCheck) {
++            stack = stack.copy();
++        }
++        // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
+         boolean flag = false;
+         int i = startIndex;
+         if (reverseDirection) {
+@@ -639,18 +_,27 @@
+             while (!stack.isEmpty() && (reverseDirection ? i >= startIndex : i < endIndex)) {
+                 Slot slot = this.slots.get(i);
+                 ItemStack item = slot.getItem();
++                // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; clone if only a check
++                if (isCheck) {
++                    item = item.copy();
++                }
++                // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
+                 if (!item.isEmpty() && ItemStack.isSameItemSameComponents(stack, item)) {
+                     int i1 = item.getCount() + stack.getCount();
+                     int maxStackSize = slot.getMaxStackSize(item);
+                     if (i1 <= maxStackSize) {
+                         stack.setCount(0);
+                         item.setCount(i1);
++                        if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                         slot.setChanged();
++                        } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                         flag = true;
+                     } else if (item.getCount() < maxStackSize) {
+                         stack.shrink(maxStackSize - item.getCount());
+                         item.setCount(maxStackSize);
++                         if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                         slot.setChanged();
++                        } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                         flag = true;
+                     }
+                 }
+@@ -673,10 +_,21 @@
+             while (reverseDirection ? i >= startIndex : i < endIndex) {
+                 Slot slotx = this.slots.get(i);
+                 ItemStack itemx = slotx.getItem();
++                // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
++                if (isCheck) {
++                    itemx = itemx.copy();
++                }
++                // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
+                 if (itemx.isEmpty() && slotx.mayPlace(stack)) {
+                     int i1 = slotx.getMaxStackSize(stack);
++                    // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
++                    if (isCheck) {
++                        stack.shrink(Math.min(stack.getCount(), i1));
++                    } else {
++                    // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
+                     slotx.setByPlayer(stack.split(Math.min(stack.getCount(), i1)));
+                     slotx.setChanged();
++                    } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                     flag = true;
+                     break;
+                 }
+@@ -760,6 +_,11 @@
+     }
+ 
+     public ItemStack getCarried() {
++        // CraftBukkit start
++        if (this.carried.isEmpty()) {
++            this.setCarried(ItemStack.EMPTY);
++        }
++        // CraftBukkit end
+         return this.carried;
+     }
+ 
+@@ -808,4 +_,15 @@
+         this.stateId = this.stateId + 1 & 32767;
+         return this.stateId;
+     }
++
++    // Paper start - Add missing InventoryHolders
++    // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question
++    // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily
++    // initializes the InventoryHolder safely.
++    protected final Supplier<org.bukkit.inventory.BlockInventoryHolder> createBlockHolder(final ContainerLevelAccess context) {
++        //noinspection ConstantValue
++        com.google.common.base.Preconditions.checkArgument(context != null, "context was null");
++        return () -> context.createBlockHolder(this);
++    }
++    // Paper end - Add missing InventoryHolders
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
new file mode 100644
index 0000000000..5e22ef73ce
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/inventory/AbstractCraftingMenu.java
++++ b/net/minecraft/world/inventory/AbstractCraftingMenu.java
+@@ -12,14 +_,17 @@
+ public abstract class AbstractCraftingMenu extends RecipeBookMenu {
+     private final int width;
+     private final int height;
+-    public final CraftingContainer craftSlots;
++    public final TransientCraftingContainer craftSlots; // CraftBukkit
+     public final ResultContainer resultSlots = new ResultContainer();
+ 
+-    public AbstractCraftingMenu(MenuType<?> menuType, int containerId, int width, int height) {
++    public AbstractCraftingMenu(MenuType<?> menuType, int containerId, int width, int height, Inventory playerInventory) { // CraftBukkit
+         super(menuType, containerId);
+         this.width = width;
+         this.height = height;
+-        this.craftSlots = new TransientCraftingContainer(this, width, height);
++        // CraftBukkit start
++        this.craftSlots = new TransientCraftingContainer(this, width, height, playerInventory.player); // CraftBukkit - pass player
++        this.craftSlots.resultInventory = this.resultSlots; // CraftBukkit - let InventoryCrafting know about its result slot
++        // CraftBukkit end
+     }
+ 
+     protected Slot addResultSlot(Player player, int x, int y) {
+@@ -35,13 +_,13 @@
+     }
+ 
+     @Override
+-    public RecipeBookMenu.PostPlaceAction handlePlacement(
++    public PostPlaceAction handlePlacement(
+         boolean useMaxItems, boolean isCreative, RecipeHolder<?> recipe, ServerLevel level, Inventory playerInventory
+     ) {
+         RecipeHolder<CraftingRecipe> recipeHolder = (RecipeHolder<CraftingRecipe>)recipe;
+         this.beginPlacingRecipe();
+ 
+-        RecipeBookMenu.PostPlaceAction var8;
++        PostPlaceAction var8;
+         try {
+             List<Slot> inputGridSlots = this.getInputGridSlots();
+             var8 = ServerPlaceRecipe.placeRecipe(new ServerPlaceRecipe.CraftingMenuAccess<CraftingRecipe>() {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
new file mode 100644
index 0000000000..17fc9ef5af
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java
++++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java
+@@ -34,6 +_,21 @@
+     private final RecipeType<? extends AbstractCookingRecipe> recipeType;
+     private final RecipePropertySet acceptedInputs;
+     private final RecipeBookType recipeBookType;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftFurnaceView bukkitEntity = null;
++    private Inventory player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftFurnaceView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryFurnace inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryFurnace((net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity) this.container);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftFurnaceView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     protected AbstractFurnaceMenu(
+         MenuType<?> menuType,
+@@ -68,6 +_,7 @@
+         this.addSlot(new Slot(container, 0, 56, 17));
+         this.addSlot(new FurnaceFuelSlot(this, container, 1, 56, 53));
+         this.addSlot(new FurnaceResultSlot(inventory.player, container, 2, 116, 35));
++        this.player = inventory; // CraftBukkit - save player
+         this.addStandardInventorySlots(inventory, 8, 84);
+         this.addDataSlots(data);
+     }
+@@ -85,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.container.stillValid(player);
+     }
+ 
+@@ -170,7 +_,7 @@
+     }
+ 
+     @Override
+-    public RecipeBookMenu.PostPlaceAction handlePlacement(
++    public PostPlaceAction handlePlacement(
+         boolean useMaxItems, boolean isCreative, RecipeHolder<?> recipe, final ServerLevel level, Inventory playerInventory
+     ) {
+         final List<Slot> list = List.of(this.getSlot(0), this.getSlot(2));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/AnvilMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/inventory/AnvilMenu.java.patch
rename to paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch
index 1c6f48d25b..a1143aed14 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/AnvilMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AnvilMenu.java.patch
@@ -1,68 +1,55 @@
 --- a/net/minecraft/world/inventory/AnvilMenu.java
 +++ b/net/minecraft/world/inventory/AnvilMenu.java
-@@ -21,6 +21,10 @@
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.view.CraftAnvilView;
-+// CraftBukkit end
-+
- public class AnvilMenu extends ItemCombinerMenu {
- 
-     public static final int INPUT_SLOT = 0;
-@@ -45,6 +49,12 @@
+@@ -43,6 +_,12 @@
      private static final int ADDITIONAL_SLOT_X_PLACEMENT = 76;
      private static final int RESULT_SLOT_X_PLACEMENT = 134;
      private static final int SLOT_Y_PLACEMENT = 47;
 +    // CraftBukkit start
 +    public static final int DEFAULT_DENIED_COST = -1;
 +    public int maximumRepairCost = 40;
-+    private CraftAnvilView bukkitEntity;
++    private org.bukkit.craftbukkit.inventory.view.CraftAnvilView bukkitEntity;
 +    // CraftBukkit end
 +    public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions
  
-     public AnvilMenu(int syncId, Inventory inventory) {
-         this(syncId, inventory, ContainerLevelAccess.NULL);
-@@ -72,7 +82,7 @@
+     public AnvilMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -68,7 +_,7 @@
  
      @Override
-     protected boolean mayPickup(Player player, boolean present) {
+     protected boolean mayPickup(Player player, boolean hasStack) {
 -        return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > 0;
-+        return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item
++        return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && hasStack; // CraftBukkit - allow cost 0 like a free item
      }
  
      @Override
-@@ -94,7 +104,7 @@
+@@ -89,12 +_,22 @@
              this.inputSlots.setItem(1, ItemStack.EMPTY);
          }
  
 -        this.cost.set(0);
 +        this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
          this.inputSlots.setItem(0, ItemStack.EMPTY);
-         this.access.execute((world, blockposition) -> {
-             BlockState iblockdata = world.getBlockState(blockposition);
-@@ -102,6 +112,16 @@
-             if (!player.hasInfiniteMaterials() && iblockdata.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) {
-                 BlockState iblockdata1 = AnvilBlock.damage(iblockdata);
- 
+         this.access.execute((level, blockPos) -> {
+             BlockState blockState = level.getBlockState(blockPos);
+             if (!player.hasInfiniteMaterials() && blockState.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) {
+                 BlockState blockState1 = AnvilBlock.damage(blockState);
 +                // Paper start - AnvilDamageEvent
-+                com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), iblockdata1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(iblockdata1) : null);
++                com.destroystokyo.paper.event.block.AnvilDamagedEvent event = new com.destroystokyo.paper.event.block.AnvilDamagedEvent(getBukkitView(), blockState1 != null ? org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState1) : null);
 +                if (!event.callEvent()) {
 +                    return;
 +                } else if (event.getDamageState() == com.destroystokyo.paper.event.block.AnvilDamagedEvent.DamageState.BROKEN) {
-+                    iblockdata1 = null;
++                    blockState1 = null;
 +                } else {
-+                    iblockdata1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, iblockdata.getValue(AnvilBlock.FACING));
++                    blockState1 = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getDamageState().getMaterial().createBlockData()).getState().setValue(AnvilBlock.FACING, blockState.getValue(AnvilBlock.FACING));
 +                }
 +                // Paper end - AnvilDamageEvent
-                 if (iblockdata1 == null) {
-                     world.removeBlock(blockposition, false);
-                     world.levelEvent(1029, blockposition, 0);
-@@ -143,8 +163,8 @@
-                 if (itemstack1.isDamageableItem() && itemstack.isValidRepairItem(itemstack2)) {
-                     k = Math.min(itemstack1.getDamageValue(), itemstack1.getMaxDamage() / 4);
-                     if (k <= 0) {
+                 if (blockState1 == null) {
+                     level.removeBlock(blockPos, false);
+                     level.levelEvent(1029, blockPos, 0);
+@@ -128,8 +_,8 @@
+                 if (itemStack.isDamageableItem() && item.isValidRepairItem(item1)) {
+                     int min = Math.min(itemStack.getDamageValue(), itemStack.getMaxDamage() / 4);
+                     if (min <= 0) {
 -                        this.resultSlots.setItem(0, ItemStack.EMPTY);
 -                        this.cost.set(0);
 +                        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit
@@ -70,10 +57,10 @@
                          return;
                      }
  
-@@ -158,8 +178,8 @@
-                     this.repairItemCountCost = i1;
+@@ -144,8 +_,8 @@
+                     this.repairItemCountCost = i2;
                  } else {
-                     if (!flag && (!itemstack1.is(itemstack2.getItem()) || !itemstack1.isDamageableItem())) {
+                     if (!hasStoredEnchantments && (!itemStack.is(item1.getItem()) || !itemStack.isDamageableItem())) {
 -                        this.resultSlots.setItem(0, ItemStack.EMPTY);
 -                        this.cost.set(0);
 +                        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit
@@ -81,19 +68,19 @@
                          return;
                      }
  
-@@ -214,7 +234,7 @@
-                             flag2 = true;
-                         } else {
+@@ -191,7 +_,7 @@
                              flag1 = true;
--                            if (i2 > enchantment.getMaxLevel()) {
-+                            if (i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions
-                                 i2 = enchantment.getMaxLevel();
+                         } else {
+                             flag = true;
+-                            if (intValue > enchantment.getMaxLevel()) {
++                            if (intValue > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions
+                                 intValue = enchantment.getMaxLevel();
                              }
  
-@@ -233,8 +253,8 @@
+@@ -209,8 +_,8 @@
                      }
  
-                     if (flag2 && !flag1) {
+                     if (flag1 && !flag) {
 -                        this.resultSlots.setItem(0, ItemStack.EMPTY);
 -                        this.cost.set(0);
 +                        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit
@@ -101,14 +88,16 @@
                          return;
                      }
                  }
-@@ -260,14 +280,14 @@
+@@ -235,14 +_,16 @@
              }
  
-             if (b0 == i && b0 > 0) {
+             if (i1 == i && i1 > 0) {
 -                if (this.cost.get() >= 40) {
 -                    this.cost.set(39);
-+                if (this.cost.get() >= this.maximumRepairCost) { // CraftBukkit
-+                    this.cost.set(this.maximumRepairCost - 1); // CraftBukkit
++                // CraftBukkit start
++                if (this.cost.get() >= this.maximumRepairCost) {
++                    this.cost.set(this.maximumRepairCost - 1);
++                // CraftBukkit end
                  }
  
                  this.onlyRenaming = true;
@@ -116,15 +105,15 @@
  
 -            if (this.cost.get() >= 40 && !this.player.getAbilities().instabuild) {
 +            if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit
-                 itemstack1 = ItemStack.EMPTY;
+                 itemStack = ItemStack.EMPTY;
              }
  
-@@ -285,12 +305,13 @@
-                 EnchantmentHelper.setEnchantments(itemstack1, itemenchantments_a.toImmutable());
+@@ -260,12 +_,13 @@
+                 EnchantmentHelper.setEnchantments(itemStack, mutable.toImmutable());
              }
  
--            this.resultSlots.setItem(0, itemstack1);
-+            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit
+-            this.resultSlots.setItem(0, itemStack);
++            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemStack); // CraftBukkit
              this.broadcastChanges();
          } else {
 -            this.resultSlots.setItem(0, ItemStack.EMPTY);
@@ -135,8 +124,8 @@
 +        this.sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686, SPIGOT-7931: Always send completed inventory to stay in sync with client
      }
  
-     public static int calculateIncreasedRepairCost(int cost) {
-@@ -313,6 +334,7 @@
+     public static int calculateIncreasedRepairCost(int oldRepairCost) {
+@@ -286,6 +_,7 @@
              }
  
              this.createResult();
@@ -144,21 +133,21 @@
              return true;
          } else {
              return false;
-@@ -329,4 +351,19 @@
+@@ -301,4 +_,19 @@
      public int getCost() {
          return this.cost.get();
      }
 +
 +    // CraftBukkit start
 +    @Override
-+    public CraftAnvilView getBukkitView() {
++    public org.bukkit.craftbukkit.inventory.view.CraftAnvilView getBukkitView() {
 +        if (this.bukkitEntity != null) {
 +            return this.bukkitEntity;
 +        }
 +
 +        org.bukkit.craftbukkit.inventory.CraftInventoryAnvil inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryAnvil(
 +                this.access.getLocation(), this.inputSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftAnvilView(this.player.getBukkitEntity(), inventory, this);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftAnvilView(this.player.getBukkitEntity(), inventory, this);
 +        this.bukkitEntity.updateFromLegacy(inventory);
 +        return this.bukkitEntity;
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
new file mode 100644
index 0000000000..48cbfd3a8e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
@@ -0,0 +1,121 @@
+--- a/net/minecraft/world/inventory/BeaconMenu.java
++++ b/net/minecraft/world/inventory/BeaconMenu.java
+@@ -22,20 +_,14 @@
+     private static final int USE_ROW_SLOT_START = 28;
+     private static final int USE_ROW_SLOT_END = 37;
+     private static final int NO_EFFECT = 0;
+-    private final Container beacon = new SimpleContainer(1) {
+-        @Override
+-        public boolean canPlaceItem(int slot, ItemStack stack) {
+-            return stack.is(ItemTags.BEACON_PAYMENT_ITEMS);
+-        }
+-
+-        @Override
+-        public int getMaxStackSize() {
+-            return 1;
+-        }
+-    };
+-    private final BeaconMenu.PaymentSlot paymentSlot;
++    private final Container beacon; // Paper - Add missing InventoryHolders Move down
++    private final PaymentSlot paymentSlot;
+     private final ContainerLevelAccess access;
+     private final ContainerData beaconData;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftBeaconView bukkitEntity = null;
++    private net.minecraft.world.entity.player.Inventory player;
++    // CraftBukkit end
+ 
+     public BeaconMenu(int containerId, Container container) {
+         this(containerId, container, new SimpleContainerData(3), ContainerLevelAccess.NULL);
+@@ -43,10 +_,31 @@
+ 
+     public BeaconMenu(int containerId, Container container, ContainerData beaconData, ContainerLevelAccess access) {
+         super(MenuType.BEACON, containerId);
++        this.player = (net.minecraft.world.entity.player.Inventory) container; // CraftBukkit - TODO: check this
++        // Paper - Add missing InventoryHolders
++        this.beacon = new SimpleContainer(this.createBlockHolder(access), 1) {
++            @Override
++            public boolean canPlaceItem(int slot, ItemStack stack) {
++                return stack.is(ItemTags.BEACON_PAYMENT_ITEMS);
++            }
++
++            @Override
++            public int getMaxStackSize() {
++                return 1;
++            }
++
++            // Paper start - Fix inventories returning null Locations
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // Paper end - Fix inventories returning null Locations
++        };
++        // Paper end
+         checkContainerDataCount(beaconData, 3);
+         this.beaconData = beaconData;
+         this.access = access;
+-        this.paymentSlot = new BeaconMenu.PaymentSlot(this.beacon, 0, 136, 110);
++        this.paymentSlot = new PaymentSlot(this.beacon, 0, 136, 110);
+         this.addSlot(this.paymentSlot);
+         this.addDataSlots(beaconData);
+         this.addStandardInventorySlots(container, 36, 137);
+@@ -65,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.BEACON);
+     }
+ 
+@@ -141,13 +_,30 @@
+     public Holder<MobEffect> getSecondaryEffect() {
+         return decodeEffect(this.beaconData.get(2));
+     }
++    // Paper start - Add PlayerChangeBeaconEffectEvent
++    private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional<Holder<MobEffect>> optionalEffect) {
++        return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null);
++    }
++    // Paper end - Add PlayerChangeBeaconEffectEvent
+ 
+     public void updateEffects(Optional<Holder<MobEffect>> primaryEffect, Optional<Holder<MobEffect>> secondaryEffect) {
++        // Paper start - fix MC-174630 - validate secondary power
++        if (secondaryEffect.isPresent() && secondaryEffect.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primaryEffect.isPresent() && secondaryEffect.get() != primaryEffect.get())) {
++            secondaryEffect = Optional.empty();
++        }
++        // Paper end
+         if (this.paymentSlot.hasItem()) {
+-            this.beaconData.set(1, encodeEffect(primaryEffect.orElse(null)));
+-            this.beaconData.set(2, encodeEffect(secondaryEffect.orElse(null)));
++            // Paper start - Add PlayerChangeBeaconEffectEvent
++            io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primaryEffect), convert(secondaryEffect), this.access.getLocation().getBlock());
++            if (event.callEvent()) {
++                // Paper end - Add PlayerChangeBeaconEffectEvent
++                this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error
++                this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error
++            if (event.willConsumeItem()) { // Paper
+             this.paymentSlot.remove(1);
++            } // Paper
+             this.access.execute(Level::blockEntityChanged);
++            } // Paper end - Add PlayerChangeBeaconEffectEvent
+         }
+     }
+ 
+@@ -170,4 +_,17 @@
+             return 1;
+         }
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftBeaconView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryBeacon inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon(this.beacon);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftBeaconView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
new file mode 100644
index 0000000000..f7754c7cbf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -0,0 +1,139 @@
+--- a/net/minecraft/world/inventory/BrewingStandMenu.java
++++ b/net/minecraft/world/inventory/BrewingStandMenu.java
+@@ -1,6 +_,5 @@
+ package net.minecraft.world.inventory;
+ 
+-import java.util.Optional;
+ import net.minecraft.advancements.CriteriaTriggers;
+ import net.minecraft.core.Holder;
+ import net.minecraft.core.component.DataComponents;
+@@ -16,6 +_,7 @@
+ import net.minecraft.world.item.alchemy.Potion;
+ import net.minecraft.world.item.alchemy.PotionBrewing;
+ import net.minecraft.world.item.alchemy.PotionContents;
++import java.util.Optional;
+ 
+ public class BrewingStandMenu extends AbstractContainerMenu {
+     static final ResourceLocation EMPTY_SLOT_FUEL = ResourceLocation.withDefaultNamespace("container/slot/brewing_fuel");
+@@ -33,29 +_,50 @@
+     private final Container brewingStand;
+     public final ContainerData brewingStandData;
+     private final Slot ingredientSlot;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftBrewingStandView bukkitEntity = null;
++    private Inventory player;
++    // CraftBukkit end
+ 
+     public BrewingStandMenu(int containerId, Inventory playerInventory) {
+-        this(containerId, playerInventory, new SimpleContainer(5), new SimpleContainerData(2));
++        this(containerId, playerInventory, new SimpleContainer(5), new io.papermc.paper.inventory.BrewingSimpleContainerData()); // Paper - Add totalBrewTime
+     }
+ 
+     public BrewingStandMenu(int containerId, Inventory playerInventory, Container brewingStandContainer, ContainerData brewingStandData) {
+         super(MenuType.BREWING_STAND, containerId);
++        this.player = playerInventory; // CraftBukkit
+         checkContainerSize(brewingStandContainer, 5);
+         checkContainerDataCount(brewingStandData, 2);
+         this.brewingStand = brewingStandContainer;
+         this.brewingStandData = brewingStandData;
+         PotionBrewing potionBrewing = playerInventory.player.level().potionBrewing();
+-        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 0, 56, 51));
+-        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 1, 79, 58));
+-        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 2, 102, 51));
+-        this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionBrewing, brewingStandContainer, 3, 79, 17));
+-        this.addSlot(new BrewingStandMenu.FuelSlot(brewingStandContainer, 4, 17, 17));
+-        this.addDataSlots(brewingStandData);
++        // Paper start - custom potion mixes
++        this.addSlot(new PotionSlot(brewingStandContainer, 0, 56, 51, potionBrewing));
++        this.addSlot(new PotionSlot(brewingStandContainer, 1, 79, 58, potionBrewing));
++        this.addSlot(new PotionSlot(brewingStandContainer, 2, 102, 51, potionBrewing));
++        // Paper end - custom potion mixes
++        this.ingredientSlot = this.addSlot(new IngredientsSlot(potionBrewing, brewingStandContainer, 3, 79, 17));
++        this.addSlot(new FuelSlot(brewingStandContainer, 4, 17, 17));
++        // Paper start - Add recipeBrewTime
++        this.addDataSlots(new SimpleContainerData(2) {
++            @Override
++            public int get(final int index) {
++                if (index == 0) return 400 * brewingStandData.get(index) / brewingStandData.get(2);
++                return brewingStandData.get(index);
++            }
++
++            @Override
++            public void set(final int index, final int value) {
++                brewingStandData.set(index, value);
++            }
++        });
++        // Paper end - Add recipeBrewTime
+         this.addStandardInventorySlots(playerInventory, 8, 84);
+     }
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.brewingStand.stillValid(player);
+     }
+ 
+@@ -67,7 +_,7 @@
+             ItemStack item = slot.getItem();
+             itemStack = item.copy();
+             if ((index < 0 || index > 2) && index != 3 && index != 4) {
+-                if (BrewingStandMenu.FuelSlot.mayPlaceItem(itemStack)) {
++                if (FuelSlot.mayPlaceItem(itemStack)) {
+                     if (this.moveItemStackTo(item, 4, 5, false) || this.ingredientSlot.mayPlace(item) && !this.moveItemStackTo(item, 3, 4, false)) {
+                         return ItemStack.EMPTY;
+                     }
+@@ -75,7 +_,7 @@
+                     if (!this.moveItemStackTo(item, 3, 4, false)) {
+                         return ItemStack.EMPTY;
+                     }
+-                } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemStack)) {
++                } else if (PotionSlot.mayPlaceItem(itemStack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes
+                     if (!this.moveItemStackTo(item, 0, 3, false)) {
+                         return ItemStack.EMPTY;
+                     }
+@@ -157,13 +_,15 @@
+     }
+ 
+     static class PotionSlot extends Slot {
+-        public PotionSlot(Container container, int slot, int x, int y) {
++        private final PotionBrewing potionBrewing; // Paper - custom potion mixes
++        public PotionSlot(Container container, int slot, int x, int y, PotionBrewing potionBrewing) { // Paper - custom potion mixes
+             super(container, slot, x, y);
++            this.potionBrewing = potionBrewing; // Paper - custom potion mixes
+         }
+ 
+         @Override
+         public boolean mayPlace(ItemStack stack) {
+-            return mayPlaceItem(stack);
++            return mayPlaceItem(stack, this.potionBrewing); // Paper - custom potion mixes
+         }
+ 
+         @Override
+@@ -181,8 +_,8 @@
+             super.onTake(player, stack);
+         }
+ 
+-        public static boolean mayPlaceItem(ItemStack stack) {
+-            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE);
++        public static boolean mayPlaceItem(ItemStack stack, PotionBrewing potionBrewing) { // Paper - custom potion mixes
++            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack); // Paper - Custom Potion Mixes
+         }
+ 
+         @Override
+@@ -190,4 +_,16 @@
+             return BrewingStandMenu.EMPTY_SLOT_POTION;
+         }
+     }
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftBrewingStandView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryBrewer inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBrewer(this.brewingStand);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftBrewingStandView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
new file mode 100644
index 0000000000..854683fadb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
@@ -0,0 +1,103 @@
+--- a/net/minecraft/world/inventory/CartographyTableMenu.java
++++ b/net/minecraft/world/inventory/CartographyTableMenu.java
+@@ -15,6 +_,21 @@
+ import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
+ 
+ public class CartographyTableMenu extends AbstractContainerMenu {
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryCartography inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryCartography(this.container, this.resultContainer);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+     public static final int MAP_SLOT = 0;
+     public static final int ADDITIONAL_SLOT = 1;
+     public static final int RESULT_SLOT = 2;
+@@ -24,20 +_,8 @@
+     private static final int USE_ROW_SLOT_END = 39;
+     private final ContainerLevelAccess access;
+     long lastSoundTime;
+-    public final Container container = new SimpleContainer(2) {
+-        @Override
+-        public void setChanged() {
+-            CartographyTableMenu.this.slotsChanged(this);
+-            super.setChanged();
+-        }
+-    };
+-    private final ResultContainer resultContainer = new ResultContainer() {
+-        @Override
+-        public void setChanged() {
+-            CartographyTableMenu.this.slotsChanged(this);
+-            super.setChanged();
+-        }
+-    };
++    public final Container container; // Paper - Add missing InventoryHolders - move down
++    private final ResultContainer resultContainer; // Paper - Add missing InventoryHolders - move down
+ 
+     public CartographyTableMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -45,6 +_,34 @@
+ 
+     public CartographyTableMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+         super(MenuType.CARTOGRAPHY_TABLE, containerId);
++        // Paper Start - Add missing InventoryHolders - move down
++        this.container = new SimpleContainer(this.createBlockHolder(access), 2) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                CartographyTableMenu.this.slotsChanged(this);
++                super.setChanged();
++            }
++            // CraftBukkit start
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // CraftBukkit end
++        };
++        this.resultContainer = new ResultContainer(this.createBlockHolder(access)) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                CartographyTableMenu.this.slotsChanged(this);
++                super.setChanged();
++            }
++            // CraftBukkit start
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // CraftBukkit end
++        };
++        // Paper Start - Add missing InventoryHolders - move down
+         this.access = access;
+         this.addSlot(new Slot(this.container, 0, 15, 15) {
+             @Override
+@@ -80,10 +_,12 @@
+             }
+         });
+         this.addStandardInventorySlots(playerInventory, 8, 84);
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+     }
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.CARTOGRAPHY_TABLE);
+     }
+ 
+@@ -99,6 +_,7 @@
+         } else {
+             this.resultContainer.removeItemNoUpdate(2);
+         }
++        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
+     }
+ 
+     private void setupResultSlot(ItemStack map, ItemStack firstSlotStack, ItemStack resultOutput) {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch
new file mode 100644
index 0000000000..c86ead27cc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ChestMenu.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/inventory/ChestMenu.java
++++ b/net/minecraft/world/inventory/ChestMenu.java
+@@ -9,6 +_,29 @@
+ public class ChestMenu extends AbstractContainerMenu {
+     private final Container container;
+     private final int containerRows;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    private Inventory player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventory inventory;
++        if (this.container instanceof Inventory) {
++            inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryPlayer((Inventory) this.container);
++        } else if (this.container instanceof net.minecraft.world.CompoundContainer) {
++            inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((net.minecraft.world.CompoundContainer) this.container);
++        } else {
++            inventory = new org.bukkit.craftbukkit.inventory.CraftInventory(this.container);
++        }
++
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     private ChestMenu(MenuType<?> type, int containerId, Inventory playerInventory, int rows) {
+         this(type, containerId, playerInventory, new SimpleContainer(9 * rows), rows);
+@@ -52,6 +_,9 @@
+         this.container = container;
+         this.containerRows = rows;
+         container.startOpen(playerInventory.player);
++        // CraftBukkit start - Save player
++        this.player = playerInventory;
++        // CraftBukkit end
+         int i = 18;
+         this.addChestGrid(container, 8, 18);
+         int i1 = 18 + this.containerRows * 18 + 13;
+@@ -68,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.container.stillValid(player);
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerLevelAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
rename to paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
index 3202223f24..273a80aee1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
@@ -1,9 +1,46 @@
 --- a/net/minecraft/world/inventory/ContainerLevelAccess.java
 +++ b/net/minecraft/world/inventory/ContainerLevelAccess.java
-@@ -8,16 +8,66 @@
+@@ -12,6 +_,12 @@
+         public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> levelPosConsumer) {
+             return Optional.empty();
+         }
++        // Paper start - fix menus with empty level accesses
++        @Override
++        public org.bukkit.Location getLocation() {
++            return null;
++        }
++        // Paper end - fix menus with empty level accesses
+     };
  
- public interface ContainerLevelAccess {
+     static ContainerLevelAccess create(final Level level, final BlockPos pos) {
+@@ -20,6 +_,23 @@
+             public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> levelPosConsumer) {
+                 return Optional.of(levelPosConsumer.apply(level, pos));
+             }
++            // CraftBukkit start
++            @Override
++            public Level getWorld() {
++                return level;
++            }
++
++            @Override
++            public BlockPos getPosition() {
++                return pos;
++            }
++            // CraftBukkit end
++            // Paper start - Add missing InventoryHolders
++            @Override
++            public boolean isBlock() {
++                return true;
++            }
++            // Paper end - Add missing InventoryHolders
+         };
+     }
  
+@@ -35,4 +_,29 @@
+             return Optional.empty();
+         });
+     }
 +    // CraftBukkit start
 +    default Level getWorld() {
 +        throw new UnsupportedOperationException("Not supported yet.");
@@ -29,41 +66,4 @@
 +        return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory());
 +    }
 +    // Paper end - Add missing InventoryHolders
-+
-     ContainerLevelAccess NULL = new ContainerLevelAccess() {
-         @Override
-         public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> getter) {
-             return Optional.empty();
-         }
-+        // Paper start - fix menus with empty level accesses
-+        @Override
-+        public org.bukkit.Location getLocation() {
-+            return null;
-+        }
-+        // Paper end - fix menus with empty level accesses
-     };
- 
-     static ContainerLevelAccess create(final Level world, final BlockPos pos) {
-         return new ContainerLevelAccess() {
-+            // CraftBukkit start
-             @Override
-+            public Level getWorld() {
-+                return world;
-+            }
-+
-+            @Override
-+            public BlockPos getPosition() {
-+                return pos;
-+            }
-+            // CraftBukkit end
-+            // Paper start - Add missing InventoryHolders
-+            @Override
-+            public boolean isBlock() {
-+                return true;
-+            }
-+            // Paper end - Add missing InventoryHolders
-+
-+            @Override
-             public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> getter) {
-                 return Optional.of(getter.apply(world, pos));
-             }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch
new file mode 100644
index 0000000000..0f457f3e8b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerListener.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/inventory/ContainerListener.java
++++ b/net/minecraft/world/inventory/ContainerListener.java
+@@ -6,4 +_,10 @@
+     void slotChanged(AbstractContainerMenu containerToSend, int dataSlotIndex, ItemStack stack);
+ 
+     void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value);
++
++    // Paper start - Add PlayerInventorySlotChangeEvent
++    default void slotChanged(AbstractContainerMenu containerToSend, int dataSlotIndex, ItemStack oldStack, ItemStack stack) {
++        slotChanged(containerToSend, dataSlotIndex, stack);
++    }
++    // Paper end - Add PlayerInventorySlotChangeEvent
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
new file mode 100644
index 0000000000..a5f19041a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/inventory/ContainerSynchronizer.java
++++ b/net/minecraft/world/inventory/ContainerSynchronizer.java
+@@ -11,4 +_,6 @@
+     void sendCarriedChange(AbstractContainerMenu containerMenu, ItemStack stack);
+ 
+     void sendDataChange(AbstractContainerMenu container, int id, int value);
++
++    default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch
new file mode 100644
index 0000000000..e5fb558412
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CrafterMenu.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/inventory/CrafterMenu.java
++++ b/net/minecraft/world/inventory/CrafterMenu.java
+@@ -19,6 +_,20 @@
+     private final ContainerData containerData;
+     private final Player player;
+     private final CraftingContainer container;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftCrafterView bukkitEntity = null;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftCrafterView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryCrafter inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryCrafter(this.container, this.resultContainer);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftCrafterView(this.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public CrafterMenu(int containerId, Inventory playerInventory) {
+         super(MenuType.CRAFTER_3x3, containerId);
+@@ -100,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.container.stillValid(player);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
new file mode 100644
index 0000000000..2225d1d404
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/inventory/CraftingContainer.java
++++ b/net/minecraft/world/inventory/CraftingContainer.java
+@@ -12,6 +_,15 @@
+ 
+     List<ItemStack> getItems();
+ 
++    // CraftBukkit start
++    default net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++        return null;
++    }
++
++    default void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> recipe) {
++    }
++    // CraftBukkit end
++
+     default CraftingInput asCraftInput() {
+         return this.asPositionedCraftInput().input();
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
new file mode 100644
index 0000000000..a569fb6dd8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/inventory/CraftingMenu.java
++++ b/net/minecraft/world/inventory/CraftingMenu.java
+@@ -30,13 +_,16 @@
+     public final ContainerLevelAccess access;
+     private final Player player;
+     private boolean placingRecipe;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    // CraftBukkit end
+ 
+     public CraftingMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+     }
+ 
+     public CraftingMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
+-        super(MenuType.CRAFTING, containerId, 3, 3);
++        super(MenuType.CRAFTING, containerId, 3, 3, playerInventory); // CraftBukkit - pass player
+         this.access = access;
+         this.player = playerInventory.player;
+         this.addResultSlot(this.player, 124, 35);
+@@ -56,6 +_,7 @@
+         ServerPlayer serverPlayer = (ServerPlayer)player;
+         ItemStack itemStack = ItemStack.EMPTY;
+         Optional<RecipeHolder<CraftingRecipe>> recipeFor = level.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftInput, level, recipe);
++        craftSlots.setCurrentRecipe(recipeFor.orElse(null)); // CraftBukkit
+         if (recipeFor.isPresent()) {
+             RecipeHolder<CraftingRecipe> recipeHolder = recipeFor.get();
+             CraftingRecipe craftingRecipe = recipeHolder.value();
+@@ -66,6 +_,7 @@
+                 }
+             }
+         }
++        itemStack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(craftSlots, resultSlots, itemStack, menu.getBukkitView(), recipeFor.map(RecipeHolder::value).orElse(null) instanceof net.minecraft.world.item.crafting.RepairItemRecipe); // CraftBukkit
+ 
+         resultSlots.setItem(0, itemStack);
+         menu.setRemoteSlot(0, itemStack);
+@@ -102,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.CRAFTING_TABLE);
+     }
+ 
+@@ -176,4 +_,17 @@
+     protected Player owner() {
+         return this.player;
+     }
++
++    // CraftBukkit start
++    @Override
++    public CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
++        this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch
new file mode 100644
index 0000000000..de6614874c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/DispenserMenu.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/inventory/DispenserMenu.java
++++ b/net/minecraft/world/inventory/DispenserMenu.java
+@@ -13,6 +_,10 @@
+     private static final int USE_ROW_SLOT_START = 36;
+     private static final int USE_ROW_SLOT_END = 45;
+     public final Container dispenser;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    private Inventory player;
++    // CraftBukkit end
+ 
+     public DispenserMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, new SimpleContainer(9));
+@@ -20,6 +_,9 @@
+ 
+     public DispenserMenu(int containerId, Inventory playerInventory, Container container) {
+         super(MenuType.GENERIC_3x3, containerId);
++        // CraftBukkit start - Save player
++        this.player = playerInventory;
++        // CraftBukkit end
+         checkContainerSize(container, 9);
+         this.dispenser = container;
+         container.startOpen(playerInventory.player);
+@@ -38,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.dispenser.stillValid(player);
+     }
+ 
+@@ -77,4 +_,17 @@
+         super.removed(player);
+         this.dispenser.stopOpen(player);
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventory(this.dispenser);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
new file mode 100644
index 0000000000..bb590914a6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
@@ -0,0 +1,205 @@
+--- a/net/minecraft/world/inventory/EnchantmentMenu.java
++++ b/net/minecraft/world/inventory/EnchantmentMenu.java
+@@ -31,19 +_,17 @@
+ 
+ public class EnchantmentMenu extends AbstractContainerMenu {
+     static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = ResourceLocation.withDefaultNamespace("container/slot/lapis_lazuli");
+-    private final Container enchantSlots = new SimpleContainer(2) {
+-        @Override
+-        public void setChanged() {
+-            super.setChanged();
+-            EnchantmentMenu.this.slotsChanged(this);
+-        }
+-    };
++    private final Container enchantSlots; // Paper - Add missing InventoryHolders - move down
+     private final ContainerLevelAccess access;
+     private final RandomSource random = RandomSource.create();
+     private final DataSlot enchantmentSeed = DataSlot.standalone();
+     public final int[] costs = new int[3];
+     public final int[] enchantClue = new int[]{-1, -1, -1};
+     public final int[] levelClue = new int[]{-1, -1, -1};
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++    // CraftBukkit end
+ 
+     public EnchantmentMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -51,6 +_,22 @@
+ 
+     public EnchantmentMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
+         super(MenuType.ENCHANTMENT, containerId);
++        // Paper start - Add missing InventoryHolders
++        this.enchantSlots = new SimpleContainer(this.createBlockHolder(access), 2) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                super.setChanged();
++                EnchantmentMenu.this.slotsChanged(this);
++            }
++
++            // CraftBukkit start
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // CraftBukkit end
++        };
++        // Paper end - Add missing InventoryHolders
+         this.access = access;
+         this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) {
+             @Override
+@@ -80,13 +_,16 @@
+         this.addDataSlot(DataSlot.shared(this.levelClue, 0));
+         this.addDataSlot(DataSlot.shared(this.levelClue, 1));
+         this.addDataSlot(DataSlot.shared(this.levelClue, 2));
++        // CraftBukkit start
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity();
++        // CraftBukkit end
+     }
+ 
+     @Override
+     public void slotsChanged(Container inventory) {
+         if (inventory == this.enchantSlots) {
+             ItemStack item = inventory.getItem(0);
+-            if (!item.isEmpty() && item.isEnchantable()) {
++            if (!item.isEmpty()) { // CraftBukkit - relax condition
+                 this.access.execute((level, blockPos) -> {
+                     IdMap<Holder<Enchantment>> holderIdMap = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
+                     int i1 = 0;
+@@ -119,6 +_,42 @@
+                         }
+                     }
+ 
++                    // CraftBukkit start
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item);
++                    org.bukkit.enchantments.EnchantmentOffer[] offers = new org.bukkit.enchantments.EnchantmentOffer[3];
++                    for (int j = 0; j < 3; ++j) {
++                        org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(holderIdMap.byId(this.enchantClue[j])) : null;
++                        offers[j] = (enchantment != null) ? new org.bukkit.enchantments.EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null;
++                    }
++
++                    org.bukkit.event.enchantment.PrepareItemEnchantEvent event = new org.bukkit.event.enchantment.PrepareItemEnchantEvent(this.player, this.getBukkitView(), this.access.getLocation().getBlock(), craftItemStack, offers, i1);
++                    event.setCancelled(!item.isEnchantable());
++                    level.getCraftServer().getPluginManager().callEvent(event);
++
++                    if (event.isCancelled()) {
++                        for (int j = 0; j < 3; ++j) {
++                            this.costs[j] = 0;
++                            this.enchantClue[j] = -1;
++                            this.levelClue[j] = -1;
++                        }
++                        return;
++                    }
++
++                    for (int j = 0; j < 3; j++) {
++                        org.bukkit.enchantments.EnchantmentOffer offer = event.getOffers()[j];
++                        if (offer != null) {
++                            this.costs[j] = offer.getCost();
++                            this.enchantClue[j] = holderIdMap.getId(org.bukkit.craftbukkit.enchantments.CraftEnchantment
++                                .bukkitToMinecraftHolder(offer.getEnchantment()));
++                            this.levelClue[j] = offer.getEnchantmentLevel();
++                        } else {
++                            this.costs[j] = 0;
++                            this.enchantClue[j] = -1;
++                            this.levelClue[j] = -1;
++                        }
++                    }
++                    // CraftBukkit end
++
+                     this.broadcastChanges();
+                 });
+             } else {
+@@ -145,19 +_,51 @@
+                 return false;
+             } else {
+                 this.access.execute((level, blockPos) -> {
+-                    ItemStack itemStack = item;
++                    ItemStack itemStack = item; // Paper - diff on change
+                     List<EnchantmentInstance> enchantmentList = this.getEnchantmentList(level.registryAccess(), item, id, this.costs[id]);
+-                    if (!enchantmentList.isEmpty()) {
++                    // CraftBukkit start
++                    IdMap<Holder<Enchantment>> registry = level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
++                    if (true || !enchantmentList.isEmpty()) {
++                        // player.onEnchantmentPerformed(item, i); // Moved down
++                        java.util.Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<>();
++                        for (EnchantmentInstance instance : enchantmentList) {
++                            enchants.put(org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(instance.enchantment), instance.level);
++                        }
++                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++                        org.bukkit.enchantments.Enchantment hintedEnchantment = org.bukkit.craftbukkit.enchantments.CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[id]));
++                        int hintedEnchantmentLevel = this.levelClue[id];
++                        org.bukkit.event.enchantment.EnchantItemEvent event = new org.bukkit.event.enchantment.EnchantItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), this.getBukkitView(), this.access.getLocation().getBlock(), craftItemStack, this.costs[id], enchants, hintedEnchantment, hintedEnchantmentLevel, id);
++                        level.getCraftServer().getPluginManager().callEvent(event);
++                        int itemLevel = event.getExpLevelCost();
++                        if (event.isCancelled() || (itemLevel > player.experienceLevel && !player.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
++                            return;
++                        }
++                        // CraftBukkit end
++                        // Paper start
++                        itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(craftItemStack, event.getItem());
++                        if (itemStack != item) {
++                            this.enchantSlots.setItem(0, itemStack);
++                        }
++                        if (itemStack.is(Items.BOOK)) {
++                            itemStack = itemStack.transmuteCopy(Items.ENCHANTED_BOOK);
++                            this.enchantSlots.setItem(0, itemStack);
++                        }
++                        // Paper end
++
++                        // CraftBukkit start
++                        for (java.util.Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
++                            Holder<Enchantment> nms = org.bukkit.craftbukkit.enchantments.CraftEnchantment.bukkitToMinecraftHolder(entry.getKey());
++                            if (nms == null) {
++                                continue;
++                            }
++
++                            EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue());
++                            itemStack.enchant(weightedrandomenchant.enchantment, weightedrandomenchant.level);
++                        }
+                         player.onEnchantmentPerformed(item, i);
+-                        if (item.is(Items.BOOK)) {
+-                            itemStack = item.transmuteCopy(Items.ENCHANTED_BOOK);
+-                            this.enchantSlots.setItem(0, itemStack);
+-                        }
+-
+-                        for (EnchantmentInstance enchantmentInstance : enchantmentList) {
+-                            itemStack.enchant(enchantmentInstance.enchantment, enchantmentInstance.level);
+-                        }
+-
++                        // CraftBukkit end
++
++                        // CraftBukkit - TODO: let plugins change this
+                         item1.consume(i, player);
+                         if (item1.isEmpty()) {
+                             this.enchantSlots.setItem(1, ItemStack.EMPTY);
+@@ -214,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.ENCHANTING_TABLE);
+     }
+ 
+@@ -261,4 +_,22 @@
+ 
+         return itemStack;
+     }
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting(this.enchantSlots);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
++
++    // Paper start - add enchantment seed update API
++    public void setEnchantmentSeed(int seed) {
++        this.enchantmentSeed.set(seed);
++    }
++    // Paper end - add enchantment seed update API
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
new file mode 100644
index 0000000000..8cf409634d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/inventory/FurnaceResultSlot.java
++++ b/net/minecraft/world/inventory/FurnaceResultSlot.java
+@@ -45,7 +_,7 @@
+     protected void checkTakeAchievements(ItemStack stack) {
+         stack.onCraftedBy(this.player.level(), this.player, this.removeCount);
+         if (this.player instanceof ServerPlayer serverPlayer && this.container instanceof AbstractFurnaceBlockEntity abstractFurnaceBlockEntity) {
+-            abstractFurnaceBlockEntity.awardUsedRecipesAndPopExperience(serverPlayer);
++            abstractFurnaceBlockEntity.awardUsedRecipesAndPopExperience(serverPlayer, stack, this.removeCount); // CraftBukkit
+         }
+ 
+         this.removeCount = 0;
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch
new file mode 100644
index 0000000000..3ef40a1183
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/GrindstoneMenu.java.patch
@@ -0,0 +1,108 @@
+--- a/net/minecraft/world/inventory/GrindstoneMenu.java
++++ b/net/minecraft/world/inventory/GrindstoneMenu.java
+@@ -20,6 +_,21 @@
+ import net.minecraft.world.phys.Vec3;
+ 
+ public class GrindstoneMenu extends AbstractContainerMenu {
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryGrindstone inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryGrindstone(this.repairSlots, this.resultSlots);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+     public static final int MAX_NAME_LENGTH = 35;
+     public static final int INPUT_SLOT = 0;
+     public static final int ADDITIONAL_SLOT = 1;
+@@ -28,14 +_,8 @@
+     private static final int INV_SLOT_END = 30;
+     private static final int USE_ROW_SLOT_START = 30;
+     private static final int USE_ROW_SLOT_END = 39;
+-    private final Container resultSlots = new ResultContainer();
+-    final Container repairSlots = new SimpleContainer(2) {
+-        @Override
+-        public void setChanged() {
+-            super.setChanged();
+-            GrindstoneMenu.this.slotsChanged(this);
+-        }
+-    };
++    private final Container resultSlots; // Paper - Add missing InventoryHolders - move down
++    final Container repairSlots; // Paper - Add missing InventoryHolders - move down
+     private final ContainerLevelAccess access;
+ 
+     public GrindstoneMenu(int containerId, Inventory playerInventory) {
+@@ -44,6 +_,22 @@
+ 
+     public GrindstoneMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+         super(MenuType.GRINDSTONE, containerId);
++        // Paper start - Add missing InventoryHolders
++        this.resultSlots = new ResultContainer(this.createBlockHolder(access)); // Paper - Add missing InventoryHolders
++        this.repairSlots = new SimpleContainer(this.createBlockHolder(access), 2) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                super.setChanged();
++                GrindstoneMenu.this.slotsChanged(this);
++            }
++            // CraftBukkit start
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // CraftBukkit end
++        };
++        // Paper end - Add missing InventoryHolders
+         this.access = access;
+         this.addSlot(new Slot(this.repairSlots, 0, 49, 19) {
+             @Override
+@@ -67,7 +_,11 @@
+             public void onTake(Player player, ItemStack stack) {
+                 access.execute((level, blockPos) -> {
+                     if (level instanceof ServerLevel) {
+-                        ExperienceOrb.award((ServerLevel)level, Vec3.atCenterOf(blockPos), this.getExperienceAmount(level));
++                        // Paper start - Fire BlockExpEvent on grindstone use
++                        org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos), this.getExperienceAmount(level));
++                        event.callEvent();
++                        ExperienceOrb.award((ServerLevel) level, Vec3.atCenterOf(blockPos), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player);
++                        // Paper end - Fire BlockExpEvent on grindstone use
+                     }
+ 
+                     level.levelEvent(1042, blockPos, 0);
+@@ -104,6 +_,7 @@
+             }
+         });
+         this.addStandardInventorySlots(playerInventory, 8, 84);
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+     }
+ 
+     @Override
+@@ -111,11 +_,13 @@
+         super.slotsChanged(inventory);
+         if (inventory == this.repairSlots) {
+             this.createResult();
++            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
+         }
+     }
+ 
+     private void createResult() {
+-        this.resultSlots.setItem(0, this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1)));
++        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(this.getBukkitView(), this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1))); // CraftBukkit
++        this.sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client
+         this.broadcastChanges();
+     }
+ 
+@@ -201,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.GRINDSTONE);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch
new file mode 100644
index 0000000000..d6d2f6c1de
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/HopperMenu.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/inventory/HopperMenu.java
++++ b/net/minecraft/world/inventory/HopperMenu.java
+@@ -9,6 +_,21 @@
+ public class HopperMenu extends AbstractContainerMenu {
+     public static final int CONTAINER_SIZE = 5;
+     private final Container hopper;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    private Inventory player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventory(this.hopper);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public HopperMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, new SimpleContainer(5));
+@@ -17,6 +_,7 @@
+     public HopperMenu(int containerId, Inventory playerInventory, Container container) {
+         super(MenuType.HOPPER, containerId);
+         this.hopper = container;
++        this.player = playerInventory; // CraftBukkit - save player
+         checkContainerSize(container, 5);
+         container.startOpen(playerInventory.player);
+ 
+@@ -29,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.hopper.stillValid(player);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
new file mode 100644
index 0000000000..8128aca0d5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/inventory/HorseInventoryMenu.java
++++ b/net/minecraft/world/inventory/HorseInventoryMenu.java
+@@ -19,9 +_,23 @@
+     private final AbstractHorse horse;
+     public static final int SLOT_BODY_ARMOR = 1;
+     private static final int SLOT_HORSE_INVENTORY_START = 2;
++    // CraftBukkit start
++    org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
++    Inventory player;
++
++    @Override
++    public org.bukkit.inventory.InventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        return this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), this.horseContainer.getOwner().getInventory(), this);
++    }
++    // CraftBukkit end
+ 
+     public HorseInventoryMenu(int containerId, Inventory inventory, Container horseContainer, final AbstractHorse horse, int columns) {
+         super(null, containerId);
++        this.player = inventory; // CraftBukkit - save player
+         this.horseContainer = horseContainer;
+         this.armorContainer = horse.getBodyArmorAccess();
+         this.horse = horse;
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch
new file mode 100644
index 0000000000..678db72493
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/InventoryMenu.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/inventory/InventoryMenu.java
++++ b/net/minecraft/world/inventory/InventoryMenu.java
+@@ -2,6 +_,7 @@
+ 
+ import java.util.List;
+ import java.util.Map;
++import net.minecraft.network.chat.Component;
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.world.Container;
+@@ -44,9 +_,15 @@
+     private static final EquipmentSlot[] SLOT_IDS = new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET};
+     public final boolean active;
+     private final Player owner;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity = null;
++    // CraftBukkit end
+ 
+     public InventoryMenu(Inventory playerInventory, boolean active, final Player owner) {
+-        super(null, 0, 2, 2);
++        // CraftBukkit start
++        super((MenuType) null, 0, 2, 2, playerInventory); // CraftBukkit - save player
++        this.setTitle(Component.translatable("container.crafting")); // SPIGOT-4722: Allocate title for player inventory
++        // CraftBukkit end
+         this.active = active;
+         this.owner = owner;
+         this.addResultSlot(owner, 154, 28);
+@@ -188,4 +_,17 @@
+     protected Player owner() {
+         return this.owner;
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryCrafting inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryCrafting(this.craftSlots, this.resultSlots);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.owner.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ItemCombinerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
rename to paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
index 7d50fbdafb..447c6217c1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/inventory/ItemCombinerMenu.java
 +++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
-@@ -17,12 +17,7 @@
+@@ -15,12 +_,7 @@
      protected final ContainerLevelAccess access;
      protected final Player player;
      protected final Container inputSlots;
@@ -13,11 +13,11 @@
 +    protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init
      private final int resultSlotIndex;
  
-     protected boolean mayPickup(Player player, boolean present) {
-@@ -36,6 +31,14 @@
-     public ItemCombinerMenu(@Nullable MenuType<?> type, int syncId, Inventory playerInventory, ContainerLevelAccess context, ItemCombinerMenuSlotDefinition forgingSlotsManager) {
-         super(type, syncId);
-         this.access = context;
+     protected boolean mayPickup(Player player, boolean hasStack) {
+@@ -36,6 +_,14 @@
+     ) {
+         super(menuType, containerId);
+         this.access = access;
 +        // Paper start - Add missing InventoryHolders; delay field init
 +        this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)) {
 +            @Override
@@ -26,19 +26,10 @@
 +            }
 +        };
 +        // Paper end - Add missing InventoryHolders; delay field init
-         this.player = playerInventory.player;
-         this.inputSlots = this.createContainer(forgingSlotsManager.getNumOfInputSlots());
-         this.resultSlotIndex = forgingSlotsManager.getResultSlotIndex();
-@@ -50,7 +53,7 @@
-         while (iterator.hasNext()) {
-             final ItemCombinerMenuSlotDefinition.SlotDefinition itemcombinermenuslotdefinition_b = (ItemCombinerMenuSlotDefinition.SlotDefinition) iterator.next();
- 
--            this.addSlot(new Slot(this, this.inputSlots, itemcombinermenuslotdefinition_b.slotIndex(), itemcombinermenuslotdefinition_b.x(), itemcombinermenuslotdefinition_b.y()) {
-+            this.addSlot(new Slot(this.inputSlots, itemcombinermenuslotdefinition_b.slotIndex(), itemcombinermenuslotdefinition_b.x(), itemcombinermenuslotdefinition_b.y()) { // CraftBukkit - decompile error
-                 @Override
-                 public boolean mayPlace(ItemStack stack) {
-                     return itemcombinermenuslotdefinition_b.mayPlace().test(stack);
-@@ -82,7 +85,7 @@
+         this.player = inventory.player;
+         this.inputSlots = this.createContainer(slotDefinition.getNumOfInputSlots());
+         this.resultSlotIndex = slotDefinition.getResultSlotIndex();
+@@ -79,7 +_,7 @@
      public abstract void createResult();
  
      private SimpleContainer createContainer(int size) {
@@ -47,19 +38,19 @@
              @Override
              public void setChanged() {
                  super.setChanged();
-@@ -96,6 +99,7 @@
+@@ -93,6 +_,7 @@
          super.slotsChanged(inventory);
          if (inventory == this.inputSlots) {
              this.createResult();
 +            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent
          }
- 
      }
-@@ -110,6 +114,7 @@
+ 
+@@ -104,6 +_,7 @@
  
      @Override
      public boolean stillValid(Player player) {
 +        if (!this.checkReachable) return true; // CraftBukkit
-         return (Boolean) this.access.evaluate((world, blockposition) -> {
-             return !this.isValidBlock(world.getBlockState(blockposition)) ? false : player.canInteractWithBlock(blockposition, 4.0D);
-         }, true);
+         return this.access
+             .evaluate((level, blockPos) -> !this.isValidBlock(level.getBlockState(blockPos)) ? false : player.canInteractWithBlock(blockPos, 4.0), true);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
new file mode 100644
index 0000000000..eb58b2f0ef
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
@@ -0,0 +1,103 @@
+--- a/net/minecraft/world/inventory/LecternMenu.java
++++ b/net/minecraft/world/inventory/LecternMenu.java
+@@ -14,12 +_,29 @@
+     public static final int BUTTON_PAGE_JUMP_RANGE_START = 100;
+     private final Container lectern;
+     private final ContainerData lecternData;
+-
+-    public LecternMenu(int containerId) {
+-        this(containerId, new SimpleContainer(1), new SimpleContainerData(1));
+-    }
+-
+-    public LecternMenu(int containerId, Container lectern, ContainerData lecternData) {
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftLecternView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftLecternView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryLectern inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryLectern(this.lectern);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftLecternView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
++
++    // CraftBukkit start - add player
++    public LecternMenu(int containerId, net.minecraft.world.entity.player.Inventory playerinventory) {
++        this(containerId, new SimpleContainer(1), new SimpleContainerData(1), playerinventory);
++    // CraftBukkit end - add player
++    }
++
++    public LecternMenu(int containerId, Container lectern, ContainerData lecternData, net.minecraft.world.entity.player.Inventory playerinventory) {
+         super(MenuType.LECTERN, containerId);
+         checkContainerSize(lectern, 1);
+         checkContainerDataCount(lecternData, 1);
+@@ -33,10 +_,12 @@
+             }
+         });
+         this.addDataSlots(lecternData);
++        this.player = (org.bukkit.entity.Player) playerinventory.player.getBukkitEntity(); // CraftBukkit
+     }
+ 
+     @Override
+     public boolean clickMenuButton(Player player, int id) {
++        io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; org.bukkit.craftbukkit.inventory.CraftInventoryLectern bukkitView; // Paper - Add PlayerLecternPageChangeEvent
+         if (id >= 100) {
+             int i = id - 100;
+             this.setData(0, i);
+@@ -45,12 +_,26 @@
+             switch (id) {
+                 case 1: {
+                     int i = this.lecternData.get(0);
+-                    this.setData(0, i - 1);
++                    // Paper start - Add PlayerLecternPageChangeEvent
++                    bukkitView = (org.bukkit.craftbukkit.inventory.CraftInventoryLectern) getBukkitView().getTopInventory();
++                    playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, i, i - 1);
++                    if (!playerLecternPageChangeEvent.callEvent()) {
++                        return false;
++                    }
++                    this.setData(0, playerLecternPageChangeEvent.getNewPage());
++                    // Paper end - Add PlayerLecternPageChangeEvent
+                     return true;
+                 }
+                 case 2: {
+                     int i = this.lecternData.get(0);
+-                    this.setData(0, i + 1);
++                    // Paper start - Add PlayerLecternPageChangeEvent
++                    bukkitView = (org.bukkit.craftbukkit.inventory.CraftInventoryLectern) getBukkitView().getTopInventory();
++                    playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, i, i + 1);
++                    if (!playerLecternPageChangeEvent.callEvent()) {
++                        return false;
++                    }
++                    this.setData(0, playerLecternPageChangeEvent.getNewPage());
++                    // Paper end - Add PlayerLecternPageChangeEvent
+                     return true;
+                 }
+                 case 3:
+@@ -58,6 +_,13 @@
+                         return false;
+                     }
+ 
++                    // CraftBukkit start - Event for taking the book
++                    org.bukkit.event.player.PlayerTakeLecternBookEvent event = new org.bukkit.event.player.PlayerTakeLecternBookEvent(this.player, ((org.bukkit.craftbukkit.inventory.CraftInventoryLectern) this.getBukkitView().getTopInventory()).getHolder());
++                    org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event);
++                    if (event.isCancelled()) {
++                        return false;
++                    }
++                    // CraftBukkit end
+                     ItemStack itemStack = this.lectern.removeItemNoUpdate(0);
+                     this.lectern.setChanged();
+                     if (!player.getInventory().add(itemStack)) {
+@@ -84,6 +_,8 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (this.lectern instanceof net.minecraft.world.level.block.entity.LecternBlockEntity.LecternInventory && !((net.minecraft.world.level.block.entity.LecternBlockEntity.LecternInventory) this.lectern).getLectern().hasBook()) return false; // CraftBukkit
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.lectern.stillValid(player);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch
new file mode 100644
index 0000000000..8182e17e77
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/LoomMenu.java.patch
@@ -0,0 +1,143 @@
+--- a/net/minecraft/world/inventory/LoomMenu.java
++++ b/net/minecraft/world/inventory/LoomMenu.java
+@@ -38,21 +_,23 @@
+     private final Slot patternSlot;
+     private final Slot resultSlot;
+     long lastSoundTime;
+-    private final Container inputContainer = new SimpleContainer(3) {
+-        @Override
+-        public void setChanged() {
+-            super.setChanged();
+-            LoomMenu.this.slotsChanged(this);
+-            LoomMenu.this.slotUpdateListener.run();
+-        }
+-    };
+-    private final Container outputContainer = new SimpleContainer(1) {
+-        @Override
+-        public void setChanged() {
+-            super.setChanged();
+-            LoomMenu.this.slotUpdateListener.run();
+-        }
+-    };
++    private final Container inputContainer; // Paper - Add missing InventoryHolders - move down
++    private final Container outputContainer; // Paper - Add missing InventoryHolders - move down
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftLoomView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftLoomView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryLoom inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryLoom(this.inputContainer, this.outputContainer);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftLoomView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public LoomMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -61,6 +_,28 @@
+     public LoomMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+         super(MenuType.LOOM, containerId);
+         this.access = access;
++        // CraftBukkit start
++        this.inputContainer = new SimpleContainer(this.createBlockHolder(access), 3) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                super.setChanged();
++                LoomMenu.this.slotsChanged(this);
++                LoomMenu.this.slotUpdateListener.run();
++            }
++        };
++        this.outputContainer =  new SimpleContainer(this.createBlockHolder(access), 1) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                super.setChanged();
++                LoomMenu.this.slotUpdateListener.run();
++            }
++
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++        };
++        // CraftBukkit end
+         this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) {
+             @Override
+             public boolean mayPlace(ItemStack stack) {
+@@ -106,18 +_,44 @@
+         this.addStandardInventorySlots(playerInventory, 8, 84);
+         this.addDataSlot(this.selectedBannerPatternIndex);
+         this.patternGetter = playerInventory.player.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN);
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+     }
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.LOOM);
+     }
+ 
+     @Override
+     public boolean clickMenuButton(Player player, int id) {
+         if (id >= 0 && id < this.selectablePatterns.size()) {
+-            this.selectedBannerPatternIndex.set(id);
+-            this.setupResultSlot(this.selectablePatterns.get(id));
++            // Paper start - Add PlayerLoomPatternSelectEvent
++            int selectablePatternIndex = id;
++            io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((org.bukkit.entity.Player) player.getBukkitEntity(), ((org.bukkit.craftbukkit.inventory.CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex)));
++            if (!event.callEvent()) {
++                player.containerMenu.sendAllDataToRemote();
++                return false;
++            }
++            final Holder<BannerPattern> eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType());
++            Holder<BannerPattern> selectedPattern = null;
++            for (int i = 0; i < this.selectablePatterns.size(); i++) {
++                final Holder<BannerPattern> holder = this.selectablePatterns.get(i);
++                if (eventPattern.equals(holder)) {
++                    selectablePatternIndex = i;
++                    selectedPattern = holder;
++                    break;
++                }
++            }
++            if (selectedPattern == null) {
++                selectedPattern = eventPattern;
++                selectablePatternIndex = -1;
++            }
++
++            player.containerMenu.sendAllDataToRemote();
++            this.selectedBannerPatternIndex.set(selectablePatternIndex);
++            this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected"));
++            // Paper end - Add PlayerLoomPatternSelectEvent
+             return true;
+         } else {
+             return false;
+@@ -180,7 +_,8 @@
+                 this.resultSlot.set(ItemStack.EMPTY);
+             }
+ 
+-            this.broadcastChanges();
++            // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below
++            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent
+         } else {
+             this.resultSlot.set(ItemStack.EMPTY);
+             this.selectablePatterns = List.of();
+@@ -269,7 +_,14 @@
+             itemStack.update(
+                 DataComponents.BANNER_PATTERNS,
+                 BannerPatternLayers.EMPTY,
+-                bannerPatternLayers -> new BannerPatternLayers.Builder().addAll(bannerPatternLayers).add(pattern, dyeColor).build()
++                // CraftBukkit start
++                bannerPatternLayers -> {
++                    if (bannerPatternLayers.layers().size() > 20) {
++                        bannerPatternLayers = new BannerPatternLayers(List.copyOf(bannerPatternLayers.layers().subList(0, 20)));
++                    }
++                    return new BannerPatternLayers.Builder().addAll(bannerPatternLayers).add(pattern, dyeColor).build();
++                }
++                // CraftBukkit end
+             );
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
new file mode 100644
index 0000000000..f28a7e6d38
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/inventory/MenuType.java
++++ b/net/minecraft/world/inventory/MenuType.java
+@@ -26,7 +_,7 @@
+     public static final MenuType<FurnaceMenu> FURNACE = register("furnace", FurnaceMenu::new);
+     public static final MenuType<GrindstoneMenu> GRINDSTONE = register("grindstone", GrindstoneMenu::new);
+     public static final MenuType<HopperMenu> HOPPER = register("hopper", HopperMenu::new);
+-    public static final MenuType<LecternMenu> LECTERN = register("lectern", (containerId, playerInventory) -> new LecternMenu(containerId));
++    public static final MenuType<LecternMenu> LECTERN = register("lectern", LecternMenu::new); // CraftBukkit
+     public static final MenuType<LoomMenu> LOOM = register("loom", LoomMenu::new);
+     public static final MenuType<MerchantMenu> MERCHANT = register("merchant", MerchantMenu::new);
+     public static final MenuType<ShulkerBoxMenu> SHULKER_BOX = register("shulker_box", ShulkerBoxMenu::new);
+@@ -35,17 +_,17 @@
+     public static final MenuType<CartographyTableMenu> CARTOGRAPHY_TABLE = register("cartography_table", CartographyTableMenu::new);
+     public static final MenuType<StonecutterMenu> STONECUTTER = register("stonecutter", StonecutterMenu::new);
+     private final FeatureFlagSet requiredFeatures;
+-    private final MenuType.MenuSupplier<T> constructor;
++    private final MenuSupplier<T> constructor;
+ 
+-    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuType.MenuSupplier<T> factory) {
++    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuSupplier<T> factory) {
+         return Registry.register(BuiltInRegistries.MENU, key, new MenuType<>(factory, FeatureFlags.VANILLA_SET));
+     }
+ 
+-    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuType.MenuSupplier<T> factory, FeatureFlag... requiredFeatures) {
++    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuSupplier<T> factory, FeatureFlag... requiredFeatures) {
+         return Registry.register(BuiltInRegistries.MENU, key, new MenuType<>(factory, FeatureFlags.REGISTRY.subset(requiredFeatures)));
+     }
+ 
+-    private MenuType(MenuType.MenuSupplier<T> constructor, FeatureFlagSet requiredFeatures) {
++    private MenuType(MenuSupplier<T> constructor, FeatureFlagSet requiredFeatures) {
+         this.constructor = constructor;
+         this.requiredFeatures = requiredFeatures;
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
new file mode 100644
index 0000000000..a460afb9b4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/inventory/MerchantContainer.java
++++ b/net/minecraft/world/inventory/MerchantContainer.java
+@@ -17,6 +_,45 @@
+     private MerchantOffer activeOffer;
+     public int selectionHint;
+     private int futureXp;
++    // CraftBukkit start - add fields and methods
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
++    private int maxStack = MAX_STACK;
++
++    public java.util.List<ItemStack> getContents() {
++        return this.itemStacks;
++    }
++
++    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);
++        this.merchant.setTradingPlayer((Player) null); // SPIGOT-4860
++    }
++
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int i) {
++        this.maxStack = i;
++    }
++
++    public org.bukkit.inventory.InventoryHolder getOwner() {
++        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) ? (org.bukkit.craftbukkit.entity.CraftAbstractVillager) ((net.minecraft.world.entity.npc.AbstractVillager) this.merchant).getBukkitEntity() : null;
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager) ? ((net.minecraft.world.entity.npc.AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations
++    }
++    // CraftBukkit end
+ 
+     public MerchantContainer(Merchant merchant) {
+         this.merchant = merchant;
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch
new file mode 100644
index 0000000000..d3c402326c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantMenu.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/inventory/MerchantMenu.java
++++ b/net/minecraft/world/inventory/MerchantMenu.java
+@@ -30,6 +_,18 @@
+     private int merchantLevel;
+     private boolean showProgressBar;
+     private boolean canRestock;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftMerchantView bukkitEntity = null;
++    private Inventory player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftMerchantView getBukkitView() {
++        if (this.bukkitEntity == null) {
++            this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftMerchantView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(this.trader, this.tradeContainer), this, this.trader);
++        }
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public MerchantMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, new ClientSideMerchant(playerInventory.player));
+@@ -42,6 +_,7 @@
+         this.addSlot(new Slot(this.tradeContainer, 0, 136, 37));
+         this.addSlot(new Slot(this.tradeContainer, 1, 162, 37));
+         this.addSlot(new MerchantResultSlot(playerInventory.player, trader, this.tradeContainer, 2, 220, 37));
++        this.player = playerInventory; // CraftBukkit - save player
+         this.addStandardInventorySlots(playerInventory, 108, 84);
+     }
+ 
+@@ -105,12 +_,12 @@
+             ItemStack item = slot.getItem();
+             itemStack = item.copy();
+             if (index == 2) {
+-                if (!this.moveItemStackTo(item, 3, 39, true)) {
++                if (!this.moveItemStackTo(item, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+                     return ItemStack.EMPTY;
+                 }
+ 
+-                slot.onQuickCraft(item, itemStack);
+-                this.playTradeSound();
++                // slot.onQuickCraft(item, itemStack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call
++                // this.playTradeSound();
+             } else if (index != 0 && index != 1) {
+                 if (index >= 3 && index < 30) {
+                     if (!this.moveItemStackTo(item, 30, 39, false)) {
+@@ -123,6 +_,7 @@
+                 return ItemStack.EMPTY;
+             }
+ 
++            if (index != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2
+             if (item.isEmpty()) {
+                 slot.setByPlayer(ItemStack.EMPTY);
+             } else {
+@@ -134,13 +_,28 @@
+             }
+ 
+             slot.onTake(player, item);
++            } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2
++            if (index == 2) { // is merchant result slot
++                slot.onTake(player, item);
++                if (item.isEmpty()) {
++                    slot.set(ItemStack.EMPTY);
++                    return ItemStack.EMPTY;
++                }
++
++                this.moveItemStackTo(item, 3, 39, true, false); // This should always succeed because it's checked above
++
++                slot.onQuickCraft(item, itemStack);
++                this.playTradeSound();
++                slot.set(ItemStack.EMPTY); // item should ALWAYS be empty
++            }
++            // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
+         }
+ 
+         return itemStack;
+     }
+ 
+     private void playTradeSound() {
+-        if (!this.trader.isClientSide()) {
++        if (!this.trader.isClientSide() && this.trader instanceof Entity) { // CraftBukkit - SPIGOT-5035
+             Entity entity = (Entity)this.trader;
+             entity.level()
+                 .playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantResultSlot.java.patch
rename to paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch
index f363fb2ef5..ceb2902468 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantResultSlot.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantResultSlot.java.patch
@@ -1,19 +1,19 @@
 --- a/net/minecraft/world/inventory/MerchantResultSlot.java
 +++ b/net/minecraft/world/inventory/MerchantResultSlot.java
-@@ -47,13 +47,32 @@
+@@ -47,13 +_,32 @@
  
      @Override
      public void onTake(Player player, ItemStack stack) {
 -        this.checkTakeAchievements(stack);
 +        // this.checkTakeAchievements(stack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; move to after event is called and not cancelled
-         MerchantOffer merchantOffer = this.slots.getActiveOffer();
+         MerchantOffer activeOffer = this.slots.getActiveOffer();
 +        // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
 +        io.papermc.paper.event.player.PlayerPurchaseEvent event = null;
-+        if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
++        if (activeOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
 +            if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) {
-+                event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true);
++                event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), activeOffer.asBukkit(), true, true);
 +            } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) {
-+                event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true);
++                event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), activeOffer.asBukkit(), false, true);
 +            }
 +            if (event != null) {
 +                if (!event.callEvent()) {
@@ -21,17 +21,17 @@
 +                    event.getPlayer().updateInventory();
 +                    return;
 +                }
-+                merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft();
++                activeOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft();
 +            }
 +        }
 +        this.checkTakeAchievements(stack);
 +        // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-         if (merchantOffer != null) {
-             ItemStack itemStack = this.slots.getItem(0);
-             ItemStack itemStack2 = this.slots.getItem(1);
-             if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) {
--                this.merchant.notifyTrade(merchantOffer);
-+                this.merchant.processTrade(merchantOffer, event); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
+         if (activeOffer != null) {
+             ItemStack item = this.slots.getItem(0);
+             ItemStack item1 = this.slots.getItem(1);
+             if (activeOffer.take(item, item1) || activeOffer.take(item1, item)) {
+-                this.merchant.notifyTrade(activeOffer);
++                this.merchant.processTrade(activeOffer, event); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
                  player.awardStat(Stats.TRADED_WITH_VILLAGER);
-                 this.slots.setItem(0, itemStack);
-                 this.slots.setItem(1, itemStack2);
+                 this.slots.setItem(0, item);
+                 this.slots.setItem(1, item1);
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
new file mode 100644
index 0000000000..62ebcb22a9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
++++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+@@ -12,9 +_,22 @@
+ public class PlayerEnderChestContainer extends SimpleContainer {
+     @Nullable
+     private EnderChestBlockEntity activeChest;
+-
+-    public PlayerEnderChestContainer() {
++    // CraftBukkit start
++    private final Player owner;
++
++    public org.bukkit.inventory.InventoryHolder getBukkitOwner() {
++        return this.owner.getBukkitEntity();
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        return this.activeChest != null ? org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.activeChest.getBlockPos(), this.activeChest.getLevel().getWorld()) : null;
++    }
++
++    public PlayerEnderChestContainer(Player owner) {
+         super(27);
++        this.owner = owner;
++        // CraftBukkit end
+     }
+ 
+     public void setActiveChest(EnderChestBlockEntity enderChestBlockEntity) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ResultContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/inventory/ResultContainer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
index a26bc470cf..da8f31ffe7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ResultContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
@@ -1,21 +1,9 @@
 --- a/net/minecraft/world/inventory/ResultContainer.java
 +++ b/net/minecraft/world/inventory/ResultContainer.java
-@@ -9,12 +9,64 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.crafting.RecipeHolder;
- 
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
- public class ResultContainer implements Container, RecipeCraftingHolder {
- 
-     private final NonNullList<ItemStack> itemStacks;
+@@ -12,6 +_,53 @@
+     private final NonNullList<ItemStack> itemStacks = NonNullList.withSize(1, ItemStack.EMPTY);
      @Nullable
      private RecipeHolder<?> recipeUsed;
- 
 +    // CraftBukkit start
 +    private int maxStack = MAX_STACK;
 +
@@ -33,10 +21,10 @@
 +    }
 +
 +    // Don't need a transaction; the InventoryCrafting keeps track of it for us
-+    public void onOpen(CraftHumanEntity who) {}
-+    public void onClose(CraftHumanEntity who) {}
-+    public java.util.List<HumanEntity> getViewers() {
-+        return new java.util.ArrayList<HumanEntity>();
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {}
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {}
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++        return new java.util.ArrayList<>();
 +    }
 +
 +    @Override
@@ -49,7 +37,7 @@
 +    }
 +
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        return null;
 +    }
 +    // CraftBukkit end
@@ -57,11 +45,12 @@
 +    private @Nullable java.util.function.Supplier<? extends org.bukkit.inventory.InventoryHolder> holderCreator;
 +    private @Nullable org.bukkit.inventory.InventoryHolder holder;
 +    public ResultContainer(java.util.function.Supplier<? extends org.bukkit.inventory.InventoryHolder> holderCreator) {
-+        this();
 +        this.holderCreator = holderCreator;
 +    }
-+    // Paper end - Add missing InventoryHolders
 +
-     public ResultContainer() {
-         this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY);
-     }
++    public ResultContainer() {
++    }
++    // Paper end - Add missing InventoryHolders
+ 
+     @Override
+     public int getContainerSize() {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
new file mode 100644
index 0000000000..a65b4c24dc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/inventory/ShulkerBoxMenu.java
++++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java
+@@ -9,6 +_,20 @@
+ public class ShulkerBoxMenu extends AbstractContainerMenu {
+     private static final int CONTAINER_SIZE = 27;
+     private final Container container;
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
++    private Inventory player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventory(this.container), this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public ShulkerBoxMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, new SimpleContainer(27));
+@@ -18,6 +_,7 @@
+         super(MenuType.SHULKER_BOX, containerId);
+         checkContainerSize(container, 27);
+         this.container = container;
++        this.player = playerInventory; // CraftBukkit - save player
+         container.startOpen(playerInventory.player);
+         int i = 3;
+         int i1 = 9;
+@@ -33,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return this.container.stillValid(player);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch
new file mode 100644
index 0000000000..1277f3d7ac
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/SmithingMenu.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/inventory/SmithingMenu.java
++++ b/net/minecraft/world/inventory/SmithingMenu.java
+@@ -32,6 +_,9 @@
+     private final RecipePropertySet templateItemTest;
+     private final RecipePropertySet additionItemTest;
+     private final DataSlot hasRecipeError = DataSlot.standalone();
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
++    // CraftBukkit end
+ 
+     public SmithingMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -99,6 +_,7 @@
+         if (this.level instanceof ServerLevel) {
+             boolean flag = this.getSlot(0).hasItem() && this.getSlot(1).hasItem() && this.getSlot(2).hasItem() && !this.getSlot(this.getResultSlot()).hasItem();
+             this.hasRecipeError.set(flag ? 1 : 0);
++            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
+         }
+     }
+ 
+@@ -115,7 +_,9 @@
+         recipeFor.ifPresentOrElse(recipe -> {
+             ItemStack itemStack = recipe.value().assemble(smithingRecipeInput, this.level.registryAccess());
+             this.resultSlots.setRecipeUsed((RecipeHolder<?>)recipe);
+-            this.resultSlots.setItem(0, itemStack);
++            // CraftBukkit start
++            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(this.getBukkitView(), itemStack);
++            // CraftBukkit end
+         }, () -> {
+             this.resultSlots.setRecipeUsed(null);
+             this.resultSlots.setItem(0, ItemStack.EMPTY);
+@@ -137,4 +_,18 @@
+     public boolean hasRecipeError() {
+         return this.hasRecipeError.get() > 0;
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
++        }
++
++        org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventorySmithing(
++                this.access.getLocation(), this.inputSlots, this.resultSlots);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
new file mode 100644
index 0000000000..4f3754e2e4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/world/inventory/StonecutterMenu.java
++++ b/net/minecraft/world/inventory/StonecutterMenu.java
+@@ -25,7 +_,7 @@
+     private static final int USE_ROW_SLOT_START = 29;
+     private static final int USE_ROW_SLOT_END = 38;
+     private final ContainerLevelAccess access;
+-    final DataSlot selectedRecipeIndex = DataSlot.standalone();
++    final DataSlot selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent
+     private final Level level;
+     private SelectableRecipe.SingleInputSet<StonecutterRecipe> recipesForInput = SelectableRecipe.SingleInputSet.empty();
+     private ItemStack input = ItemStack.EMPTY;
+@@ -33,15 +_,23 @@
+     final Slot inputSlot;
+     final Slot resultSlot;
+     Runnable slotUpdateListener = () -> {};
+-    public final Container container = new SimpleContainer(1) {
+-        @Override
+-        public void setChanged() {
+-            super.setChanged();
+-            StonecutterMenu.this.slotsChanged(this);
+-            StonecutterMenu.this.slotUpdateListener.run();
++    public final Container container; // Paper - Add missing InventoryHolders - move down
++    final ResultContainer resultContainer; // Paper - Add missing InventoryHolders - move down
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.view.CraftStonecutterView bukkitEntity = null;
++    private org.bukkit.entity.Player player;
++
++    @Override
++    public org.bukkit.craftbukkit.inventory.view.CraftStonecutterView getBukkitView() {
++        if (this.bukkitEntity != null) {
++            return this.bukkitEntity;
+         }
+-    };
+-    final ResultContainer resultContainer = new ResultContainer();
++
++        org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter(this.container, this.resultContainer);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftStonecutterView(this.player, inventory, this);
++        return this.bukkitEntity;
++    }
++    // CraftBukkit end
+ 
+     public StonecutterMenu(int containerId, Inventory playerInventory) {
+         this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -51,6 +_,23 @@
+         super(MenuType.STONECUTTER, containerId);
+         this.access = access;
+         this.level = playerInventory.player.level();
++        // Paper start
++        this.container = new SimpleContainer(this.createBlockHolder(access), 1) { // Paper - Add missing InventoryHolders
++            @Override
++            public void setChanged() {
++                super.setChanged();
++                StonecutterMenu.this.slotsChanged(this);
++                StonecutterMenu.this.slotUpdateListener.run();
++            }
++            // CraftBukkit start
++            @Override
++            public org.bukkit.Location getLocation() {
++                return access.getLocation();
++            }
++            // CraftBukkit end
++        };
++        this.resultContainer = new ResultContainer(this.createBlockHolder(access)); // Paper - Add missing InventoryHolders
++        // Paper end
+         this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33));
+         this.resultSlot = this.addSlot(new Slot(this.resultContainer, 1, 143, 33) {
+             @Override
+@@ -83,6 +_,7 @@
+         });
+         this.addStandardInventorySlots(playerInventory, 8, 84);
+         this.addDataSlot(this.selectedRecipeIndex);
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+     }
+ 
+     public int getSelectedRecipeIndex() {
+@@ -103,6 +_,7 @@
+ 
+     @Override
+     public boolean stillValid(Player player) {
++        if (!this.checkReachable) return true; // CraftBukkit
+         return stillValid(this.access, player, Blocks.STONECUTTER);
+     }
+ 
+@@ -112,8 +_,34 @@
+             return false;
+         } else {
+             if (this.isValidRecipeIndex(id)) {
+-                this.selectedRecipeIndex.set(id);
+-                this.setupResultSlot(id);
++                // Paper start - Add PlayerStonecutterRecipeSelectEvent
++                int recipeIndex = id;
++                this.selectedRecipeIndex.set(recipeIndex);
++                this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed
++                paperEventBlock: if (this.isValidRecipeIndex(id)) {
++                    final Optional<RecipeHolder<StonecutterRecipe>> recipe = this.recipesForInput.entries().get(id).recipe().recipe();
++                    if (recipe.isEmpty()) break paperEventBlock; // The recipe selected does not have an actual server recipe (presumably its the empty one). Cannot call the event, just break.
++
++                    io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((org.bukkit.entity.Player) player.getBukkitEntity(), getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) recipe.get().toBukkitRecipe());
++                    if (!event.callEvent()) {
++                        player.containerMenu.sendAllDataToRemote();
++                        return false;
++                    }
++
++                    net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey());
++                    if (!recipe.get().id().location().equals(key)) { // If the recipe did NOT stay the same
++                        for (int newRecipeIndex = 0; newRecipeIndex < this.recipesForInput.entries().size(); newRecipeIndex++) {
++                            if (this.recipesForInput.entries().get(newRecipeIndex).recipe().recipe().filter(r -> r.id().location().equals(key)).isPresent()) {
++                                recipeIndex = newRecipeIndex;
++                                break;
++                            }
++                        }
++                    }
++                }
++                player.containerMenu.sendAllDataToRemote();
++                this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it
++                this.setupResultSlot(recipeIndex);
++                // Paper end - Add PlayerStonecutterRecipeSelectEvent
+             }
+ 
+             return true;
+@@ -131,6 +_,7 @@
+             this.input = item.copy();
+             this.setupRecipeList(item);
+         }
++        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
+     }
+ 
+     private void setupRecipeList(ItemStack stack) {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
new file mode 100644
index 0000000000..bc3a488239
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/world/inventory/TransientCraftingContainer.java
++++ b/net/minecraft/world/inventory/TransientCraftingContainer.java
+@@ -13,6 +_,68 @@
+     private final int height;
+     private final AbstractContainerMenu menu;
+ 
++    // CraftBukkit start - add fields
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++    private net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe;
++    public net.minecraft.world.Container resultInventory;
++    private Player owner;
++    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 org.bukkit.event.inventory.InventoryType getInvType() {
++        return this.items.size() == 4 ? org.bukkit.event.inventory.InventoryType.CRAFTING : org.bukkit.event.inventory.InventoryType.WORKBENCH;
++    }
++
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.remove(who);
++    }
++
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    public org.bukkit.inventory.InventoryHolder getOwner() {
++        return (this.owner == null) ? null : this.owner.getBukkitEntity();
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++        this.resultInventory.setMaxStackSize(size);
++    }
++
++    @Override
++    public org.bukkit.Location getLocation() {
++        return this.menu instanceof CraftingMenu ? ((CraftingMenu) this.menu).access.getLocation() : this.owner.getBukkitEntity().getLocation();
++    }
++
++    @Override
++    public net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++        return this.currentRecipe;
++    }
++
++    @Override
++    public void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe) {
++        this.currentRecipe = currentRecipe;
++    }
++
++    public TransientCraftingContainer(AbstractContainerMenu menu, int width, int height, Player player) {
++        this(menu, width, height);
++        this.owner = player;
++    }
++    // CraftBukkit end
++
+     public TransientCraftingContainer(AbstractContainerMenu menu, int width, int height) {
+         this(menu, width, height, NonNullList.withSize(width * height, ItemStack.EMPTY));
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
deleted file mode 100644
index a0c2f5bf6b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
+++ /dev/null
@@ -1,310 +0,0 @@
---- a/net/minecraft/world/inventory/AbstractContainerMenu.java
-+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
-@@ -21,6 +21,8 @@
- import net.minecraft.ReportedException;
- import net.minecraft.core.NonNullList;
- import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.network.chat.Component;
-+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.Mth;
- import net.minecraft.world.Container;
-@@ -35,6 +37,18 @@
- import net.minecraft.world.level.block.entity.BlockEntity;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import com.google.common.base.Preconditions;
-+import java.util.HashMap;
-+import java.util.Map;
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.Event.Result;
-+import org.bukkit.event.inventory.InventoryDragEvent;
-+import org.bukkit.event.inventory.InventoryType;
-+import org.bukkit.inventory.InventoryView;
-+// CraftBukkit end
-+
- public abstract class AbstractContainerMenu {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -67,6 +81,32 @@
-     private ContainerSynchronizer synchronizer;
-     private boolean suppressRemoteUpdates;
- 
-+    // CraftBukkit start
-+    public boolean checkReachable = true;
-+    public abstract InventoryView getBukkitView();
-+    public void transferTo(AbstractContainerMenu other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
-+        InventoryView source = this.getBukkitView(), destination = other.getBukkitView();
-+        ((CraftInventory) source.getTopInventory()).getInventory().onClose(player);
-+        ((CraftInventory) source.getBottomInventory()).getInventory().onClose(player);
-+        ((CraftInventory) destination.getTopInventory()).getInventory().onOpen(player);
-+        ((CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player);
-+    }
-+    private Component title;
-+    public final Component getTitle() {
-+        // Paper start - return chat component with empty text instead of throwing error
-+        // Preconditions.checkState(this.title != null, "Title not set");
-+        if (this.title == null){
-+            return Component.literal("");
-+        }
-+        // Paper end - return chat component with empty text instead of throwing error
-+        return this.title;
-+    }
-+    public final void setTitle(Component title) {
-+        Preconditions.checkState(this.title == null, "Title already set");
-+        this.title = title;
-+    }
-+    // CraftBukkit end
-+
-     protected AbstractContainerMenu(@Nullable MenuType<?> type, int syncId) {
-         this.carried = ItemStack.EMPTY;
-         this.remoteSlots = NonNullList.create();
-@@ -188,10 +228,20 @@
- 
-         if (this.synchronizer != null) {
-             this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray());
-+            this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot
-         }
- 
-     }
- 
-+    // CraftBukkit start
-+    public void broadcastCarriedItem() {
-+        this.remoteCarried = this.getCarried().copy();
-+        if (this.synchronizer != null) {
-+            this.synchronizer.sendCarriedChange(this, this.remoteCarried);
-+        }
-+    }
-+    // CraftBukkit end
-+
-     public void removeSlotListener(ContainerListener listener) {
-         this.containerListeners.remove(listener);
-     }
-@@ -281,7 +331,7 @@
-             while (iterator.hasNext()) {
-                 ContainerListener icrafting = (ContainerListener) iterator.next();
- 
--                icrafting.slotChanged(this, slot, itemstack2);
-+                icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent
-             }
-         }
- 
-@@ -410,6 +460,7 @@
-                     this.resetQuickCraft();
-                 }
-             } else if (this.quickcraftStatus == 1) {
-+                if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks
-                 slot = (Slot) this.slots.get(slotIndex);
-                 itemstack = this.getCarried();
-                 if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) {
-@@ -417,7 +468,7 @@
-                 }
-             } else if (this.quickcraftStatus == 2) {
-                 if (!this.quickcraftSlots.isEmpty()) {
--                    if (this.quickcraftSlots.size() == 1) {
-+                    if (this.quickcraftSlots.size() == 1) { // Paper - Fix CraftBukkit drag system
-                         k = ((Slot) this.quickcraftSlots.iterator().next()).index;
-                         this.resetQuickCraft();
-                         this.doClick(k, this.quickcraftType, ClickType.PICKUP, player);
-@@ -433,6 +484,7 @@
-                     l = this.getCarried().getCount();
-                     Iterator iterator = this.quickcraftSlots.iterator();
- 
-+                    Map<Integer, ItemStack> draggedSlots = new HashMap<Integer, ItemStack>(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack)
-                     while (iterator.hasNext()) {
-                         Slot slot1 = (Slot) iterator.next();
-                         ItemStack itemstack2 = this.getCarried();
-@@ -443,12 +495,48 @@
-                             int l1 = Math.min(AbstractContainerMenu.getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemstack1) + j1, k1);
- 
-                             l -= l1 - j1;
--                            slot1.setByPlayer(itemstack1.copyWithCount(l1));
-+                            // slot1.setByPlayer(itemstack1.copyWithCount(l1));
-+                            draggedSlots.put(slot1.index, itemstack1.copyWithCount(l1)); // CraftBukkit - Put in map instead of setting
-                         }
-                     }
- 
--                    itemstack1.setCount(l);
--                    this.setCarried(itemstack1);
-+                    // CraftBukkit start - InventoryDragEvent
-+                    InventoryView view = this.getBukkitView();
-+                    org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack1);
-+                    newcursor.setAmount(l);
-+                    Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new HashMap<Integer, org.bukkit.inventory.ItemStack>();
-+                    for (Map.Entry<Integer, ItemStack> ditem : draggedSlots.entrySet()) {
-+                        eventmap.put(ditem.getKey(), CraftItemStack.asBukkitCopy(ditem.getValue()));
-+                    }
-+
-+                    // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory.
-+                    ItemStack oldCursor = this.getCarried();
-+                    this.setCarried(CraftItemStack.asNMSCopy(newcursor));
-+
-+                    InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.quickcraftType == 1, eventmap);
-+                    player.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                    // Whether or not a change was made to the inventory that requires an update.
-+                    boolean needsUpdate = event.getResult() != Result.DEFAULT;
-+
-+                    if (event.getResult() != Result.DENY) {
-+                        for (Map.Entry<Integer, ItemStack> dslot : draggedSlots.entrySet()) {
-+                            view.setItem(dslot.getKey(), CraftItemStack.asBukkitCopy(dslot.getValue()));
-+                        }
-+                        // The only time the carried item will be set to null is if the inventory is closed by the server.
-+                        // If the inventory is closed by the server, then the cursor items are dropped.  This is why we change the cursor early.
-+                        if (this.getCarried() != null) {
-+                            this.setCarried(CraftItemStack.asNMSCopy(event.getCursor()));
-+                            needsUpdate = true;
-+                        }
-+                    } else {
-+                        this.setCarried(oldCursor);
-+                    }
-+
-+                    if (needsUpdate && player instanceof ServerPlayer) {
-+                        this.sendAllDataToRemote();
-+                    }
-+                    // CraftBukkit end
-                 }
- 
-                 this.resetQuickCraft();
-@@ -466,8 +554,11 @@
-                 if (slotIndex == -999) {
-                     if (!this.getCarried().isEmpty()) {
-                         if (clickaction == ClickAction.PRIMARY) {
--                            player.drop(this.getCarried(), true);
-+                            // CraftBukkit start
-+                            ItemStack carried = this.getCarried();
-                             this.setCarried(ItemStack.EMPTY);
-+                            player.drop(carried, true);
-+                            // CraftBukkit start
-                         } else {
-                             player.drop(this.getCarried().split(1), true);
-                         }
-@@ -530,11 +621,21 @@
-                     }
- 
-                     slot.setChanged();
-+                    // CraftBukkit start - Make sure the client has the right slot contents
-+                    if (player instanceof ServerPlayer && slot.getMaxStackSize() != 64) {
-+                        ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem()));
-+                        // Updating a crafting inventory makes the client reset the result slot, have to send it again
-+                        if (this.getBukkitView().getType() == InventoryType.WORKBENCH || this.getBukkitView().getType() == InventoryType.CRAFTING) {
-+                            ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem()));
-+                        }
-+                    }
-+                    // CraftBukkit end
-                 }
-             } else {
-                 int j2;
- 
-                 if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) {
-+                    if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks
-                     ItemStack itemstack4 = playerinventory.getItem(button);
- 
-                     slot = (Slot) this.slots.get(slotIndex);
-@@ -662,8 +763,9 @@
-             ItemStack itemstack = this.getCarried();
- 
-             if (!itemstack.isEmpty()) {
-+                this.setCarried(ItemStack.EMPTY); // CraftBukkit - SPIGOT-4556 - from below
-                 AbstractContainerMenu.dropOrPlaceInInventory(player, itemstack);
--                this.setCarried(ItemStack.EMPTY);
-+                // this.setCarried(ItemStack.EMPTY); // CraftBukkit - moved up
-             }
- 
-         }
-@@ -729,6 +831,14 @@
-     public abstract boolean stillValid(Player player);
- 
-     protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) {
-+        // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
-+        return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false);
-+    }
-+    protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) {
-+        if (isCheck) {
-+            stack = stack.copy();
-+        }
-+        // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-         boolean flag1 = false;
-         int k = startIndex;
- 
-@@ -752,6 +862,11 @@
- 
-                 slot = (Slot) this.slots.get(k);
-                 itemstack1 = slot.getItem();
-+                // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; clone if only a check
-+                if (isCheck) {
-+                    itemstack1 = itemstack1.copy();
-+                }
-+                // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-                 if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(stack, itemstack1)) {
-                     l = itemstack1.getCount() + stack.getCount();
-                     int i1 = slot.getMaxStackSize(itemstack1);
-@@ -759,12 +874,16 @@
-                     if (l <= i1) {
-                         stack.setCount(0);
-                         itemstack1.setCount(l);
-+                        if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                         slot.setChanged();
-+                        } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                         flag1 = true;
-                     } else if (itemstack1.getCount() < i1) {
-                         stack.shrink(i1 - itemstack1.getCount());
-                         itemstack1.setCount(i1);
-+                        if (!isCheck) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                         slot.setChanged();
-+                        } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                         flag1 = true;
-                     }
-                 }
-@@ -795,10 +914,21 @@
- 
-                 slot = (Slot) this.slots.get(k);
-                 itemstack1 = slot.getItem();
-+                // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
-+                if (isCheck) {
-+                    itemstack1 = itemstack1.copy();
-+                }
-+                // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-                 if (itemstack1.isEmpty() && slot.mayPlace(stack)) {
-                     l = slot.getMaxStackSize(stack);
-+                    // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent
-+                    if (isCheck) {
-+                        stack.shrink(Math.min(stack.getCount(), l));
-+                    } else {
-+                    // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-                     slot.setByPlayer(stack.split(Math.min(stack.getCount(), l)));
-                     slot.setChanged();
-+                    } // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                     flag1 = true;
-                     break;
-                 }
-@@ -893,6 +1023,11 @@
-     }
- 
-     public ItemStack getCarried() {
-+        // CraftBukkit start
-+        if (this.carried.isEmpty()) {
-+            this.setCarried(ItemStack.EMPTY);
-+        }
-+        // CraftBukkit end
-         return this.carried;
-     }
- 
-@@ -947,4 +1082,15 @@
-         this.stateId = this.stateId + 1 & 32767;
-         return this.stateId;
-     }
-+
-+    // Paper start - Add missing InventoryHolders
-+    // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question
-+    // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily
-+    // initializes the InventoryHolder safely.
-+    protected final Supplier<org.bukkit.inventory.BlockInventoryHolder> createBlockHolder(final ContainerLevelAccess context) {
-+        //noinspection ConstantValue
-+        Preconditions.checkArgument(context != null, "context was null");
-+        return () -> context.createBlockHolder(this);
-+    }
-+    // Paper end - Add missing InventoryHolders
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
deleted file mode 100644
index bda213246a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/net/minecraft/world/inventory/AbstractCraftingMenu.java
-+++ b/net/minecraft/world/inventory/AbstractCraftingMenu.java
-@@ -13,14 +13,17 @@
- 
-     private final int width;
-     private final int height;
--    public final CraftingContainer craftSlots;
-+    public final TransientCraftingContainer craftSlots; // CraftBukkit
-     public final ResultContainer resultSlots = new ResultContainer();
- 
--    public AbstractCraftingMenu(MenuType<?> type, int syncId, int width, int height) {
--        super(type, syncId);
--        this.width = width;
--        this.height = height;
--        this.craftSlots = new TransientCraftingContainer(this, width, height);
-+    public AbstractCraftingMenu(MenuType<?> containers, int i, int j, int k, Inventory playerInventory) { // CraftBukkit
-+        super(containers, i);
-+        this.width = j;
-+        this.height = k;
-+        // CraftBukkit start
-+        this.craftSlots = new TransientCraftingContainer(this, j, k, playerInventory.player); // CraftBukkit - pass player
-+        this.craftSlots.resultInventory = this.resultSlots; // CraftBukkit - let InventoryCrafting know about its result slot
-+        // CraftBukkit end
-     }
- 
-     protected Slot addResultSlot(Player player, int x, int y) {
-@@ -38,7 +41,7 @@
- 
-     @Override
-     public RecipeBookMenu.PostPlaceAction handlePlacement(boolean craftAll, boolean creative, RecipeHolder<?> recipe, ServerLevel world, Inventory inventory) {
--        RecipeHolder<CraftingRecipe> recipeholder1 = recipe;
-+        RecipeHolder<CraftingRecipe> recipeholder1 = (RecipeHolder<CraftingRecipe>) recipe; // CraftBukkit - decompile error
- 
-         this.beginPlacingRecipe();
- 
-@@ -65,7 +68,7 @@
-                 }
-             }, this.width, this.height, list, list, inventory, recipeholder1, craftAll, creative);
-         } finally {
--            this.finishPlacingRecipe(world, recipe);
-+            this.finishPlacingRecipe(world, recipeholder1); // CraftBukkit - decompile error
-         }
- 
-         return containerrecipebook_a;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
deleted file mode 100644
index 42a568418f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
+++ /dev/null
@@ -1,60 +0,0 @@
---- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java
-+++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java
-@@ -17,6 +17,10 @@
- import net.minecraft.world.item.crafting.RecipeType;
- import net.minecraft.world.item.crafting.SingleRecipeInput;
- import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryFurnace;
-+import org.bukkit.craftbukkit.inventory.view.CraftFurnaceView;
-+// CraftBukkit end
- 
- public abstract class AbstractFurnaceMenu extends RecipeBookMenu {
- 
-@@ -36,6 +40,22 @@
-     private final RecipePropertySet acceptedInputs;
-     private final RecipeBookType recipeBookType;
- 
-+    // CraftBukkit start
-+    private CraftFurnaceView bukkitEntity = null;
-+    private Inventory player;
-+
-+    @Override
-+    public CraftFurnaceView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryFurnace inventory = new CraftInventoryFurnace((AbstractFurnaceBlockEntity) this.container);
-+        this.bukkitEntity = new CraftFurnaceView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     protected AbstractFurnaceMenu(MenuType<?> type, RecipeType<? extends AbstractCookingRecipe> recipeType, ResourceKey<RecipePropertySet> recipePropertySetKey, RecipeBookType category, int syncId, Inventory platerInventory) {
-         this(type, recipeType, recipePropertySetKey, category, syncId, platerInventory, new SimpleContainer(3), new SimpleContainerData(4));
-     }
-@@ -53,6 +73,7 @@
-         this.addSlot(new Slot(inventory, 0, 56, 17));
-         this.addSlot(new FurnaceFuelSlot(this, inventory, 1, 56, 53));
-         this.addSlot(new FurnaceResultSlot(platerInventory.player, inventory, 2, 116, 35));
-+        this.player = platerInventory; // CraftBukkit - save player
-         this.addStandardInventorySlots(platerInventory, 8, 84);
-         this.addDataSlots(propertyDelegate);
-     }
-@@ -71,6 +92,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.container.stillValid(player);
-     }
- 
-@@ -180,6 +202,6 @@
-             public boolean recipeMatches(RecipeHolder<AbstractCookingRecipe> entry) {
-                 return ((AbstractCookingRecipe) entry.value()).matches(new SingleRecipeInput(AbstractFurnaceMenu.this.container.getItem(0)), world);
-             }
--        }, 1, 1, List.of(this.getSlot(0)), list, inventory, recipe, craftAll, creative);
-+        }, 1, 1, List.of(this.getSlot(0)), list, inventory, (RecipeHolder<AbstractCookingRecipe>) recipe, craftAll, creative); // CraftBukkit - decompile error
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/BeaconMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/BeaconMenu.java.patch
deleted file mode 100644
index 587f354b49..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/BeaconMenu.java.patch
+++ /dev/null
@@ -1,109 +0,0 @@
---- a/net/minecraft/world/inventory/BeaconMenu.java
-+++ b/net/minecraft/world/inventory/BeaconMenu.java
-@@ -8,10 +8,13 @@
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.effect.MobEffect;
-+import net.minecraft.world.entity.player.Inventory;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Blocks;
-+import org.bukkit.craftbukkit.inventory.view.CraftBeaconView;
-+// CraftBukkit end
- 
- public class BeaconMenu extends AbstractContainerMenu {
- 
-@@ -27,6 +30,10 @@
-     private final BeaconMenu.PaymentSlot paymentSlot;
-     private final ContainerLevelAccess access;
-     private final ContainerData beaconData;
-+    // CraftBukkit start
-+    private CraftBeaconView bukkitEntity = null;
-+    private Inventory player;
-+    // CraftBukkit end
- 
-     public BeaconMenu(int syncId, Container inventory) {
-         this(syncId, inventory, new SimpleContainerData(3), ContainerLevelAccess.NULL);
-@@ -34,7 +41,8 @@
- 
-     public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) {
-         super(MenuType.BEACON, syncId);
--        this.beacon = new SimpleContainer(this, 1) {
-+        this.player = (Inventory) inventory; // CraftBukkit - TODO: check this
-+        this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // CraftBukkit - decompile error // Paper - Add missing InventoryHolders
-             @Override
-             public boolean canPlaceItem(int slot, ItemStack stack) {
-                 return stack.is(ItemTags.BEACON_PAYMENT_ITEMS);
-@@ -44,6 +52,12 @@
-             public int getMaxStackSize() {
-                 return 1;
-             }
-+            // Paper start - Fix inventories returning null Locations
-+            @Override
-+            public org.bukkit.Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // Paper end - Fix inventories returning null Locations
-         };
-         checkContainerDataCount(propertyDelegate, 3);
-         this.beaconData = propertyDelegate;
-@@ -69,6 +83,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.BEACON);
-     }
- 
-@@ -148,12 +163,30 @@
-         return BeaconMenu.decodeEffect(this.beaconData.get(2));
-     }
- 
-+    // Paper start - Add PlayerChangeBeaconEffectEvent
-+    private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional<Holder<MobEffect>> optionalEffect) {
-+        return optionalEffect.map(org.bukkit.craftbukkit.potion.CraftPotionEffectType::minecraftHolderToBukkit).orElse(null);
-+    }
-+    // Paper end - Add PlayerChangeBeaconEffectEvent
-+
-     public void updateEffects(Optional<Holder<MobEffect>> primary, Optional<Holder<MobEffect>> secondary) {
-+        // Paper start - fix MC-174630 - validate secondary power
-+        if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) {
-+            secondary = Optional.empty();
-+        }
-+        // Paper end
-         if (this.paymentSlot.hasItem()) {
--            this.beaconData.set(1, BeaconMenu.encodeEffect((Holder) primary.orElse((Object) null)));
--            this.beaconData.set(2, BeaconMenu.encodeEffect((Holder) secondary.orElse((Object) null)));
-+            // Paper start - Add PlayerChangeBeaconEffectEvent
-+            io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock());
-+            if (event.callEvent()) {
-+                // Paper end - Add PlayerChangeBeaconEffectEvent
-+                this.beaconData.set(1, BeaconMenu.encodeEffect(event.getPrimary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getPrimary())));// CraftBukkit - decompile error
-+                this.beaconData.set(2, BeaconMenu.encodeEffect(event.getSecondary() == null ? null : org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(event.getSecondary())));// CraftBukkit - decompile error
-+            if (event.willConsumeItem()) { // Paper
-             this.paymentSlot.remove(1);
-+            } // Paper
-             this.access.execute(Level::blockEntityChanged);
-+            } // Paper end - Add PlayerChangeBeaconEffectEvent
-         }
- 
-     }
-@@ -178,4 +211,17 @@
-             return 1;
-         }
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftBeaconView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        org.bukkit.craftbukkit.inventory.CraftInventoryBeacon inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon(this.beacon);
-+        this.bukkitEntity = new CraftBeaconView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/BrewingStandMenu.java.patch
deleted file mode 100644
index 36dcdb7277..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/BrewingStandMenu.java.patch
+++ /dev/null
@@ -1,127 +0,0 @@
---- a/net/minecraft/world/inventory/BrewingStandMenu.java
-+++ b/net/minecraft/world/inventory/BrewingStandMenu.java
-@@ -16,6 +16,10 @@
- import net.minecraft.world.item.alchemy.Potion;
- import net.minecraft.world.item.alchemy.PotionBrewing;
- import net.minecraft.world.item.alchemy.PotionContents;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventoryBrewer;
-+import org.bukkit.craftbukkit.inventory.view.CraftBrewingStandView;
-+// CraftBukkit end
- 
- public class BrewingStandMenu extends AbstractContainerMenu {
- 
-@@ -35,29 +39,51 @@
-     public final ContainerData brewingStandData;
-     private final Slot ingredientSlot;
- 
-+    // CraftBukkit start
-+    private CraftBrewingStandView bukkitEntity = null;
-+    private Inventory player;
-+    // CraftBukkit end
-+
-     public BrewingStandMenu(int syncId, Inventory playerInventory) {
--        this(syncId, playerInventory, new SimpleContainer(5), new SimpleContainerData(2));
-+        this(syncId, playerInventory, new SimpleContainer(5), new io.papermc.paper.inventory.BrewingSimpleContainerData()); // Paper - Add totalBrewTime
-     }
- 
-     public BrewingStandMenu(int syncId, Inventory playerInventory, Container inventory, ContainerData propertyDelegate) {
-         super(MenuType.BREWING_STAND, syncId);
-+        this.player = playerInventory; // CraftBukkit
-         checkContainerSize(inventory, 5);
--        checkContainerDataCount(propertyDelegate, 2);
-+        checkContainerDataCount(propertyDelegate, 3); // Paper - Add recipeBrewTime
-         this.brewingStand = inventory;
-         this.brewingStandData = propertyDelegate;
-         PotionBrewing potionbrewer = playerInventory.player.level().potionBrewing();
- 
--        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51));
--        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58));
--        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51));
-+        // Paper start - custom potion mixes
-+        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51, potionbrewer));
-+        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58, potionbrewer));
-+        this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51, potionbrewer));
-+        // Paper end - custom potion mixes
-         this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionbrewer, inventory, 3, 79, 17));
-         this.addSlot(new BrewingStandMenu.FuelSlot(inventory, 4, 17, 17));
--        this.addDataSlots(propertyDelegate);
-+        // Paper start - Add recipeBrewTime
-+        this.addDataSlots(new SimpleContainerData(2) {
-+            @Override
-+            public int get(final int index) {
-+                if (index == 0) return 400 * propertyDelegate.get(index) / propertyDelegate.get(2);
-+                return propertyDelegate.get(index);
-+            }
-+
-+            @Override
-+            public void set(final int index, final int value) {
-+                propertyDelegate.set(index, value);
-+            }
-+        });
-+        // Paper end - Add recipeBrewTime
-         this.addStandardInventorySlots(playerInventory, 8, 84);
-     }
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.brewingStand.stillValid(player);
-     }
- 
-@@ -79,7 +105,7 @@
-                     if (!this.moveItemStackTo(itemstack1, 3, 4, false)) {
-                         return ItemStack.EMPTY;
-                     }
--                } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack)) {
-+                } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes
-                     if (!this.moveItemStackTo(itemstack1, 0, 3, false)) {
-                         return ItemStack.EMPTY;
-                     }
-@@ -128,13 +154,15 @@
- 
-     private static class PotionSlot extends Slot {
- 
--        public PotionSlot(Container inventory, int index, int x, int y) {
-+        private final PotionBrewing potionBrewing; // Paper - custom potion mixes
-+        public PotionSlot(Container inventory, int index, int x, int y, PotionBrewing potionBrewing) { // Paper - custom potion mixes
-             super(inventory, index, x, y);
-+            this.potionBrewing = potionBrewing; // Paper - custom potion mixes
-         }
- 
-         @Override
-         public boolean mayPlace(ItemStack stack) {
--            return PotionSlot.mayPlaceItem(stack);
-+            return PotionSlot.mayPlaceItem(stack, this.potionBrewing); // Paper - custom potion mixes
-         }
- 
-         @Override
-@@ -153,8 +181,8 @@
-             super.onTake(player, stack);
-         }
- 
--        public static boolean mayPlaceItem(ItemStack stack) {
--            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE);
-+        public static boolean mayPlaceItem(ItemStack stack, PotionBrewing potionBrewing) { // Paper - custom potion mixes
-+            return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack); // Paper - Custom Potion Mixes
-         }
- 
-         @Override
-@@ -198,4 +226,17 @@
-             return BrewingStandMenu.EMPTY_SLOT_FUEL;
-         }
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftBrewingStandView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryBrewer inventory = new CraftInventoryBrewer(this.brewingStand);
-+        this.bukkitEntity = new CraftBrewingStandView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/CartographyTableMenu.java.patch
deleted file mode 100644
index b46cd6b42c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/CartographyTableMenu.java.patch
+++ /dev/null
@@ -1,146 +0,0 @@
---- a/net/minecraft/world/inventory/CartographyTableMenu.java
-+++ b/net/minecraft/world/inventory/CartographyTableMenu.java
-@@ -6,16 +6,36 @@
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.MapItem;
- import net.minecraft.world.item.component.MapPostProcessing;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryCartography;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
- 
- public class CartographyTableMenu extends AbstractContainerMenu {
- 
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    private Player player;
-+
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryCartography inventory = new CraftInventoryCartography(this.container, this.resultContainer);
-+        this.bukkitEntity = new CraftInventoryView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-     public static final int MAP_SLOT = 0;
-     public static final int ADDITIONAL_SLOT = 1;
-     public static final int RESULT_SLOT = 2;
-@@ -34,28 +54,42 @@
- 
-     public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) {
-         super(MenuType.CARTOGRAPHY_TABLE, syncId);
--        this.container = new SimpleContainer(2) {
-+        this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 CartographyTableMenu.this.slotsChanged(this);
-                 super.setChanged();
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
--        this.resultContainer = new ResultContainer() {
-+        this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
--                CartographyTableMenu.this.slotsChanged(this);
-+                // CartographyTableMenu.this.slotsChanged(this); // Paper - Add CatographyItemEvent - do not recompute results if the result slot changes - allows to set the result slot via api
-                 super.setChanged();
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
-         this.access = context;
--        this.addSlot(new Slot(this, this.container, 0, 15, 15) {
-+        this.addSlot(new Slot(this.container, 0, 15, 15) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.has(DataComponents.MAP_ID);
-             }
-         });
--        this.addSlot(new Slot(this, this.container, 1, 15, 52) {
-+        this.addSlot(new Slot(this.container, 1, 15, 52) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.is(Items.PAPER) || stack.is(Items.MAP) || stack.is(Items.GLASS_PANE);
-@@ -68,7 +102,7 @@
-             }
- 
-             @Override
--            public void onTake(Player player, ItemStack stack) {
-+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
-                 ((Slot) CartographyTableMenu.this.slots.get(0)).remove(1);
-                 ((Slot) CartographyTableMenu.this.slots.get(1)).remove(1);
-                 stack.getItem().onCraftedBy(stack, player.level(), player);
-@@ -76,7 +110,7 @@
-                     long j = world.getGameTime();
- 
-                     if (CartographyTableMenu.this.lastSoundTime != j) {
--                        world.playSound((Player) null, blockposition, SoundEvents.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                        world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_CARTOGRAPHY_TABLE_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-                         CartographyTableMenu.this.lastSoundTime = j;
-                     }
- 
-@@ -85,10 +119,12 @@
-             }
-         });
-         this.addStandardInventorySlots(inventory, 8, 84);
-+        this.player = (Player) inventory.player.getBukkitEntity(); // CraftBukkit
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.CARTOGRAPHY_TABLE);
-     }
- 
-@@ -104,6 +140,7 @@
-             this.setupResultSlot(itemstack, itemstack1, itemstack2);
-         }
- 
-+        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
-     }
- 
-     private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) {
-@@ -147,7 +184,7 @@
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         ItemStack itemstack = ItemStack.EMPTY;
-         Slot slot1 = (Slot) this.slots.get(slot);
- 
-@@ -199,7 +236,7 @@
-     }
- 
-     @Override
--    public void removed(Player player) {
-+    public void removed(net.minecraft.world.entity.player.Player player) {
-         super.removed(player);
-         this.resultContainer.removeItemNoUpdate(2);
-         this.access.execute((world, blockposition) -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ChestMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/ChestMenu.java.patch
deleted file mode 100644
index 8ba0e1a6a1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ChestMenu.java.patch
+++ /dev/null
@@ -1,64 +0,0 @@
---- a/net/minecraft/world/inventory/ChestMenu.java
-+++ b/net/minecraft/world/inventory/ChestMenu.java
-@@ -1,16 +1,43 @@
- package net.minecraft.world.inventory;
- 
-+import net.minecraft.world.CompoundContainer;
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.player.Inventory;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
- 
- public class ChestMenu extends AbstractContainerMenu {
- 
-     private final Container container;
-     private final int containerRows;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    private Inventory player;
- 
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventory inventory;
-+        if (this.container instanceof Inventory) {
-+            inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryPlayer((Inventory) this.container);
-+        } else if (this.container instanceof CompoundContainer) {
-+            inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) this.container);
-+        } else {
-+            inventory = new CraftInventory(this.container);
-+        }
-+
-+        this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     private ChestMenu(MenuType<?> type, int syncId, Inventory playerInventory, int rows) {
-         this(type, syncId, playerInventory, new SimpleContainer(9 * rows), rows);
-     }
-@@ -53,6 +80,9 @@
-         this.container = inventory;
-         this.containerRows = rows;
-         inventory.startOpen(playerInventory.player);
-+        // CraftBukkit start - Save player
-+        this.player = playerInventory;
-+        // CraftBukkit end
-         boolean flag = true;
- 
-         this.addChestGrid(inventory, 8, 18);
-@@ -72,6 +102,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.container.stillValid(player);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerListener.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerListener.java.patch
deleted file mode 100644
index 7d9e714dfe..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerListener.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/inventory/ContainerListener.java
-+++ b/net/minecraft/world/inventory/ContainerListener.java
-@@ -5,5 +5,11 @@
- public interface ContainerListener {
-     void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack);
- 
-+    // Paper start - Add PlayerInventorySlotChangeEvent
-+    default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) {
-+        slotChanged(handler, slotId, stack);
-+    }
-+    // Paper end - Add PlayerInventorySlotChangeEvent
-+
-     void dataChanged(AbstractContainerMenu handler, int property, int value);
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerSynchronizer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
deleted file mode 100644
index a76ea56e99..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ContainerSynchronizer.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/inventory/ContainerSynchronizer.java
-+++ b/net/minecraft/world/inventory/ContainerSynchronizer.java
-@@ -6,6 +6,7 @@
- public interface ContainerSynchronizer {
-     void sendInitialData(AbstractContainerMenu handler, NonNullList<ItemStack> stacks, ItemStack cursorStack, int[] properties);
- 
-+    default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus
-     void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack);
- 
-     void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/CrafterMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/CrafterMenu.java.patch
deleted file mode 100644
index e998f9ae45..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/CrafterMenu.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/world/inventory/CrafterMenu.java
-+++ b/net/minecraft/world/inventory/CrafterMenu.java
-@@ -10,8 +10,27 @@
- import net.minecraft.world.item.crafting.CraftingRecipe;
- import net.minecraft.world.level.block.CrafterBlock;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventoryCrafter;
-+import org.bukkit.craftbukkit.inventory.view.CraftCrafterView;
-+// CraftBukkit end
-+
- public class CrafterMenu extends AbstractContainerMenu implements ContainerListener {
- 
-+    // CraftBukkit start
-+    private CraftCrafterView bukkitEntity = null;
-+
-+    @Override
-+    public CraftCrafterView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryCrafter inventory = new CraftInventoryCrafter(this.container, this.resultContainer);
-+        this.bukkitEntity = new CraftCrafterView(this.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-     protected static final int SLOT_COUNT = 9;
-     private static final int INV_SLOT_START = 9;
-     private static final int INV_SLOT_END = 36;
-@@ -106,6 +125,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.container.stillValid(player);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingContainer.java.patch
deleted file mode 100644
index 0c5babe735..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingContainer.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/inventory/CraftingContainer.java
-+++ b/net/minecraft/world/inventory/CraftingContainer.java
-@@ -5,6 +5,10 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.crafting.CraftingInput;
- 
-+// CraftBukkit start
-+import net.minecraft.world.item.crafting.RecipeHolder;
-+// CraftBukkit end
-+
- public interface CraftingContainer extends Container, StackedContentsCompatible {
- 
-     int getWidth();
-@@ -13,6 +17,15 @@
- 
-     List<ItemStack> getItems();
- 
-+    // CraftBukkit start
-+    default RecipeHolder<?> getCurrentRecipe() {
-+        return null;
-+    }
-+
-+    default void setCurrentRecipe(RecipeHolder<?> recipe) {
-+    }
-+    // CraftBukkit end
-+
-     default CraftingInput asCraftInput() {
-         return this.asPositionedCraftInput().input();
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingMenu.java.patch
deleted file mode 100644
index 9f0941d40b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/CraftingMenu.java.patch
+++ /dev/null
@@ -1,74 +0,0 @@
---- a/net/minecraft/world/inventory/CraftingMenu.java
-+++ b/net/minecraft/world/inventory/CraftingMenu.java
-@@ -14,7 +14,11 @@
- import net.minecraft.world.item.crafting.CraftingRecipe;
- import net.minecraft.world.item.crafting.RecipeHolder;
- import net.minecraft.world.item.crafting.RecipeType;
-+import net.minecraft.world.item.crafting.RepairItemRecipe;
- import net.minecraft.world.level.block.Blocks;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
- 
- public class CraftingMenu extends AbstractCraftingMenu {
- 
-@@ -31,13 +35,16 @@
-     public final ContainerLevelAccess access;
-     private final Player player;
-     private boolean placingRecipe;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    // CraftBukkit end
- 
-     public CraftingMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, ContainerLevelAccess.NULL);
-     }
- 
-     public CraftingMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) {
--        super(MenuType.CRAFTING, syncId, 3, 3);
-+        super(MenuType.CRAFTING, syncId, 3, 3, playerInventory); // CraftBukkit - pass player
-         this.access = context;
-         this.player = playerInventory.player;
-         this.addResultSlot(this.player, 124, 35);
-@@ -50,6 +57,7 @@
-         ServerPlayer entityplayer = (ServerPlayer) player;
-         ItemStack itemstack = ItemStack.EMPTY;
-         Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe);
-+        craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit
- 
-         if (optional.isPresent()) {
-             RecipeHolder<CraftingRecipe> recipeholder1 = (RecipeHolder) optional.get();
-@@ -63,6 +71,7 @@
-                 }
-             }
-         }
-+        itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(craftingInventory, resultInventory, itemstack, handler.getBukkitView(), optional.map(RecipeHolder::value).orElse(null) instanceof RepairItemRecipe); // CraftBukkit
- 
-         resultInventory.setItem(0, itemstack);
-         handler.setRemoteSlot(0, itemstack);
-@@ -103,6 +112,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.CRAFTING_TABLE);
-     }
- 
-@@ -181,4 +191,17 @@
-     protected Player owner() {
-         return this.player;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/DispenserMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/DispenserMenu.java.patch
deleted file mode 100644
index 18ed7c2049..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/DispenserMenu.java.patch
+++ /dev/null
@@ -1,62 +0,0 @@
---- a/net/minecraft/world/inventory/DispenserMenu.java
-+++ b/net/minecraft/world/inventory/DispenserMenu.java
-@@ -6,6 +6,11 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
-+
- public class DispenserMenu extends AbstractContainerMenu {
- 
-     private static final int SLOT_COUNT = 9;
-@@ -14,6 +19,10 @@
-     private static final int USE_ROW_SLOT_START = 36;
-     private static final int USE_ROW_SLOT_END = 45;
-     public final Container dispenser;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    private Inventory player;
-+    // CraftBukkit end
- 
-     public DispenserMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, new SimpleContainer(9));
-@@ -21,6 +30,10 @@
- 
-     public DispenserMenu(int syncId, Inventory playerInventory, Container inventory) {
-         super(MenuType.GENERIC_3x3, syncId);
-+        // CraftBukkit start - Save player
-+        this.player = playerInventory;
-+        // CraftBukkit end
-+
-         checkContainerSize(inventory, 9);
-         this.dispenser = inventory;
-         inventory.startOpen(playerInventory.player);
-@@ -41,6 +54,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.dispenser.stillValid(player);
-     }
- 
-@@ -82,4 +96,17 @@
-         super.removed(player);
-         this.dispenser.stopOpen(player);
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventory inventory = new CraftInventory(this.dispenser);
-+        this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/EnchantmentMenu.java.patch
deleted file mode 100644
index 531e6792cc..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/EnchantmentMenu.java.patch
+++ /dev/null
@@ -1,270 +0,0 @@
---- a/net/minecraft/world/inventory/EnchantmentMenu.java
-+++ b/net/minecraft/world/inventory/EnchantmentMenu.java
-@@ -21,7 +21,6 @@
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.enchantment.Enchantment;
-@@ -29,6 +28,18 @@
- import net.minecraft.world.item.enchantment.EnchantmentInstance;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.EnchantingTableBlock;
-+// CraftBukkit start
-+import java.util.Map;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView;
-+import org.bukkit.enchantments.EnchantmentOffer;
-+import org.bukkit.event.enchantment.EnchantItemEvent;
-+import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
- 
- public class EnchantmentMenu extends AbstractContainerMenu {
- 
-@@ -40,6 +51,10 @@
-     public final int[] costs;
-     public final int[] enchantClue;
-     public final int[] levelClue;
-+    // CraftBukkit start
-+    private CraftEnchantmentView bukkitEntity = null;
-+    private Player player;
-+    // CraftBukkit end
- 
-     public EnchantmentMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, ContainerLevelAccess.NULL);
-@@ -47,12 +62,19 @@
- 
-     public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) {
-         super(MenuType.ENCHANTMENT, syncId);
--        this.enchantSlots = new SimpleContainer(2) {
-+        this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 EnchantmentMenu.this.slotsChanged(this);
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
-         this.random = RandomSource.create();
-         this.enchantmentSeed = DataSlot.standalone();
-@@ -60,13 +82,13 @@
-         this.enchantClue = new int[]{-1, -1, -1};
-         this.levelClue = new int[]{-1, -1, -1};
-         this.access = context;
--        this.addSlot(new Slot(this, this.enchantSlots, 0, 15, 47) {
-+        this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) { // CraftBukkit - decompile error
-             @Override
-             public int getMaxStackSize() {
-                 return 1;
-             }
-         });
--        this.addSlot(new Slot(this, this.enchantSlots, 1, 35, 47) {
-+        this.addSlot(new Slot(this.enchantSlots, 1, 35, 47) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.is(Items.LAPIS_LAZULI);
-@@ -88,6 +110,9 @@
-         this.addDataSlot(DataSlot.shared(this.levelClue, 0));
-         this.addDataSlot(DataSlot.shared(this.levelClue, 1));
-         this.addDataSlot(DataSlot.shared(this.levelClue, 2));
-+        // CraftBukkit start
-+        this.player = (Player) playerInventory.player.getBukkitEntity();
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -95,7 +120,7 @@
-         if (inventory == this.enchantSlots) {
-             ItemStack itemstack = inventory.getItem(0);
- 
--            if (!itemstack.isEmpty() && itemstack.isEnchantable()) {
-+            if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
-                 this.access.execute((world, blockposition) -> {
-                     IdMap<Holder<Enchantment>> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
-                     int i = 0;
-@@ -135,6 +160,41 @@
-                         }
-                     }
- 
-+                    // CraftBukkit start
-+                    CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
-+                    org.bukkit.enchantments.EnchantmentOffer[] offers = new EnchantmentOffer[3];
-+                    for (j = 0; j < 3; ++j) {
-+                        org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[j])) : null;
-+                        offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null;
-+                    }
-+
-+                    PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(this.player, this.getBukkitView(), this.access.getLocation().getBlock(), item, offers, i);
-+                    event.setCancelled(!itemstack.isEnchantable());
-+                    world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (event.isCancelled()) {
-+                        for (j = 0; j < 3; ++j) {
-+                            this.costs[j] = 0;
-+                            this.enchantClue[j] = -1;
-+                            this.levelClue[j] = -1;
-+                        }
-+                        return;
-+                    }
-+
-+                    for (j = 0; j < 3; j++) {
-+                        EnchantmentOffer offer = event.getOffers()[j];
-+                        if (offer != null) {
-+                            this.costs[j] = offer.getCost();
-+                            this.enchantClue[j] = registry.getId(CraftEnchantment.bukkitToMinecraftHolder(offer.getEnchantment()));
-+                            this.levelClue[j] = offer.getEnchantmentLevel();
-+                        } else {
-+                            this.costs[j] = 0;
-+                            this.enchantClue[j] = -1;
-+                            this.levelClue[j] = -1;
-+                        }
-+                    }
-+                    // CraftBukkit end
-+
-                     this.broadcastChanges();
-                 });
-             } else {
-@@ -149,7 +209,7 @@
-     }
- 
-     @Override
--    public boolean clickMenuButton(Player player, int id) {
-+    public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
-         if (id >= 0 && id < this.costs.length) {
-             ItemStack itemstack = this.enchantSlots.getItem(0);
-             ItemStack itemstack1 = this.enchantSlots.getItem(1);
-@@ -159,24 +219,55 @@
-                 return false;
-             } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) {
-                 this.access.execute((world, blockposition) -> {
--                    ItemStack itemstack2 = itemstack;
-+                    ItemStack itemstack2 = itemstack; // Paper - diff on change
-                     List<EnchantmentInstance> list = this.getEnchantmentList(world.registryAccess(), itemstack, id, this.costs[id]);
- 
--                    if (!list.isEmpty()) {
--                        player.onEnchantmentPerformed(itemstack, j);
--                        if (itemstack.is(Items.BOOK)) {
--                            itemstack2 = itemstack.transmuteCopy(Items.ENCHANTED_BOOK);
--                            this.enchantSlots.setItem(0, itemstack2);
-+                    // CraftBukkit start
-+                    IdMap<Holder<Enchantment>> registry = world.registryAccess().lookupOrThrow(Registries.ENCHANTMENT).asHolderIdMap();
-+                    if (true || !list.isEmpty()) {
-+                        // entityhuman.onEnchantmentPerformed(itemstack, j); // Moved down
-+                        Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
-+                        for (EnchantmentInstance instance : list) {
-+                            enchants.put(CraftEnchantment.minecraftHolderToBukkit(instance.enchantment), instance.level);
-                         }
-+                        CraftItemStack item = CraftItemStack.asCraftMirror(itemstack2);
- 
--                        Iterator iterator = list.iterator();
-+                        org.bukkit.enchantments.Enchantment hintedEnchantment = CraftEnchantment.minecraftHolderToBukkit(registry.byId(this.enchantClue[id]));
-+                        int hintedEnchantmentLevel = this.levelClue[id];
-+                        EnchantItemEvent event = new EnchantItemEvent((Player) player.getBukkitEntity(), this.getBukkitView(), this.access.getLocation().getBlock(), item, this.costs[id], enchants, hintedEnchantment, hintedEnchantmentLevel, id);
-+                        world.getCraftServer().getPluginManager().callEvent(event);
- 
--                        while (iterator.hasNext()) {
--                            EnchantmentInstance weightedrandomenchant = (EnchantmentInstance) iterator.next();
-+                        int level = event.getExpLevelCost();
-+                        if (event.isCancelled() || (level > player.experienceLevel && !player.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
-+                            return;
-+                        }
-+                        // CraftBukkit end
-+                        // Paper start
-+                        itemstack2 = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(item, event.getItem());
-+                        if (itemstack2 != itemstack) {
-+                            this.enchantSlots.setItem(0, itemstack2);
-+                        }
-+                        if (itemstack2.is(Items.BOOK)) {
-+                            itemstack2 = itemstack2.transmuteCopy(Items.ENCHANTED_BOOK);
-+                            this.enchantSlots.setItem(0, itemstack2);
-+                        }
-+                        // Paper end
- 
-+                        // CraftBukkit start
-+                        for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
-+                            Holder<Enchantment> nms = CraftEnchantment.bukkitToMinecraftHolder(entry.getKey());
-+                            if (nms == null) {
-+                                continue;
-+                            }
-+
-+                            EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue());
-                             itemstack2.enchant(weightedrandomenchant.enchantment, weightedrandomenchant.level);
-                         }
- 
-+                        player.onEnchantmentPerformed(itemstack, j);
-+                        // CraftBukkit end
-+
-+                        // CraftBukkit - TODO: let plugins change this
-                         itemstack1.consume(j, player);
-                         if (itemstack1.isEmpty()) {
-                             this.enchantSlots.setItem(1, ItemStack.EMPTY);
-@@ -190,7 +281,7 @@
-                         this.enchantSlots.setChanged();
-                         this.enchantmentSeed.set(player.getEnchantmentSeed());
-                         this.slotsChanged(this.enchantSlots);
--                        world.playSound((Player) null, blockposition, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1.0F, world.random.nextFloat() * 0.1F + 0.9F);
-+                        world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1.0F, world.random.nextFloat() * 0.1F + 0.9F);
-                     }
- 
-                 });
-@@ -234,7 +325,7 @@
-     }
- 
-     @Override
--    public void removed(Player player) {
-+    public void removed(net.minecraft.world.entity.player.Player player) {
-         super.removed(player);
-         this.access.execute((world, blockposition) -> {
-             this.clearContainer(player, this.enchantSlots);
-@@ -242,12 +333,13 @@
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.ENCHANTING_TABLE);
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         ItemStack itemstack = ItemStack.EMPTY;
-         Slot slot1 = (Slot) this.slots.get(slot);
- 
-@@ -293,4 +385,23 @@
- 
-         return itemstack;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftEnchantmentView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
-+        this.bukkitEntity = new CraftEnchantmentView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-+    // Paper start - add enchantment seed update API
-+    public void setEnchantmentSeed(int seed) {
-+        this.enchantmentSeed.set(seed);
-+    }
-+    // Paper end - add enchantment seed update API
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/FurnaceResultSlot.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
deleted file mode 100644
index 488614cbf6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/inventory/FurnaceResultSlot.java
-+++ b/net/minecraft/world/inventory/FurnaceResultSlot.java
-@@ -51,7 +51,7 @@
-             Container iinventory = this.container;
- 
-             if (iinventory instanceof AbstractFurnaceBlockEntity tileentityfurnace) {
--                tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer);
-+                tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer, stack, this.removeCount); // CraftBukkit
-             }
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/GrindstoneMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/GrindstoneMenu.java.patch
deleted file mode 100644
index 0ba3910b94..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/GrindstoneMenu.java.patch
+++ /dev/null
@@ -1,141 +0,0 @@
---- a/net/minecraft/world/inventory/GrindstoneMenu.java
-+++ b/net/minecraft/world/inventory/GrindstoneMenu.java
-@@ -10,7 +10,6 @@
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.ExperienceOrb;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.enchantment.Enchantment;
-@@ -19,9 +18,30 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryGrindstone;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
- 
- public class GrindstoneMenu extends AbstractContainerMenu {
- 
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    private Player player;
-+
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.repairSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftInventoryView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-     public static final int MAX_NAME_LENGTH = 35;
-     public static final int INPUT_SLOT = 0;
-     public static final int ADDITIONAL_SLOT = 1;
-@@ -40,22 +60,29 @@
- 
-     public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) {
-         super(MenuType.GRINDSTONE, syncId);
--        this.resultSlots = new ResultContainer();
--        this.repairSlots = new SimpleContainer(2) {
-+        this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders
-+        this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 GrindstoneMenu.this.slotsChanged(this);
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
-         this.access = context;
--        this.addSlot(new Slot(this, this.repairSlots, 0, 49, 19) {
-+        this.addSlot(new Slot(this.repairSlots, 0, 49, 19) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.isDamageableItem() || EnchantmentHelper.hasAnyEnchantments(stack);
-             }
-         });
--        this.addSlot(new Slot(this, this.repairSlots, 1, 49, 40) {
-+        this.addSlot(new Slot(this.repairSlots, 1, 49, 40) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.isDamageableItem() || EnchantmentHelper.hasAnyEnchantments(stack);
-@@ -68,10 +95,14 @@
-             }
- 
-             @Override
--            public void onTake(Player player, ItemStack stack) {
-+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
-                 context.execute((world, blockposition) -> {
-                     if (world instanceof ServerLevel) {
--                        ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world));
-+                        // Paper start - Fire BlockExpEvent on grindstone use
-+                        org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition), this.getExperienceAmount(world));
-+                        event.callEvent();
-+                        ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player);
-+                        // Paper end - Fire BlockExpEvent on grindstone use
-                     }
- 
-                     world.levelEvent(1042, blockposition, 0);
-@@ -113,6 +144,7 @@
-             }
-         });
-         this.addStandardInventorySlots(playerInventory, 8, 84);
-+        this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
-     }
- 
-     @Override
-@@ -120,12 +152,14 @@
-         super.slotsChanged(inventory);
-         if (inventory == this.repairSlots) {
-             this.createResult();
-+            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
-         }
- 
-     }
- 
-     private void createResult() {
--        this.resultSlots.setItem(0, this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1)));
-+        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(this.getBukkitView(), this.computeResult(this.repairSlots.getItem(0), this.repairSlots.getItem(1))); // CraftBukkit
-+        this.sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client
-         this.broadcastChanges();
-     }
- 
-@@ -218,7 +252,7 @@
-     }
- 
-     @Override
--    public void removed(Player player) {
-+    public void removed(net.minecraft.world.entity.player.Player player) {
-         super.removed(player);
-         this.access.execute((world, blockposition) -> {
-             this.clearContainer(player, this.repairSlots);
-@@ -226,12 +260,13 @@
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.GRINDSTONE);
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         ItemStack itemstack = ItemStack.EMPTY;
-         Slot slot1 = (Slot) this.slots.get(slot);
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/HopperMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/HopperMenu.java.patch
deleted file mode 100644
index 2174c38e81..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/HopperMenu.java.patch
+++ /dev/null
@@ -1,51 +0,0 @@
---- a/net/minecraft/world/inventory/HopperMenu.java
-+++ b/net/minecraft/world/inventory/HopperMenu.java
-@@ -6,11 +6,32 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
-+
- public class HopperMenu extends AbstractContainerMenu {
- 
-     public static final int CONTAINER_SIZE = 5;
-     private final Container hopper;
- 
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    private Inventory player;
-+
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventory inventory = new CraftInventory(this.hopper);
-+        this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     public HopperMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, new SimpleContainer(5));
-     }
-@@ -18,6 +39,7 @@
-     public HopperMenu(int syncId, Inventory playerInventory, Container inventory) {
-         super(MenuType.HOPPER, syncId);
-         this.hopper = inventory;
-+        this.player = playerInventory; // CraftBukkit - save player
-         checkContainerSize(inventory, 5);
-         inventory.startOpen(playerInventory.player);
- 
-@@ -30,6 +52,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.hopper.stillValid(player);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
deleted file mode 100644
index 2657cf4ccb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
+++ /dev/null
@@ -1,53 +0,0 @@
---- a/net/minecraft/world/inventory/HorseInventoryMenu.java
-+++ b/net/minecraft/world/inventory/HorseInventoryMenu.java
-@@ -11,6 +11,11 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+import org.bukkit.inventory.InventoryView;
-+// CraftBukkit end
-+
- public class HorseInventoryMenu extends AbstractContainerMenu {
- 
-     static final ResourceLocation SADDLE_SLOT_SPRITE = ResourceLocation.withDefaultNamespace("container/slot/saddle");
-@@ -22,13 +27,28 @@
-     public static final int SLOT_BODY_ARMOR = 1;
-     private static final int SLOT_HORSE_INVENTORY_START = 2;
- 
-+    // CraftBukkit start
-+    org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
-+    Inventory player;
-+
-+    @Override
-+    public InventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        return this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), this.horseContainer.getOwner().getInventory(), this);
-+    }
-+
-     public HorseInventoryMenu(int syncId, Inventory playerInventory, Container inventory, final AbstractHorse entity, int slotColumnCount) {
-         super((MenuType) null, syncId);
-+        this.player = playerInventory;
-+        // CraftBukkit end
-         this.horseContainer = inventory;
-         this.armorContainer = entity.getBodyArmorAccess();
-         this.horse = entity;
-         inventory.startOpen(playerInventory.player);
--        this.addSlot(new Slot(this, inventory, 0, 8, 18) {
-+        this.addSlot(new Slot(inventory, 0, 8, 18) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.is(Items.SADDLE) && !this.hasItem() && entity.isSaddleable();
-@@ -46,7 +66,7 @@
-         });
-         ResourceLocation minecraftkey = entity instanceof Llama ? HorseInventoryMenu.LLAMA_ARMOR_SLOT_SPRITE : HorseInventoryMenu.ARMOR_SLOT_SPRITE;
- 
--        this.addSlot(new ArmorSlot(this, this.armorContainer, entity, EquipmentSlot.BODY, 0, 8, 36, minecraftkey) {
-+        this.addSlot(new ArmorSlot(this.armorContainer, entity, EquipmentSlot.BODY, 0, 8, 36, minecraftkey) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return entity.isEquippableInSlot(stack, EquipmentSlot.BODY);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/InventoryMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/InventoryMenu.java.patch
deleted file mode 100644
index 78a5d31f7d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/InventoryMenu.java.patch
+++ /dev/null
@@ -1,64 +0,0 @@
---- a/net/minecraft/world/inventory/InventoryMenu.java
-+++ b/net/minecraft/world/inventory/InventoryMenu.java
-@@ -2,6 +2,7 @@
- 
- import java.util.List;
- import java.util.Map;
-+import net.minecraft.network.chat.Component;
- import net.minecraft.resources.ResourceLocation;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.Container;
-@@ -11,6 +12,9 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.crafting.RecipeHolder;
- import net.minecraft.world.level.Level;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
- 
- public class InventoryMenu extends AbstractCraftingMenu {
- 
-@@ -38,9 +42,15 @@
-     private static final EquipmentSlot[] SLOT_IDS = new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET};
-     public final boolean active;
-     private final Player owner;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity = null;
-+    // CraftBukkit end
- 
-     public InventoryMenu(Inventory inventory, boolean onServer, final Player owner) {
--        super((MenuType) null, 0, 2, 2);
-+        // CraftBukkit start
-+        super((MenuType) null, 0, 2, 2, inventory); // CraftBukkit - save player
-+        this.setTitle(Component.translatable("container.crafting")); // SPIGOT-4722: Allocate title for player inventory
-+        // CraftBukkit end
-         this.active = onServer;
-         this.owner = owner;
-         this.addResultSlot(owner, 154, 28);
-@@ -54,7 +64,7 @@
-         }
- 
-         this.addStandardInventorySlots(inventory, 8, 84);
--        this.addSlot(new Slot(this, inventory, 40, 77, 62) {
-+        this.addSlot(new Slot(inventory, 40, 77, 62) { // CraftBukkit - decompile error
-             @Override
-             public void setByPlayer(ItemStack stack, ItemStack previousStack) {
-                 owner.onEquipItem(EquipmentSlot.OFFHAND, previousStack, stack);
-@@ -190,4 +200,17 @@
-     protected Player owner() {
-         return this.owner;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftInventoryView(this.owner.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/LecternMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/LecternMenu.java.patch
deleted file mode 100644
index 667c188455..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/LecternMenu.java.patch
+++ /dev/null
@@ -1,143 +0,0 @@
---- a/net/minecraft/world/inventory/LecternMenu.java
-+++ b/net/minecraft/world/inventory/LecternMenu.java
-@@ -2,11 +2,33 @@
- 
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
--import net.minecraft.world.entity.player.Player;
-+import net.minecraft.world.entity.player.Inventory;
- import net.minecraft.world.item.ItemStack;
-+import net.minecraft.world.level.block.entity.LecternBlockEntity.LecternInventory;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryLectern;
-+import org.bukkit.craftbukkit.inventory.view.CraftLecternView;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.player.PlayerTakeLecternBookEvent;
-+// CraftBukkit end
- 
- public class LecternMenu extends AbstractContainerMenu {
- 
-+    // CraftBukkit start
-+    private CraftLecternView bukkitEntity = null;
-+    private Player player;
-+
-+    @Override
-+    public CraftLecternView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryLectern inventory = new CraftInventoryLectern(this.lectern);
-+        this.bukkitEntity = new CraftLecternView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-     private static final int DATA_COUNT = 1;
-     private static final int SLOT_COUNT = 1;
-     public static final int BUTTON_PREV_PAGE = 1;
-@@ -16,29 +38,33 @@
-     private final Container lectern;
-     private final ContainerData lecternData;
- 
--    public LecternMenu(int syncId) {
--        this(syncId, new SimpleContainer(1), new SimpleContainerData(1));
-+    // CraftBukkit start - add player
-+    public LecternMenu(int i, Inventory playerinventory) {
-+        this(i, new SimpleContainer(1), new SimpleContainerData(1), playerinventory);
-     }
- 
--    public LecternMenu(int syncId, Container inventory, ContainerData propertyDelegate) {
--        super(MenuType.LECTERN, syncId);
--        checkContainerSize(inventory, 1);
--        checkContainerDataCount(propertyDelegate, 1);
--        this.lectern = inventory;
--        this.lecternData = propertyDelegate;
--        this.addSlot(new Slot(inventory, 0, 0, 0) {
-+    public LecternMenu(int i, Container iinventory, ContainerData icontainerproperties, Inventory playerinventory) {
-+        // CraftBukkit end
-+        super(MenuType.LECTERN, i);
-+        checkContainerSize(iinventory, 1);
-+        checkContainerDataCount(icontainerproperties, 1);
-+        this.lectern = iinventory;
-+        this.lecternData = icontainerproperties;
-+        this.addSlot(new Slot(iinventory, 0, 0, 0) {
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 LecternMenu.this.slotsChanged(this.container);
-             }
-         });
--        this.addDataSlots(propertyDelegate);
-+        this.addDataSlots(icontainerproperties);
-+        this.player = (Player) playerinventory.player.getBukkitEntity(); // CraftBukkit
-     }
- 
-     @Override
--    public boolean clickMenuButton(Player player, int id) {
-+    public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
-         int j;
-+        io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper - Add PlayerLecternPageChangeEvent
- 
-         if (id >= 100) {
-             j = id - 100;
-@@ -48,17 +74,38 @@
-             switch (id) {
-                 case 1:
-                     j = this.lecternData.get(0);
--                    this.setData(0, j - 1);
-+                    // Paper start - Add PlayerLecternPageChangeEvent
-+                    bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory();
-+                    playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1);
-+                    if (!playerLecternPageChangeEvent.callEvent()) {
-+                        return false;
-+                    }
-+                    this.setData(0, playerLecternPageChangeEvent.getNewPage());
-+                    // Paper end - Add PlayerLecternPageChangeEvent
-                     return true;
-                 case 2:
-                     j = this.lecternData.get(0);
--                    this.setData(0, j + 1);
-+                    // Paper start - Add PlayerLecternPageChangeEvent
-+                    bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory();
-+                    playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1);
-+                    if (!playerLecternPageChangeEvent.callEvent()) {
-+                        return false;
-+                    }
-+                    this.setData(0, playerLecternPageChangeEvent.getNewPage());
-+                    // Paper end - Add PlayerLecternPageChangeEvent
-                     return true;
-                 case 3:
-                     if (!player.mayBuild()) {
-                         return false;
-                     }
- 
-+                    // CraftBukkit start - Event for taking the book
-+                    PlayerTakeLecternBookEvent event = new PlayerTakeLecternBookEvent(this.player, ((CraftInventoryLectern) this.getBukkitView().getTopInventory()).getHolder());
-+                    Bukkit.getServer().getPluginManager().callEvent(event);
-+                    if (event.isCancelled()) {
-+                        return false;
-+                    }
-+                    // CraftBukkit end
-                     ItemStack itemstack = this.lectern.removeItemNoUpdate(0);
- 
-                     this.lectern.setChanged();
-@@ -74,7 +121,7 @@
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         return ItemStack.EMPTY;
-     }
- 
-@@ -85,7 +132,9 @@
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (this.lectern instanceof LecternInventory && !((LecternInventory) this.lectern).getLectern().hasBook()) return false; // CraftBukkit
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.lectern.stillValid(player);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/LoomMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/LoomMenu.java.patch
deleted file mode 100644
index 1d534db5e0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/LoomMenu.java.patch
+++ /dev/null
@@ -1,203 +0,0 @@
---- a/net/minecraft/world/inventory/LoomMenu.java
-+++ b/net/minecraft/world/inventory/LoomMenu.java
-@@ -12,7 +12,6 @@
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.BannerItem;
- import net.minecraft.world.item.BannerPatternItem;
- import net.minecraft.world.item.DyeColor;
-@@ -22,9 +21,30 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.entity.BannerPattern;
- import net.minecraft.world.level.block.entity.BannerPatternLayers;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryLoom;
-+import org.bukkit.craftbukkit.inventory.view.CraftLoomView;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
- 
- public class LoomMenu extends AbstractContainerMenu {
- 
-+    // CraftBukkit start
-+    private CraftLoomView bukkitEntity = null;
-+    private Player player;
-+
-+    @Override
-+    public CraftLoomView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryLoom inventory = new CraftInventoryLoom(this.inputContainer, this.outputContainer);
-+        this.bukkitEntity = new CraftLoomView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-     private static final int PATTERN_NOT_SET = -1;
-     private static final int INV_SLOT_START = 4;
-     private static final int INV_SLOT_END = 31;
-@@ -53,35 +73,49 @@
-         this.selectablePatterns = List.of();
-         this.slotUpdateListener = () -> {
-         };
--        this.inputContainer = new SimpleContainer(3) {
-+        this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 LoomMenu.this.slotsChanged(this);
-                 LoomMenu.this.slotUpdateListener.run();
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
--        this.outputContainer = new SimpleContainer(1) {
-+        this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 LoomMenu.this.slotUpdateListener.run();
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
-         this.access = context;
--        this.bannerSlot = this.addSlot(new Slot(this, this.inputContainer, 0, 13, 26) {
-+        this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.getItem() instanceof BannerItem;
-             }
-         });
--        this.dyeSlot = this.addSlot(new Slot(this, this.inputContainer, 1, 33, 26) {
-+        this.dyeSlot = this.addSlot(new Slot(this.inputContainer, 1, 33, 26) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.getItem() instanceof DyeItem;
-             }
-         });
--        this.patternSlot = this.addSlot(new Slot(this, this.inputContainer, 2, 23, 45) {
-+        this.patternSlot = this.addSlot(new Slot(this.inputContainer, 2, 23, 45) { // CraftBukkit - decompile error
-             @Override
-             public boolean mayPlace(ItemStack stack) {
-                 return stack.getItem() instanceof BannerPatternItem;
-@@ -94,7 +128,7 @@
-             }
- 
-             @Override
--            public void onTake(Player player, ItemStack stack) {
-+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
-                 LoomMenu.this.bannerSlot.remove(1);
-                 LoomMenu.this.dyeSlot.remove(1);
-                 if (!LoomMenu.this.bannerSlot.hasItem() || !LoomMenu.this.dyeSlot.hasItem()) {
-@@ -105,7 +139,7 @@
-                     long j = world.getGameTime();
- 
-                     if (LoomMenu.this.lastSoundTime != j) {
--                        world.playSound((Player) null, blockposition, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                        world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-                         LoomMenu.this.lastSoundTime = j;
-                     }
- 
-@@ -116,18 +150,44 @@
-         this.addStandardInventorySlots(playerInventory, 8, 84);
-         this.addDataSlot(this.selectedBannerPatternIndex);
-         this.patternGetter = playerInventory.player.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN);
-+        this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.LOOM);
-     }
- 
-     @Override
--    public boolean clickMenuButton(Player player, int id) {
-+    public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
-         if (id >= 0 && id < this.selectablePatterns.size()) {
--            this.selectedBannerPatternIndex.set(id);
--            this.setupResultSlot((Holder) this.selectablePatterns.get(id));
-+            // Paper start - Add PlayerLoomPatternSelectEvent
-+            int selectablePatternIndex = id;
-+            io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex)));
-+            if (!event.callEvent()) {
-+                player.containerMenu.sendAllDataToRemote();
-+                return false;
-+            }
-+            final Holder<BannerPattern> eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType());
-+            Holder<BannerPattern> selectedPattern = null;
-+            for (int i = 0; i < this.selectablePatterns.size(); i++) {
-+                final Holder<BannerPattern> holder = this.selectablePatterns.get(i);
-+                if (eventPattern.equals(holder)) {
-+                    selectablePatternIndex = i;
-+                    selectedPattern = holder;
-+                    break;
-+                }
-+            }
-+            if (selectedPattern == null) {
-+                selectedPattern = eventPattern;
-+                selectablePatternIndex = -1;
-+            }
-+
-+            player.containerMenu.sendAllDataToRemote();
-+            this.selectedBannerPatternIndex.set(selectablePatternIndex);
-+            this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected"));
-+            // Paper end - Add PlayerLoomPatternSelectEvent
-             return true;
-         } else {
-             return false;
-@@ -201,7 +261,8 @@
-                 this.resultSlot.set(ItemStack.EMPTY);
-             }
- 
--            this.broadcastChanges();
-+            // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below
-+            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent
-         } else {
-             this.resultSlot.set(ItemStack.EMPTY);
-             this.selectablePatterns = List.of();
-@@ -222,7 +283,7 @@
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         ItemStack itemstack = ItemStack.EMPTY;
-         Slot slot1 = (Slot) this.slots.get(slot);
- 
-@@ -277,7 +338,7 @@
-     }
- 
-     @Override
--    public void removed(Player player) {
-+    public void removed(net.minecraft.world.entity.player.Player player) {
-         super.removed(player);
-         this.access.execute((world, blockposition) -> {
-             this.clearContainer(player, this.inputContainer);
-@@ -294,6 +355,11 @@
-             DyeColor enumcolor = ((DyeItem) itemstack1.getItem()).getDyeColor();
- 
-             itemstack2.update(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY, (bannerpatternlayers) -> {
-+                // CraftBukkit start
-+                if (bannerpatternlayers.layers().size() > 20) {
-+                    bannerpatternlayers = new BannerPatternLayers(List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
-+                }
-+                // CraftBukkit end
-                 return (new BannerPatternLayers.Builder()).addAll(bannerpatternlayers).add(pattern, enumcolor).build();
-             });
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/MenuType.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/MenuType.java.patch
deleted file mode 100644
index ac3dc03175..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/MenuType.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/inventory/MenuType.java
-+++ b/net/minecraft/world/inventory/MenuType.java
-@@ -28,7 +28,7 @@
-     public static final MenuType<GrindstoneMenu> GRINDSTONE = MenuType.register("grindstone", GrindstoneMenu::new);
-     public static final MenuType<HopperMenu> HOPPER = MenuType.register("hopper", HopperMenu::new);
-     public static final MenuType<LecternMenu> LECTERN = MenuType.register("lectern", (i, playerinventory) -> {
--        return new LecternMenu(i);
-+        return new LecternMenu(i, playerinventory); // CraftBukkit
-     });
-     public static final MenuType<LoomMenu> LOOM = MenuType.register("loom", LoomMenu::new);
-     public static final MenuType<MerchantMenu> MERCHANT = MenuType.register("merchant", MerchantMenu::new);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantContainer.java.patch
deleted file mode 100644
index 82bbe5e4c7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantContainer.java.patch
+++ /dev/null
@@ -1,70 +0,0 @@
---- a/net/minecraft/world/inventory/MerchantContainer.java
-+++ b/net/minecraft/world/inventory/MerchantContainer.java
-@@ -5,11 +5,20 @@
- import net.minecraft.core.NonNullList;
- import net.minecraft.world.Container;
- import net.minecraft.world.ContainerHelper;
-+import net.minecraft.world.entity.npc.AbstractVillager;
-+import net.minecraft.world.entity.npc.Villager;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.trading.Merchant;
- import net.minecraft.world.item.trading.MerchantOffer;
- import net.minecraft.world.item.trading.MerchantOffers;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.entity.CraftAbstractVillager;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class MerchantContainer implements Container {
- 
-@@ -20,6 +29,46 @@
-     public int selectionHint;
-     private int futureXp;
- 
-+    // 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.itemStacks;
-+    }
-+
-+    public void onOpen(CraftHumanEntity who) {
-+        this.transaction.add(who);
-+    }
-+
-+    public void onClose(CraftHumanEntity who) {
-+        this.transaction.remove(who);
-+        this.merchant.setTradingPlayer((Player) null); // SPIGOT-4860
-+    }
-+
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int i) {
-+        this.maxStack = i;
-+    }
-+
-+    public org.bukkit.inventory.InventoryHolder getOwner() {
-+        return (this.merchant instanceof AbstractVillager) ? (CraftAbstractVillager) ((AbstractVillager) this.merchant).getBukkitEntity() : null;
-+    }
-+
-+    @Override
-+    public Location getLocation() {
-+        return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations
-+    }
-+    // CraftBukkit end
-+
-     public MerchantContainer(Merchant merchant) {
-         this.itemStacks = NonNullList.withSize(3, ItemStack.EMPTY);
-         this.merchant = merchant;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantMenu.java.patch
deleted file mode 100644
index 9fecebd95a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/MerchantMenu.java.patch
+++ /dev/null
@@ -1,92 +0,0 @@
---- a/net/minecraft/world/inventory/MerchantMenu.java
-+++ b/net/minecraft/world/inventory/MerchantMenu.java
-@@ -12,6 +12,7 @@
- import net.minecraft.world.item.trading.Merchant;
- import net.minecraft.world.item.trading.MerchantOffer;
- import net.minecraft.world.item.trading.MerchantOffers;
-+import org.bukkit.craftbukkit.inventory.view.CraftMerchantView; // CraftBukkit
- 
- public class MerchantMenu extends AbstractContainerMenu {
- 
-@@ -32,6 +33,19 @@
-     private boolean showProgressBar;
-     private boolean canRestock;
- 
-+    // CraftBukkit start
-+    private CraftMerchantView bukkitEntity = null;
-+    private Inventory player;
-+
-+    @Override
-+    public CraftMerchantView getBukkitView() {
-+        if (this.bukkitEntity == null) {
-+            this.bukkitEntity = new CraftMerchantView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(this.trader, this.tradeContainer), this, this.trader);
-+        }
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     public MerchantMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, new ClientSideMerchant(playerInventory.player));
-     }
-@@ -43,6 +57,7 @@
-         this.addSlot(new Slot(this.tradeContainer, 0, 136, 37));
-         this.addSlot(new Slot(this.tradeContainer, 1, 162, 37));
-         this.addSlot(new MerchantResultSlot(playerInventory.player, merchant, this.tradeContainer, 2, 220, 37));
-+        this.player = playerInventory; // CraftBukkit - save player
-         this.addStandardInventorySlots(playerInventory, 108, 84);
-     }
- 
-@@ -108,12 +123,12 @@
- 
-             itemstack = itemstack1.copy();
-             if (slot == 2) {
--                if (!this.moveItemStackTo(itemstack1, 3, 39, true)) {
-+                if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
-                     return ItemStack.EMPTY;
-                 }
- 
--                slot1.onQuickCraft(itemstack1, itemstack);
--                this.playTradeSound();
-+                //  slot1.onQuickCraft(itemstack1, itemstack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call
-+                // this.playTradeSound();
-             } else if (slot != 0 && slot != 1) {
-                 if (slot >= 3 && slot < 30) {
-                     if (!this.moveItemStackTo(itemstack1, 30, 39, false)) {
-@@ -126,6 +141,7 @@
-                 return ItemStack.EMPTY;
-             }
- 
-+            if (slot != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2
-             if (itemstack1.isEmpty()) {
-                 slot1.setByPlayer(ItemStack.EMPTY);
-             } else {
-@@ -137,13 +153,28 @@
-             }
- 
-             slot1.onTake(player, itemstack1);
-+            } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2
-+            if (slot == 2) { // is merchant result slot
-+                slot1.onTake(player, itemstack1);
-+                if (itemstack1.isEmpty()) {
-+                    slot1.set(ItemStack.EMPTY);
-+                    return ItemStack.EMPTY;
-+                }
-+
-+                this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above
-+
-+                slot1.onQuickCraft(itemstack1, itemstack);
-+                this.playTradeSound();
-+                slot1.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty
-+            }
-+            // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
-         }
- 
-         return itemstack;
-     }
- 
-     private void playTradeSound() {
--        if (!this.trader.isClientSide()) {
-+        if (!this.trader.isClientSide() && this.trader instanceof Entity) { // CraftBukkit - SPIGOT-5035
-             Entity entity = (Entity) this.trader;
- 
-             entity.level().playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
deleted file mode 100644
index e83e892d8e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
+++ /dev/null
@@ -1,36 +0,0 @@
---- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
-+++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
-@@ -8,14 +8,32 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.inventory.InventoryHolder;
-+// CraftBukkit end
- 
- public class PlayerEnderChestContainer extends SimpleContainer {
- 
-     @Nullable
-     private EnderChestBlockEntity activeChest;
-+    // CraftBukkit start
-+    private final Player owner;
- 
--    public PlayerEnderChestContainer() {
-+    public InventoryHolder getBukkitOwner() {
-+        return this.owner.getBukkitEntity();
-+    }
-+
-+    @Override
-+    public Location getLocation() {
-+        return this.activeChest != null ? CraftLocation.toBukkit(this.activeChest.getBlockPos(), this.activeChest.getLevel().getWorld()) : null;
-+    }
-+
-+    public PlayerEnderChestContainer(Player owner) {
-         super(27);
-+        this.owner = owner;
-+        // CraftBukkit end
-     }
- 
-     public void setActiveChest(EnderChestBlockEntity blockEntity) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
deleted file mode 100644
index 4efe4724bc..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/world/inventory/ShulkerBoxMenu.java
-+++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java
-@@ -6,11 +6,30 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
-+// CraftBukkit end
-+
- public class ShulkerBoxMenu extends AbstractContainerMenu {
- 
-     private static final int CONTAINER_SIZE = 27;
-     private final Container container;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity;
-+    private Inventory player;
- 
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new CraftInventory(this.container), this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     public ShulkerBoxMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, new SimpleContainer(27));
-     }
-@@ -19,6 +38,7 @@
-         super(MenuType.SHULKER_BOX, syncId);
-         checkContainerSize(inventory, 27);
-         this.container = inventory;
-+        this.player = playerInventory; // CraftBukkit - save player
-         inventory.startOpen(playerInventory.player);
-         boolean flag = true;
-         boolean flag1 = true;
-@@ -34,6 +54,7 @@
- 
-     @Override
-     public boolean stillValid(Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return this.container.stillValid(player);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/SmithingMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/SmithingMenu.java.patch
deleted file mode 100644
index 46bc8ef32c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/SmithingMenu.java.patch
+++ /dev/null
@@ -1,66 +0,0 @@
---- a/net/minecraft/world/inventory/SmithingMenu.java
-+++ b/net/minecraft/world/inventory/SmithingMenu.java
-@@ -17,6 +17,7 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
- 
- public class SmithingMenu extends ItemCombinerMenu {
- 
-@@ -34,6 +35,9 @@
-     private final RecipePropertySet templateItemTest;
-     private final RecipePropertySet additionItemTest;
-     private final DataSlot hasRecipeError;
-+    // CraftBukkit start
-+    private CraftInventoryView bukkitEntity;
-+    // CraftBukkit end
- 
-     public SmithingMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, ContainerLevelAccess.NULL);
-@@ -111,13 +115,14 @@
-             this.hasRecipeError.set(flag ? 1 : 0);
-         }
- 
-+        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
-     }
- 
-     @Override
-     public void createResult() {
-         SmithingRecipeInput smithingrecipeinput = this.createRecipeInput();
-         Level world = this.level;
--        Optional optional;
-+        Optional<RecipeHolder<SmithingRecipe>> optional; // CraftBukkit - decompile error
- 
-         if (world instanceof ServerLevel worldserver) {
-             optional = worldserver.recipeAccess().getRecipeFor(RecipeType.SMITHING, smithingrecipeinput, worldserver);
-@@ -129,7 +134,9 @@
-             ItemStack itemstack = ((SmithingRecipe) recipeholder.value()).assemble(smithingrecipeinput, this.level.registryAccess());
- 
-             this.resultSlots.setRecipeUsed(recipeholder);
--            this.resultSlots.setItem(0, itemstack);
-+            // CraftBukkit start
-+            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(this.getBukkitView(), itemstack);
-+            // CraftBukkit end
-         }, () -> {
-             this.resultSlots.setRecipeUsed((RecipeHolder) null);
-             this.resultSlots.setItem(0, ItemStack.EMPTY);
-@@ -149,4 +156,18 @@
-     public boolean hasRecipeError() {
-         return this.hasRecipeError.get() > 0;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftInventoryView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventorySmithing(
-+                this.access.getLocation(), this.inputSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/StonecutterMenu.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/StonecutterMenu.java.patch
deleted file mode 100644
index 95cd1f3220..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/StonecutterMenu.java.patch
+++ /dev/null
@@ -1,188 +0,0 @@
---- a/net/minecraft/world/inventory/StonecutterMenu.java
-+++ b/net/minecraft/world/inventory/StonecutterMenu.java
-@@ -7,7 +7,6 @@
- import net.minecraft.world.Container;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.crafting.RecipeHolder;
-@@ -17,6 +16,13 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Blocks;
- 
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter;
-+import org.bukkit.craftbukkit.inventory.view.CraftStonecutterView;
-+import org.bukkit.entity.Player;
-+// CraftBukkit end
-+
- public class StonecutterMenu extends AbstractContainerMenu {
- 
-     public static final int INPUT_SLOT = 0;
-@@ -36,27 +42,49 @@
-     Runnable slotUpdateListener;
-     public final Container container;
-     final ResultContainer resultContainer;
-+    // CraftBukkit start
-+    private CraftStonecutterView bukkitEntity = null;
-+    private Player player;
- 
-+    @Override
-+    public CraftStonecutterView getBukkitView() {
-+        if (this.bukkitEntity != null) {
-+            return this.bukkitEntity;
-+        }
-+
-+        CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.container, this.resultContainer);
-+        this.bukkitEntity = new CraftStonecutterView(this.player, inventory, this);
-+        return this.bukkitEntity;
-+    }
-+    // CraftBukkit end
-+
-     public StonecutterMenu(int syncId, Inventory playerInventory) {
-         this(syncId, playerInventory, ContainerLevelAccess.NULL);
-     }
- 
-     public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) {
-         super(MenuType.STONECUTTER, syncId);
--        this.selectedRecipeIndex = DataSlot.standalone();
-+        this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - Add PlayerStonecutterRecipeSelectEvent
-         this.recipesForInput = SelectableRecipe.SingleInputSet.empty();
-         this.input = ItemStack.EMPTY;
-         this.slotUpdateListener = () -> {
-         };
--        this.container = new SimpleContainer(1) {
-+        this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders
-             @Override
-             public void setChanged() {
-                 super.setChanged();
-                 StonecutterMenu.this.slotsChanged(this);
-                 StonecutterMenu.this.slotUpdateListener.run();
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public Location getLocation() {
-+                return context.getLocation();
-+            }
-+            // CraftBukkit end
-         };
--        this.resultContainer = new ResultContainer();
-+        this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders
-         this.access = context;
-         this.level = playerInventory.player.level();
-         this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33));
-@@ -67,7 +95,7 @@
-             }
- 
-             @Override
--            public void onTake(Player player, ItemStack stack) {
-+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
-                 stack.onCraftedBy(player.level(), player, stack.getCount());
-                 StonecutterMenu.this.resultContainer.awardUsedRecipes(player, this.getRelevantItems());
-                 ItemStack itemstack1 = StonecutterMenu.this.inputSlot.remove(1);
-@@ -80,7 +108,7 @@
-                     long j = world.getGameTime();
- 
-                     if (StonecutterMenu.this.lastSoundTime != j) {
--                        world.playSound((Player) null, blockposition, SoundEvents.UI_STONECUTTER_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                        world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_STONECUTTER_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-                         StonecutterMenu.this.lastSoundTime = j;
-                     }
- 
-@@ -94,6 +122,7 @@
-         });
-         this.addStandardInventorySlots(playerInventory, 8, 84);
-         this.addDataSlot(this.selectedRecipeIndex);
-+        this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
-     }
- 
-     public int getSelectedRecipeIndex() {
-@@ -113,18 +142,45 @@
-     }
- 
-     @Override
--    public boolean stillValid(Player player) {
-+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
-+        if (!this.checkReachable) return true; // CraftBukkit
-         return stillValid(this.access, player, Blocks.STONECUTTER);
-     }
- 
-     @Override
--    public boolean clickMenuButton(Player player, int id) {
-+    public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
-         if (this.selectedRecipeIndex.get() == id) {
-             return false;
-         } else {
-             if (this.isValidRecipeIndex(id)) {
--                this.selectedRecipeIndex.set(id);
--                this.setupResultSlot(id);
-+                // Paper start - Add PlayerStonecutterRecipeSelectEvent
-+                int recipeIndex = id;
-+                this.selectedRecipeIndex.set(recipeIndex);
-+                this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed
-+                paperEventBlock: if (this.isValidRecipeIndex(id)) {
-+                    final Optional<RecipeHolder<StonecutterRecipe>> recipe = this.recipesForInput.entries().get(id).recipe().recipe();
-+                    if (recipe.isEmpty()) break paperEventBlock; // The recipe selected does not have an actual server recipe (presumably its the empty one). Cannot call the event, just break.
-+
-+                    io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) recipe.get().toBukkitRecipe());
-+                    if (!event.callEvent()) {
-+                        player.containerMenu.sendAllDataToRemote();
-+                        return false;
-+                    }
-+
-+                    net.minecraft.resources.ResourceLocation key = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey());
-+                    if (!recipe.get().id().location().equals(key)) { // If the recipe did NOT stay the same
-+                        for (int newRecipeIndex = 0; newRecipeIndex < this.recipesForInput.entries().size(); newRecipeIndex++) {
-+                            if (this.recipesForInput.entries().get(newRecipeIndex).recipe().recipe().filter(r -> r.id().location().equals(key)).isPresent()) {
-+                                recipeIndex = newRecipeIndex;
-+                                break;
-+                            }
-+                        }
-+                    }
-+                }
-+                player.containerMenu.sendAllDataToRemote();
-+                this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it
-+                this.setupResultSlot(recipeIndex);
-+                // Paper end - Add PlayerStonecutterRecipeSelectEvent
-             }
- 
-             return true;
-@@ -144,6 +200,7 @@
-             this.setupRecipeList(itemstack);
-         }
- 
-+        org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent
-     }
- 
-     private void setupRecipeList(ItemStack stack) {
-@@ -158,7 +215,7 @@
-     }
- 
-     void setupResultSlot(int selectedId) {
--        Optional optional;
-+        Optional<RecipeHolder<StonecutterRecipe>> optional; // CraftBukkit - decompile error
- 
-         if (!this.recipesForInput.isEmpty() && this.isValidRecipeIndex(selectedId)) {
-             SelectableRecipe.SingleInputEntry<StonecutterRecipe> selectablerecipe_a = (SelectableRecipe.SingleInputEntry) this.recipesForInput.entries().get(selectedId);
-@@ -193,7 +250,7 @@
-     }
- 
-     @Override
--    public ItemStack quickMoveStack(Player player, int slot) {
-+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
-         ItemStack itemstack = ItemStack.EMPTY;
-         Slot slot1 = (Slot) this.slots.get(slot);
- 
-@@ -246,7 +303,7 @@
-     }
- 
-     @Override
--    public void removed(Player player) {
-+    public void removed(net.minecraft.world.entity.player.Player player) {
-         super.removed(player);
-         this.resultContainer.removeItemNoUpdate(1);
-         this.access.execute((world, blockposition) -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
deleted file mode 100644
index 67ed6a0fb4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
+++ /dev/null
@@ -1,93 +0,0 @@
---- a/net/minecraft/world/inventory/TransientCraftingContainer.java
-+++ b/net/minecraft/world/inventory/TransientCraftingContainer.java
-@@ -3,11 +3,21 @@
- import java.util.Iterator;
- import java.util.List;
- import net.minecraft.core.NonNullList;
-+import net.minecraft.world.Container;
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.player.StackedItemContents;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import java.util.List;
-+import net.minecraft.world.item.crafting.RecipeHolder;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.event.inventory.InventoryType;
-+// CraftBukkit end
-+
- public class TransientCraftingContainer implements CraftingContainer {
- 
-     private final NonNullList<ItemStack> items;
-@@ -15,6 +25,68 @@
-     private final int height;
-     private final AbstractContainerMenu menu;
- 
-+    // CraftBukkit start - add fields
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
-+    private RecipeHolder<?> currentRecipe;
-+    public Container resultInventory;
-+    private Player owner;
-+    private int maxStack = MAX_STACK;
-+
-+    public List<ItemStack> getContents() {
-+        return this.items;
-+    }
-+
-+    public void onOpen(CraftHumanEntity who) {
-+        this.transaction.add(who);
-+    }
-+
-+    public InventoryType getInvType() {
-+        return this.items.size() == 4 ? InventoryType.CRAFTING : InventoryType.WORKBENCH;
-+    }
-+
-+    public void onClose(CraftHumanEntity who) {
-+        this.transaction.remove(who);
-+    }
-+
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    public org.bukkit.inventory.InventoryHolder getOwner() {
-+        return (this.owner == null) ? null : this.owner.getBukkitEntity();
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+        this.resultInventory.setMaxStackSize(size);
-+    }
-+
-+    @Override
-+    public Location getLocation() {
-+        return this.menu instanceof CraftingMenu ? ((CraftingMenu) this.menu).access.getLocation() : this.owner.getBukkitEntity().getLocation();
-+    }
-+
-+    @Override
-+    public RecipeHolder<?> getCurrentRecipe() {
-+        return this.currentRecipe;
-+    }
-+
-+    @Override
-+    public void setCurrentRecipe(RecipeHolder<?> currentRecipe) {
-+        this.currentRecipe = currentRecipe;
-+    }
-+
-+    public TransientCraftingContainer(AbstractContainerMenu container, int i, int j, Player player) {
-+        this(container, i, j);
-+        this.owner = player;
-+    }
-+    // CraftBukkit end
-+
-     public TransientCraftingContainer(AbstractContainerMenu handler, int width, int height) {
-         this(handler, width, height, NonNullList.withSize(width * height, ItemStack.EMPTY));
-     }

From ea14971545abf281c8b604c03a44d30ee92d4182 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 18:12:30 +0100
Subject: [PATCH 067/285] More entity classes

---
 .../world/entity/EntityType.java.patch        | 155 +++++++++++++++
 .../world/entity/Leashable.java.patch         |  98 ++++++++++
 .../world/entity/EntityType.java.patch        | 179 ------------------
 .../world/entity/Leashable.java.patch         | 157 ---------------
 4 files changed, 253 insertions(+), 336 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/EntityType.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/Leashable.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch
new file mode 100644
index 0000000000..df8e7e81b0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch
@@ -0,0 +1,155 @@
+--- a/net/minecraft/world/entity/EntityType.java
++++ b/net/minecraft/world/entity/EntityType.java
+@@ -176,6 +_,7 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.entity.CreatureSpawnEvent;
+ import org.slf4j.Logger;
+ 
+ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeTest<Entity, T> {
+@@ -215,7 +_,7 @@
+             .fireImmune()
+             .sized(6.0F, 0.5F)
+             .clientTrackingRange(10)
+-            .updateInterval(Integer.MAX_VALUE)
++            .updateInterval(10) // CraftBukkit - SPIGOT-3729: track area effect clouds
+     );
+     public static final EntityType<Armadillo> ARMADILLO = register(
+         "armadillo", EntityType.Builder.of(Armadillo::new, MobCategory.CREATURE).sized(0.7F, 0.65F).eyeHeight(0.26F).clientTrackingRange(10)
+@@ -1132,6 +_,22 @@
+         boolean shouldOffsetY,
+         boolean shouldOffsetYMore
+     ) {
++        // CraftBukkit start
++        return this.spawn(level, spawnedFrom, player, pos, reason, shouldOffsetY, shouldOffsetYMore, reason == EntitySpawnReason.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs
++    }
++
++    @Nullable
++    public T spawn(
++        ServerLevel level,
++        @Nullable ItemStack spawnedFrom,
++        @Nullable Player player,
++        BlockPos pos,
++        EntitySpawnReason reason,
++        boolean shouldOffsetY,
++        boolean shouldOffsetYMore,
++        org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason
++    ) {
++        // CraftBukkit end
+         Consumer<T> consumer;
+         if (spawnedFrom != null) {
+             consumer = createDefaultStackConfig(level, spawnedFrom, player);
+@@ -1139,7 +_,7 @@
+             consumer = entity -> {};
+         }
+ 
+-        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore);
++        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore, createSpawnReason); // CraftBukkit
+     }
+ 
+     public static <T extends Entity> Consumer<T> createDefaultStackConfig(Level level, ItemStack spawnedFrom, @Nullable Player player) {
+@@ -1159,19 +_,54 @@
+         Consumer<T> consumer, Level level, ItemStack spawnedFrom, @Nullable Player player
+     ) {
+         CustomData customData = spawnedFrom.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY);
+-        return !customData.isEmpty() ? consumer.andThen(entity -> updateCustomEntityTag(level, player, entity, customData)) : consumer;
++        // CraftBukkit start - SPIGOT-5665
++        return !customData.isEmpty() ? consumer.andThen(entity -> {
++            try {
++                updateCustomEntityTag(level, player, entity, customData);
++            } catch (Throwable t) {
++                EntityType.LOGGER.warn("Error loading spawn egg NBT", t);
++            }
++        }) : consumer;
++        // CraftBukkit end
+     }
+ 
+     @Nullable
+     public T spawn(ServerLevel level, BlockPos pos, EntitySpawnReason reason) {
+-        return this.spawn(level, null, pos, reason, false, false);
++        // CraftBukkit start
++        return this.spawn(level, pos, reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++    @Nullable
++    public T spawn(ServerLevel level, BlockPos pos, EntitySpawnReason reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
++        return this.spawn(level, null, pos, reason, false, false, creatureSpawnReason);
++        // CraftBukkit End
+     }
+ 
+     @Nullable
+     public T spawn(ServerLevel level, @Nullable Consumer<T> consumer, BlockPos pos, EntitySpawnReason reason, boolean shouldOffsetY, boolean shouldOffsetYMore) {
++        // CraftBukkit start
++        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++    @Nullable
++    public T spawn(ServerLevel level, @Nullable Consumer<T> consumer, BlockPos pos, EntitySpawnReason reason, boolean shouldOffsetY, boolean shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
++        // CraftBukkit end
++        // Paper start - PreCreatureSpawnEvent
++        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
++            io.papermc.paper.util.MCUtil.toLocation(level, pos),
++            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(this),
++            creatureSpawnReason
++        );
++        if (!event.callEvent()) {
++            return null;
++        }
++        // Paper end - PreCreatureSpawnEvent
+         T entity = this.create(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore);
+         if (entity != null) {
+-            level.addFreshEntityWithPassengers(entity);
++            // CraftBukkit start
++            level.addFreshEntityWithPassengers(entity, creatureSpawnReason);
++            if (entity.isRemoved()) {
++                return null; // Don't return an entity when CreatureSpawnEvent is canceled
++            }
++            // CraftBukkit end
+             if (entity instanceof Mob mob) {
+                 mob.playAmbientSound();
+             }
+@@ -1225,6 +_,15 @@
+             EntityType<?> entityType = customData.parseEntityType(server.registryAccess(), Registries.ENTITY_TYPE);
+             if (entity.getType() == entityType) {
+                 if (level.isClientSide || !entity.getType().onlyOpCanSetNbt() || player != null && server.getPlayerList().isOp(player.getGameProfile())) {
++                    // Paper start - filter out protected tags
++                    if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) {
++                        customData = customData.update((compound) -> {
++                            for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : level.paperConfig().entities.spawning.filteredEntityTagNbtPaths) {
++                                tag.remove(compound);
++                            }
++                        });
++                    }
++                    // Paper end - filter out protected tags
+                     customData.loadInto(entity);
+                 }
+             }
+@@ -1296,9 +_,19 @@
+     }
+ 
+     public static Optional<Entity> create(CompoundTag tag, Level level, EntitySpawnReason spawnReason) {
++        // Paper start - Don't fire sync event during generation
++        return create(tag, level, spawnReason, false);
++    }
++    public static Optional<Entity> create(CompoundTag tag, Level level, EntitySpawnReason spawnReason, boolean generation) {
++        // Paper end - Don't fire sync event during generation
+         return Util.ifElse(
+             by(tag).map(entityType -> entityType.create(level, spawnReason)),
+-            entity -> entity.load(tag),
++            // Paper start - Don't fire sync event during generation
++            entity -> {
++                if (generation) entity.generation = true; // Paper - Don't fire sync event during generation
++                entity.load(tag);
++            },
++            // Paper end - Don't fire sync event during generation
+             () -> LOGGER.warn("Skipping Entity with id {}", tag.getString("id"))
+         );
+     }
+@@ -1325,7 +_,7 @@
+     }
+ 
+     public static Optional<EntityType<?>> by(CompoundTag tag) {
+-        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(tag.getString("id")));
++        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(tag.getString("id"))); // Paper - Validate ResourceLocation
+     }
+ 
+     @Nullable
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch
new file mode 100644
index 0000000000..691174efb1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Leashable.java.patch
@@ -0,0 +1,98 @@
+--- a/net/minecraft/world/entity/Leashable.java
++++ b/net/minecraft/world/entity/Leashable.java
+@@ -56,7 +_,13 @@
+     @Nullable
+     private static Leashable.LeashData readLeashDataInternal(CompoundTag tag) {
+         if (tag.contains("leash", 10)) {
+-            return new Leashable.LeashData(Either.left(tag.getCompound("leash").getUUID("UUID")));
++            // Paper start
++            final CompoundTag leashTag = tag.getCompound("leash");
++            if (!leashTag.hasUUID("UUID")) {
++                return null;
++            }
++            return new Leashable.LeashData(Either.left(leashTag.getUUID("UUID")));
++            // Paper end
+         } else {
+             if (tag.contains("leash", 11)) {
+                 Either<UUID, BlockPos> either = NbtUtils.readBlockPos(tag, "leash").<Either<UUID, BlockPos>>map(Either::right).orElse(null);
+@@ -72,6 +_,11 @@
+     default void writeLeashData(CompoundTag tag, @Nullable Leashable.LeashData leashData) {
+         if (leashData != null) {
+             Either<UUID, BlockPos> either = leashData.delayedLeashInfo;
++            // CraftBukkit start - SPIGOT-7487: Don't save (and possible drop) leash, when the holder was removed by a plugin
++            if (leashData.leashHolder != null && leashData.leashHolder.pluginRemoved) {
++                return;
++            }
++            // CraftBukkit end
+             if (leashData.leashHolder instanceof LeashFenceKnotEntity leashFenceKnotEntity) {
+                 either = Either.right(leashFenceKnotEntity.getPos());
+             } else if (leashData.leashHolder != null) {
+@@ -104,7 +_,9 @@
+             }
+ 
+             if (entity.tickCount > 100) {
++                entity.forceDrops = true; // CraftBukkit
+                 entity.spawnAtLocation(serverLevel, Items.LEAD);
++                entity.forceDrops = false; // CraftBukkit
+                 entity.setLeashData(null);
+             }
+         }
+@@ -128,7 +_,9 @@
+             entity.onLeashRemoved();
+             if (entity.level() instanceof ServerLevel serverLevel) {
+                 if (dropItem) {
++                    entity.forceDrops = true; // CraftBukkit
+                     entity.spawnAtLocation(serverLevel, Items.LEAD);
++                    entity.forceDrops = false; // CraftBukkit
+                 }
+ 
+                 if (broadcastPacket) {
+@@ -146,7 +_,15 @@
+ 
+         if (leashData != null && leashData.leashHolder != null) {
+             if (!entity.isAlive() || !leashData.leashHolder.isAlive()) {
+-                if (level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
++                // Paper start - Expand EntityUnleashEvent
++                final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(
++                    entity.getBukkitEntity(),
++                    !entity.isAlive() ? org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH : org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.HOLDER_GONE,
++                    level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved
++                );
++                event.callEvent();
++                if (event.isDropLeash()) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
++                    // Paper end - Expand EntityUnleashEvent
+                     entity.dropLeash();
+                 } else {
+                     entity.removeLeash();
+@@ -160,7 +_,7 @@
+                     return;
+                 }
+ 
+-                if (f > 10.0) {
++                if (f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
+                     entity.leashTooFarBehaviour();
+                 } else if (f > 6.0) {
+                     entity.elasticRangeLeashBehaviour(leashHolder, f);
+@@ -177,7 +_,21 @@
+     }
+ 
+     default void leashTooFarBehaviour() {
+-        this.dropLeash();
++        // CraftBukkit start
++        boolean dropLeash = true; // Paper
++        if (this instanceof Entity entity) {
++            // Paper start - Expand EntityUnleashEvent
++            final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(entity.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.DISTANCE, true);
++            if (!event.callEvent()) return;
++            dropLeash = event.isDropLeash();
++        }
++        // CraftBukkit end
++        if (dropLeash) {
++            this.dropLeash();
++        } else {
++            this.removeLeash();
++        }
++        // Paper end - Expand EntityUnleashEvent
+     }
+ 
+     default void closeRangeLeashBehaviour(Entity entity) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/EntityType.java.patch
deleted file mode 100644
index 1ff8d4806a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/EntityType.java.patch
+++ /dev/null
@@ -1,179 +0,0 @@
---- a/net/minecraft/world/entity/EntityType.java
-+++ b/net/minecraft/world/entity/EntityType.java
-@@ -176,6 +176,7 @@
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
- import org.slf4j.Logger;
- 
- public class EntityType<T extends Entity> implements FeatureElement, EntityTypeTest<Entity, T> {
-@@ -191,7 +192,7 @@
-         return Items.ACACIA_CHEST_BOAT;
-     }), MobCategory.MISC).noLootTable().sized(1.375F, 0.5625F).eyeHeight(0.5625F).clientTrackingRange(10));
-     public static final EntityType<Allay> ALLAY = EntityType.register("allay", EntityType.Builder.of(Allay::new, MobCategory.CREATURE).sized(0.35F, 0.6F).eyeHeight(0.36F).ridingOffset(0.04F).clientTrackingRange(8).updateInterval(2));
--    public static final EntityType<AreaEffectCloud> AREA_EFFECT_CLOUD = EntityType.register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).noLootTable().fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(Integer.MAX_VALUE));
-+    public static final EntityType<AreaEffectCloud> AREA_EFFECT_CLOUD = EntityType.register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).noLootTable().fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(10)); // CraftBukkit - SPIGOT-3729: track area effect clouds
-     public static final EntityType<Armadillo> ARMADILLO = EntityType.register("armadillo", EntityType.Builder.of(Armadillo::new, MobCategory.CREATURE).sized(0.7F, 0.65F).eyeHeight(0.26F).clientTrackingRange(10));
-     public static final EntityType<ArmorStand> ARMOR_STAND = EntityType.register("armor_stand", EntityType.Builder.of(ArmorStand::new, MobCategory.MISC).sized(0.5F, 1.975F).eyeHeight(1.7775F).clientTrackingRange(10));
-     public static final EntityType<Arrow> ARROW = EntityType.register("arrow", EntityType.Builder.of(Arrow::new, MobCategory.MISC).noLootTable().sized(0.5F, 0.5F).eyeHeight(0.13F).clientTrackingRange(4).updateInterval(20));
-@@ -399,7 +400,7 @@
-         return ResourceKey.create(Registries.ENTITY_TYPE, ResourceLocation.withDefaultNamespace(id));
-     }
- 
--    private static <T extends Entity> EntityType<T> register(String id, EntityType.Builder<T> type) {
-+    private static <T extends Entity> EntityType<T> register(String id, EntityType.Builder type) { // CraftBukkit - decompile error
-         return EntityType.register(EntityType.vanillaEntityId(id), type);
-     }
- 
-@@ -431,16 +432,23 @@
- 
-     @Nullable
-     public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, EntitySpawnReason spawnReason, boolean alignPosition, boolean invertY) {
--        Consumer consumer;
-+        // CraftBukkit start
-+        return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == EntitySpawnReason.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs
-+    }
- 
--        if (stack != null) {
--            consumer = EntityType.createDefaultStackConfig(world, stack, player);
-+    @Nullable
-+    public T spawn(ServerLevel worldserver, @Nullable ItemStack itemstack, @Nullable Player entityhuman, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
-+        // CraftBukkit end
-+        Consumer<T> consumer; // CraftBukkit - decompile error
-+
-+        if (itemstack != null) {
-+            consumer = EntityType.createDefaultStackConfig(worldserver, itemstack, entityhuman);
-         } else {
-             consumer = (entity) -> {
-             };
-         }
- 
--        return this.spawn(world, consumer, pos, spawnReason, alignPosition, invertY);
-+        return this.spawn(worldserver, consumer, blockposition, entityspawnreason, flag, flag1, spawnReason); // CraftBukkit
-     }
- 
-     public static <T extends Entity> Consumer<T> createDefaultStackConfig(Level world, ItemStack stack, @Nullable Player player) {
-@@ -464,21 +472,50 @@
-         CustomData customdata = (CustomData) stack.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY);
- 
-         return !customdata.isEmpty() ? chained.andThen((entity) -> {
--            EntityType.updateCustomEntityTag(world, player, entity, customdata);
-+            try { EntityType.updateCustomEntityTag(world, player, entity, customdata); } catch (Throwable t) { EntityType.LOGGER.warn("Error loading spawn egg NBT", t); } // CraftBukkit - SPIGOT-5665
-         }) : chained;
-     }
- 
-     @Nullable
-     public T spawn(ServerLevel world, BlockPos pos, EntitySpawnReason reason) {
--        return this.spawn(world, (Consumer) null, pos, reason, false, false);
-+        // CraftBukkit start
-+        return this.spawn(world, pos, reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-     }
- 
-     @Nullable
-+    public T spawn(ServerLevel worldserver, BlockPos blockposition, EntitySpawnReason entityspawnreason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
-+        return this.spawn(worldserver, (Consumer<T>) null, blockposition, entityspawnreason, false, false, spawnReason); // CraftBukkit - decompile error
-+        // CraftBukkit end
-+    }
-+
-+    @Nullable
-     public T spawn(ServerLevel world, @Nullable Consumer<T> afterConsumer, BlockPos pos, EntitySpawnReason reason, boolean alignPosition, boolean invertY) {
--        T t0 = this.create(world, afterConsumer, pos, reason, alignPosition, invertY);
-+        // CraftBukkit start
-+        return this.spawn(world, afterConsumer, pos, reason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
-+
-+    @Nullable
-+    public T spawn(ServerLevel worldserver, @Nullable Consumer<T> consumer, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
-+        // CraftBukkit end
-+        // Paper start - PreCreatureSpawnEvent
-+        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
-+            io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition),
-+            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(this),
-+            spawnReason
-+        );
-+        if (!event.callEvent()) {
-+            return null;
-+        }
-+        // Paper end - PreCreatureSpawnEvent
-+        T t0 = this.create(worldserver, consumer, blockposition, entityspawnreason, flag, flag1);
- 
-         if (t0 != null) {
--            world.addFreshEntityWithPassengers(t0);
-+            // CraftBukkit start
-+            worldserver.addFreshEntityWithPassengers(t0, spawnReason);
-+            if (t0.isRemoved()) {
-+                return null; // Don't return an entity when CreatureSpawnEvent is canceled
-+            }
-+            // CraftBukkit end
-             if (t0 instanceof Mob) {
-                 Mob entityinsentient = (Mob) t0;
- 
-@@ -542,6 +579,15 @@
- 
-             if (entity.getType() == entitytypes) {
-                 if (world.isClientSide || !entity.getType().onlyOpCanSetNbt() || player != null && minecraftserver.getPlayerList().isOp(player.getGameProfile())) {
-+                    // Paper start - filter out protected tags
-+                    if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) {
-+                        nbt = nbt.update((compound) -> {
-+                            for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : world.paperConfig().entities.spawning.filteredEntityTagNbtPaths) {
-+                                tag.remove(compound);
-+                            }
-+                        });
-+                    }
-+                    // Paper end - filter out protected tags
-                     nbt.loadInto(entity);
-                 }
-             }
-@@ -613,9 +659,15 @@
-     }
- 
-     public static Optional<Entity> create(CompoundTag nbt, Level world, EntitySpawnReason reason) {
-+    // Paper start - Don't fire sync event during generation
-+        return create(nbt, world, reason, false);
-+    }
-+    public static Optional<Entity> create(CompoundTag nbt, Level world, EntitySpawnReason reason, boolean generation) {
-+    // Paper end - Don't fire sync event during generation
-         return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> {
-             return entitytypes.create(world, reason);
-         }), (entity) -> {
-+            if (generation) entity.generation = true; // Paper - Don't fire sync event during generation
-             entity.load(nbt);
-         }, () -> {
-             EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id"));
-@@ -638,7 +690,7 @@
-     }
- 
-     public static Optional<EntityType<?>> by(CompoundTag nbt) {
--        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(nbt.getString("id")));
-+        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation
-     }
- 
-     @Nullable
-@@ -657,7 +709,7 @@
-             }
- 
-             return entity;
--        }).orElse((Object) null);
-+        }).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     public static Stream<Entity> loadEntitiesRecursive(final List<? extends Tag> entityNbtList, final Level world, final EntitySpawnReason reason) {
-@@ -718,7 +770,7 @@
- 
-     @Nullable
-     public T tryCast(Entity obj) {
--        return obj.getType() == this ? obj : null;
-+        return obj.getType() == this ? (T) obj : null; // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -791,7 +843,7 @@
-             this.canSpawnFarFromPlayer = spawnGroup == MobCategory.CREATURE || spawnGroup == MobCategory.MISC;
-         }
- 
--        public static <T extends Entity> EntityType.Builder<T> of(EntityType.EntityFactory<T> factory, MobCategory spawnGroup) {
-+        public static <T extends Entity> EntityType.Builder<T> of(EntityType.EntityFactory factory, MobCategory spawnGroup) { // CraftBukkit - decompile error
-             return new EntityType.Builder<>(factory, spawnGroup);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Leashable.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/Leashable.java.patch
deleted file mode 100644
index aa36549d47..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/Leashable.java.patch
+++ /dev/null
@@ -1,157 +0,0 @@
---- a/net/minecraft/world/entity/Leashable.java
-+++ b/net/minecraft/world/entity/Leashable.java
-@@ -15,6 +15,10 @@
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.ItemLike;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityUnleashEvent;
-+import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
-+// CraftBukkit end
- 
- public interface Leashable {
- 
-@@ -45,7 +49,7 @@
- 
-     default void setDelayedLeashHolderId(int unresolvedLeashHolderId) {
-         this.setLeashData(new Leashable.LeashData(unresolvedLeashHolderId));
--        Leashable.dropLeash((Entity) this, false, false);
-+        Leashable.dropLeash((Entity & Leashable) this, false, false); // CraftBukkit - decompile error
-     }
- 
-     default void readLeashData(CompoundTag nbt) {
-@@ -61,10 +65,16 @@
-     @Nullable
-     private static Leashable.LeashData readLeashDataInternal(CompoundTag nbt) {
-         if (nbt.contains("leash", 10)) {
--            return new Leashable.LeashData(Either.left(nbt.getCompound("leash").getUUID("UUID")));
-+            // Paper start
-+            final CompoundTag leashTag = nbt.getCompound("leash");
-+            if (!leashTag.hasUUID("UUID")) {
-+                return null;
-+            }
-+            return new Leashable.LeashData(Either.left(leashTag.getUUID("UUID")));
-+            // Paper end
-         } else {
-             if (nbt.contains("leash", 11)) {
--                Either<UUID, BlockPos> either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse((Object) null);
-+                Either<UUID, BlockPos> either = (Either) NbtUtils.readBlockPos(nbt, "leash").map(Either::right).orElse(null); // CraftBukkit - decompile error
- 
-                 if (either != null) {
-                     return new Leashable.LeashData(either);
-@@ -79,6 +89,11 @@
-         if (leashData != null) {
-             Either<UUID, BlockPos> either = leashData.delayedLeashInfo;
-             Entity entity = leashData.leashHolder;
-+            // CraftBukkit start - SPIGOT-7487: Don't save (and possible drop) leash, when the holder was removed by a plugin
-+            if (entity != null && entity.pluginRemoved) {
-+                return;
-+            }
-+            // CraftBukkit end
- 
-             if (entity instanceof LeashFenceKnotEntity) {
-                 LeashFenceKnotEntity entityleash = (LeashFenceKnotEntity) entity;
-@@ -121,7 +136,9 @@
-                 }
- 
-                 if (entity.tickCount > 100) {
-+                    entity.forceDrops = true; // CraftBukkit
-                     entity.spawnAtLocation(worldserver, (ItemLike) Items.LEAD);
-+                    entity.forceDrops = false; // CraftBukkit
-                     ((Leashable) entity).setLeashData((Leashable.LeashData) null);
-                 }
-             }
-@@ -130,11 +147,11 @@
-     }
- 
-     default void dropLeash() {
--        Leashable.dropLeash((Entity) this, true, true);
-+        Leashable.dropLeash((Entity & Leashable) this, true, true); // CraftBukkit - decompile error
-     }
- 
-     default void removeLeash() {
--        Leashable.dropLeash((Entity) this, true, false);
-+        Leashable.dropLeash((Entity & Leashable) this, true, false); // CraftBukkit - decompile error
-     }
- 
-     default void onLeashRemoved() {}
-@@ -151,7 +168,9 @@
-                 ServerLevel worldserver = (ServerLevel) world;
- 
-                 if (dropItem) {
-+                    entity.forceDrops = true; // CraftBukkit
-                     entity.spawnAtLocation(worldserver, (ItemLike) Items.LEAD);
-+                    entity.forceDrops = false; // CraftBukkit
-                 }
- 
-                 if (sendPacket) {
-@@ -171,7 +190,11 @@
- 
-         if (leashable_a != null && leashable_a.leashHolder != null) {
-             if (!entity.isAlive() || !leashable_a.leashHolder.isAlive()) {
--                if (world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-+                // Paper start - Expand EntityUnleashEvent
-+                final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), (!entity.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved);
-+                event.callEvent();
-+                if (event.isDropLeash()) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
-+                    // Paper end - Expand EntityUnleashEvent
-                     ((Leashable) entity).dropLeash();
-                 } else {
-                     ((Leashable) entity).removeLeash();
-@@ -187,7 +210,7 @@
-                     return;
-                 }
- 
--                if ((double) f > 10.0D) {
-+                if ((double) f > entity.level().paperConfig().misc.maxLeashDistance.or(LEASH_TOO_FAR_DIST)) { // Paper - Configurable max leash distance
-                     ((Leashable) entity).leashTooFarBehaviour();
-                 } else if ((double) f > 6.0D) {
-                     ((Leashable) entity).elasticRangeLeashBehaviour(entity1, f);
-@@ -205,13 +228,27 @@
-     }
- 
-     default void leashTooFarBehaviour() {
--        this.dropLeash();
-+        // CraftBukkit start
-+        boolean dropLeash = true; // Paper
-+        if (this instanceof Entity entity) {
-+            // Paper start - Expand EntityUnleashEvent
-+            final EntityUnleashEvent event = new EntityUnleashEvent(entity.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true);
-+            if (!event.callEvent()) return;
-+            dropLeash = event.isDropLeash();
-+        }
-+        // CraftBukkit end
-+        if (dropLeash) {
-+            this.dropLeash();
-+        } else {
-+            this.removeLeash();
-+        }
-+        // Paper end - Expand EntityUnleashEvent
-     }
- 
-     default void closeRangeLeashBehaviour(Entity entity) {}
- 
-     default void elasticRangeLeashBehaviour(Entity leashHolder, float distance) {
--        Leashable.legacyElasticRangeLeashBehaviour((Entity) this, leashHolder, distance);
-+        Leashable.legacyElasticRangeLeashBehaviour((Entity & Leashable) this, leashHolder, distance); // CraftBukkit - decompile error
-     }
- 
-     private static <E extends Entity & Leashable> void legacyElasticRangeLeashBehaviour(E entity, Entity leashHolder, float distance) {
-@@ -223,7 +260,7 @@
-     }
- 
-     default void setLeashedTo(Entity leashHolder, boolean sendPacket) {
--        this.setLeashedTo((Entity) this, leashHolder, sendPacket);
-+        Leashable.setLeashedTo((Entity & Leashable) this, leashHolder, sendPacket); // CraftBukkit - decompile error
-     }
- 
-     private static <E extends Entity & Leashable> void setLeashedTo(E entity, Entity leashHolder, boolean sendPacket) {
-@@ -254,7 +291,7 @@
- 
-     @Nullable
-     default Entity getLeashHolder() {
--        return Leashable.getLeashHolder((Entity) this);
-+        return Leashable.getLeashHolder((Entity & Leashable) this); // CraftBukkit - decompile error
-     }
- 
-     @Nullable

From 71cac092eabd21562f1b3217d7858201bc7258fc Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 12:14:12 -0500
Subject: [PATCH 068/285] net/minecraft/world/inventory fixes

---
 .../inventory/AbstractCraftingMenu.java.patch | 16 --------------
 .../inventory/AbstractFurnaceMenu.java.patch  |  9 --------
 .../world/inventory/MenuType.java.patch       | 22 -------------------
 3 files changed, 47 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
index 5e22ef73ce..0daa1431a6 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractCraftingMenu.java.patch
@@ -21,19 +21,3 @@
      }
  
      protected Slot addResultSlot(Player player, int x, int y) {
-@@ -35,13 +_,13 @@
-     }
- 
-     @Override
--    public RecipeBookMenu.PostPlaceAction handlePlacement(
-+    public PostPlaceAction handlePlacement(
-         boolean useMaxItems, boolean isCreative, RecipeHolder<?> recipe, ServerLevel level, Inventory playerInventory
-     ) {
-         RecipeHolder<CraftingRecipe> recipeHolder = (RecipeHolder<CraftingRecipe>)recipe;
-         this.beginPlacingRecipe();
- 
--        RecipeBookMenu.PostPlaceAction var8;
-+        PostPlaceAction var8;
-         try {
-             List<Slot> inputGridSlots = this.getInputGridSlots();
-             var8 = ServerPlaceRecipe.placeRecipe(new ServerPlaceRecipe.CraftingMenuAccess<CraftingRecipe>() {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
index 17fc9ef5af..4d7c9ff8ff 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
@@ -38,12 +38,3 @@
          return this.container.stillValid(player);
      }
  
-@@ -170,7 +_,7 @@
-     }
- 
-     @Override
--    public RecipeBookMenu.PostPlaceAction handlePlacement(
-+    public PostPlaceAction handlePlacement(
-         boolean useMaxItems, boolean isCreative, RecipeHolder<?> recipe, final ServerLevel level, Inventory playerInventory
-     ) {
-         final List<Slot> list = List.of(this.getSlot(0), this.getSlot(2));
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
index f28a7e6d38..a5767492ae 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MenuType.java.patch
@@ -9,25 +9,3 @@
      public static final MenuType<LoomMenu> LOOM = register("loom", LoomMenu::new);
      public static final MenuType<MerchantMenu> MERCHANT = register("merchant", MerchantMenu::new);
      public static final MenuType<ShulkerBoxMenu> SHULKER_BOX = register("shulker_box", ShulkerBoxMenu::new);
-@@ -35,17 +_,17 @@
-     public static final MenuType<CartographyTableMenu> CARTOGRAPHY_TABLE = register("cartography_table", CartographyTableMenu::new);
-     public static final MenuType<StonecutterMenu> STONECUTTER = register("stonecutter", StonecutterMenu::new);
-     private final FeatureFlagSet requiredFeatures;
--    private final MenuType.MenuSupplier<T> constructor;
-+    private final MenuSupplier<T> constructor;
- 
--    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuType.MenuSupplier<T> factory) {
-+    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuSupplier<T> factory) {
-         return Registry.register(BuiltInRegistries.MENU, key, new MenuType<>(factory, FeatureFlags.VANILLA_SET));
-     }
- 
--    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuType.MenuSupplier<T> factory, FeatureFlag... requiredFeatures) {
-+    private static <T extends AbstractContainerMenu> MenuType<T> register(String key, MenuSupplier<T> factory, FeatureFlag... requiredFeatures) {
-         return Registry.register(BuiltInRegistries.MENU, key, new MenuType<>(factory, FeatureFlags.REGISTRY.subset(requiredFeatures)));
-     }
- 
--    private MenuType(MenuType.MenuSupplier<T> constructor, FeatureFlagSet requiredFeatures) {
-+    private MenuType(MenuSupplier<T> constructor, FeatureFlagSet requiredFeatures) {
-         this.constructor = constructor;
-         this.requiredFeatures = requiredFeatures;
-     }

From d027a2c341019daae289136392712a06a62181a7 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:17:02 +0100
Subject: [PATCH 069/285] net.minecraft.resources

---
 .../resources/RegistryDataLoader.java.patch   | 71 +++++++++++++++++
 .../resources/ResourceLocation.java.patch     |  8 +-
 .../resources/RegistryDataLoader.java.patch   | 79 -------------------
 3 files changed, 75 insertions(+), 83 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/resources/ResourceLocation.java.patch (96%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/resources/RegistryDataLoader.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
new file mode 100644
index 0000000000..c625d649d2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/resources/RegistryDataLoader.java
++++ b/net/minecraft/resources/RegistryDataLoader.java
+@@ -247,13 +_,14 @@
+         RegistryOps<JsonElement> ops,
+         ResourceKey<E> resourceKey,
+         Resource resource,
+-        RegistrationInfo registrationInfo
++        RegistrationInfo registrationInfo,
++        io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions
+     ) throws IOException {
+         try (Reader reader = resource.openAsReader()) {
+             JsonElement jsonElement = JsonParser.parseReader(reader);
+             DataResult<E> dataResult = codec.parse(ops, jsonElement);
+             E orThrow = dataResult.getOrThrow();
+-            registry.register(resourceKey, orThrow, registrationInfo);
++            io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, resourceKey, orThrow, registrationInfo, conversions); // Paper - register with listeners
+         }
+     }
+ 
+@@ -267,6 +_,7 @@
+         FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key());
+         RegistryOps<JsonElement> registryOps = RegistryOps.create(JsonOps.INSTANCE, registryInfoLookup);
+ 
++        final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryInfoLookup); // Paper - create conversions
+         for (Entry<ResourceLocation, Resource> entry : fileToIdConverter.listMatchingResources(resourceManager).entrySet()) {
+             ResourceLocation resourceLocation = entry.getKey();
+             ResourceKey<E> resourceKey = ResourceKey.create(registry.key(), fileToIdConverter.fileToId(resourceLocation));
+@@ -274,7 +_,7 @@
+             RegistrationInfo registrationInfo = REGISTRATION_INFO_CACHE.apply(resource.knownPackInfo());
+ 
+             try {
+-                loadElementFromResource(registry, codec, registryOps, resourceKey, resource, registrationInfo);
++                loadElementFromResource(registry, codec, registryOps, resourceKey, resource, registrationInfo, conversions); // Paper - pass conversions
+             } catch (Exception var14) {
+                 loadingErrors.put(
+                     resourceKey,
+@@ -283,7 +_,8 @@
+             }
+         }
+ 
+-        TagLoader.loadTagsForRegistry(resourceManager, registry);
++        io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), conversions); // Paper - run pre-freeze listeners
++        TagLoader.loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - tag lifecycle - add cause
+     }
+ 
+     static <E> void loadContentsFromNetwork(
+@@ -300,6 +_,7 @@
+             RegistryOps<JsonElement> registryOps1 = RegistryOps.create(JsonOps.INSTANCE, registryInfoLookup);
+             FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key());
+ 
++            final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(registryInfoLookup); // Paper - create conversions
+             for (RegistrySynchronization.PackedRegistryEntry packedRegistryEntry : networkedRegistryData.elements) {
+                 ResourceKey<E> resourceKey = ResourceKey.create(registry.key(), packedRegistryEntry.id());
+                 Optional<Tag> optional = packedRegistryEntry.data();
+@@ -318,7 +_,7 @@
+ 
+                     try {
+                         Resource resourceOrThrow = resourceProvider.getResourceOrThrow(resourceLocation);
+-                        loadElementFromResource(registry, codec, registryOps1, resourceKey, resourceOrThrow, NETWORK_REGISTRATION_INFO);
++                        loadElementFromResource(registry, codec, registryOps1, resourceKey, resourceOrThrow, NETWORK_REGISTRATION_INFO, conversions); // Paper - pass conversions
+                     } catch (Exception var17) {
+                         loadingErrors.put(resourceKey, new IllegalStateException("Failed to parse local data", var17));
+                     }
+@@ -360,6 +_,7 @@
+ 
+         RegistryDataLoader.Loader<T> create(Lifecycle registryLifecycle, Map<ResourceKey<?>, Exception> loadingErrors) {
+             WritableRegistry<T> writableRegistry = new MappedRegistry<>(this.key, registryLifecycle);
++            io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry
+             return new RegistryDataLoader.Loader<>(this, writableRegistry, loadingErrors);
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/resources/ResourceLocation.java.patch b/paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/resources/ResourceLocation.java.patch
rename to paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch
index 4f2bd280fb..db3f21405a 100644
--- a/paper-server/patches/unapplied/net/minecraft/resources/ResourceLocation.java.patch
+++ b/paper-server/patches/sources/net/minecraft/resources/ResourceLocation.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/resources/ResourceLocation.java
 +++ b/net/minecraft/resources/ResourceLocation.java
-@@ -32,6 +32,7 @@
+@@ -32,6 +_,7 @@
      public static final char NAMESPACE_SEPARATOR = ':';
      public static final String DEFAULT_NAMESPACE = "minecraft";
      public static final String REALMS_NAMESPACE = "realms";
@@ -8,7 +8,7 @@
      private final String namespace;
      private final String path;
  
-@@ -40,6 +41,13 @@
+@@ -40,6 +_,13 @@
  
          assert isValidPath(path);
  
@@ -22,7 +22,7 @@
          this.namespace = namespace;
          this.path = path;
      }
-@@ -246,7 +254,7 @@
+@@ -252,7 +_,7 @@
  
      private static String assertValidNamespace(String namespace, String path) {
          if (!isValidNamespace(namespace)) {
@@ -31,7 +31,7 @@
          } else {
              return namespace;
          }
-@@ -267,7 +275,7 @@
+@@ -277,7 +_,7 @@
  
      private static String assertValidPath(String namespace, String path) {
          if (!isValidPath(path)) {
diff --git a/paper-server/patches/unapplied/net/minecraft/resources/RegistryDataLoader.java.patch b/paper-server/patches/unapplied/net/minecraft/resources/RegistryDataLoader.java.patch
deleted file mode 100644
index 41773a3732..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/resources/RegistryDataLoader.java.patch
+++ /dev/null
@@ -1,79 +0,0 @@
---- a/net/minecraft/resources/RegistryDataLoader.java
-+++ b/net/minecraft/resources/RegistryDataLoader.java
-@@ -74,7 +74,7 @@
- 
- public class RegistryDataLoader {
-     private static final Logger LOGGER = LogUtils.getLogger();
--    private static final Comparator<ResourceKey<?>> ERROR_KEY_COMPARATOR = Comparator.comparing(ResourceKey::registry).thenComparing(ResourceKey::location);
-+    private static final Comparator<ResourceKey<?>> ERROR_KEY_COMPARATOR = Comparator.<ResourceKey<?>, ResourceLocation>comparing(ResourceKey::registry).thenComparing(ResourceKey::location); // Paper - decompile fix
-     private static final RegistrationInfo NETWORK_REGISTRATION_INFO = new RegistrationInfo(Optional.empty(), Lifecycle.experimental());
-     private static final Function<Optional<KnownPack>, RegistrationInfo> REGISTRATION_INFO_CACHE = Util.memoize(knownPacks -> {
-         Lifecycle lifecycle = knownPacks.map(KnownPack::isVanilla).map(vanilla -> Lifecycle.stable()).orElse(Lifecycle.experimental());
-@@ -238,13 +238,13 @@
-     }
- 
-     private static <E> void loadElementFromResource(
--        WritableRegistry<E> registry, Decoder<E> decoder, RegistryOps<JsonElement> ops, ResourceKey<E> key, Resource resource, RegistrationInfo entryInfo
-+        WritableRegistry<E> registry, Decoder<E> decoder, RegistryOps<JsonElement> ops, ResourceKey<E> key, Resource resource, RegistrationInfo entryInfo, io.papermc.paper.registry.data.util.Conversions conversions // Paper - pass conversions
-     ) throws IOException {
-         try (Reader reader = resource.openAsReader()) {
-             JsonElement jsonElement = JsonParser.parseReader(reader);
-             DataResult<E> dataResult = decoder.parse(ops, jsonElement);
-             E object = dataResult.getOrThrow();
--            registry.register(key, object, entryInfo);
-+            io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo, conversions); // Paper - register with listeners
-         }
-     }
- 
-@@ -258,6 +258,7 @@
-         FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key());
-         RegistryOps<JsonElement> registryOps = RegistryOps.create(JsonOps.INSTANCE, infoGetter);
- 
-+        final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(infoGetter); // Paper - create conversions
-         for (Entry<ResourceLocation, Resource> entry : fileToIdConverter.listMatchingResources(resourceManager).entrySet()) {
-             ResourceLocation resourceLocation = entry.getKey();
-             ResourceKey<E> resourceKey = ResourceKey.create(registry.key(), fileToIdConverter.fileToId(resourceLocation));
-@@ -265,7 +266,7 @@
-             RegistrationInfo registrationInfo = REGISTRATION_INFO_CACHE.apply(resource.knownPackInfo());
- 
-             try {
--                loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo);
-+                loadElementFromResource(registry, elementDecoder, registryOps, resourceKey, resource, registrationInfo, conversions); // Paper - pass conversions
-             } catch (Exception var14) {
-                 errors.put(
-                     resourceKey,
-@@ -274,7 +275,8 @@
-             }
-         }
- 
--        TagLoader.loadTagsForRegistry(resourceManager, registry);
-+        io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), conversions); // Paper - run pre-freeze listeners
-+        TagLoader.loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - tag lifecycle - add cause
-     }
- 
-     static <E> void loadContentsFromNetwork(
-@@ -291,6 +293,7 @@
-             RegistryOps<JsonElement> registryOps2 = RegistryOps.create(JsonOps.INSTANCE, infoGetter);
-             FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key());
- 
-+            final io.papermc.paper.registry.data.util.Conversions conversions = new io.papermc.paper.registry.data.util.Conversions(infoGetter); // Paper - create conversions
-             for (RegistrySynchronization.PackedRegistryEntry packedRegistryEntry : networkedRegistryData.elements) {
-                 ResourceKey<E> resourceKey = ResourceKey.create(registry.key(), packedRegistryEntry.id());
-                 Optional<Tag> optional = packedRegistryEntry.data();
-@@ -309,7 +312,7 @@
- 
-                     try {
-                         Resource resource = factory.getResourceOrThrow(resourceLocation);
--                        loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO);
-+                        loadElementFromResource(registry, decoder, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO, conversions); // Paper - pass conversions
-                     } catch (Exception var17) {
-                         loadingErrors.put(resourceKey, new IllegalStateException("Failed to parse local data", var17));
-                     }
-@@ -349,6 +352,7 @@
- 
-         RegistryDataLoader.Loader<T> create(Lifecycle lifecycle, Map<ResourceKey<?>, Exception> errors) {
-             WritableRegistry<T> writableRegistry = new MappedRegistry<>(this.key, lifecycle);
-+            io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry
-             return new RegistryDataLoader.Loader<>(this, writableRegistry, errors);
-         }
- 

From cb84eaf87adcef1b8f954d41c016c4a61651550c Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sat, 14 Dec 2024 18:22:38 +0100
Subject: [PATCH 070/285] some fixes

---
 .../network/LegacyQueryHandler.java.patch     | 34 +++------
 .../ServerCommonPacketListenerImpl.java.patch |  2 +-
 ...ConfigurationPacketListenerImpl.java.patch |  5 +-
 .../ServerConnectionListener.java.patch       |  4 +-
 .../ServerGamePacketListenerImpl.java.patch   | 76 ++++++++++---------
 .../ServerStatusPacketListenerImpl.java.patch |  2 +-
 6 files changed, 58 insertions(+), 65 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
index 5ba39612d8..5e6a096b22 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -26,26 +26,24 @@
          byteBuf.markReaderIndex();
          boolean flag = true;
  
-@@ -33,9 +_,21 @@
+@@ -33,9 +_,19 @@
  
                  SocketAddress socketAddress = context.channel().remoteAddress();
                  int i = byteBuf.readableBytes();
 +                String string = null; // Paper
-+                // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketAddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper
-+                com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper
                  if (i == 0) {
 -                    LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
 -                    String string = createVersion0Response(this.server);
 +                    LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress: "<ip address withheld>"); // Paper - Respect logIPs option
 +                    // Paper start - Call PaperServerListPingEvent and use results
-+                    event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null);
++                    com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null);
 +                    if (event == null) {
 +                        context.close();
 +                        byteBuf.release();
 +                        flag = false;
 +                        return;
 +                    }
-+                    string = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
++                    string = String.format(Locale.ROOT, "%s§%d§%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
 +                    // Paper end - Call PaperServerListPingEvent and use results
                      sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
                  } else {
@@ -74,7 +72,7 @@
 +
 +                    if (string == null) {
 +                        // Paper start - Call PaperServerListPingEvent and use results
-+                        event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper
++                        com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper
 +                        if (event == null) {
 +                            context.close();
 +                            byteBuf.release();
@@ -85,7 +83,7 @@
 -                        LOGGER.debug("Ping: (1.6) from {}", socketAddress);
 -                    } else {
 -                        LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
-+                        string = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit
++                        string = String.format(Locale.ROOT, "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit
 +                        // Paper end - Call PaperServerListPingEvent and use results
                      }
 -
@@ -93,25 +91,11 @@
                      sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
                  }
  
-@@ -95,21 +_,97 @@
-         }
+@@ -110,6 +_,98 @@
+             server.getMaxPlayers()
+         );
      }
- 
--    private static String createVersion0Response(ServerInfo server) {
--        return String.format(Locale.ROOT, "%s§%d§%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
--    }
--
--    private static String createVersion1Response(ServerInfo server) {
--        return String.format(
--            Locale.ROOT,
--            "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d",
--            127,
--            server.getServerVersion(),
--            server.getMotd(),
--            server.getPlayerCount(),
--            server.getMaxPlayers()
--        );
--    }
++
 +    // Paper start
 +    private static String readLegacyString(ByteBuf buf) {
 +        int size = buf.readShort() * Character.BYTES;
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
index 3d98b71ca6..43bacc9139 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -281,8 +281,8 @@
 +    public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
 +        this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
 +    }
-+
 +    // Paper end - adventure
++
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes
      public void disconnect(Component reason) {
 -        this.disconnect(new DisconnectionDetails(reason));
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
index 08bccdf22e..2ff7ea70fa 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
@@ -13,14 +13,15 @@
          this.gameProfile = cookie.gameProfile();
          this.clientInformation = cookie.clientInformation();
      }
-@@ -61,6 +_,10 @@
+@@ -61,6 +_,11 @@
  
      @Override
      public void onDisconnect(DisconnectionDetails details) {
 +        // Paper start - Debugging
 +        if (this.server.isDebugging()) {
 +            ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, details.reason().getString(), this.currentTask != null ? this.currentTask.type().id() : "null");
-+        } else // Paper end
++        } else
++        // Paper end
          LOGGER.info("{} lost connection: {}", this.gameProfile, details.reason().getString());
          super.onDisconnect(details);
      }
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
index fa6e15d0d7..97e6b6ed34 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -68,7 +68,7 @@
                                          ? new RateKickingConnection(rateLimitPacketsPerSecond)
                                          : new Connection(PacketFlow.SERVERBOUND));
 -                                    ServerConnectionListener.this.connections.add(connection);
-+                                    // ServerConnectionListener.this.connections.add(connection);
++                                    // ServerConnectionListener.this.connections.add(connection); // Paper
 +                                    // Paper start - Add support for Proxy Protocol
 +                                    if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
 +                                        channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
@@ -98,7 +98,7 @@
 +                                        });
 +                                    }
 +                                    // Paper end - Add support for proxy protocol
-+                                    pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
++                                    ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
                                      connection.configurePacketHandler(channelPipeline);
                                      connection.setListenerForServerboundHandshake(
                                          new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 80b64ccf25..7058df616f 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -147,7 +147,7 @@
 +        }
 +        // Paper start - Prevent causing expired keys from impacting new joins
 +        if (!this.hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) {
-+            LOGGER.info("Player profile key for {} has expired!", this.player.getGameProfile().getName());
++            LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString());
 +            this.hasLoggedExpiry = true;
 +        }
 +        // Paper end - Prevent causing expired keys from impacting new joins
@@ -374,7 +374,7 @@
          this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering());
      }
  
-@@ -536,14 +_,84 @@
+@@ -536,25 +_,110 @@
          }
      }
  
@@ -386,7 +386,7 @@
      @Override
      public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
 -        PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
-+        // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++        // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async
 +        // CraftBukkit start
 +        if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits
 +            this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause // Paper - add proper async disconnect
@@ -458,9 +458,16 @@
 +    private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringReader) {
 +        // Paper end - AsyncTabCompleteEvent
          ParseResults<CommandSourceStack> parseResults = this.server.getCommands().getDispatcher().parse(stringReader, this.player.createCommandSourceStack());
++        // Paper start - Handle non-recoverable exceptions
++        if (!parseResults.getExceptions().isEmpty()
++            && parseResults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) {
++            this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM);
++            return;
++        }
++        // Paper end - Handle non-recoverable exceptions
          this.server
              .getCommands()
-@@ -551,10 +_,18 @@
+             .getDispatcher()
              .getCompletionSuggestions(parseResults)
              .thenAccept(
                  suggestions -> {
@@ -802,7 +809,7 @@
                              d3 = d - this.player.getX();
                              d4 = d1 - this.player.getY();
                              if (d4 > -0.5 || d4 < 0.5) {
-@@ -970,21 +_,106 @@
+@@ -970,20 +_,104 @@
  
                              d5 = d2 - this.player.getZ();
                              d7 = d3 * d3 + d4 * d4 + d5 * d5;
@@ -815,21 +822,20 @@
                                  && !this.player.gameMode.isCreative()
                                  && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) {
 -                                flag2 = true;
--                                LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
--                            }
--
--                            if (this.player.noPhysics
--                                || this.player.isSleeping()
--                                || (!flag2 || !serverLevel.noCollision(this.player, boundingBox))
--                                    && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) {
 +                                // Paper start - Add fail move event
 +                                io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY,
 +                                    toX, toY, toZ, toYaw, toPitch, true);
 +                                if (!event.isAllowed()) {
 +                                    movedWrongly = true;
 +                                    if (event.getLogWarning())
-+                                        // Paper end
-+                                        LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
++                                    // Paper end
+                                 LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+-                            }
+-
+-                            if (this.player.noPhysics
+-                                || this.player.isSleeping()
+-                                || (!flag2 || !serverLevel.noCollision(this.player, boundingBox))
+-                                    && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) {
 +                                } // Paper
 +                            }
 +
@@ -915,18 +921,8 @@
 +                                }
 +                                // CraftBukkit end
                                  this.player.absMoveTo(d, d1, d2, f, f1);
-+
                                  boolean isAutoSpinAttack = this.player.isAutoSpinAttack();
                                  this.clientIsFloating = d4 >= -0.03125
-                                     && !flag1
-@@ -996,6 +_,7 @@
-                                     && !isAutoSpinAttack
-                                     && this.noBlocksAround(this.player);
-                                 this.player.serverLevel().getChunkSource().move(this.player);
-+
-                                 Vec3 vec3 = new Vec3(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z);
-                                 this.player.setOnGroundWithMovement(packet.isOnGround(), packet.horizontalCollision(), vec3);
-                                 this.player.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.isOnGround());
 @@ -1018,9 +_,6 @@
                                  this.lastGoodX = this.player.getX();
                                  this.lastGoodY = this.player.getY();
@@ -945,12 +941,11 @@
  
              return true;
          } else {
-@@ -1076,10 +_,78 @@
+@@ -1076,10 +_,77 @@
      }
  
      public void teleport(double x, double y, double z, float yaw, float pitch) {
 -        this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet());
-+        //this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet());
 +        // CraftBukkit start - Delegate to teleport(Location)
 +        this.teleport(x, y, z, yaw, pitch, PlayerTeleportEvent.TeleportCause.UNKNOWN);
 +    }
@@ -1412,7 +1407,7 @@
          ParseResults<CommandSourceStack> parseResults = this.parseCommand(command);
          if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseResults)) {
              LOGGER.error(
-@@ -1335,13 +_,29 @@
+@@ -1335,26 +_,55 @@
          Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages());
          if (!optional.isEmpty()) {
              this.tryHandleChat(packet.command(), () -> {
@@ -1440,14 +1435,18 @@
 +        this.cserver.getPluginManager().callEvent(event);
 +        command = event.getMessage().substring(1);
 +
-+        // Paper start - Fix cancellation and message changing
          ParseResults<CommandSourceStack> parseResults = this.parseCommand(packet.command());
  
          Map<String, PlayerChatMessage> map;
-@@ -1352,9 +_,21 @@
+         try {
++            // Paper - Always parse the original command to add to the chat chain
+             map = this.collectSignedArguments(packet, SignableCommand.of(parseResults), lastSeenMessages);
+         } catch (SignedMessageChain.DecodeException var6) {
+             this.handleMessageDecodeFailure(var6);
              return;
          }
  
++        // Paper start - Fix cancellation and message changing
 +        if (event.isCancelled()) {
 +            // Only now are we actually good to return
 +            return;
@@ -1492,6 +1491,15 @@
          }
      }
  
+@@ -1434,7 +_,7 @@
+             Optional<LastSeenMessages> optional = this.lastSeenMessages.applyUpdate(update);
+             if (optional.isEmpty()) {
+                 LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
+-                this.disconnect(CHAT_VALIDATION_FAILED);
++                this.disconnectAsync(CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes & add proper async disconnect
+             }
+ 
+             return optional;
 @@ -1451,22 +_,155 @@
          return false;
      }
@@ -1818,7 +1826,7 @@
  
                  AABB boundingBox = target.getBoundingBox();
 -                if (this.player.canInteractWithEntity(boundingBox, 3.0)) {
-+                if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range
++                if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0))) { // Paper - configurable lenience value for interact range
                      packet.dispatch(
                          new ServerboundInteractPacket.Handler() {
 -                            private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction) {
@@ -2068,7 +2076,7 @@
 +                                }
 +                            }
 +                            break;
-+                            // TODO check on updates
++                        // TODO check on updates
 +                        case QUICK_MOVE:
 +                            if (packet.getButtonNum() == 0) {
 +                                click = ClickType.SHIFT_LEFT;
@@ -2289,13 +2297,13 @@
 +                                        break;
 +                                    // Modified cursor only
 +                                    case DROP_ALL_CURSOR:
-+                                        case DROP_ONE_CURSOR:
-+                                        case CLONE_STACK:
++                                    case DROP_ONE_CURSOR:
++                                    case CLONE_STACK:
 +                                        this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor
 +                                        break;
 +                                    // Nothing
 +                                    case NOTHING:
-+                                    break;
++                                        break;
 +                                }
 +                        }
 +
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
index 7a4ce9578f..b6419959d2 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
@@ -5,7 +5,7 @@
          } else {
              this.hasRequestedStatus = true;
 -            this.connection.send(new ClientboundStatusResponsePacket(this.status));
-+            // this.connection.send(new ClientboundStatusResponsePacket(this.status));
++            // this.connection.send(new ClientboundStatusResponsePacket(this.status)); // Paper
 +            com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(net.minecraft.server.MinecraftServer.getServer(), this.connection); // Paper - handle status request
          }
      }

From 016503a85fa10b27ae06974bcdd0271e58aba7a1 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 12:28:42 -0500
Subject: [PATCH 071/285] net/minecraft/world/entity/animal/goat/

---
 .../world/entity/animal/goat/Goat.java.patch  | 51 +++++++++++++
 .../world/entity/animal/goat/Goat.java.patch  | 76 -------------------
 2 files changed, 51 insertions(+), 76 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/goat/Goat.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
new file mode 100644
index 0000000000..0c41c3aea7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/entity/animal/goat/Goat.java
++++ b/net/minecraft/world/entity/animal/goat/Goat.java
+@@ -231,13 +_,22 @@
+     public InteractionResult mobInteract(Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
++            // CraftBukkit start - Got milk?
++            org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemInHand, Items.MILK_BUCKET, hand);
++
++            if (event.isCancelled()) {
++                player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                return InteractionResult.PASS;
++            }
++            // CraftBukkit end
+             player.playSound(this.getMilkingSound(), 1.0F, 1.0F);
+-            ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, Items.MILK_BUCKET.getDefaultInstance());
++            ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+             player.setItemInHand(hand, itemStack);
+             return InteractionResult.SUCCESS;
+         } else {
++            boolean isFood = this.isFood(itemInHand); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739)
+             InteractionResult interactionResult = super.mobInteract(player, hand);
+-            if (interactionResult.consumesAction() && this.isFood(itemInHand)) {
++            if (interactionResult.consumesAction() && isFood) { // Paper
+                 this.playEatingSound();
+             }
+ 
+@@ -350,7 +_,7 @@
+             double d2 = Mth.randomBetween(this.random, -0.2F, 0.2F);
+             ItemEntity itemEntity = new ItemEntity(this.level(), vec3.x(), vec3.y(), vec3.z(), itemStack, d, d1, d2);
+             this.level().addFreshEntity(itemEntity);
+-            return true;
++            return this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), itemEntity) != null; // Paper - Call EntityDropItemEvent
+         }
+     }
+ 
+@@ -381,4 +_,14 @@
+     ) {
+         return level.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(level, pos);
+     }
++    // Paper start - Goat ram API
++    public void ram(net.minecraft.world.entity.LivingEntity entity) {
++        Brain<Goat> brain = this.getBrain();
++        brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
++        brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);
++        brain.eraseMemory(MemoryModuleType.BREED_TARGET);
++        brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
++        brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM);
++    }
++    // Paper end - Goat ram API
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/goat/Goat.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/goat/Goat.java.patch
deleted file mode 100644
index 44290161de..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/goat/Goat.java.patch
+++ /dev/null
@@ -1,76 +0,0 @@
---- a/net/minecraft/world/entity/animal/goat/Goat.java
-+++ b/net/minecraft/world/entity/animal/goat/Goat.java
-@@ -53,6 +53,11 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.player.PlayerBucketFillEvent;
-+// CraftBukkit end
- 
- public class Goat extends Animal {
- 
-@@ -184,7 +189,7 @@
- 
-     @Override
-     public Brain<Goat> getBrain() {
--        return super.getBrain();
-+        return (Brain<Goat>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -229,15 +234,24 @@
-         ItemStack itemstack = player.getItemInHand(hand);
- 
-         if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
-+            // CraftBukkit start - Got milk?
-+            PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
-+
-+            if (event.isCancelled()) {
-+                player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-+                return InteractionResult.PASS;
-+            }
-+            // CraftBukkit end
-             player.playSound(this.getMilkingSound(), 1.0F, 1.0F);
--            ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance());
-+            ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
- 
-             player.setItemInHand(hand, itemstack1);
-             return InteractionResult.SUCCESS;
-         } else {
-+            boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739)
-             InteractionResult enuminteractionresult = super.mobInteract(player, hand);
- 
--            if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) {
-+            if (enuminteractionresult.consumesAction() && isFood) { // Paper
-                 this.playEatingSound();
-             }
- 
-@@ -353,8 +367,7 @@
-             double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F);
-             ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2);
- 
--            this.level().addFreshEntity(entityitem);
--            return true;
-+            return this.spawnAtLocation((net.minecraft.server.level.ServerLevel) this.level(), entityitem) != null; // Paper - Call EntityDropItemEvent
-         }
-     }
- 
-@@ -383,4 +396,15 @@
-     public static boolean checkGoatSpawnRules(EntityType<? extends Animal> entityType, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
-         return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos);
-     }
-+
-+    // Paper start - Goat ram API
-+    public void ram(net.minecraft.world.entity.LivingEntity entity) {
-+        Brain<Goat> brain = this.getBrain();
-+        brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
-+        brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);
-+        brain.eraseMemory(MemoryModuleType.BREED_TARGET);
-+        brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
-+        brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM);
-+    }
-+    // Paper end - Goat ram API
- }

From d7eab570bde699ca11ab876644135983bde5fb73 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sat, 14 Dec 2024 18:41:01 +0100
Subject: [PATCH 072/285] components

---
 .../item/component/Consumable.java.patch      | 39 ++++++++++++++
 .../component/ConsumableListener.java.patch   |  9 ++++
 .../item/component/CustomData.java.patch      |  6 +--
 .../item/component/DeathProtection.java.patch | 11 ++++
 .../component/LodestoneTracker.java.patch     |  6 +--
 .../OminousBottleAmplifier.java.patch         | 20 +++++++
 .../component/ResolvableProfile.java.patch    | 20 +++----
 .../SuspiciousStewEffects.java.patch          | 18 +++++++
 .../item/component/Consumable.java.patch      | 53 -------------------
 .../component/ConsumableListener.java.patch   |  9 ----
 .../item/component/DeathProtection.java.patch | 22 --------
 .../OminousBottleAmplifier.java.patch         | 18 -------
 .../SuspiciousStewEffects.java.patch          | 28 ----------
 13 files changed, 113 insertions(+), 146 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/component/CustomData.java.patch (83%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/component/LodestoneTracker.java.patch (70%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/component/ResolvableProfile.java.patch (58%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/component/Consumable.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/component/ConsumableListener.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/component/DeathProtection.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch
new file mode 100644
index 0000000000..d39f21455d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/Consumable.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/item/component/Consumable.java
++++ b/net/minecraft/world/item/component/Consumable.java
+@@ -84,13 +_,35 @@
+ 
+         stack.getAllOfType(ConsumableListener.class).forEach(consumableListener -> consumableListener.onConsume(level, entity, stack, this));
+         if (!level.isClientSide) {
+-            this.onConsumeEffects.forEach(consumeEffect -> consumeEffect.apply(level, stack, entity));
++            // CraftBukkit start
++            org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause;
++            if (stack.is(net.minecraft.world.item.Items.MILK_BUCKET)) {
++                cause = org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK;
++            } else if (stack.is(net.minecraft.world.item.Items.POTION)) {
++                cause = org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK;
++            } else {
++                cause = org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD;
++            }
++
++            this.onConsumeEffects.forEach(consumeEffect -> consumeEffect.apply(level, stack, entity, cause));
++            // CraftBukkit end
+         }
+ 
+         entity.gameEvent(this.animation == ItemUseAnimation.DRINK ? GameEvent.DRINK : GameEvent.EAT);
+         stack.consume(1, entity);
+         return stack;
+     }
++
++    // CraftBukkit start
++    public void cancelUsingItem(ServerPlayer player, ItemStack stack) {
++        final java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> packets = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Paper - properly resend entities - collect packets for bundle
++        stack.getAllOfType(ConsumableListener.class).forEach(listener -> {
++            listener.cancelUsingItem(player, stack, packets); // Paper - properly resend entities - collect packets for bundle
++        });
++        player.server.getPlayerList().sendActiveEffects(player, packets::add); // Paper - properly resend entities - collect packets for bundle
++        player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(packets));
++    }
++    // CraftBukkit end
+ 
+     public boolean canConsume(LivingEntity entity, ItemStack stack) {
+         FoodProperties foodProperties = stack.get(DataComponents.FOOD);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch
new file mode 100644
index 0000000000..a47c2b2a62
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/ConsumableListener.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/item/component/ConsumableListener.java
++++ b/net/minecraft/world/item/component/ConsumableListener.java
+@@ -6,4 +_,6 @@
+ 
+ public interface ConsumableListener {
+     void onConsume(Level level, LivingEntity entity, ItemStack stack, Consumable consumable);
++
++    default void cancelUsingItem(net.minecraft.server.level.ServerPlayer player, ItemStack stack, java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) {} // CraftBukkit // Paper - properly resend entities - collect packets for bundle
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/CustomData.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/world/item/component/CustomData.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch
index 73089c0170..33b96fd4de 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/CustomData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/CustomData.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/component/CustomData.java
 +++ b/net/minecraft/world/item/component/CustomData.java
-@@ -34,7 +34,17 @@
+@@ -34,7 +_,17 @@
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final CustomData EMPTY = new CustomData(new CompoundTag());
      private static final String TYPE_TAG = "id";
@@ -16,6 +16,6 @@
 +            }
 +        })
 +        // Paper end - Item serialization as json
-         .xmap(CustomData::new, component -> component.tag);
+         .xmap(CustomData::new, customData -> customData.tag);
      public static final Codec<CustomData> CODEC_WITH_ID = CODEC.validate(
-         component -> component.getUnsafe().contains("id", 8) ? DataResult.success(component) : DataResult.error(() -> "Missing id for entity in: " + component)
+         data -> data.getUnsafe().contains("id", 8) ? DataResult.success(data) : DataResult.error(() -> "Missing id for entity in: " + data)
diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch
new file mode 100644
index 0000000000..b2efb28173
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/DeathProtection.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/component/DeathProtection.java
++++ b/net/minecraft/world/item/component/DeathProtection.java
+@@ -37,7 +_,7 @@
+ 
+     public void applyEffects(ItemStack stack, LivingEntity entity) {
+         for (ConsumeEffect consumeEffect : this.deathEffects) {
+-            consumeEffect.apply(entity.level(), stack, entity);
++            consumeEffect.apply(entity.level(), stack, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM); // CraftBukkit
+         }
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/LodestoneTracker.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/item/component/LodestoneTracker.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch
index 79d33fc6bf..6a668d9014 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/LodestoneTracker.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/item/component/LodestoneTracker.java
 +++ b/net/minecraft/world/item/component/LodestoneTracker.java
-@@ -29,7 +29,7 @@
+@@ -29,7 +_,7 @@
                  return this;
              } else {
                  BlockPos blockPos = this.target.get().pos();
--                return world.isInWorldBounds(blockPos) && world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)
-+                return world.isInWorldBounds(blockPos) && (!world.hasChunkAt(blockPos) || world.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks
+-                return level.isInWorldBounds(blockPos) && level.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)
++                return level.isInWorldBounds(blockPos) && (!level.hasChunkAt(blockPos) || level.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks
                      ? this
                      : new LodestoneTracker(Optional.empty(), true);
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
new file mode 100644
index 0000000000..6a00c3c6f2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/component/OminousBottleAmplifier.java
++++ b/net/minecraft/world/item/component/OminousBottleAmplifier.java
+@@ -28,8 +_,15 @@
+ 
+     @Override
+     public void onConsume(Level level, LivingEntity entity, ItemStack stack, Consumable consumable) {
+-        entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true));
+-    }
++        entity.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below
++    }
++
++    // Paper start - properly resend entities - collect packets for bundle
++    @Override
++    public void cancelUsingItem(net.minecraft.server.level.ServerPlayer player, ItemStack stack, List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) {
++        collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(player.getId(), MobEffects.BAD_OMEN));
++    }
++    // Paper end - properly resend entities - collect packets for bundle
+ 
+     @Override
+     public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltipAdder, TooltipFlag tooltipFlag) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/ResolvableProfile.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/item/component/ResolvableProfile.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch
index c0d2bc1d79..e12079be3f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/ResolvableProfile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch
@@ -1,18 +1,18 @@
 --- a/net/minecraft/world/item/component/ResolvableProfile.java
 +++ b/net/minecraft/world/item/component/ResolvableProfile.java
-@@ -20,9 +20,10 @@
+@@ -20,9 +_,10 @@
          instance -> instance.group(
-                     ExtraCodecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ResolvableProfile::name),
-                     UUIDUtil.CODEC.optionalFieldOf("id").forGetter(ResolvableProfile::id),
-+                    UUIDUtil.STRING_CODEC.lenientOptionalFieldOf("Id").forGetter($ -> Optional.empty()), // Paper
-                     ExtraCodecs.PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ResolvableProfile::properties)
-                 )
--                .apply(instance, ResolvableProfile::new)
-+                .apply(instance, (s, uuid, uuid2, propertyMap) -> new ResolvableProfile(s, uuid2.or(() -> uuid), propertyMap)) // Paper
+                 ExtraCodecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ResolvableProfile::name),
+                 UUIDUtil.CODEC.optionalFieldOf("id").forGetter(ResolvableProfile::id),
++                UUIDUtil.STRING_CODEC.lenientOptionalFieldOf("Id").forGetter($ -> Optional.empty()), // Paper
+                 ExtraCodecs.PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ResolvableProfile::properties)
+             )
+-            .apply(instance, ResolvableProfile::new)
++            .apply(instance, (name, uuid, uuid2, propertyMap) -> new ResolvableProfile(name, uuid2.or(() -> uuid), propertyMap)) // Paper
      );
      public static final Codec<ResolvableProfile> CODEC = Codec.withAlternative(
          FULL_CODEC, ExtraCodecs.PLAYER_NAME, name -> new ResolvableProfile(Optional.of(name), Optional.empty(), new PropertyMap())
-@@ -49,7 +50,7 @@
+@@ -49,7 +_,7 @@
          if (this.isResolved()) {
              return CompletableFuture.completedFuture(this);
          } else {
@@ -20,4 +20,4 @@
 +            return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).thenApply(optional -> { // Paper - player profile events
                  GameProfile gameProfile = optional.orElseGet(() -> new GameProfile(this.id.get(), this.name.orElse("")));
                  return new ResolvableProfile(gameProfile);
-             }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(profile -> {
+             }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(optional -> {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch
new file mode 100644
index 0000000000..d84c096c8b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/item/component/SuspiciousStewEffects.java
++++ b/net/minecraft/world/item/component/SuspiciousStewEffects.java
+@@ -41,6 +_,15 @@
+         }
+     }
+ 
++    // CraftBukkit start
++    @Override
++    public void cancelUsingItem(net.minecraft.server.level.ServerPlayer player, ItemStack stack, List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) { // Paper - properly resend entities - collect packets for bundle
++        for (SuspiciousStewEffects.Entry entry : this.effects) {
++            collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(player.getId(), entry.effect())); // Paper - bundlize packets
++        }
++    }
++    // CraftBukkit end
++
+     @Override
+     public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltipAdder, TooltipFlag tooltipFlag) {
+         if (tooltipFlag.isCreative()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/Consumable.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/component/Consumable.java.patch
deleted file mode 100644
index a163ec68c0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/Consumable.java.patch
+++ /dev/null
@@ -1,53 +0,0 @@
---- a/net/minecraft/world/item/component/Consumable.java
-+++ b/net/minecraft/world/item/component/Consumable.java
-@@ -29,6 +29,11 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start
-+import net.minecraft.world.item.Items;
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
-+
- public record Consumable(float consumeSeconds, ItemUseAnimation animation, Holder<SoundEvent> sound, boolean hasConsumeParticles, List<ConsumeEffect> onConsumeEffects) {
- 
-     public static final float DEFAULT_CONSUME_SECONDS = 1.6F;
-@@ -69,8 +74,19 @@
-             consumablelistener.onConsume(world, user, stack, this);
-         });
-         if (!world.isClientSide) {
-+            // CraftBukkit start
-+            EntityPotionEffectEvent.Cause cause;
-+            if (stack.is(Items.MILK_BUCKET)) {
-+                cause = EntityPotionEffectEvent.Cause.MILK;
-+            } else if (stack.is(Items.POTION)) {
-+                cause = EntityPotionEffectEvent.Cause.POTION_DRINK;
-+            } else {
-+                cause = EntityPotionEffectEvent.Cause.FOOD;
-+            }
-+
-             this.onConsumeEffects.forEach((consumeeffect) -> {
--                consumeeffect.apply(world, stack, user);
-+                consumeeffect.apply(world, stack, user, cause);
-+                // CraftBukkit end
-             });
-         }
- 
-@@ -79,6 +95,17 @@
-         return stack;
-     }
- 
-+    // CraftBukkit start
-+    public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack) {
-+        final java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> packets = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Paper - properly resend entities - collect packets for bundle
-+        itemstack.getAllOfType(ConsumableListener.class).forEach((consumablelistener) -> {
-+            consumablelistener.cancelUsingItem(entityplayer, itemstack, packets); // Paper - properly resend entities - collect packets for bundle
-+        });
-+        entityplayer.server.getPlayerList().sendActiveEffects(entityplayer, packets::add); // Paper - properly resend entities - collect packets for bundle
-+        entityplayer.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(packets));
-+    }
-+    // CraftBukkit end
-+
-     public boolean canConsume(LivingEntity user, ItemStack stack) {
-         FoodProperties foodinfo = (FoodProperties) stack.get(DataComponents.FOOD);
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/ConsumableListener.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/component/ConsumableListener.java.patch
deleted file mode 100644
index 2e31354cba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/ConsumableListener.java.patch
+++ /dev/null
@@ -1,9 +0,0 @@
---- a/net/minecraft/world/item/component/ConsumableListener.java
-+++ b/net/minecraft/world/item/component/ConsumableListener.java
-@@ -7,4 +7,6 @@
- public interface ConsumableListener {
- 
-     void onConsume(Level world, LivingEntity user, ItemStack stack, Consumable consumable);
-+
-+    default void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) {} // CraftBukkit // Paper - properly resend entities - collect packets for bundle
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/DeathProtection.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/component/DeathProtection.java.patch
deleted file mode 100644
index 8516839b42..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/DeathProtection.java.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- a/net/minecraft/world/item/component/DeathProtection.java
-+++ b/net/minecraft/world/item/component/DeathProtection.java
-@@ -15,6 +15,10 @@
- import net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect;
- import net.minecraft.world.item.consume_effects.ConsumeEffect;
- 
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
-+
- public record DeathProtection(List<ConsumeEffect> deathEffects) {
- 
-     public static final Codec<DeathProtection> CODEC = RecordCodecBuilder.create((instance) -> {
-@@ -29,7 +33,7 @@
-         while (iterator.hasNext()) {
-             ConsumeEffect consumeeffect = (ConsumeEffect) iterator.next();
- 
--            consumeeffect.apply(entity.level(), stack, entity);
-+            consumeeffect.apply(entity.level(), stack, entity, EntityPotionEffectEvent.Cause.TOTEM); // CraftBukkit
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
deleted file mode 100644
index 57338edcbf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/OminousBottleAmplifier.java.patch
+++ /dev/null
@@ -1,18 +0,0 @@
---- a/net/minecraft/world/item/component/OminousBottleAmplifier.java
-+++ b/net/minecraft/world/item/component/OminousBottleAmplifier.java
-@@ -28,8 +28,14 @@
- 
-     @Override
-     public void onConsume(Level world, LivingEntity user, ItemStack stack, Consumable consumable) {
--        user.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true));
-+        user.addEffect(new MobEffectInstance(MobEffects.BAD_OMEN, 120000, this.value, false, false, true)); // Paper - properly resend entities - diff on change for below
-     }
-+    // Paper start - properly resend entities - collect packets for bundle
-+    @Override
-+    public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) {
-+        collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(entityplayer.getId(), MobEffects.BAD_OMEN));
-+    }
-+    // Paper end - properly resend entities - collect packets for bundle
- 
-     @Override
-     public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltip, TooltipFlag type) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch
deleted file mode 100644
index 2836856f9c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/component/SuspiciousStewEffects.java.patch
+++ /dev/null
@@ -1,28 +0,0 @@
---- a/net/minecraft/world/item/component/SuspiciousStewEffects.java
-+++ b/net/minecraft/world/item/component/SuspiciousStewEffects.java
-@@ -29,7 +29,7 @@
-     public static final StreamCodec<RegistryFriendlyByteBuf, SuspiciousStewEffects> STREAM_CODEC = SuspiciousStewEffects.Entry.STREAM_CODEC.apply(ByteBufCodecs.list()).map(SuspiciousStewEffects::new, SuspiciousStewEffects::effects);
- 
-     public SuspiciousStewEffects withEffectAdded(SuspiciousStewEffects.Entry stewEffect) {
--        return new SuspiciousStewEffects(Util.copyAndAdd(this.effects, (Object) stewEffect));
-+        return new SuspiciousStewEffects(Util.copyAndAdd(this.effects, stewEffect)); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -44,7 +44,16 @@
- 
-     }
- 
-+    // CraftBukkit start
-     @Override
-+    public void cancelUsingItem(net.minecraft.server.level.ServerPlayer entityplayer, ItemStack itemstack, java.util.List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> collectedPackets) { // Paper - properly resend entities - collect packets for bundle
-+        for (SuspiciousStewEffects.Entry suspicioussteweffects_a : this.effects) {
-+            collectedPackets.add(new net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket(entityplayer.getId(), suspicioussteweffects_a.effect())); // Paper - bundlize packets
-+        }
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public void addToTooltip(Item.TooltipContext context, Consumer<Component> tooltip, TooltipFlag type) {
-         if (type.isCreative()) {
-             List<MobEffectInstance> list = new ArrayList();

From 1f128f76c75d46b7c0c03f68c0aa95d163481274 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:20:14 +0100
Subject: [PATCH 073/285] net.minecraft.data.loot.packs

---
 .../net/minecraft/data/loot/packs/VanillaChestLoot.java.patch   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch (97%)

diff --git a/paper-server/patches/unapplied/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch b/paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch
rename to paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch
index 02e103aaf6..5d8aa86e40 100644
--- a/paper-server/patches/unapplied/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch
+++ b/paper-server/patches/sources/net/minecraft/data/loot/packs/VanillaChestLoot.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/data/loot/packs/VanillaChestLoot.java
 +++ b/net/minecraft/data/loot/packs/VanillaChestLoot.java
-@@ -946,7 +946,6 @@
+@@ -946,7 +_,6 @@
                          .add(
                              LootItem.lootTableItem(Items.COMPASS)
                                  .apply(SetItemCountFunction.setCount(ConstantValue.exactly(1.0F)))

From 52d26db5a0345a2e5e575fcececb4727fdcb5107 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:27:10 +0100
Subject: [PATCH 074/285] net.minecraft.world.item.consume_effects

---
 ...ApplyStatusEffectsConsumeEffect.java.patch | 19 +++++++++++
 ...arAllStatusEffectsConsumeEffect.java.patch | 14 ++++++++
 .../consume_effects/ConsumeEffect.java.patch  | 19 +++++++++++
 ...emoveStatusEffectsConsumeEffect.java.patch | 16 ++++++++++
 .../TeleportRandomlyConsumeEffect.java.patch  | 17 ++++++++++
 ...ApplyStatusEffectsConsumeEffect.java.patch | 32 -------------------
 ...arAllStatusEffectsConsumeEffect.java.patch | 24 --------------
 .../consume_effects/ConsumeEffect.java.patch  | 30 -----------------
 ...emoveStatusEffectsConsumeEffect.java.patch | 29 -----------------
 .../TeleportRandomlyConsumeEffect.java.patch  | 20 ------------
 10 files changed, 85 insertions(+), 135 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
new file mode 100644
index 0000000000..4d29a75fd9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java
++++ b/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java
+@@ -46,14 +_,14 @@
+     }
+ 
+     @Override
+-    public boolean apply(Level level, ItemStack stack, LivingEntity entity) {
++    public boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { // CraftBukkit
+         if (entity.getRandom().nextFloat() >= this.probability) {
+             return false;
+         } else {
+             boolean flag = false;
+ 
+             for (MobEffectInstance mobEffectInstance : this.effects) {
+-                if (entity.addEffect(new MobEffectInstance(mobEffectInstance))) {
++                if (entity.addEffect(new MobEffectInstance(mobEffectInstance), cause)) { // CraftBukkit
+                     flag = true;
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
new file mode 100644
index 0000000000..37064488ba
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
++++ b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
+@@ -18,7 +_,9 @@
+     }
+ 
+     @Override
+-    public boolean apply(Level level, ItemStack stack, LivingEntity entity) {
+-        return entity.removeAllEffects();
++    // CraftBukkit start
++    public boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
++        return entity.removeAllEffects(cause);
++        // CraftBukkit end
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
new file mode 100644
index 0000000000..af59bc597a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/item/consume_effects/ConsumeEffect.java
++++ b/net/minecraft/world/item/consume_effects/ConsumeEffect.java
+@@ -19,7 +_,15 @@
+ 
+     ConsumeEffect.Type<? extends ConsumeEffect> getType();
+ 
+-    boolean apply(Level level, ItemStack stack, LivingEntity entity);
++    // CraftBukkit start
++    default boolean apply(Level level, ItemStack stack, LivingEntity entity) {
++        return this.apply(level, stack, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    default boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
++        return this.apply(level, stack, entity);
++    }
++    // CraftBukkit end
+ 
+     public record Type<T extends ConsumeEffect>(MapCodec<T> codec, StreamCodec<RegistryFriendlyByteBuf, T> streamCodec) {
+         public static final ConsumeEffect.Type<ApplyStatusEffectsConsumeEffect> APPLY_EFFECTS = register(
diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
new file mode 100644
index 0000000000..9cd988de23
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java
++++ b/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java
+@@ -35,11 +_,11 @@
+     }
+ 
+     @Override
+-    public boolean apply(Level level, ItemStack stack, LivingEntity entity) {
++    public boolean apply(Level level, ItemStack stack, LivingEntity entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { // CraftBukkit
+         boolean flag = false;
+ 
+         for (Holder<MobEffect> holder : this.effects) {
+-            if (entity.removeEffect(holder)) {
++            if (entity.removeEffect(holder, cause)) { // CraftBukkit
+                 flag = true;
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
new file mode 100644
index 0000000000..9a43ef7d58
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java
++++ b/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java
+@@ -55,7 +_,13 @@
+             }
+ 
+             Vec3 vec3 = entity.position();
+-            if (entity.randomTeleport(d, d1, d2, true)) {
++            // CraftBukkit start - handle canceled status of teleport event
++            java.util.Optional<Boolean> status = entity.randomTeleport(d, d1, d2, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT);
++
++            // teleport event was canceled, no more tries
++            if (!status.isPresent()) break;
++            if (status.get()) {
++                // CraftBukkit end
+                 level.gameEvent(GameEvent.TELEPORT, vec3, GameEvent.Context.of(entity));
+                 SoundSource soundSource;
+                 SoundEvent soundEvent;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
deleted file mode 100644
index 2551be5478..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java
-+++ b/net/minecraft/world/item/consume_effects/ApplyStatusEffectsConsumeEffect.java
-@@ -12,6 +12,9 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
- 
- public record ApplyStatusEffectsConsumeEffect(List<MobEffectInstance> effects, float probability) implements ConsumeEffect {
- 
-@@ -38,8 +41,8 @@
-     }
- 
-     @Override
--    public boolean apply(Level world, ItemStack stack, LivingEntity user) {
--        if (user.getRandom().nextFloat() >= this.probability) {
-+    public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { // CraftBukkit
-+        if (entityliving.getRandom().nextFloat() >= this.probability) {
-             return false;
-         } else {
-             boolean flag = false;
-@@ -48,7 +51,7 @@
-             while (iterator.hasNext()) {
-                 MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
- 
--                if (user.addEffect(new MobEffectInstance(mobeffect))) {
-+                if (entityliving.addEffect(new MobEffectInstance(mobeffect), cause)) { // CraftBukkit
-                     flag = true;
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
deleted file mode 100644
index 35168c5339..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
-+++ b/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java
-@@ -6,6 +6,9 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
- 
- public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect {
- 
-@@ -19,7 +22,9 @@
-     }
- 
-     @Override
--    public boolean apply(Level world, ItemStack stack, LivingEntity user) {
--        return user.removeAllEffects();
-+    // CraftBukkit start
-+    public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) {
-+        return entityliving.removeAllEffects(cause);
-+        // CraftBukkit end
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
deleted file mode 100644
index c5d56842c7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/ConsumeEffect.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/item/consume_effects/ConsumeEffect.java
-+++ b/net/minecraft/world/item/consume_effects/ConsumeEffect.java
-@@ -11,6 +11,9 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
- 
- public interface ConsumeEffect {
- 
-@@ -19,8 +22,16 @@
- 
-     ConsumeEffect.Type<? extends ConsumeEffect> getType();
- 
--    boolean apply(Level world, ItemStack stack, LivingEntity user);
-+    // CraftBukkit start
-+    default boolean apply(Level world, ItemStack stack, LivingEntity user) {
-+        return this.apply(world, stack, user, EntityPotionEffectEvent.Cause.UNKNOWN);
-+    }
- 
-+    default boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) {
-+        return this.apply(world, itemstack, entityliving);
-+    }
-+    // CraftBukkit end
-+
-     public static record Type<T extends ConsumeEffect>(MapCodec<T> codec, StreamCodec<RegistryFriendlyByteBuf, T> streamCodec) {
- 
-         public static final ConsumeEffect.Type<ApplyStatusEffectsConsumeEffect> APPLY_EFFECTS = register("apply_effects", ApplyStatusEffectsConsumeEffect.CODEC, ApplyStatusEffectsConsumeEffect.STREAM_CODEC);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
deleted file mode 100644
index c5eacaef69..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java
-+++ b/net/minecraft/world/item/consume_effects/RemoveStatusEffectsConsumeEffect.java
-@@ -14,6 +14,9 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityPotionEffectEvent;
-+// CraftBukkit end
- 
- public record RemoveStatusEffectsConsumeEffect(HolderSet<MobEffect> effects) implements ConsumeEffect {
- 
-@@ -32,14 +35,14 @@
-     }
- 
-     @Override
--    public boolean apply(Level world, ItemStack stack, LivingEntity user) {
-+    public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { // CraftBukkit
-         boolean flag = false;
-         Iterator iterator = this.effects.iterator();
- 
-         while (iterator.hasNext()) {
-             Holder<MobEffect> holder = (Holder) iterator.next();
- 
--            if (user.removeEffect(holder)) {
-+            if (entityliving.removeEffect(holder, cause)) { // CraftBukkit
-                 flag = true;
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
deleted file mode 100644
index b419363bd1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java
-+++ b/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java
-@@ -53,7 +53,16 @@
- 
-             Vec3 vec3d = user.position();
- 
--            if (user.randomTeleport(d0, d1, d2, true)) {
-+            // CraftBukkit start - handle canceled status of teleport event
-+            java.util.Optional<Boolean> status = user.randomTeleport(d0, d1, d2, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT);
-+
-+            if (!status.isPresent()) {
-+                // teleport event was canceled, no more tries
-+                break;
-+            }
-+
-+            if (status.get()) {
-+                // CraftBukkit end
-                 world.gameEvent((Holder) GameEvent.TELEPORT, vec3d, GameEvent.Context.of((Entity) user));
-                 SoundEvent soundeffect;
-                 SoundSource soundcategory;

From 02fb33c3ee8378b2c57434a341ef760d86b9f2ab Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:44:07 +0100
Subject: [PATCH 075/285] net.minecraft.network.chat

---
 .../network/chat/ChatDecorator.java.patch     | 20 +++++++++
 .../network/chat/Component.java.patch         | 14 +++---
 .../chat/ComponentSerialization.java.patch    | 26 +++++------
 .../network/chat/ComponentUtils.java.patch    | 33 +++++++++-----
 .../network/chat/MessageSignature.java.patch  |  2 +-
 .../network/chat/MutableComponent.java.patch  | 12 ++---
 .../chat/OutgoingChatMessage.java.patch       | 43 ++++++++++++++++++
 .../network/chat/PlayerChatMessage.java.patch | 11 ++---
 .../chat/SignedMessageChain.java.patch        | 16 +++----
 .../network/chat/TextColor.java.patch         | 34 ++++++++++++++
 .../network/chat/ChatDecorator.java.patch     | 23 ----------
 .../chat/OutgoingChatMessage.java.patch       | 44 -------------------
 .../network/chat/TextColor.java.patch         | 37 ----------------
 13 files changed, 156 insertions(+), 159 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/Component.java.patch (58%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/ComponentSerialization.java.patch (90%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/ComponentUtils.java.patch (57%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/MessageSignature.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/MutableComponent.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/PlayerChatMessage.java.patch (89%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/chat/SignedMessageChain.java.patch (91%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/chat/ChatDecorator.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/chat/OutgoingChatMessage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/chat/TextColor.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
new file mode 100644
index 0000000000..26ba981ff1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/network/chat/ChatDecorator.java
++++ b/net/minecraft/network/chat/ChatDecorator.java
+@@ -5,7 +_,14 @@
+ 
+ @FunctionalInterface
+ public interface ChatDecorator {
+-    ChatDecorator PLAIN = (player, message) -> message;
+-
+-    Component decorate(@Nullable ServerPlayer player, Component message);
++    ChatDecorator PLAIN = (sender, message) -> java.util.concurrent.CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events
++
++    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events (callers should use the overload with CommandSourceStack)
++    java.util.concurrent.CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, Component message); // Paper - adventure; support async chat decoration events
++
++    // Paper start - adventure; support async chat decoration events
++    default java.util.concurrent.CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) {
++        throw new UnsupportedOperationException("Must override this implementation");
++    }
++    // Paper end - adventure; support async chat decoration events
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/Component.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/Component.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
index 5db0b612a2..96b7d8debc 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/Component.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
@@ -1,23 +1,19 @@
 --- a/net/minecraft/network/chat/Component.java
 +++ b/net/minecraft/network/chat/Component.java
-@@ -37,9 +37,23 @@
- import net.minecraft.resources.ResourceLocation;
+@@ -37,7 +_,19 @@
  import net.minecraft.util.FormattedCharSequence;
  import net.minecraft.world.level.ChunkPos;
-+// CraftBukkit start
-+import java.util.stream.Stream;
-+// CraftBukkit end
  
 -public interface Component extends Message, FormattedText {
 +public interface Component extends Message, FormattedText, Iterable<Component> { // CraftBukkit
 +
 +    // CraftBukkit start
-+    default Stream<Component> stream() {
-+        return com.google.common.collect.Streams.concat(new Stream[]{Stream.of(this), this.getSiblings().stream().flatMap(Component::stream)});
++    default java.util.stream.Stream<Component> stream() {
++        return com.google.common.collect.Streams.concat(new java.util.stream.Stream[]{java.util.stream.Stream.of(this), this.getSiblings().stream().flatMap(Component::stream)});
 +    }
- 
++
 +    @Override
-+    default Iterator<Component> iterator() {
++    default java.util.Iterator<Component> iterator() {
 +        return this.stream().iterator();
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/ComponentSerialization.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
similarity index 90%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/ComponentSerialization.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
index 4136889ef4..1ae5d92462 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/ComponentSerialization.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/chat/ComponentSerialization.java
 +++ b/net/minecraft/network/chat/ComponentSerialization.java
-@@ -37,9 +37,31 @@
+@@ -37,9 +_,31 @@
  
  public class ComponentSerialization {
      public static final Codec<Component> CODEC = Codec.recursive("Component", ComponentSerialization::createCodec);
@@ -34,8 +34,8 @@
      public static final StreamCodec<RegistryFriendlyByteBuf, Optional<Component>> TRUSTED_OPTIONAL_STREAM_CODEC = TRUSTED_STREAM_CODEC.apply(
          ByteBufCodecs::optional
      );
-@@ -100,7 +122,27 @@
-         return ExtraCodecs.orCompressed(mapCodec3, mapCodec2);
+@@ -102,7 +_,25 @@
+         return ExtraCodecs.orCompressed(mapCodec2, mapCodec1);
      }
  
 +    // Paper start - adventure; create separate codec for each locale
@@ -48,23 +48,21 @@
 +        return LOCALIZED_CODECS.computeIfAbsent(locale,
 +            loc -> Codec.recursive("Component", selfCodec -> createCodec(selfCodec, loc)));
 +    }
-+
-+
 +    // Paper end - adventure; create separate codec for each locale
 +
-     private static Codec<Component> createCodec(Codec<Component> selfCodec) {
+     private static Codec<Component> createCodec(Codec<Component> codec) {
 +        // Paper start - adventure; create separate codec for each locale
-+        return createCodec(selfCodec, null);
++        return createCodec(codec, null);
 +    }
 +
-+    private static Codec<Component> createCodec(Codec<Component> selfCodec, @javax.annotation.Nullable java.util.Locale locale) {
++    private static Codec<Component> createCodec(Codec<Component> codec, @javax.annotation.Nullable java.util.Locale locale) {
 +        // Paper end - adventure; create separate codec for each locale
          ComponentContents.Type<?>[] types = new ComponentContents.Type[]{
              PlainTextContents.TYPE, TranslatableContents.TYPE, KeybindContents.TYPE, ScoreContents.TYPE, SelectorContents.TYPE, NbtContents.TYPE
          };
-@@ -113,6 +155,34 @@
-                     )
-                     .apply(instance, MutableComponent::new)
+@@ -115,6 +_,34 @@
+                 )
+                 .apply(instance, MutableComponent::new)
          );
 +        // Paper start - adventure; create separate codec for each locale
 +        final Codec<Component> origCodec = codec;
@@ -94,6 +92,6 @@
 +            }
 +        };
 +        // Paper end - adventure; create separate codec for each locale
-         return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), codec)
-             .xmap(either -> either.map(either2 -> either2.map(Component::literal, ComponentSerialization::createFromList), text -> (Component)text), text -> {
-                 String string = text.tryCollapseToString();
+         return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(codec.listOf())), codec1)
+             .xmap(
+                 either -> either.map(either1 -> either1.map(Component::literal, ComponentSerialization::createFromList), component -> (Component)component),
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/ComponentUtils.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/ComponentUtils.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch
index c7b1b54e5a..e3ea7a636b 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/ComponentUtils.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentUtils.java.patch
@@ -1,12 +1,15 @@
 --- a/net/minecraft/network/chat/ComponentUtils.java
 +++ b/net/minecraft/network/chat/ComponentUtils.java
-@@ -33,14 +33,39 @@
+@@ -33,6 +_,7 @@
          }
      }
  
 +    @io.papermc.paper.annotation.DoNotUse // Paper - validate separators - right now this method is only used for separator evaluation. Error on build if this changes to re-evaluate.
-     public static Optional<MutableComponent> updateForEntity(@Nullable CommandSourceStack source, Optional<Component> text, @Nullable Entity sender, int depth) throws CommandSyntaxException {
-         return text.isPresent() ? Optional.of(updateForEntity(source, text.get(), sender, depth)) : Optional.empty();
+     public static Optional<MutableComponent> updateForEntity(
+         @Nullable CommandSourceStack commandSourceStack, Optional<Component> optionalComponent, @Nullable Entity entity, int recursionDepth
+     ) throws CommandSyntaxException {
+@@ -41,12 +_,40 @@
+             : Optional.empty();
      }
  
 +    // Paper start - validate separator
@@ -14,13 +17,16 @@
 +        if (text.isEmpty() || !isValidSelector(text.get())) return Optional.empty();
 +        return Optional.of(updateForEntity(source, text.get(), sender, depth));
 +    }
++
 +    public static boolean isValidSelector(final Component component) {
 +        final ComponentContents contents = component.getContents();
 +
-+        if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents) return false;
++        if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents)
++            return false;
 +        if (contents instanceof final net.minecraft.network.chat.contents.TranslatableContents translatableContents) {
 +            for (final Object arg : translatableContents.getArgs()) {
-+                if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent)) return false;
++                if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent))
++                    return false;
 +            }
 +        }
 +
@@ -28,15 +34,18 @@
 +    }
 +    // Paper end - validate separator
 +
-     public static MutableComponent updateForEntity(@Nullable CommandSourceStack source, Component text, @Nullable Entity sender, int depth) throws CommandSyntaxException {
-         if (depth > 100) {
-             return text.copy();
+     public static MutableComponent updateForEntity(
+         @Nullable CommandSourceStack commandSourceStack, Component component, @Nullable Entity entity, int recursionDepth
+     ) throws CommandSyntaxException {
+         if (recursionDepth > 100) {
+             return component.copy();
          } else {
 +            // Paper start - adventure; pass actual vanilla component
-+            if (text instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) {
-+                text = adventureComponent.deepConverted();
++            if (component instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) {
++                component = adventureComponent.deepConverted();
 +            }
 +            // Paper end - adventure; pass actual vanilla component
-             MutableComponent mutableComponent = text.getContents().resolve(source, sender, depth + 1);
++
+             MutableComponent mutableComponent = component.getContents().resolve(commandSourceStack, entity, recursionDepth + 1);
  
-             for (Component component : text.getSiblings()) {
+             for (Component component1 : component.getSiblings()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/MessageSignature.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/MessageSignature.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch
index 2b6f690732..af25ad25c9 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/MessageSignature.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/MessageSignature.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/chat/MessageSignature.java
 +++ b/net/minecraft/network/chat/MessageSignature.java
-@@ -13,6 +13,7 @@
+@@ -13,6 +_,7 @@
  import net.minecraft.util.SignatureValidator;
  
  public record MessageSignature(byte[] bytes) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/MutableComponent.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/MutableComponent.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch
index c4694faca6..99e4fad8ac 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/MutableComponent.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/MutableComponent.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/network/chat/MutableComponent.java
 +++ b/net/minecraft/network/chat/MutableComponent.java
-@@ -94,6 +94,11 @@
+@@ -94,6 +_,11 @@
  
      @Override
-     public boolean equals(Object object) {
+     public boolean equals(Object other) {
 +        // Paper start - make AdventureComponent equivalent
-+        if (object instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) {
-+            object = adventureComponent.deepConverted();
++        if (other instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) {
++            other = adventureComponent.deepConverted();
 +        }
 +        // Paper end - make AdventureComponent equivalent
-         return this == object
-             || object instanceof MutableComponent mutableComponent
+         return this == other
+             || other instanceof MutableComponent mutableComponent
                  && this.contents.equals(mutableComponent.contents)
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch
new file mode 100644
index 0000000000..4dd32b8b49
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/chat/OutgoingChatMessage.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/network/chat/OutgoingChatMessage.java
++++ b/net/minecraft/network/chat/OutgoingChatMessage.java
+@@ -7,6 +_,12 @@
+ 
+     void sendToPlayer(ServerPlayer player, boolean filtered, ChatType.Bound boundType);
+ 
++    // Paper start
++    default void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) {
++        this.sendToPlayer(sender, filterMaskEnabled, params);
++    }
++    // Paper end
++
+     static OutgoingChatMessage create(PlayerChatMessage message) {
+         return (OutgoingChatMessage)(message.isSystem()
+             ? new OutgoingChatMessage.Disguised(message.decoratedContent())
+@@ -16,7 +_,12 @@
+     public record Disguised(@Override Component content) implements OutgoingChatMessage {
+         @Override
+         public void sendToPlayer(ServerPlayer player, boolean filtered, ChatType.Bound boundType) {
+-            player.connection.sendDisguisedChatMessage(this.content, boundType);
++            // Paper start
++            this.sendToPlayer(player, filtered, boundType, null);
++        }
++        public void sendToPlayer(ServerPlayer player, boolean filtered, ChatType.Bound boundType, @javax.annotation.Nullable Component unsigned) {
++            player.connection.sendDisguisedChatMessage(unsigned != null ? unsigned : this.content, boundType);
++            // Paper end
+         }
+     }
+ 
+@@ -28,7 +_,13 @@
+ 
+         @Override
+         public void sendToPlayer(ServerPlayer player, boolean filtered, ChatType.Bound boundType) {
++            // Paper start
++            this.sendToPlayer(player, filtered, boundType, null);
++        }
++        public void sendToPlayer(ServerPlayer player, boolean filtered, ChatType.Bound boundType, @javax.annotation.Nullable Component unsigned) {
++            // Paper end
+             PlayerChatMessage playerChatMessage = this.message.filter(filtered);
++            playerChatMessage = unsigned != null ? playerChatMessage.withUnsignedContent(unsigned) : playerChatMessage; // Paper
+             if (!playerChatMessage.isFullyFiltered()) {
+                 player.connection.sendPlayerChatMessage(playerChatMessage, boundType);
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/PlayerChatMessage.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
similarity index 89%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/PlayerChatMessage.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
index 8722bc8269..bf7ae66175 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/PlayerChatMessage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/chat/PlayerChatMessage.java
 +++ b/net/minecraft/network/chat/PlayerChatMessage.java
-@@ -17,6 +17,42 @@
+@@ -17,6 +_,43 @@
  public record PlayerChatMessage(
      SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask
  ) {
@@ -40,14 +40,15 @@
 +        return new AdventureView();
 +    }
 +    // Paper end - adventure; support signed messages
++
      public static final MapCodec<PlayerChatMessage> MAP_CODEC = RecordCodecBuilder.mapCodec(
          instance -> instance.group(
-                     SignedMessageLink.CODEC.fieldOf("link").forGetter(PlayerChatMessage::link),
-@@ -47,7 +83,14 @@
+                 SignedMessageLink.CODEC.fieldOf("link").forGetter(PlayerChatMessage::link),
+@@ -47,7 +_,14 @@
      }
  
-     public PlayerChatMessage withUnsignedContent(Component unsignedContent) {
--        Component component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null;
+     public PlayerChatMessage withUnsignedContent(Component message) {
+-        Component component = !message.equals(Component.literal(this.signedContent())) ? message : null;
 +        // Paper start - adventure
 +        final Component component;
 +        if (unsignedContent instanceof io.papermc.paper.adventure.AdventureComponent advComponent) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/SignedMessageChain.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
similarity index 91%
rename from paper-server/patches/unapplied/net/minecraft/network/chat/SignedMessageChain.java.patch
rename to paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
index 9404843b29..39e328fbef 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/SignedMessageChain.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/network/chat/SignedMessageChain.java
 +++ b/net/minecraft/network/chat/SignedMessageChain.java
-@@ -40,14 +40,14 @@
+@@ -40,14 +_,14 @@
                  if (signature == null) {
                      throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.MISSING_PROFILE_KEY);
-                 } else if (playerPublicKey.data().hasExpired()) {
+                 } else if (publicKey.data().hasExpired()) {
 -                    throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY);
 +                    throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY,  org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes
                  } else {
@@ -17,21 +17,21 @@
                      } else {
                          SignedMessageChain.this.lastTimeStamp = body.timeStamp();
                          PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH);
-@@ -80,9 +80,16 @@
+@@ -80,8 +_,15 @@
          static final Component INVALID_SIGNATURE = Component.translatable("chat.disabled.invalid_signature");
          static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat");
  
--        public DecodeException(Component message) {
+-        public DecodeException(Component component) {
+-            super(component);
 +        // Paper start
 +        public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause;
 +        public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) {
-             super(message);
++            super(message);
 +            this.kickCause = event;
-         }
++        }
 +        // Paper end
 +        public DecodeException(Component message) {
 +            this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper
-+        }
+         }
      }
  
-     @FunctionalInterface
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch
new file mode 100644
index 0000000000..b9e08a06cf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/chat/TextColor.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/network/chat/TextColor.java
++++ b/net/minecraft/network/chat/TextColor.java
+@@ -17,23 +_,29 @@
+     public static final Codec<TextColor> CODEC = Codec.STRING.comapFlatMap(TextColor::parseColor, TextColor::serialize);
+     private static final Map<ChatFormatting, TextColor> LEGACY_FORMAT_TO_COLOR = Stream.of(ChatFormatting.values())
+         .filter(ChatFormatting::isColor)
+-        .collect(ImmutableMap.toImmutableMap(Function.identity(), formatting -> new TextColor(formatting.getColor(), formatting.getName())));
++        .collect(ImmutableMap.toImmutableMap(Function.identity(), formatting -> new TextColor(formatting.getColor(), formatting.getName(), formatting))); // CraftBukkit
+     private static final Map<String, TextColor> NAMED_COLORS = LEGACY_FORMAT_TO_COLOR.values()
+         .stream()
+         .collect(ImmutableMap.toImmutableMap(textColor -> textColor.name, Function.identity()));
+     private final int value;
+     @Nullable
+     public final String name;
++    // CraftBukkit start
++    @Nullable
++    public final ChatFormatting format;
+ 
+-    private TextColor(int value, String name) {
++    private TextColor(int value, String name, ChatFormatting format) {
+         this.value = value & 16777215;
+         this.name = name;
++        this.format = format;
+     }
+ 
+     private TextColor(int value) {
+         this.value = value & 16777215;
+         this.name = null;
++        this.format = null;
+     }
++    // CraftBukkit end
+ 
+     public int getValue() {
+         return this.value;
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/ChatDecorator.java.patch b/paper-server/patches/unapplied/net/minecraft/network/chat/ChatDecorator.java.patch
deleted file mode 100644
index 8fe79b8a75..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/ChatDecorator.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/network/chat/ChatDecorator.java
-+++ b/net/minecraft/network/chat/ChatDecorator.java
-@@ -2,10 +2,18 @@
- 
- import javax.annotation.Nullable;
- import net.minecraft.server.level.ServerPlayer;
-+import java.util.concurrent.CompletableFuture; // Paper
- 
- @FunctionalInterface
- public interface ChatDecorator {
--    ChatDecorator PLAIN = (sender, message) -> message;
-+    ChatDecorator PLAIN = (sender, message) -> CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events
- 
--    Component decorate(@Nullable ServerPlayer sender, Component message);
-+    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events (callers should use the overload with CommandSourceStack)
-+    CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, Component message); // Paper - adventure; support async chat decoration events
-+
-+    // Paper start - adventure; support async chat decoration events
-+    default CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) {
-+        throw new UnsupportedOperationException("Must override this implementation");
-+    }
-+    // Paper end - adventure; support async chat decoration events
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/OutgoingChatMessage.java.patch b/paper-server/patches/unapplied/net/minecraft/network/chat/OutgoingChatMessage.java.patch
deleted file mode 100644
index 3192e18db3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/OutgoingChatMessage.java.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/net/minecraft/network/chat/OutgoingChatMessage.java
-+++ b/net/minecraft/network/chat/OutgoingChatMessage.java
-@@ -7,6 +7,12 @@
- 
-     void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params);
- 
-+    // Paper start
-+    default void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) {
-+        this.sendToPlayer(sender, filterMaskEnabled, params);
-+    }
-+    // Paper end
-+
-     static OutgoingChatMessage create(PlayerChatMessage message) {
-         return (OutgoingChatMessage)(message.isSystem()
-             ? new OutgoingChatMessage.Disguised(message.decoratedContent())
-@@ -16,8 +22,13 @@
-     public static record Disguised(@Override Component content) implements OutgoingChatMessage {
-         @Override
-         public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params) {
--            sender.connection.sendDisguisedChatMessage(this.content, params);
-+           // Paper start
-+            this.sendToPlayer(sender, filterMaskEnabled, params, null);
-         }
-+        public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) {
-+            sender.connection.sendDisguisedChatMessage(unsigned != null ? unsigned : this.content, params);
-+            // Paper end
-+        }
-     }
- 
-     public static record Player(PlayerChatMessage message) implements OutgoingChatMessage {
-@@ -28,7 +39,13 @@
- 
-         @Override
-         public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params) {
-+            // Paper start
-+            this.sendToPlayer(sender, filterMaskEnabled, params, null);
-+        }
-+        public void sendToPlayer(ServerPlayer sender, boolean filterMaskEnabled, ChatType.Bound params, @javax.annotation.Nullable Component unsigned) {
-+            // Paper end
-             PlayerChatMessage playerChatMessage = this.message.filter(filterMaskEnabled);
-+            playerChatMessage = unsigned != null ? playerChatMessage.withUnsignedContent(unsigned) : playerChatMessage; // Paper
-             if (!playerChatMessage.isFullyFiltered()) {
-                 sender.connection.sendPlayerChatMessage(playerChatMessage, params);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/chat/TextColor.java.patch b/paper-server/patches/unapplied/net/minecraft/network/chat/TextColor.java.patch
deleted file mode 100644
index 014fd3eb10..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/chat/TextColor.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/network/chat/TextColor.java
-+++ b/net/minecraft/network/chat/TextColor.java
-@@ -17,7 +17,7 @@
-     private static final String CUSTOM_COLOR_PREFIX = "#";
-     public static final Codec<TextColor> CODEC = Codec.STRING.comapFlatMap(TextColor::parseColor, TextColor::serialize);
-     private static final Map<ChatFormatting, TextColor> LEGACY_FORMAT_TO_COLOR = (Map) Stream.of(ChatFormatting.values()).filter(ChatFormatting::isColor).collect(ImmutableMap.toImmutableMap(Function.identity(), (enumchatformat) -> {
--        return new TextColor(enumchatformat.getColor(), enumchatformat.getName());
-+        return new TextColor(enumchatformat.getColor(), enumchatformat.getName(), enumchatformat); // CraftBukkit
-     }));
-     private static final Map<String, TextColor> NAMED_COLORS = (Map) TextColor.LEGACY_FORMAT_TO_COLOR.values().stream().collect(ImmutableMap.toImmutableMap((chathexcolor) -> {
-         return chathexcolor.name;
-@@ -25,16 +25,22 @@
-     private final int value;
-     @Nullable
-     public final String name;
-+    // CraftBukkit start
-+    @Nullable
-+    public final ChatFormatting format;
- 
--    private TextColor(int rgb, String name) {
--        this.value = rgb & 16777215;
--        this.name = name;
-+    private TextColor(int i, String s, ChatFormatting format) {
-+        this.value = i & 16777215;
-+        this.name = s;
-+        this.format = format;
-     }
- 
-     private TextColor(int rgb) {
-         this.value = rgb & 16777215;
-         this.name = null;
-+        this.format = null;
-     }
-+    // CraftBukkit end
- 
-     public int getValue() {
-         return this.value;

From e50b519542af5643fbacd6bb62ec4b7d3ffcd315 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 18:45:01 +0100
Subject: [PATCH 076/285] Next one unfortunately is LivingEntity

---
 .../world/entity/ExperienceOrb.java.patch     | 227 +++++++++---------
 1 file changed, 112 insertions(+), 115 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ExperienceOrb.java.patch (50%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ExperienceOrb.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ExperienceOrb.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch
index 2e0228c9d3..69d13b3079 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ExperienceOrb.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ExperienceOrb.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/entity/ExperienceOrb.java
 +++ b/net/minecraft/world/entity/ExperienceOrb.java
-@@ -24,6 +24,13 @@
- import net.minecraft.world.level.entity.EntityTypeTest;
+@@ -24,6 +_,14 @@
  import net.minecraft.world.phys.AABB;
  import net.minecraft.world.phys.Vec3;
+ 
 +// CraftBukkit start
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.event.entity.EntityRemoveEvent;
@@ -11,20 +11,22 @@
 +import org.bukkit.event.entity.EntityTargetEvent;
 +import org.bukkit.event.player.PlayerExpCooldownChangeEvent;
 +// CraftBukkit end
- 
++
  public class ExperienceOrb extends Entity {
- 
-@@ -37,9 +44,63 @@
+     private static final int LIFETIME = 6000;
+     private static final int ENTITY_SCAN_PERIOD = 20;
+@@ -35,9 +_,63 @@
      public int value;
-     public int count;
+     public int count = 1;
      private Player followingPlayer;
+-
 +    // Paper start
 +    @javax.annotation.Nullable
 +    public java.util.UUID sourceEntityId;
 +    @javax.annotation.Nullable
 +    public java.util.UUID triggerEntityId;
 +    public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN;
- 
++
 +    private void loadPaperNBT(CompoundTag tag) {
 +        if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) {
 +            return;
@@ -61,24 +63,24 @@
 +
 +    @io.papermc.paper.annotation.DoNotUse
 +    @Deprecated
-     public ExperienceOrb(Level world, double x, double y, double z, int amount) {
-+        this(world, x, y, z, amount, null, null);
+     public ExperienceOrb(Level level, double x, double y, double z, int value) {
++        this(level, x, y, z, value, null, null);
 +    }
 +
-+    public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) {
-+        this(world, x, y, z, amount, reason, triggerId, null);
++    public ExperienceOrb(Level level, double x, double y, double z, int value, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) {
++        this(level, x, y, z, value, reason, triggerId, null);
 +    }
 +
-+    public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) {
-         this(EntityType.EXPERIENCE_ORB, world);
++    public ExperienceOrb(Level level, double x, double y, double z, int value, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) {
+         this(EntityType.EXPERIENCE_ORB, level);
 +        this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null;
 +        this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null;
 +        this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN;
 +        // Paper end
          this.setPos(x, y, z);
-         this.setYRot((float) (this.random.nextDouble() * 360.0D));
-         this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D);
-@@ -68,6 +129,7 @@
+         this.setYRot((float)(this.random.nextDouble() * 360.0));
+         this.setDeltaMovement(
+@@ -67,6 +_,7 @@
      @Override
      public void tick() {
          super.tick();
@@ -86,7 +88,7 @@
          this.xo = this.getX();
          this.yo = this.getY();
          this.zo = this.getZ();
-@@ -93,7 +155,22 @@
+@@ -92,7 +_,22 @@
              this.followingPlayer = null;
          }
  
@@ -107,161 +109,156 @@
 +
 +        if (this.followingPlayer != null && !cancelled) {
 +            // CraftBukkit end
-             Vec3 vec3d = new Vec3(this.followingPlayer.getX() - this.getX(), this.followingPlayer.getY() + (double) this.followingPlayer.getEyeHeight() / 2.0D - this.getY(), this.followingPlayer.getZ() - this.getZ());
-             double d0 = vec3d.lengthSqr();
+             Vec3 vec3 = new Vec3(
+                 this.followingPlayer.getX() - this.getX(),
+                 this.followingPlayer.getY() + this.followingPlayer.getEyeHeight() / 2.0 - this.getY(),
+@@ -120,7 +_,7 @@
  
-@@ -121,7 +198,7 @@
- 
-         ++this.age;
+         this.age++;
          if (this.age >= 6000) {
 -            this.discard();
 +            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          }
- 
-     }
-@@ -150,18 +227,27 @@
      }
  
-     public static void award(ServerLevel world, Vec3 pos, int amount) {
+@@ -143,16 +_,25 @@
+     }
+ 
+     public static void award(ServerLevel level, Vec3 pos, int amount) {
 +        // Paper start - add reasons for orbs
-+        award(world, pos, amount, null, null, null);
++        award(level, pos, amount, null, null, null);
 +    }
-+    public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) {
-+        award(world, pos, amount, reason, triggerId, null);
++    public static void award(ServerLevel level, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) {
++        award(level, pos, amount, reason, triggerId, null);
 +    }
-+    public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) {
++    public static void award(ServerLevel level, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) {
 +        // Paper end - add reasons for orbs
          while (amount > 0) {
-             int j = ExperienceOrb.getExperienceValue(amount);
- 
-             amount -= j;
-             if (!ExperienceOrb.tryMergeToExisting(world, pos, j)) {
--                world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j));
-+                world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j, reason, triggerId, sourceId)); // Paper - add reason
+             int experienceValue = getExperienceValue(amount);
+             amount -= experienceValue;
+             if (!tryMergeToExisting(level, pos, experienceValue)) {
+-                level.addFreshEntity(new ExperienceOrb(level, pos.x(), pos.y(), pos.z(), experienceValue));
++                level.addFreshEntity(new ExperienceOrb(level, pos.x(), pos.y(), pos.z(), experienceValue, reason, triggerId, sourceId)); // Paper - add reason
              }
          }
- 
      }
  
-     private static boolean tryMergeToExisting(ServerLevel world, Vec3 pos, int amount) {
+     private static boolean tryMergeToExisting(ServerLevel level, Vec3 pos, int amount) {
 +        // Paper - TODO some other event for this kind of merge
-         AABB axisalignedbb = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D);
-         int j = world.getRandom().nextInt(40);
-         List<ExperienceOrb> list = world.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), axisalignedbb, (entityexperienceorb) -> {
-@@ -188,9 +274,14 @@
+         AABB aabb = AABB.ofSize(pos, 1.0, 1.0, 1.0);
+         int randomInt = level.getRandom().nextInt(40);
+         List<ExperienceOrb> entities = level.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), aabb, orb -> canMerge(orb, randomInt, amount));
+@@ -175,9 +_,14 @@
      }
  
-     private void merge(ExperienceOrb other) {
+     private void merge(ExperienceOrb orb) {
 +        // Paper start - call orb merge event
-+        if (!new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) this.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) other.getBukkitEntity()).callEvent()) {
++        if (!new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) this.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) orb.getBukkitEntity()).callEvent()) {
 +            return;
 +        }
 +        // Paper end - call orb merge event
-         this.count += other.count;
-         this.age = Math.min(this.age, other.age);
--        other.discard();
-+        other.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause
+         this.count = this.count + orb.count;
+         this.age = Math.min(this.age, orb.age);
+-        orb.discard();
++        orb.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause
      }
  
      private void setUnderwaterMovement() {
-@@ -215,7 +306,7 @@
+@@ -202,7 +_,7 @@
              this.markHurt();
-             this.health = (int) ((float) this.health - amount);
+             this.health = (int)(this.health - amount);
              if (this.health <= 0) {
 -                this.discard();
 +                this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
              }
  
              return true;
-@@ -226,33 +317,35 @@
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         nbt.putShort("Health", (short) this.health);
-         nbt.putShort("Age", (short) this.age);
--        nbt.putShort("Value", (short) this.value);
-+        nbt.putInt("Value", this.value); // Paper - save as Integer
-         nbt.putInt("Count", this.count);
-+        this.savePaperNBT(nbt); // Paper
+@@ -213,32 +_,34 @@
+     public void addAdditionalSaveData(CompoundTag compound) {
+         compound.putShort("Health", (short)this.health);
+         compound.putShort("Age", (short)this.age);
+-        compound.putShort("Value", (short)this.value);
++        compound.putInt("Value", this.value); // Paper - save as Integer
+         compound.putInt("Count", this.count);
++        this.savePaperNBT(compound); // Paper
      }
  
      @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         this.health = nbt.getShort("Health");
-         this.age = nbt.getShort("Age");
--        this.value = nbt.getShort("Value");
-+        this.value = nbt.getInt("Value"); // Paper - load as Integer
-         this.count = Math.max(nbt.getInt("Count"), 1);
-+        this.loadPaperNBT(nbt); // Paper
+     public void readAdditionalSaveData(CompoundTag compound) {
+         this.health = compound.getShort("Health");
+         this.age = compound.getShort("Age");
+-        this.value = compound.getShort("Value");
++        this.value = compound.getInt("Value"); // Paper - load as Integer
+         this.count = Math.max(compound.getInt("Count"), 1);
++        this.loadPaperNBT(compound); // Paper
      }
  
      @Override
-     public void playerTouch(Player player) {
-         if (player instanceof ServerPlayer entityplayer) {
--            if (player.takeXpDelay == 0) {
--                player.takeXpDelay = 2;
-+            if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(entityplayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent
-+                player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
-                 player.take(this, 1);
-                 int i = this.repairPlayerItems(entityplayer, this.value);
- 
+     public void playerTouch(Player entity) {
+         if (entity instanceof ServerPlayer serverPlayer) {
+-            if (entity.takeXpDelay == 0) {
+-                entity.takeXpDelay = 2;
++            if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent
++                entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
+                 entity.take(this, 1);
+                 int i = this.repairPlayerItems(serverPlayer, this.value);
                  if (i > 0) {
--                    player.giveExperiencePoints(i);
-+                    player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object
+-                    entity.giveExperiencePoints(i);
++                    entity.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(entity, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object
                  }
  
-                 --this.count;
+                 this.count--;
                  if (this.count == 0) {
 -                    this.discard();
 +                    this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
                  }
              }
- 
-@@ -266,12 +359,23 @@
-             ItemStack itemstack = ((EnchantedItemInUse) optional.get()).itemStack();
-             int j = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, amount);
-             int k = Math.min(j, itemstack.getDamageValue());
+         }
+@@ -252,10 +_,21 @@
+             ItemStack itemStack = randomItemWith.get().itemStack();
+             int i = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemStack, value);
+             int min = Math.min(i, itemStack.getDamageValue());
 +            // CraftBukkit start
 +            // Paper start - mending event
-+            final int consumedExperience = k > 0 ? k * amount / j : 0;
-+            org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, optional.get().inSlot(), k, consumedExperience);
++            final int consumedExperience = min > 0 ? min * value / i : 0;
++            org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemStack, randomItemWith.get().inSlot(), min, consumedExperience);
 +            // Paper end - mending event
-+            k = event.getRepairAmount();
++            min = event.getRepairAmount();
 +            if (event.isCancelled()) {
-+                return amount;
++                return value;
 +            }
 +            // CraftBukkit end
- 
-             itemstack.setDamageValue(itemstack.getDamageValue() - k);
-             if (k > 0) {
--                int l = amount - k * amount / j;
-+                int l = amount - k * amount / j; // Paper - diff on change - expand PlayerMendEvents
- 
-                 if (l > 0) {
+             itemStack.setDamageValue(itemStack.getDamageValue() - min);
+             if (min > 0) {
+-                int i1 = value - min * value / i;
++                int i1 = value - min * value / i; // Paper - diff on change - expand PlayerMendEvents
+                 if (i1 > 0) {
 +                    // this.value = l; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls // Paper - the value field should not be mutated here because it doesn't take "count" into account
-                     return this.repairPlayerItems(player, l);
+                     return this.repairPlayerItems(player, i1);
                  }
              }
-@@ -291,6 +395,24 @@
+@@ -295,6 +_,24 @@
      }
  
-     public static int getExperienceValue(int value) {
+     public static int getExperienceValue(int expValue) {
 +        // CraftBukkit start
-+        if (value > 162670129) return value - 100000;
-+        if (value > 81335063) return 81335063;
-+        if (value > 40667527) return 40667527;
-+        if (value > 20333759) return 20333759;
-+        if (value > 10166857) return 10166857;
-+        if (value > 5083423) return 5083423;
-+        if (value > 2541701) return 2541701;
-+        if (value > 1270849) return 1270849;
-+        if (value > 635413) return 635413;
-+        if (value > 317701) return 317701;
-+        if (value > 158849) return 158849;
-+        if (value > 79423) return 79423;
-+        if (value > 39709) return 39709;
-+        if (value > 19853) return 19853;
-+        if (value > 9923) return 9923;
-+        if (value > 4957) return 4957;
++        if (expValue > 162670129) return expValue - 100000;
++        if (expValue > 81335063) return 81335063;
++        if (expValue > 40667527) return 40667527;
++        if (expValue > 20333759) return 20333759;
++        if (expValue > 10166857) return 10166857;
++        if (expValue > 5083423) return 5083423;
++        if (expValue > 2541701) return 2541701;
++        if (expValue > 1270849) return 1270849;
++        if (expValue > 635413) return 635413;
++        if (expValue > 317701) return 317701;
++        if (expValue > 158849) return 158849;
++        if (expValue > 79423) return 79423;
++        if (expValue > 39709) return 39709;
++        if (expValue > 19853) return 19853;
++        if (expValue > 9923) return 9923;
++        if (expValue > 4957) return 4957;
 +        // CraftBukkit end
-         return value >= 2477 ? 2477 : (value >= 1237 ? 1237 : (value >= 617 ? 617 : (value >= 307 ? 307 : (value >= 149 ? 149 : (value >= 73 ? 73 : (value >= 37 ? 37 : (value >= 17 ? 17 : (value >= 7 ? 7 : (value >= 3 ? 3 : 1)))))))));
-     }
- 
+         if (expValue >= 2477) {
+             return 2477;
+         } else if (expValue >= 1237) {

From 822b963d3fd9c88a11a5989f2c30dec2cad488f9 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 12:57:42 -0500
Subject: [PATCH 077/285] net/minecraft/network/protocol/game/

---
 ...lientboundBlockEntityDataPacket.java.patch |  8 ++--
 ...tboundContainerSetContentPacket.java.patch | 13 +++----
 ...ientboundInitializeBorderPacket.java.patch |  2 +-
 ...ClientboundLevelChunkPacketData.java.patch | 18 ++++-----
 ...ientboundPlayerInfoUpdatePacket.java.patch | 37 +++++++++----------
 ...tboundSectionBlocksUpdatePacket.java.patch | 27 +++++---------
 ...lientboundSetBorderCenterPacket.java.patch |  4 +-
 .../ClientboundSetEntityDataPacket.java.patch | 14 +++++++
 .../ClientboundSetEquipmentPacket.java.patch  | 32 ++++++++++++++++
 .../ClientboundSetPlayerTeamPacket.java.patch | 23 ++++++++++++
 .../ClientboundSystemChatPacket.java.patch    | 13 +++----
 ...verboundCommandSuggestionPacket.java.patch | 12 +++---
 .../game/ServerboundInteractPacket.java.patch |  9 +++--
 .../ServerboundUseItemOnPacket.java.patch     | 16 ++++----
 .../game/ServerboundUseItemPacket.java.patch  | 16 ++++----
 .../protocol/game/VecDeltaCodec.java.patch    |  4 +-
 .../ClientboundSetEntityDataPacket.java.patch | 14 -------
 .../ClientboundSetEquipmentPacket.java.patch  | 32 ----------------
 .../ClientboundSetPlayerTeamPacket.java.patch | 23 ------------
 19 files changed, 153 insertions(+), 164 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch (78%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch (97%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch (54%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch (84%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch (52%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch (96%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch (68%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch (52%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch (68%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch (63%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch (92%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
index 4967c17ea2..dad6ace870 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java
-@@ -29,7 +29,7 @@
+@@ -29,7 +_,7 @@
  
-     public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity, BiFunction<BlockEntity, RegistryAccess, CompoundTag> nbtGetter) {
+     public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity, BiFunction<BlockEntity, RegistryAccess, CompoundTag> dataGetter) {
          RegistryAccess registryAccess = blockEntity.getLevel().registryAccess();
--        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity, registryAccess));
-+        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity, registryAccess)));  // Paper - Sanitize sent data
+-        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), dataGetter.apply(blockEntity, registryAccess));
++        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(dataGetter.apply(blockEntity, registryAccess)));  // Paper - Sanitize sent data
      }
  
      public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch
similarity index 78%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch
index 1a14cb1295..5eb9a5a4eb 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundContainerSetContentPacket.java
-@@ -36,6 +36,21 @@
-         this.carriedItem = ItemStack.OPTIONAL_STREAM_CODEC.decode(buf);
+@@ -35,6 +_,20 @@
+         this.items = ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(buffer);
+         this.carriedItem = ItemStack.OPTIONAL_STREAM_CODEC.decode(buffer);
      }
- 
 +    // Paper start - Handle large packets disconnecting client
 +    @Override
 +    public boolean hasLargePacketFallback() {
@@ -18,7 +18,6 @@
 +        return true;
 +    }
 +    // Paper end - Handle large packets disconnecting client
-+
-     private void write(RegistryFriendlyByteBuf buf) {
-         buf.writeContainerId(this.containerId);
-         buf.writeVarInt(this.stateId);
+ 
+     private void write(RegistryFriendlyByteBuf buffer) {
+         buffer.writeContainerId(this.containerId);
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
index a1740eccbe..9e7c36741a 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
-@@ -30,8 +30,10 @@
+@@ -31,8 +_,10 @@
      }
  
      public ClientboundInitializeBorderPacket(WorldBorder worldBorder) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch
index 26f8a0aa6a..0ffb3dd705 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java.patch
@@ -1,19 +1,19 @@
 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
 +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-@@ -52,7 +52,7 @@
+@@ -52,7 +_,7 @@
              throw new RuntimeException("Can't read heightmap in packet for [" + x + ", " + z + "]");
          } else {
-             int i = buf.readVarInt();
--            if (i > 2097152) {
-+            if (i > 2097152) { // Paper - diff on change - if this changes, update PacketEncoder
+             int varInt = buffer.readVarInt();
+-            if (varInt > 2097152) {
++            if (varInt > 2097152) { // Paper - diff on change - if this changes, update PacketEncoder
                  throw new RuntimeException("Chunk Packet trying to allocate too much memory on read.");
              } else {
-                 this.buffer = new byte[i];
-@@ -154,6 +154,7 @@
-             CompoundTag compoundTag = blockEntity.getUpdateTag(blockEntity.getLevel().registryAccess());
+                 this.buffer = new byte[varInt];
+@@ -154,6 +_,7 @@
+             CompoundTag updateTag = blockEntity.getUpdateTag(blockEntity.getLevel().registryAccess());
              BlockPos blockPos = blockEntity.getBlockPos();
              int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ());
-+            blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data
-             return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag);
++            blockEntity.sanitizeSentNbt(updateTag); // Paper - Sanitize sent data
+             return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), updateTag.isEmpty() ? null : updateTag);
          }
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
similarity index 84%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
index edb5b971ee..131ab3783d 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java
-@@ -38,7 +38,18 @@
+@@ -38,6 +_,17 @@
          this.actions = EnumSet.of(action);
          this.entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(player));
      }
@@ -9,20 +9,19 @@
 +        this.actions = actions;
 +        this.entries = entries;
 +    }
- 
++
 +    public ClientboundPlayerInfoUpdatePacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, ClientboundPlayerInfoUpdatePacket.Entry entry) {
 +        this.actions = actions;
 +        this.entries = List.of(entry);
 +    }
 +    // Paper end - Add Listing API for Player
-+
-     public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection<ServerPlayer> players) {
-         EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of(
-             ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER,
-@@ -53,6 +64,46 @@
-         return new ClientboundPlayerInfoUpdatePacket(enumSet, players);
-     }
  
+     public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection<ServerPlayer> players) {
+         EnumSet<ClientboundPlayerInfoUpdatePacket.Action> set = EnumSet.of(
+@@ -52,6 +_,46 @@
+         );
+         return new ClientboundPlayerInfoUpdatePacket(set, players);
+     }
 +    // Paper start - Add Listing API for Player
 +    public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection<ServerPlayer> players, ServerPlayer forPlayer) {
 +        final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of(
@@ -63,14 +62,14 @@
 +        return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed));
 +    }
 +    // Paper end - Add Listing API for Player
-     private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buf) {
-         this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class);
-         this.entries = buf.readList(buf2 -> {
-@@ -116,7 +167,15 @@
+ 
+     private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buffer) {
+         this.actions = buffer.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class);
+@@ -116,7 +_,15 @@
          }),
          INITIALIZE_CHAT(
-             (serialized, buf) -> serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read),
--            (buf, entry) -> buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write)
+             (entryBuilder, buffer) -> entryBuilder.chatSession = buffer.readNullable(RemoteChatSession.Data::read),
+-            (buffer, entry) -> buffer.writeNullable(entry.chatSession, RemoteChatSession.Data::write)
 +            // Paper start - Prevent causing expired keys from impacting new joins
 +            (buf, entry) -> {
 +                RemoteChatSession.Data chatSession = entry.chatSession;
@@ -81,9 +80,9 @@
 +            }
 +            // Paper end - Prevent causing expired keys from impacting new joins
          ),
-         UPDATE_GAME_MODE((serialized, buf) -> serialized.gameMode = GameType.byId(buf.readVarInt()), (buf, entry) -> buf.writeVarInt(entry.gameMode().getId())),
-         UPDATE_LISTED((serialized, buf) -> serialized.listed = buf.readBoolean(), (buf, entry) -> buf.writeBoolean(entry.listed())),
-@@ -157,10 +216,15 @@
+         UPDATE_GAME_MODE(
+             (entryBuilder, buffer) -> entryBuilder.gameMode = GameType.byId(buffer.readVarInt()),
+@@ -160,10 +_,15 @@
          @Nullable RemoteChatSession.Data chatSession
      ) {
          Entry(ServerPlayer player) {
@@ -100,7 +99,7 @@
                  player.connection.latency(),
                  player.gameMode.getGameModeForPlayer(),
                  player.getTabListDisplayName(),
-@@ -169,6 +233,11 @@
+@@ -172,6 +_,11 @@
                  Optionull.map(player.getChatSession(), RemoteChatSession::asData)
              );
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
index 372b4afd93..550a6f4c4b 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
@@ -1,15 +1,14 @@
 --- a/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
-@@ -33,11 +33,19 @@
-             short short0 = (Short) shortiterator.next();
+@@ -30,10 +_,25 @@
  
-             this.positions[j] = short0;
--            this.states[j] = section.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0));
-+            this.states[j] = (section != null) ? section.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0)) : net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(); // CraftBukkit - SPIGOT-6076, Mojang bug when empty chunk section notified
+         for (short s : positions) {
+             this.positions[i] = s;
+-            this.states[i] = section.getBlockState(SectionPos.sectionRelativeX(s), SectionPos.sectionRelativeY(s), SectionPos.sectionRelativeZ(s));
++            this.states[i] = (section != null) ? section.getBlockState(SectionPos.sectionRelativeX(s), SectionPos.sectionRelativeY(s), SectionPos.sectionRelativeZ(s)) : net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(); // CraftBukkit - SPIGOT-6076, Mojang bug when empty chunk section notified
+             i++;
          }
- 
      }
- 
 +    // CraftBukkit start - Add constructor
 +    public ClientboundSectionBlocksUpdatePacket(SectionPos sectionposition, ShortSet shortset, BlockState[] states) {
 +        this.sectionPos = sectionposition;
@@ -17,14 +16,6 @@
 +        this.states = states;
 +    }
 +    // CraftBukkit end
-+
-     private ClientboundSectionBlocksUpdatePacket(FriendlyByteBuf buf) {
-         this.sectionPos = SectionPos.of(buf.readLong());
-         int i = buf.readVarInt();
-@@ -54,6 +62,14 @@
- 
-     }
- 
 +    // Paper start - Multi Block Change API
 +    public ClientboundSectionBlocksUpdatePacket(SectionPos sectionPos, it.unimi.dsi.fastutil.shorts.Short2ObjectMap<BlockState> blockChanges) {
 +        this.sectionPos = sectionPos;
@@ -33,6 +24,6 @@
 +    }
 +    // Paper end - Multi Block Change API
 +
-     private void write(FriendlyByteBuf buf) {
-         buf.writeLong(this.sectionPos.asLong());
-         buf.writeVarInt(this.positions.length);
+ 
+     private ClientboundSectionBlocksUpdatePacket(FriendlyByteBuf buffer) {
+         this.sectionPos = SectionPos.of(buffer.readLong());
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
index 139eaa42d9..f228535ae7 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
-@@ -13,8 +13,10 @@
+@@ -14,8 +_,10 @@
      private final double newCenterZ;
  
      public ClientboundSetBorderCenterPacket(WorldBorder worldBorder) {
@@ -12,4 +12,4 @@
 +        // CraftBukkit end
      }
  
-     private ClientboundSetBorderCenterPacket(FriendlyByteBuf buf) {
+     private ClientboundSetBorderCenterPacket(FriendlyByteBuf buffer) {
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
new file mode 100644
index 0000000000..cb3ea7540d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
+@@ -19,9 +_,11 @@
+     }
+ 
+     private static void pack(List<SynchedEntityData.DataValue<?>> dataValues, RegistryFriendlyByteBuf buffer) {
++        try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(true)) { // Paper - data sanitization
+         for (SynchedEntityData.DataValue<?> dataValue : dataValues) {
+             dataValue.write(buffer);
+         }
++        } // Paper - data sanitization
+ 
+         buffer.writeByte(255);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
new file mode 100644
index 0000000000..f02259d4dc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
+@@ -19,6 +_,13 @@
+     private final List<Pair<EquipmentSlot, ItemStack>> slots;
+ 
+     public ClientboundSetEquipmentPacket(int entity, List<Pair<EquipmentSlot, ItemStack>> slots) {
++    // Paper start - data sanitization
++        this(entity, slots, false);
++    }
++    private boolean sanitize;
++    public ClientboundSetEquipmentPacket(int entity, List<Pair<EquipmentSlot, ItemStack>> slots, boolean sanitize) {
++        this.sanitize = sanitize;
++    // Paper end - data sanitization
+         this.entity = entity;
+         this.slots = slots;
+     }
+@@ -40,6 +_,7 @@
+         buffer.writeVarInt(this.entity);
+         int size = this.slots.size();
+ 
++        try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) {  // Paper - data sanitization
+         for (int i = 0; i < size; i++) {
+             Pair<EquipmentSlot, ItemStack> pair = this.slots.get(i);
+             EquipmentSlot equipmentSlot = pair.getFirst();
+@@ -48,6 +_,7 @@
+             buffer.writeByte(flag ? ordinal | -128 : ordinal);
+             ItemStack.OPTIONAL_STREAM_CODEC.encode(buffer, pair.getSecond());
+         }
++        } // Paper - data sanitization
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
new file mode 100644
index 0000000000..8e6ebe5e67
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
+@@ -30,6 +_,11 @@
+     private final Collection<String> players;
+     private final Optional<ClientboundSetPlayerTeamPacket.Parameters> parameters;
+ 
++    // Paper start - Multiple Entries with Scoreboards
++    public static ClientboundSetPlayerTeamPacket createMultiplePlayerPacket(PlayerTeam team, Collection<String> players, ClientboundSetPlayerTeamPacket.Action operation) {
++        return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players);
++    }
++    // Paper end - Multiple Entries with Scoreboards
+     private ClientboundSetPlayerTeamPacket(String name, int method, Optional<ClientboundSetPlayerTeamPacket.Parameters> parameters, Collection<String> players) {
+         this.name = name;
+         this.method = method;
+@@ -198,7 +_,7 @@
+             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buffer, this.displayName);
+             buffer.writeByte(this.options);
+             buffer.writeUtf(this.nametagVisibility);
+-            buffer.writeUtf(this.collisionRule);
++            buffer.writeUtf(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions ? "never" : this.collisionRule); // Paper - Configurable player collision
+             buffer.writeEnum(this.color);
+             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buffer, this.playerPrefix);
+             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buffer, this.playerSuffix);
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
similarity index 68%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
index 64187dddbd..5d012097a1 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.network.protocol.game;
  
  import net.minecraft.network.RegistryFriendlyByteBuf;
-@@ -12,6 +13,17 @@
- 
-     public static final StreamCodec<RegistryFriendlyByteBuf, ClientboundSystemChatPacket> STREAM_CODEC = StreamCodec.composite(ComponentSerialization.TRUSTED_STREAM_CODEC, ClientboundSystemChatPacket::content, ByteBufCodecs.BOOL, ClientboundSystemChatPacket::overlay, ClientboundSystemChatPacket::new);
- 
+@@ -16,6 +_,16 @@
+         ClientboundSystemChatPacket::overlay,
+         ClientboundSystemChatPacket::new
+     );
 +    // Spigot start
 +    public ClientboundSystemChatPacket(net.md_5.bungee.api.chat.BaseComponent[] content, boolean overlay) {
 +        this(org.bukkit.craftbukkit.util.CraftChatMessage.fromJSON(net.md_5.bungee.chat.ComponentSerializer.toString(content)), overlay);
@@ -19,7 +19,6 @@
 +        this(io.papermc.paper.adventure.PaperAdventure.asVanilla(content), overlay);
 +    }
 +    // Paper end
-+
+ 
      @Override
      public PacketType<ClientboundSystemChatPacket> type() {
-         return GamePacketTypes.CLIENTBOUND_SYSTEM_CHAT;
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch
index 1771a837ae..3161af3164 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java
-@@ -19,7 +19,7 @@
+@@ -19,7 +_,7 @@
  
-     private ServerboundCommandSuggestionPacket(FriendlyByteBuf buf) {
-         this.id = buf.readVarInt();
--        this.command = buf.readUtf(32500);
-+        this.command = buf.readUtf(2048); // Paper
+     private ServerboundCommandSuggestionPacket(FriendlyByteBuf buffer) {
+         this.id = buffer.readVarInt();
+-        this.command = buffer.readUtf(32500);
++        this.command = buffer.readUtf(2048); // Paper
      }
  
-     private void write(FriendlyByteBuf buf) {
+     private void write(FriendlyByteBuf buffer) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch
similarity index 68%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch
index a4d170433a..d7bf58f0ce 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundInteractPacket.java.patch
@@ -1,10 +1,9 @@
 --- a/net/minecraft/network/protocol/game/ServerboundInteractPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundInteractPacket.java
-@@ -176,4 +176,14 @@
-             buf.writeEnum(this.hand);
+@@ -145,6 +_,15 @@
+             buffer.writeEnum(this.hand);
          }
      }
-+
 +    // Paper start - PlayerUseUnknownEntityEvent
 +    public int getEntityId() {
 +        return this.entityId;
@@ -14,4 +13,6 @@
 +        return this.action.getType() == ActionType.ATTACK;
 +    }
 +    // Paper end - PlayerUseUnknownEntityEvent
- }
+ 
+     static class InteractionAtLocationAction implements ServerboundInteractPacket.Action {
+         private final InteractionHand hand;
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
index 1e2cc2dc52..f650a0dbfb 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
@@ -1,23 +1,23 @@
 --- a/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.network.protocol.game;
  
  import net.minecraft.network.FriendlyByteBuf;
-@@ -13,6 +14,7 @@
+@@ -14,6 +_,7 @@
      private final BlockHitResult blockHit;
      private final InteractionHand hand;
      private final int sequence;
 +    public long timestamp; // Spigot
  
-     public ServerboundUseItemOnPacket(InteractionHand hand, BlockHitResult blockHitResult, int sequence) {
+     public ServerboundUseItemOnPacket(InteractionHand hand, BlockHitResult blockHit, int sequence) {
          this.hand = hand;
-@@ -21,6 +23,7 @@
+@@ -22,6 +_,7 @@
      }
  
-     private ServerboundUseItemOnPacket(FriendlyByteBuf buf) {
+     private ServerboundUseItemOnPacket(FriendlyByteBuf buffer) {
 +        this.timestamp = System.currentTimeMillis(); // Spigot
-         this.hand = (InteractionHand) buf.readEnum(InteractionHand.class);
-         this.blockHit = buf.readBlockHitResult();
-         this.sequence = buf.readVarInt();
+         this.hand = buffer.readEnum(InteractionHand.class);
+         this.blockHit = buffer.readBlockHitResult();
+         this.sequence = buffer.readVarInt();
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
index 45ba3ec19d..460014a7e7 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
@@ -1,23 +1,23 @@
 --- a/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.network.protocol.game;
  
  import net.minecraft.network.FriendlyByteBuf;
-@@ -13,6 +14,7 @@
+@@ -14,6 +_,7 @@
      private final int sequence;
      private final float yRot;
      private final float xRot;
 +    public long timestamp; // Spigot
  
-     public ServerboundUseItemPacket(InteractionHand hand, int sequence, float yaw, float pitch) {
+     public ServerboundUseItemPacket(InteractionHand hand, int sequence, float yRot, float xRot) {
          this.hand = hand;
-@@ -22,6 +24,7 @@
+@@ -23,6 +_,7 @@
      }
  
-     private ServerboundUseItemPacket(FriendlyByteBuf buf) {
+     private ServerboundUseItemPacket(FriendlyByteBuf buffer) {
 +        this.timestamp = System.currentTimeMillis(); // Spigot
-         this.hand = (InteractionHand) buf.readEnum(InteractionHand.class);
-         this.sequence = buf.readVarInt();
-         this.yRot = buf.readFloat();
+         this.hand = buffer.readEnum(InteractionHand.class);
+         this.sequence = buffer.readVarInt();
+         this.yRot = buffer.readFloat();
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch
similarity index 92%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch
index ca1a2126c9..2f0544231c 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/protocol/game/VecDeltaCodec.java
 +++ b/net/minecraft/network/protocol/game/VecDeltaCodec.java
-@@ -5,16 +5,16 @@
+@@ -5,16 +_,16 @@
  
  public class VecDeltaCodec {
      private static final double TRUNCATION_STEPS = 4096.0;
@@ -15,7 +15,7 @@
  
      @VisibleForTesting
      static double decode(long value) {
--        return (double)value / 4096.0;
+-        return value / 4096.0;
 +        return value / 4096.0; // Paper - Fix MC-4; diff on change
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
deleted file mode 100644
index 6a8d6c8ce8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
-+++ b/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
-@@ -19,9 +19,11 @@
-     }
- 
-     private static void pack(List<SynchedEntityData.DataValue<?>> trackedValues, RegistryFriendlyByteBuf buf) {
-+        try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(true)) { // Paper - data sanitization
-         for (SynchedEntityData.DataValue<?> dataValue : trackedValues) {
-             dataValue.write(buf);
-         }
-+        } // Paper - data sanitization
- 
-         buf.writeByte(255);
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
deleted file mode 100644
index e13e6cd647..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
-+++ b/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
-@@ -19,6 +19,13 @@
-     private final List<Pair<EquipmentSlot, ItemStack>> slots;
- 
-     public ClientboundSetEquipmentPacket(int entityId, List<Pair<EquipmentSlot, ItemStack>> equipmentList) {
-+        // Paper start - data sanitization
-+        this(entityId, equipmentList, false);
-+    }
-+    private boolean sanitize;
-+    public ClientboundSetEquipmentPacket(int entityId, List<Pair<EquipmentSlot, ItemStack>> equipmentList, boolean sanitize) {
-+        this.sanitize = sanitize;
-+        // Paper end - data sanitization
-         this.entity = entityId;
-         this.slots = equipmentList;
-     }
-@@ -40,6 +47,7 @@
-         buf.writeVarInt(this.entity);
-         int i = this.slots.size();
- 
-+        try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) {  // Paper - data sanitization
-         for (int j = 0; j < i; j++) {
-             Pair<EquipmentSlot, ItemStack> pair = this.slots.get(j);
-             EquipmentSlot equipmentSlot = pair.getFirst();
-@@ -48,6 +56,7 @@
-             buf.writeByte(bl ? k | -128 : k);
-             ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, pair.getSecond());
-         }
-+        } // Paper - data sanitization
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
deleted file mode 100644
index 1d4da793e7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
-+++ b/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
-@@ -58,6 +58,11 @@
-         );
-     }
- 
-+    // Paper start - Multiple Entries with Scoreboards
-+    public static ClientboundSetPlayerTeamPacket createMultiplePlayerPacket(PlayerTeam team, Collection<String> players, ClientboundSetPlayerTeamPacket.Action operation) {
-+        return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players);
-+    }
-+    // Paper end - Multiple Entries with Scoreboards
-     private ClientboundSetPlayerTeamPacket(RegistryFriendlyByteBuf buf) {
-         this.name = buf.readUtf();
-         this.method = buf.readByte();
-@@ -200,7 +205,7 @@
-             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.displayName);
-             buf.writeByte(this.options);
-             buf.writeUtf(this.nametagVisibility);
--            buf.writeUtf(this.collisionRule);
-+            buf.writeUtf(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions ? "never" : this.collisionRule); // Paper - Configurable player collision
-             buf.writeEnum(this.color);
-             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerPrefix);
-             ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerSuffix);

From 1a214aed6a7b900bbb56cf9051b8295153153f77 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 17:57:17 +0000
Subject: [PATCH 078/285] net/minecraft/world/entity/item

---
 .../entity/item/FallingBlockEntity.java.patch | 165 ++++++++++++
 .../world/entity/item/ItemEntity.java.patch   | 241 ++++++++----------
 .../world/entity/item/PrimedTnt.java.patch    |  64 +++--
 .../entity/item/FallingBlockEntity.java.patch | 162 ------------
 4 files changed, 306 insertions(+), 326 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/item/ItemEntity.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/item/PrimedTnt.java.patch (73%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/item/FallingBlockEntity.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
new file mode 100644
index 0000000000..32fc663482
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -0,0 +1,165 @@
+--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
+@@ -47,8 +_,14 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.event.entity.CreatureSpawnEvent;
+ import org.slf4j.Logger;
+ 
++// CraftBukkit start;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRemoveEvent;
++// CraftBukkit end
++
+ public class FallingBlockEntity extends Entity {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     public BlockState blockState = Blocks.SAND.defaultBlockState();
+@@ -62,6 +_,7 @@
+     public CompoundTag blockData;
+     public boolean forceTickAfterTeleportToDuplicate;
+     protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
++    public boolean autoExpire = true; // Paper - Expand FallingBlock API
+ 
+     public FallingBlockEntity(EntityType<? extends FallingBlockEntity> entityType, Level level) {
+         super(entityType, level);
+@@ -80,6 +_,10 @@
+     }
+ 
+     public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState) {
++        // CraftBukkit start
++        return FallingBlockEntity.fall(level, pos, blockState, CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++    public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+         FallingBlockEntity fallingBlockEntity = new FallingBlockEntity(
+             level,
+             pos.getX() + 0.5,
+@@ -89,6 +_,7 @@
+                 ? blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false))
+                 : blockState
+         );
++        if (!CraftEventFactory.callEntityChangeBlockEvent(fallingBlockEntity, pos, blockState.getFluidState().createLegacyBlock())) return fallingBlockEntity; // CraftBukkit
+         level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
+         level.addFreshEntity(fallingBlockEntity);
+         return fallingBlockEntity;
+@@ -139,13 +_,22 @@
+     @Override
+     public void tick() {
+         if (this.blockState.isAir()) {
+-            this.discard();
++            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else {
+             Block block = this.blockState.getBlock();
+             this.time++;
+             this.applyGravity();
+             this.move(MoverType.SELF, this.getDeltaMovement());
+             this.applyEffectsFromBlocks();
++            // Paper start - Configurable falling blocks height nerf
++            if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
++                if (this.dropItem && this.level() instanceof final ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
++                    this.spawnAtLocation(serverLevel, block);
++                }
++                this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
++                return;
++            }
++            // Paper end - Configurable falling blocks height nerf
+             this.handlePortal();
+             if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) {
+                 BlockPos blockPos = this.blockPosition();
+@@ -166,12 +_,12 @@
+                 }
+ 
+                 if (!this.onGround() && !flag1) {
+-                    if (this.time > 100 && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || this.time > 600) {
++                    if ((this.time > 100 && autoExpire) && (blockPos.getY() <= this.level().getMinY() || blockPos.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API
+                         if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                             this.spawnAtLocation(serverLevel, block);
+                         }
+ 
+-                        this.discard();
++                        this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                     }
+                 } else {
+                     BlockState blockState = this.level().getBlockState(blockPos);
+@@ -189,12 +_,18 @@
+                                     this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(true));
+                                 }
+ 
++                                // CraftBukkit start
++                                if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, this.blockState)) {
++                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // SPIGOT-6586 called before the event in previous versions
++                                    return;
++                                }
++                                // CraftBukkit end
+                                 if (this.level().setBlock(blockPos, this.blockState, 3)) {
+                                     ((ServerLevel)this.level())
+                                         .getChunkSource()
+                                         .chunkMap
+                                         .broadcast(this, new ClientboundBlockUpdatePacket(blockPos, this.level().getBlockState(blockPos)));
+-                                    this.discard();
++                                    this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                                     if (block instanceof Fallable) {
+                                         ((Fallable)block).onLand(this.level(), blockPos, this.blockState, blockState, this);
+                                     }
+@@ -218,19 +_,19 @@
+                                         }
+                                     }
+                                 } else if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+-                                    this.discard();
++                                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                                     this.callOnBrokenAfterFall(block, blockPos);
+                                     this.spawnAtLocation(serverLevel, block);
+                                 }
+                             } else {
+-                                this.discard();
++                                this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                                 if (this.dropItem && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+                                     this.callOnBrokenAfterFall(block, blockPos);
+                                     this.spawnAtLocation(serverLevel, block);
+                                 }
+                             }
+                         } else {
+-                            this.discard();
++                            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+                             this.callOnBrokenAfterFall(block, blockPos);
+                         }
+                     }
+@@ -290,6 +_,7 @@
+         }
+ 
+         compound.putBoolean("CancelDrop", this.cancelDrop);
++        if (!autoExpire) {compound.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
+     }
+ 
+     @Override
+@@ -308,7 +_,7 @@
+             this.dropItem = compound.getBoolean("DropItem");
+         }
+ 
+-        if (compound.contains("TileEntityData", 10)) {
++        if (compound.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks
+             this.blockData = compound.getCompound("TileEntityData").copy();
+         }
+ 
+@@ -316,6 +_,12 @@
+         if (this.blockState.isAir()) {
+             this.blockState = Blocks.SAND.defaultBlockState();
+         }
++
++        // Paper start - Expand FallingBlock API
++         if (compound.contains("Paper.AutoExpire")) {
++            this.autoExpire = compound.getBoolean("Paper.AutoExpire");
++         }
++        // Paper end - Expand FallingBlock API
+     }
+ 
+     public void setHurtsEntities(float fallDamagePerDistance, int fallDamageMax) {
+@@ -372,7 +_,7 @@
+         ResourceKey<Level> resourceKey1 = this.level().dimension();
+         boolean flag = (resourceKey1 == Level.END || resourceKey == Level.END) && resourceKey1 != resourceKey;
+         Entity entity = super.teleport(teleportTransition);
+-        this.forceTickAfterTeleportToDuplicate = entity != null && flag;
++        this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper
+         return entity;
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/item/ItemEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 0bc136847c..0eecee5ed4 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/item/ItemEntity.java
 +++ b/net/minecraft/world/entity/item/ItemEntity.java
-@@ -5,18 +5,6 @@
+@@ -3,18 +_,6 @@
  import java.util.Objects;
  import java.util.UUID;
  import javax.annotation.Nullable;
@@ -19,7 +19,7 @@
  import net.minecraft.world.damagesource.DamageSource;
  import net.minecraft.world.entity.Entity;
  import net.minecraft.world.entity.EntityType;
-@@ -24,7 +12,6 @@
+@@ -22,7 +_,6 @@
  import net.minecraft.world.entity.MoverType;
  import net.minecraft.world.entity.SlotAccess;
  import net.minecraft.world.entity.TraceableEntity;
@@ -27,7 +27,7 @@
  import net.minecraft.world.item.Item;
  import net.minecraft.world.item.ItemStack;
  import net.minecraft.world.level.Explosion;
-@@ -33,6 +20,27 @@
+@@ -31,6 +_,27 @@
  import net.minecraft.world.level.gameevent.GameEvent;
  import net.minecraft.world.level.portal.TeleportTransition;
  import net.minecraft.world.phys.Vec3;
@@ -54,33 +54,32 @@
 +import org.bukkit.event.player.PlayerAttemptPickupItemEvent; // Paper
  
  public class ItemEntity extends Entity implements TraceableEntity {
- 
-@@ -52,6 +60,10 @@
+     private static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK);
+@@ -49,6 +_,9 @@
      @Nullable
      public UUID target;
      public final float bobOffs;
-+    // private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit // Paper - remove anti tick skipping measures / wall time
 +    public boolean canMobPickup = true; // Paper - Item#canEntityPickup
 +    private int despawnRate = -1; // Paper - Alternative item-despawn-rate
 +    public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
  
-     public ItemEntity(EntityType<? extends ItemEntity> type, Level world) {
-         super(type, world);
-@@ -61,7 +73,12 @@
+     public ItemEntity(EntityType<? extends ItemEntity> entityType, Level level) {
+         super(entityType, level);
+@@ -57,7 +_,12 @@
      }
  
-     public ItemEntity(Level world, double x, double y, double z, ItemStack stack) {
--        this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D);
+     public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack) {
+-        this(level, posX, posY, posZ, itemStack, level.random.nextDouble() * 0.2 - 0.1, 0.2, level.random.nextDouble() * 0.2 - 0.1);
 +        // Paper start - Don't use level random in entity constructors (to make them thread-safe)
-+        this(EntityType.ITEM, world);
-+        this.setPos(x, y, z);
++        this(EntityType.ITEM, level);
++        this.setPos(posX, posY, posZ);
 +        this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D);
-+        this.setItem(stack);
++        this.setItem(itemStack);
 +        // Paper end - Don't use level random in entity constructors
      }
  
-     public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) {
-@@ -133,12 +150,14 @@
+     public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack, double deltaX, double deltaY, double deltaZ) {
+@@ -119,7 +_,7 @@
      @Override
      public void tick() {
          if (this.getItem().isEmpty()) {
@@ -88,24 +87,16 @@
 +            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          } else {
              super.tick();
-+            // Paper start - remove anti tick skipping measures / wall time - revert to vanilla
              if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
-                 --this.pickupDelay;
-             }
-+            // Paper end - remove anti tick skipping measures / wall time - revert to vanilla
- 
-             this.xo = this.getX();
-             this.yo = this.getY();
-@@ -162,12 +181,16 @@
+@@ -147,11 +_,15 @@
                  }
              }
  
--            if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) {
-+            if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change; ActivationRange immunity
+-            if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-5F || (this.tickCount + this.getId()) % 4 == 0) {
++            if (!this.onGround() || this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-5F || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change; ActivationRange immunity
                  this.move(MoverType.SELF, this.getDeltaMovement());
                  this.applyEffectsFromBlocks();
                  float f = 0.98F;
- 
 -                if (this.onGround()) {
 +                // Paper start - Friction API
 +                if (frictionState == net.kyori.adventure.util.TriState.FALSE) {
@@ -115,24 +106,15 @@
                      f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F;
                  }
  
-@@ -188,9 +211,11 @@
-                 this.mergeWithNeighbours();
-             }
- 
-+            // Paper - remove anti tick skipping measures / wall time - revert to vanilla /* CraftBukkit start - moved up
-             if (this.age != -32768) {
-                 ++this.age;
-             }
-+            // CraftBukkit end */
- 
-             this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing();
-             if (!this.level().isClientSide) {
-@@ -201,14 +226,44 @@
+@@ -184,11 +_,42 @@
                  }
              }
  
 -            if (!this.level().isClientSide && this.age >= 6000) {
 -                this.discard();
+-            }
+-        }
+-    }
 +            if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate
 +                // CraftBukkit start - fire ItemDespawnEvent
 +                if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
@@ -141,13 +123,12 @@
 +                }
 +                // CraftBukkit end
 +                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             }
- 
-         }
-     }
- 
++            }
++        }
++    }
++
 +    // Spigot start - copied from above
-     @Override
++    @Override
 +    public void inactiveTick() {
 +        // Paper start - remove anti tick skipping measures / wall time - copied from above
 +        if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
@@ -170,85 +151,76 @@
 +    }
 +    // Spigot end
 +
-+    @Override
+ 
+     @Override
      public BlockPos getBlockPosBelowThatAffectsMyMovement() {
-         return this.getOnPos(0.999999F);
-     }
-@@ -229,7 +284,10 @@
+@@ -210,9 +_,18 @@
  
      private void mergeWithNeighbours() {
          if (this.isMergable()) {
--            List<ItemEntity> list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.5D, 0.0D, 0.5D), (entityitem) -> {
-+            // Spigot start
-+            double radius = this.level().spigotConfig.itemMerge;
-+            List<ItemEntity> list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(radius, this.level().paperConfig().entities.behavior.onlyMergeItemsHorizontally ? 0 : radius - 0.5D, radius), (entityitem) -> { // Paper - configuration to only merge items horizontally
-+                // Spigot end
-                 return entityitem != this && entityitem.isMergable();
-             });
-             Iterator iterator = list.iterator();
-@@ -238,6 +296,14 @@
-                 ItemEntity entityitem = (ItemEntity) iterator.next();
- 
-                 if (entityitem.isMergable()) {
++            double radius = this.level().spigotConfig.itemMerge; // Spigot
+             for (ItemEntity itemEntity : this.level()
+-                .getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.5, 0.0, 0.5), neighbour -> neighbour != this && neighbour.isMergable())) {
++                .getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(radius, this.level().paperConfig().entities.behavior.onlyMergeItemsHorizontally ? 0 : radius - 0.5D, radius), neighbour -> neighbour != this && neighbour.isMergable())) { // Spigot // Paper - configuration to only merge items horizontally
+                 if (itemEntity.isMergable()) {
 +                    // Paper start - Fix items merging through walls
 +                    if (this.level().paperConfig().fixes.fixItemsMergingThroughWalls) {
-+                        if (this.level().clipDirect(this.position(), entityitem.position(),
++                        if (this.level().clipDirect(this.position(), itemEntity.position(),
 +                            net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.HitResult.Type.BLOCK) {
 +                            continue;
 +                        }
 +                    }
 +                    // Paper end - Fix items merging through walls
-                     this.tryToMerge(entityitem);
+                     this.tryToMerge(itemEntity);
                      if (this.isRemoved()) {
                          break;
-@@ -251,7 +317,7 @@
+@@ -224,14 +_,14 @@
+ 
      private boolean isMergable() {
-         ItemStack itemstack = this.getItem();
- 
--        return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize();
-+        return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - Alternative item-despawn-rate
+         ItemStack item = this.getItem();
+-        return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && item.getCount() < item.getMaxStackSize();
++        return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && item.getCount() < item.getMaxStackSize(); // Paper - Alternative item-despawn-rate
      }
  
-     private void tryToMerge(ItemEntity other) {
-@@ -259,7 +325,7 @@
-         ItemStack itemstack1 = other.getItem();
- 
-         if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) {
--            if (itemstack1.getCount() < itemstack.getCount()) {
-+            if (true || itemstack1.getCount() < itemstack.getCount()) { // Spigot
-                 ItemEntity.merge(this, itemstack, other, itemstack1);
+     private void tryToMerge(ItemEntity itemEntity) {
+         ItemStack item = this.getItem();
+         ItemStack item1 = itemEntity.getItem();
+         if (Objects.equals(this.target, itemEntity.target) && areMergable(item, item1)) {
+-            if (item1.getCount() < item.getCount()) {
++            if (true || item1.getCount() < item.getCount()) { // Spigot
+                 merge(this, item, itemEntity, item1);
              } else {
-                 ItemEntity.merge(other, itemstack1, this, itemstack);
-@@ -287,11 +353,16 @@
+                 merge(itemEntity, item1, this, item);
+@@ -257,11 +_,16 @@
      }
  
-     private static void merge(ItemEntity targetEntity, ItemStack targetStack, ItemEntity sourceEntity, ItemStack sourceStack) {
+     private static void merge(ItemEntity destinationEntity, ItemStack destinationStack, ItemEntity originEntity, ItemStack originStack) {
 +        // CraftBukkit start
-+        if (!CraftEventFactory.callItemMergeEvent(sourceEntity, targetEntity)) {
++        if (!CraftEventFactory.callItemMergeEvent(originEntity, destinationEntity)) {
 +            return;
 +        }
 +        // CraftBukkit end
-         ItemEntity.merge(targetEntity, targetStack, sourceStack);
-         targetEntity.pickupDelay = Math.max(targetEntity.pickupDelay, sourceEntity.pickupDelay);
-         targetEntity.age = Math.min(targetEntity.age, sourceEntity.age);
-         if (sourceStack.isEmpty()) {
--            sourceEntity.discard();
-+            sourceEntity.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause);
+         merge(destinationEntity, destinationStack, originStack);
+         destinationEntity.pickupDelay = Math.max(destinationEntity.pickupDelay, originEntity.pickupDelay);
+         destinationEntity.age = Math.min(destinationEntity.age, originEntity.age);
+         if (originStack.isEmpty()) {
+-            originEntity.discard();
++            originEntity.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause
          }
- 
      }
-@@ -320,12 +391,17 @@
-         } else if (!this.getItem().canBeHurtBy(source)) {
+ 
+@@ -289,12 +_,17 @@
+         } else if (!this.getItem().canBeHurtBy(damageSource)) {
              return false;
          } else {
 +            // CraftBukkit start
-+            if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) {
++            if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount)) {
 +                return false;
 +            }
 +            // CraftBukkit end
              this.markHurt();
-             this.health = (int) ((float) this.health - amount);
-             this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
+             this.health = (int)(this.health - amount);
+             this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
              if (this.health <= 0) {
                  this.getItem().onDestroyed(this);
 -                this.discard();
@@ -256,25 +228,25 @@
              }
  
              return true;
-@@ -339,6 +415,11 @@
+@@ -308,6 +_,11 @@
  
      @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
+     public void addAdditionalSaveData(CompoundTag compound) {
 +        // Paper start - Friction API
 +        if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
-+            nbt.putString("Paper.FrictionState", this.frictionState.toString());
++            compound.putString("Paper.FrictionState", this.frictionState.toString());
 +        }
 +        // Paper end - Friction API
-         nbt.putShort("Health", (short) this.health);
-         nbt.putShort("Age", (short) this.age);
-         nbt.putShort("PickupDelay", (short) this.pickupDelay);
-@@ -381,23 +462,98 @@
+         compound.putShort("Health", (short)this.health);
+         compound.putShort("Age", (short)this.age);
+         compound.putShort("PickupDelay", (short)this.pickupDelay);
+@@ -347,22 +_,95 @@
+         } else {
              this.setItem(ItemStack.EMPTY);
          }
- 
 +        // Paper start - Friction API
-+        if (nbt.contains("Paper.FrictionState")) {
-+            String fs = nbt.getString("Paper.FrictionState");
++        if (compound.contains("Paper.FrictionState")) {
++            String fs = compound.getString("Paper.FrictionState");
 +            try {
 +                frictionState = net.kyori.adventure.util.TriState.valueOf(fs);
 +            } catch (Exception ignored) {
@@ -282,75 +254,72 @@
 +            }
 +        }
 +        // Paper end - Friction API
-+
+ 
          if (this.getItem().isEmpty()) {
 -            this.discard();
-+            this.discard(null); // CraftBukkit - add Bukkit remove cause
++            this.discard(null);  // CraftBukkit - add Bukkit remove cause
          }
- 
      }
  
      @Override
--    public void playerTouch(Player player) {
-+    public void playerTouch(net.minecraft.world.entity.player.Player player) {
+-    public void playerTouch(Player entity) {
++    public void playerTouch(net.minecraft.world.entity.player.Player entity) {
          if (!this.level().isClientSide) {
-             ItemStack itemstack = this.getItem();
-             Item item = itemstack.getItem();
-             int i = itemstack.getCount();
-+
+             ItemStack item = this.getItem();
+             Item item1 = item.getItem();
+             int count = item.getCount();
 +            // CraftBukkit start - fire PlayerPickupItemEvent
-+            int canHold = player.getInventory().canHold(itemstack);
-+            int remaining = i - canHold;
++            int canHold = entity.getInventory().canHold(item);
++            int remaining = count - canHold;
 +            boolean flyAtPlayer = false; // Paper
 +
 +            // Paper start - PlayerAttemptPickupItemEvent
 +            if (this.pickupDelay <= 0) {
-+                PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                this.level().getCraftServer().getPluginManager().callEvent(attemptEvent);
 +
 +                flyAtPlayer = attemptEvent.getFlyAtPlayer();
 +                if (attemptEvent.isCancelled()) {
 +                    if (flyAtPlayer) {
-+                        player.take(this, i);
++                        entity.take(this, count);
 +                    }
 +
 +                    return;
 +                }
 +            }
-+            // Paper end - PlayerAttemptPickupItemEvent
- 
++
 +            if (this.pickupDelay <= 0 && canHold > 0) {
-+                itemstack.setCount(canHold);
++                item.setCount(canHold);
 +                // Call legacy event
-+                PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                playerEvent.setCancelled(!playerEvent.getPlayer().getCanPickupItems());
 +                this.level().getCraftServer().getPluginManager().callEvent(playerEvent);
 +                flyAtPlayer = playerEvent.getFlyAtPlayer(); // Paper
 +                if (playerEvent.isCancelled()) {
-+                    itemstack.setCount(i); // SPIGOT-5294 - restore count
++                    item.setCount(count); // SPIGOT-5294 - restore count
 +                    // Paper start
 +                    if (flyAtPlayer) {
-+                        player.take(this, i);
++                        entity.take(this, count);
 +                    }
 +                    // Paper end
 +                    return;
 +                }
 +
 +                // Call newer event afterwards
-+                EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) player.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                entityEvent.setCancelled(!entityEvent.getEntity().getCanPickupItems());
 +                this.level().getCraftServer().getPluginManager().callEvent(entityEvent);
 +                if (entityEvent.isCancelled()) {
-+                    itemstack.setCount(i); // SPIGOT-5294 - restore count
++                    item.setCount(count); // SPIGOT-5294 - restore count
 +                    return;
 +                }
 +
 +                // Update the ItemStack if it was changed in the event
 +                ItemStack current = this.getItem();
-+                if (!itemstack.equals(current)) {
-+                    itemstack = current;
++                if (!item.equals(current)) {
++                    item = current;
 +                } else {
-+                    itemstack.setCount(canHold + remaining); // = i
++                    item.setCount(canHold + remaining); // = i
 +                }
 +
 +                // Possibly < 0; fix here so we do not have to modify code below
@@ -360,25 +329,25 @@
 +                this.pickupDelay = -1;
 +            }
 +            // CraftBukkit end
-+
-             if (this.pickupDelay == 0 && (this.target == null || this.target.equals(player.getUUID())) && player.getInventory().add(itemstack)) {
++            // Paper end - PlayerAttemptPickupItemEvent
+             if (this.pickupDelay == 0 && (this.target == null || this.target.equals(entity.getUUID())) && entity.getInventory().add(item)) {
 +                if (flyAtPlayer) // Paper - PlayerPickupItemEvent
-                 player.take(this, i);
-                 if (itemstack.isEmpty()) {
+                 entity.take(this, count);
+                 if (item.isEmpty()) {
 -                    this.discard();
 +                    this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-                     itemstack.setCount(i);
+                     item.setCount(count);
                  }
  
-@@ -438,6 +594,7 @@
+@@ -400,6 +_,7 @@
  
      public void setItem(ItemStack stack) {
-         this.getEntityData().set(ItemEntity.DATA_ITEM, stack);
+         this.getEntityData().set(DATA_ITEM, stack);
 +        this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate
      }
  
      @Override
-@@ -492,7 +649,7 @@
+@@ -453,7 +_,7 @@
  
      public void makeFakeItem() {
          this.setNeverPickUp();
@@ -386,4 +355,4 @@
 +        this.age = this.despawnRate - 1; // Spigot // Paper - Alternative item-despawn-rate
      }
  
-     public static float getSpin(float f, float f1) {
+     public static float getSpin(float age, float bobOffset) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/item/PrimedTnt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/item/PrimedTnt.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
index 1120fe2bdf..49714990c2 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/item/PrimedTnt.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/item/PrimedTnt.java
 +++ b/net/minecraft/world/entity/item/PrimedTnt.java
-@@ -27,6 +27,12 @@
+@@ -27,6 +_,12 @@
  import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.level.portal.TeleportTransition;
  
@@ -11,26 +11,26 @@
 +// CraftBukkit end
 +
  public class PrimedTnt extends Entity implements TraceableEntity {
- 
      private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
-@@ -51,6 +57,7 @@
+     private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE);
+@@ -50,6 +_,7 @@
      public LivingEntity owner;
      private boolean usedPortal;
-     public float explosionPower;
+     public float explosionPower = 4.0F;
 +    public boolean isIncendiary = false; // CraftBukkit - add field
  
-     public PrimedTnt(EntityType<? extends PrimedTnt> type, Level world) {
-         super(type, world);
-@@ -61,7 +68,7 @@
-     public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) {
-         this(EntityType.TNT, world);
+     public PrimedTnt(EntityType<? extends PrimedTnt> entityType, Level level) {
+         super(entityType, level);
+@@ -59,7 +_,7 @@
+     public PrimedTnt(Level level, double x, double y, double z, @Nullable LivingEntity owner) {
+         this(EntityType.TNT, level);
          this.setPos(x, y, z);
--        double d3 = world.random.nextDouble() * 6.2831854820251465D;
-+        double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - Don't use level random in entity constructors
- 
-         this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D);
+-        double d = level.random.nextDouble() * (float) (Math.PI * 2);
++        double d = this.random.nextDouble() * (float) (Math.PI * 2);  // Paper - Don't use level random in entity constructors
+         this.setDeltaMovement(-Math.sin(d) * 0.02, 0.2F, -Math.cos(d) * 0.02);
          this.setFuse(80);
-@@ -94,10 +101,17 @@
+         this.xo = x;
+@@ -91,10 +_,17 @@
  
      @Override
      public void tick() {
@@ -45,16 +45,16 @@
 +            return;
 +        }
 +        // Paper end - Configurable TNT height nerf
-         this.setDeltaMovement(this.getDeltaMovement().scale(0.98D));
+         this.setDeltaMovement(this.getDeltaMovement().scale(0.98));
          if (this.onGround()) {
-             this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D));
-@@ -107,10 +121,13 @@
- 
+             this.setDeltaMovement(this.getDeltaMovement().multiply(0.7, -0.5, 0.7));
+@@ -103,19 +_,49 @@
+         int i = this.getFuse() - 1;
          this.setFuse(i);
          if (i <= 0) {
 -            this.discard();
 +            // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
-+            // this.discard();
++            //this.discard();
              if (!this.level().isClientSide) {
                  this.explode();
              }
@@ -63,10 +63,9 @@
          } else {
              this.updateInWaterStateAndDoFluidPushing();
              if (this.level().isClientSide) {
-@@ -118,10 +135,37 @@
+                 this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5, this.getZ(), 0.0, 0.0, 0.0);
              }
          }
- 
 +        // Paper start - Option to prevent TNT from moving in water
 +        if (!this.isRemoved() && this.wasTouchingWater && this.level().paperConfig().fixes.preventTntFromMovingInWater) {
 +            /*
@@ -91,19 +90,28 @@
      }
  
      private void explode() {
--        this.level().explode(this, Explosion.getDefaultDamageSource(this.level(), this), this.usedPortal ? PrimedTnt.USED_PORTAL_DAMAGE_CALCULATOR : null, this.getX(), this.getY(0.0625D), this.getZ(), this.explosionPower, false, Level.ExplosionInteraction.TNT);
 +        // CraftBukkit start
 +        ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
 +        if (event.isCancelled()) {
 +            return;
 +        }
-+        this.level().explode(this, Explosion.getDefaultDamageSource(this.level(), this), this.usedPortal ? PrimedTnt.USED_PORTAL_DAMAGE_CALCULATOR : null, this.getX(), this.getY(0.0625D), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.TNT);
-+        // CraftBukkit end
++        // Craftbukkit end
+         this.level()
+             .explode(
+                 this,
+@@ -124,8 +_,8 @@
+                 this.getX(),
+                 this.getY(0.0625),
+                 this.getZ(),
+-                this.explosionPower,
+-                false,
++                event.getRadius(), // CraftBukkit
++                event.getFire(), // CraftBukkit
+                 Level.ExplosionInteraction.TNT
+             );
      }
- 
-     @Override
-@@ -198,4 +242,11 @@
-     public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
+@@ -200,4 +_,11 @@
+     public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
          return false;
      }
 +
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
deleted file mode 100644
index 8f36430b05..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
+++ /dev/null
@@ -1,162 +0,0 @@
---- a/net/minecraft/world/entity/item/FallingBlockEntity.java
-+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
-@@ -52,6 +52,11 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
-+
- public class FallingBlockEntity extends Entity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -66,6 +71,7 @@
-     public CompoundTag blockData;
-     public boolean forceTickAfterTeleportToDuplicate;
-     protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
-+    public boolean autoExpire = true; // Paper - Expand FallingBlock API
- 
-     public FallingBlockEntity(EntityType<? extends FallingBlockEntity> type, Level world) {
-         super(type, world);
-@@ -87,10 +93,17 @@
-     }
- 
-     public static FallingBlockEntity fall(Level world, BlockPos pos, BlockState state) {
--        FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, state.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) state.setValue(BlockStateProperties.WATERLOGGED, false) : state);
-+        // CraftBukkit start
-+        return FallingBlockEntity.fall(world, pos, state, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
- 
--        world.setBlock(pos, state.getFluidState().createLegacyBlock(), 3);
--        world.addFreshEntity(entityfallingblock);
-+    public static FallingBlockEntity fall(Level world, BlockPos blockposition, BlockState iblockdata, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
-+        // CraftBukkit end
-+        FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, iblockdata.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) iblockdata.setValue(BlockStateProperties.WATERLOGGED, false) : iblockdata);
-+        if (!CraftEventFactory.callEntityChangeBlockEvent(entityfallingblock, blockposition, iblockdata.getFluidState().createLegacyBlock())) return entityfallingblock; // CraftBukkit
-+
-+        world.setBlock(blockposition, iblockdata.getFluidState().createLegacyBlock(), 3);
-+        world.addFreshEntity(entityfallingblock, spawnReason); // CraftBukkit
-         return entityfallingblock;
-     }
- 
-@@ -139,7 +152,7 @@
-     @Override
-     public void tick() {
-         if (this.blockState.isAir()) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else {
-             Block block = this.blockState.getBlock();
- 
-@@ -147,6 +160,16 @@
-             this.applyGravity();
-             this.move(MoverType.SELF, this.getDeltaMovement());
-             this.applyEffectsFromBlocks();
-+            // Paper start - Configurable falling blocks height nerf
-+            if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) {
-+                if (this.dropItem && this.level() instanceof final ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-+                    this.spawnAtLocation(serverLevel, block);
-+                }
-+
-+                this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD);
-+                return;
-+            }
-+            // Paper end - Configurable falling blocks height nerf
-             this.handlePortal();
-             Level world = this.level();
- 
-@@ -169,12 +192,12 @@
-                     }
- 
-                     if (!this.onGround() && !flag1) {
--                        if (this.time > 100 && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || this.time > 600) {
-+                        if ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API
-                             if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                                 this.spawnAtLocation(worldserver, (ItemLike) block);
-                             }
- 
--                            this.discard();
-+                            this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
-                         }
-                     } else {
-                         BlockState iblockdata = this.level().getBlockState(blockposition);
-@@ -191,9 +214,15 @@
-                                         this.blockState = (BlockState) this.blockState.setValue(BlockStateProperties.WATERLOGGED, true);
-                                     }
- 
-+                                    // CraftBukkit start
-+                                    if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, this.blockState)) {
-+                                        this.discard(EntityRemoveEvent.Cause.DESPAWN); // SPIGOT-6586 called before the event in previous versions
-+                                        return;
-+                                    }
-+                                    // CraftBukkit end
-                                     if (this.level().setBlock(blockposition, this.blockState, 3)) {
-                                         ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition)));
--                                        this.discard();
-+                                        this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                                         if (block instanceof Fallable) {
-                                             ((Fallable) block).onLand(this.level(), blockposition, this.blockState, iblockdata, this);
-                                         }
-@@ -221,19 +250,19 @@
-                                             }
-                                         }
-                                     } else if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
--                                        this.discard();
-+                                        this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
-                                         this.callOnBrokenAfterFall(block, blockposition);
-                                         this.spawnAtLocation(worldserver, (ItemLike) block);
-                                     }
-                                 } else {
--                                    this.discard();
-+                                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
-                                     if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                                         this.callOnBrokenAfterFall(block, blockposition);
-                                         this.spawnAtLocation(worldserver, (ItemLike) block);
-                                     }
-                                 }
-                             } else {
--                                this.discard();
-+                                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-                                 this.callOnBrokenAfterFall(block, blockposition);
-                             }
-                         }
-@@ -310,6 +339,7 @@
-         }
- 
-         nbt.putBoolean("CancelDrop", this.cancelDrop);
-+        if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
-     }
- 
-     @Override
-@@ -328,7 +358,7 @@
-             this.dropItem = nbt.getBoolean("DropItem");
-         }
- 
--        if (nbt.contains("TileEntityData", 10)) {
-+        if (nbt.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks
-             this.blockData = nbt.getCompound("TileEntityData").copy();
-         }
- 
-@@ -337,6 +367,11 @@
-             this.blockState = Blocks.SAND.defaultBlockState();
-         }
- 
-+        // Paper start - Expand FallingBlock API
-+         if (nbt.contains("Paper.AutoExpire")) {
-+            this.autoExpire = nbt.getBoolean("Paper.AutoExpire");
-+         }
-+        // Paper end - Expand FallingBlock API
-     }
- 
-     public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) {
-@@ -395,7 +430,7 @@
-         boolean flag = (resourcekey1 == Level.END || resourcekey == Level.END) && resourcekey1 != resourcekey;
-         Entity entity = super.teleport(teleportTarget);
- 
--        this.forceTickAfterTeleportToDuplicate = entity != null && flag;
-+        this.forceTickAfterTeleportToDuplicate = entity != null && flag && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowUnsafeEndPortalTeleportation; // Paper
-         return entity;
-     }
- }

From 902965e66a9bc7b4eb172dd8b56e703fa0cd75e3 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:52:05 +0100
Subject: [PATCH 079/285] net.minecraft.network.protocol.common(.custom)

---
 .../ServerboundCustomPayloadPacket.java.patch | 13 ++++++++++
 .../common/custom/DiscardedPayload.java.patch | 25 ++++++++-----------
 .../ServerboundCustomPayloadPacket.java.patch | 20 ---------------
 3 files changed, 24 insertions(+), 34 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch (50%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
new file mode 100644
index 0000000000..b7477f930e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
++++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
+@@ -14,9 +_,7 @@
+     private static final int MAX_PAYLOAD_SIZE = 32767;
+     public static final StreamCodec<FriendlyByteBuf, ServerboundCustomPayloadPacket> STREAM_CODEC = CustomPacketPayload.<FriendlyByteBuf>codec(
+             id -> DiscardedPayload.codec(id, 32767),
+-            Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)), list -> {})
+-        )
+-        .map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload);
++        java.util.Collections.emptyList()).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload); // CraftBukkit - treat all packets the same
+ 
+     @Override
+     public PacketType<ServerboundCustomPayloadPacket> type() {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
index 785d4efd47..034c2906c4 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
@@ -1,24 +1,21 @@
 --- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
 +++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
-@@ -4,16 +4,18 @@
+@@ -4,13 +_,15 @@
  import net.minecraft.network.codec.StreamCodec;
  import net.minecraft.resources.ResourceLocation;
  
 -public record DiscardedPayload(ResourceLocation id) implements CustomPacketPayload {
 +public record DiscardedPayload(ResourceLocation id, io.netty.buffer.ByteBuf data) implements CustomPacketPayload { // CraftBukkit - store data
- 
-     public static <T extends FriendlyByteBuf> StreamCodec<T, DiscardedPayload> codec(ResourceLocation id, int maxBytes) {
-         return CustomPacketPayload.codec((discardedpayload, packetdataserializer) -> {
-+            packetdataserializer.writeBytes(discardedpayload.data); // CraftBukkit - serialize
-         }, (packetdataserializer) -> {
-             int j = packetdataserializer.readableBytes();
- 
-             if (j >= 0 && j <= maxBytes) {
--                packetdataserializer.skipBytes(j);
+     public static <T extends FriendlyByteBuf> StreamCodec<T, DiscardedPayload> codec(ResourceLocation id, int maxSize) {
+-        return CustomPacketPayload.codec((value, output) -> {}, buffer -> {
++        return CustomPacketPayload.codec((value, output) -> {
++            output.writeBytes(value.data); // CraftBukkit - serialize
++        }, buffer -> {
+             int i = buffer.readableBytes();
+             if (i >= 0 && i <= maxSize) {
+                 buffer.skipBytes(i);
 -                return new DiscardedPayload(id);
-+                // CraftBukkit start
-+                return new DiscardedPayload(id, packetdataserializer.readBytes(j));
-+                // CraftBukkit end
++                return new DiscardedPayload(id, buffer.readBytes(i)); // CraftBukkit
              } else {
-                 throw new IllegalArgumentException("Payload may not be larger than " + maxBytes + " bytes");
+                 throw new IllegalArgumentException("Payload may not be larger than " + maxSize + " bytes");
              }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
deleted file mode 100644
index e17dc6200b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
-+++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
-@@ -2,7 +2,6 @@
- 
- import com.google.common.collect.Lists;
- import java.util.List;
--import net.minecraft.Util;
- import net.minecraft.network.FriendlyByteBuf;
- import net.minecraft.network.codec.StreamCodec;
- import net.minecraft.network.protocol.Packet;
-@@ -16,8 +15,7 @@
-     private static final int MAX_PAYLOAD_SIZE = 32767;
-     public static final StreamCodec<FriendlyByteBuf, ServerboundCustomPayloadPacket> STREAM_CODEC = CustomPacketPayload.codec((minecraftkey) -> {
-         return DiscardedPayload.codec(minecraftkey, 32767);
--    }, (List) Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec[]{new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)}), (arraylist) -> {
--    })).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload);
-+    }, java.util.Collections.emptyList()).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload); // CraftBukkit - treat all packets the same
- 
-     @Override
-     public PacketType<ServerboundCustomPayloadPacket> type() {

From 2a274e38c6769d3b5e9c6e7a750f0ba4522fda28 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 18:57:57 +0100
Subject: [PATCH 080/285] net.minecraft.tags

---
 .../net/minecraft/tags/TagLoader.java.patch   | 52 ++++++++++---------
 1 file changed, 27 insertions(+), 25 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/tags/TagLoader.java.patch (63%)

diff --git a/paper-server/patches/unapplied/net/minecraft/tags/TagLoader.java.patch b/paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/tags/TagLoader.java.patch
rename to paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch
index 10e1ff5cb3..fc75f3c8b7 100644
--- a/paper-server/patches/unapplied/net/minecraft/tags/TagLoader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/tags/TagLoader.java.patch
@@ -1,56 +1,58 @@
 --- a/net/minecraft/tags/TagLoader.java
 +++ b/net/minecraft/tags/TagLoader.java
-@@ -86,7 +86,10 @@
-         return list.isEmpty() ? Either.right(List.copyOf(sequencedSet)) : Either.left(list);
+@@ -86,7 +_,10 @@
+         return list.isEmpty() ? Either.right(List.copyOf(set)) : Either.left(list);
      }
  
--    public Map<ResourceLocation, List<T>> build(Map<ResourceLocation, List<TagLoader.EntryWithSource>> tags) {
+-    public Map<ResourceLocation, List<T>> build(Map<ResourceLocation, List<TagLoader.EntryWithSource>> builders) {
 +    // Paper start - fire tag registrar events
-+    public Map<ResourceLocation, List<T>> build(Map<ResourceLocation, List<TagLoader.EntryWithSource>> tags, @Nullable io.papermc.paper.tag.TagEventConfig<T, ?> eventConfig) {
-+        tags = io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.firePreFlattenEvent(tags, eventConfig);
-+    // Paper end - fire tag registrar event
++    public Map<ResourceLocation, List<T>> build(Map<ResourceLocation, List<TagLoader.EntryWithSource>> builders, @Nullable io.papermc.paper.tag.TagEventConfig<T, ?> eventConfig) {
++        builders = io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.firePreFlattenEvent(builders, eventConfig);
++        // Paper end
          final Map<ResourceLocation, List<T>> map = new HashMap<>();
          TagEntry.Lookup<T> lookup = new TagEntry.Lookup<T>() {
              @Nullable
-@@ -114,7 +117,7 @@
-                     )
-                     .ifRight(values -> map.put(id, (List<T>)values))
+@@ -114,7 +_,7 @@
+                 )
+                 .ifRight(list -> map.put(path, (List<T>)list))
          );
 -        return map;
 +        return io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.firePostFlattenEvent(map, eventConfig); // Paper - fire tag registrar events
      }
  
-     public static <T> void loadTagsFromNetwork(TagNetworkSerialization.NetworkPayload tags, WritableRegistry<T> registry) {
-@@ -122,28 +125,38 @@
+     public static <T> void loadTagsFromNetwork(TagNetworkSerialization.NetworkPayload payload, WritableRegistry<T> registry) {
+@@ -122,16 +_,27 @@
      }
  
-     public static List<Registry.PendingTags<?>> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryManager) {
-+    // Paper start - tag lifecycle - add cause
-+        return loadTagsForExistingRegistries(resourceManager, registryManager, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL);
+     public static List<Registry.PendingTags<?>> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryAccess) {
++        // Paper start - tag lifecycle - add cause
++        return loadTagsForExistingRegistries(resourceManager, registryAccess, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL);
 +    }
-+    public static List<Registry.PendingTags<?>> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryManager, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) {
-+    // Paper end - tag lifecycle - add cause
-         return registryManager.registries()
--            .map(registry -> loadPendingTags(resourceManager, registry.value()))
-+            .map(registry -> loadPendingTags(resourceManager, registry.value(), cause)) // Paper - tag lifecycle - add cause
++
++    public static List<Registry.PendingTags<?>> loadTagsForExistingRegistries(ResourceManager resourceManager, RegistryAccess registryAccess, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) {
++        // Paper end - tag lifecycle - add cause
+         return registryAccess.registries()
+-            .map(registryEntry -> loadPendingTags(resourceManager, registryEntry.value()))
++            .map(registryEntry -> loadPendingTags(resourceManager, registryEntry.value(), cause)) // Paper - tag lifecycle - add cause
              .flatMap(Optional::stream)
              .collect(Collectors.toUnmodifiableList());
      }
  
      public static <T> void loadTagsForRegistry(ResourceManager resourceManager, WritableRegistry<T> registry) {
-+    // Paper start - tag lifecycle - add registrar event cause
++        // Paper start - tag lifecycle - add registrar event cause
 +        loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL);
 +    }
 +    public static <T> void loadTagsForRegistry(ResourceManager resourceManager, WritableRegistry<T> registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause cause) {
-+    // Paper end - tag lifecycle - add registrar event cause
++        // Paper end - tag lifecycle - add registrar event cause
          ResourceKey<? extends Registry<T>> resourceKey = registry.key();
          TagLoader<Holder<T>> tagLoader = new TagLoader<>(TagLoader.ElementLookup.fromWritableRegistry(registry), Registries.tagsDirPath(resourceKey));
--        tagLoader.build(tagLoader.load(resourceManager)).forEach((id, entries) -> registry.bindTag(TagKey.create(resourceKey, id), (List<Holder<T>>)entries));
-+        tagLoader.build(tagLoader.load(resourceManager), io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.createEventConfig(registry, cause)).forEach((id, entries) -> registry.bindTag(TagKey.create(resourceKey, id), (List<Holder<T>>)entries)); // Paper - tag lifecycle - add registrar event cause
+-        tagLoader.build(tagLoader.load(resourceManager))
++        tagLoader.build(tagLoader.load(resourceManager), io.papermc.paper.tag.PaperTagListenerManager.INSTANCE.createEventConfig(registry, cause)) // Paper - tag lifecycle - add registrar event cause
+             .forEach((resourceLocation, list) -> registry.bindTag(TagKey.create(resourceKey, resourceLocation), (List<Holder<T>>)list));
      }
  
-     private static <T> Map<TagKey<T>, List<Holder<T>>> wrapTags(ResourceKey<? extends Registry<T>> registryRef, Map<ResourceLocation, List<Holder<T>>> tags) {
-         return tags.entrySet().stream().collect(Collectors.toUnmodifiableMap(entry -> TagKey.create(registryRef, entry.getKey()), Entry::getValue));
+@@ -139,12 +_,12 @@
+         return tags.entrySet().stream().collect(Collectors.toUnmodifiableMap(entry -> TagKey.create(registryKey, entry.getKey()), Entry::getValue));
      }
  
 -    private static <T> Optional<Registry.PendingTags<T>> loadPendingTags(ResourceManager resourceManager, Registry<T> registry) {

From b602dc968ee90f95911d440d072c9767ae026eeb Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 19:06:52 +0100
Subject: [PATCH 081/285] net.minecraft

---
 .../net/minecraft/ChatFormatting.java.patch   | 11 +--
 .../net/minecraft/CrashReport.java.patch      | 16 ++---
 .../minecraft/CrashReportCategory.java.patch  |  6 +-
 .../net/minecraft/Util.java.patch             | 70 ++++++++-----------
 4 files changed, 46 insertions(+), 57 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/ChatFormatting.java.patch (60%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/CrashReport.java.patch (54%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/CrashReportCategory.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/Util.java.patch (76%)

diff --git a/paper-server/patches/unapplied/net/minecraft/ChatFormatting.java.patch b/paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/ChatFormatting.java.patch
rename to paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch
index eb700c93b5..658c5707e3 100644
--- a/paper-server/patches/unapplied/net/minecraft/ChatFormatting.java.patch
+++ b/paper-server/patches/sources/net/minecraft/ChatFormatting.java.patch
@@ -1,11 +1,12 @@
 --- a/net/minecraft/ChatFormatting.java
 +++ b/net/minecraft/ChatFormatting.java
-@@ -112,6 +112,18 @@
-         return name == null ? null : FORMATTING_BY_NAME.get(cleanName(name));
+@@ -112,6 +_,19 @@
+         return friendlyName == null ? null : FORMATTING_BY_NAME.get(cleanName(friendlyName));
      }
  
 +    // Paper start - add method to get by hex value
-+    @Nullable public static ChatFormatting getByHexValue(int i) {
++    @Nullable
++    public static ChatFormatting getByHexValue(int i) {
 +        for (ChatFormatting value : values()) {
 +            if (value.getColor() != null && value.getColor() == i) {
 +                return value;
@@ -17,5 +18,5 @@
 +    // Paper end - add method to get by hex value
 +
      @Nullable
-     public static ChatFormatting getById(int colorIndex) {
-         if (colorIndex < 0) {
+     public static ChatFormatting getById(int index) {
+         if (index < 0) {
diff --git a/paper-server/patches/unapplied/net/minecraft/CrashReport.java.patch b/paper-server/patches/sources/net/minecraft/CrashReport.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/CrashReport.java.patch
rename to paper-server/patches/sources/net/minecraft/CrashReport.java.patch
index 0b8863cd41..d9c00ba981 100644
--- a/paper-server/patches/unapplied/net/minecraft/CrashReport.java.patch
+++ b/paper-server/patches/sources/net/minecraft/CrashReport.java.patch
@@ -1,22 +1,22 @@
 --- a/net/minecraft/CrashReport.java
 +++ b/net/minecraft/CrashReport.java
-@@ -34,8 +34,10 @@
+@@ -32,8 +_,10 @@
      private final SystemReport systemReport = new SystemReport();
  
-     public CrashReport(String message, Throwable cause) {
-+        io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper
-         this.title = message;
-         this.exception = cause;
+     public CrashReport(String title, Throwable exception) {
++        io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper
+         this.title = title;
+         this.exception = exception;
 +        this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
      }
  
      public String getTitle() {
-@@ -251,7 +253,7 @@
+@@ -218,7 +_,7 @@
      }
  
      public static void preload() {
 -        MemoryReserve.allocate();
-+        // MemoryReserve.allocate(); // Paper - Disable memory reserve allocating
-         (new CrashReport("Don't panic!", new Throwable())).getFriendlyReport(ReportType.CRASH);
++        //MemoryReserve.allocate(); // Paper - Disable memory reserve allocating
+         new CrashReport("Don't panic!", new Throwable()).getFriendlyReport(ReportType.CRASH);
      }
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/CrashReportCategory.java.patch b/paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/CrashReportCategory.java.patch
rename to paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch
index 23f5297d2e..4120655a29 100644
--- a/paper-server/patches/unapplied/net/minecraft/CrashReportCategory.java.patch
+++ b/paper-server/patches/sources/net/minecraft/CrashReportCategory.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/CrashReportCategory.java
 +++ b/net/minecraft/CrashReportCategory.java
-@@ -110,6 +110,7 @@
+@@ -138,6 +_,7 @@
          } else {
-             this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount];
-             System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length);
+             this.stackTrace = new StackTraceElement[stackTrace.length - 3 - size];
+             System.arraycopy(stackTrace, 3 + size, this.stackTrace, 0, this.stackTrace.length);
 +            this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper
              return this.stackTrace.length;
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/Util.java.patch b/paper-server/patches/sources/net/minecraft/Util.java.patch
similarity index 76%
rename from paper-server/patches/unapplied/net/minecraft/Util.java.patch
rename to paper-server/patches/sources/net/minecraft/Util.java.patch
index 378b82b378..bcf7f18858 100644
--- a/paper-server/patches/unapplied/net/minecraft/Util.java.patch
+++ b/paper-server/patches/sources/net/minecraft/Util.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/Util.java
 +++ b/net/minecraft/Util.java
-@@ -92,9 +92,26 @@
+@@ -92,9 +_,26 @@
      private static final int DEFAULT_MAX_THREADS = 255;
      private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10;
      private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads";
@@ -28,7 +28,7 @@
      private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT);
      public static final int LINEAR_LOOKUP_THRESHOLD = 8;
      private static final Set<String> ALLOWED_UNTRUSTED_LINK_PROTOCOLS = Set.of("http", "https");
-@@ -136,7 +153,7 @@
+@@ -135,7 +_,7 @@
      }
  
      public static long getNanos() {
@@ -37,36 +37,37 @@
      }
  
      public static long getEpochMillis() {
-@@ -147,15 +164,16 @@
+@@ -146,15 +_,17 @@
          return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now());
      }
  
 -    private static TracingExecutor makeExecutor(String name) {
 +    private static TracingExecutor makeExecutor(String name, final int priorityModifier) { // Paper - Perf: add priority
          int i = maxAllowedExecutorThreads();
--        ExecutorService executorService;
+-        ExecutorService directExecutorService;
 +        // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs
-+        final ExecutorService executorService;
++        final ExecutorService directExecutorService;
          if (i <= 0) {
-             executorService = MoreExecutors.newDirectExecutorService();
+             directExecutorService = MoreExecutors.newDirectExecutorService();
          } else {
--            AtomicInteger atomicInteger = new AtomicInteger(1);
--            executorService = new ForkJoinPool(i, pool -> {
--                final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
-+            executorService = Executors.newFixedThreadPool(i, target -> new io.papermc.paper.util.ServerWorkerThread(target, name, priorityModifier));
+             AtomicInteger atomicInteger = new AtomicInteger(1);
+-            directExecutorService = new ForkJoinPool(i, forkJoinPool -> {
+-                final String string = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
++            directExecutorService = Executors.newFixedThreadPool(i, target -> new io.papermc.paper.util.ServerWorkerThread(target, name, priorityModifier));
 +        }
-+        /*      final String string2 = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
-                 ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) {
++        /*  final String string = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
+                 ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(forkJoinPool) {
                      @Override
                      protected void onStart() {
-@@ -177,13 +195,26 @@
-                 forkJoinWorkerThread.setName(string2);
+@@ -176,13 +_,27 @@
+                 forkJoinWorkerThread.setName(string);
                  return forkJoinWorkerThread;
              }, Util::onThreadException, true);
 -        }
 +        }*/
++        // Paper end
  
-         return new TracingExecutor(executorService);
+         return new TracingExecutor(directExecutorService);
      }
  
      public static int maxAllowedExecutorThreads() {
@@ -88,15 +89,10 @@
      }
  
      private static int getMaxThreads() {
-@@ -229,10 +260,25 @@
-             TracyClient.setThreadName(string2, namePrefix.hashCode());
-             thread.setName(string2);
-             thread.setDaemon(daemon);
-+            thread.setUncaughtExceptionHandler(Util::onThreadException);
-+            return thread;
-+        }));
-+    }
-+
+@@ -233,6 +_,21 @@
+         }));
+     }
+ 
 +    // Paper start - Separate dimension data IO pool
 +    private static TracingExecutor makeExtraIoExecutor(String namePrefix) {
 +        AtomicInteger atomicInteger = new AtomicInteger(1);
@@ -106,24 +102,16 @@
 +            TracyClient.setThreadName(string2, namePrefix.hashCode());
 +            thread.setName(string2);
 +            thread.setDaemon(false);
-             thread.setUncaughtExceptionHandler(Util::onThreadException);
-             return thread;
-         }));
-     }
++            thread.setUncaughtExceptionHandler(Util::onThreadException);
++            return thread;
++        }));
++    }
 +    // Paper end - Separate dimension data IO pool
- 
-     public static void throwAsRuntime(Throwable t) {
-         throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
-@@ -537,7 +583,7 @@
-     public static <K extends Enum<K>, V> EnumMap<K, V> makeEnumMap(Class<K> enumClass, Function<K, V> mapper) {
-         EnumMap<K, V> enumMap = new EnumMap<>(enumClass);
- 
--        for (K enum_ : (Enum[])enumClass.getEnumConstants()) {
-+        for (K enum_ : enumClass.getEnumConstants()) { // Paper - decompile error
-             enumMap.put(enum_, mapper.apply(enum_));
-         }
- 
-@@ -1057,16 +1103,7 @@
++
+     public static void throwAsRuntime(Throwable throwable) {
+         throw throwable instanceof RuntimeException ? (RuntimeException)throwable : new RuntimeException(throwable);
+     }
+@@ -1075,16 +_,7 @@
          }
  
          public void openUri(URI uri) {

From db81fd3455a9ad1d176ceaa6831734827ff07600 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 10:07:36 -0800
Subject: [PATCH 082/285] net.minecraft.world.item.crafting

---
 .../item/crafting/BlastingRecipe.java.patch   |  20 ++++
 .../crafting/CampfireCookingRecipe.java.patch |  20 ++++
 .../item/crafting/CustomRecipe.java.patch     |  22 ++++
 .../world/item/crafting/Ingredient.java.patch |  55 +++++++++
 .../world/item/crafting/Recipe.java.patch     |   2 +-
 .../item/crafting/RecipeHolder.java.patch     |  15 +++
 .../item/crafting/RecipeManager.java.patch    |  64 ++++++++++
 .../world/item/crafting/RecipeMap.java.patch  |  32 ++---
 .../item/crafting/ShapedRecipe.java.patch     |  39 ++----
 .../item/crafting/ShapelessRecipe.java.patch  |  24 ++++
 .../item/crafting/SmeltingRecipe.java.patch   |  20 ++++
 .../SmithingTransformRecipe.java.patch        |  46 ++++++++
 .../crafting/SmithingTrimRecipe.java.patch    |  58 +++++++++
 .../item/crafting/SmokingRecipe.java.patch    |  20 ++++
 .../crafting/StonecutterRecipe.java.patch     |  19 +++
 .../item/crafting/TransmuteRecipe.java.patch  |  16 +++
 .../item/crafting/BlastingRecipe.java.patch   |  35 ------
 .../crafting/CampfireCookingRecipe.java.patch |  35 ------
 .../item/crafting/CustomRecipe.java.patch     |  54 ---------
 .../world/item/crafting/Ingredient.java.patch |  66 -----------
 .../item/crafting/RecipeHolder.java.patch     |  26 ----
 .../item/crafting/RecipeManager.java.patch    | 111 ------------------
 .../item/crafting/ShapelessRecipe.java.patch  |  39 ------
 .../item/crafting/SmeltingRecipe.java.patch   |  35 ------
 .../SmithingTransformRecipe.java.patch        |  61 ----------
 .../crafting/SmithingTrimRecipe.java.patch    |  69 -----------
 .../item/crafting/SmokingRecipe.java.patch    |  35 ------
 .../crafting/StonecutterRecipe.java.patch     |  34 ------
 .../item/crafting/TransmuteRecipe.java.patch  |  31 -----
 29 files changed, 421 insertions(+), 682 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/crafting/Recipe.java.patch (93%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/crafting/RecipeMap.java.patch (69%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/crafting/ShapedRecipe.java.patch (57%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/CustomRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/Ingredient.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeHolder.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeManager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
new file mode 100644
index 0000000000..320c3c5413
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/crafting/BlastingRecipe.java
++++ b/net/minecraft/world/item/crafting/BlastingRecipe.java
+@@ -31,4 +_,17 @@
+             case FOOD, MISC -> RecipeBookCategories.BLAST_FURNACE_MISC;
+         };
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result());
++
++        org.bukkit.craftbukkit.inventory.CraftBlastingRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftBlastingRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
++        recipe.setGroup(this.group());
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        return recipe;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
new file mode 100644
index 0000000000..876f5b6f1d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
++++ b/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
+@@ -28,4 +_,17 @@
+     public RecipeBookCategory recipeBookCategory() {
+         return RecipeBookCategories.CAMPFIRE;
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result());
++
++        org.bukkit.craftbukkit.inventory.CraftCampfireRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftCampfireRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
++        recipe.setGroup(this.group());
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        return recipe;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch
new file mode 100644
index 0000000000..bbf2d64506
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/CustomRecipe.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/crafting/CustomRecipe.java
++++ b/net/minecraft/world/item/crafting/CustomRecipe.java
+@@ -30,6 +_,19 @@
+     @Override
+     public abstract RecipeSerializer<? extends CustomRecipe> getSerializer();
+ 
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.EMPTY);
++
++        org.bukkit.craftbukkit.inventory.CraftComplexRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftComplexRecipe(id, result, this);
++        recipe.setGroup(this.group());
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        return recipe;
++    }
++    // CraftBukkit end
++
+     public static class Serializer<T extends CraftingRecipe> implements RecipeSerializer<T> {
+         private final MapCodec<T> codec;
+         private final StreamCodec<RegistryFriendlyByteBuf, T> streamCodec;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch
new file mode 100644
index 0000000000..6e0c1b88c1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/Ingredient.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/item/crafting/Ingredient.java
++++ b/net/minecraft/world/item/crafting/Ingredient.java
+@@ -33,6 +_,25 @@
+     public static final Codec<Ingredient> CODEC = ExtraCodecs.nonEmptyHolderSet(NON_AIR_HOLDER_SET_CODEC)
+         .xmap(Ingredient::new, ingredient -> ingredient.values);
+     private final HolderSet<Item> values;
++    // CraftBukkit start
++    @javax.annotation.Nullable
++    private java.util.List<ItemStack> itemStacks;
++
++    public boolean isExact() {
++        return this.itemStacks != null;
++    }
++
++    @javax.annotation.Nullable
++    public java.util.List<ItemStack> itemStacks() {
++        return this.itemStacks;
++    }
++
++    public static Ingredient ofStacks(java.util.List<ItemStack> stacks) {
++        Ingredient recipe = Ingredient.of(stacks.stream().map(ItemStack::getItem));
++        recipe.itemStacks = stacks;
++        return recipe;
++    }
++    // CraftBukkit end
+ 
+     private Ingredient(HolderSet<Item> values) {
+         values.unwrap().ifRight(list -> {
+@@ -60,6 +_,17 @@
+ 
+     @Override
+     public boolean test(ItemStack stack) {
++        // CraftBukkit start
++        if (this.isExact()) {
++            for (ItemStack itemstack1 : this.itemStacks()) {
++                if (itemstack1.getItem() == stack.getItem() && ItemStack.isSameItemSameComponents(stack, itemstack1)) {
++                    return true;
++                }
++            }
++
++            return false;
++        }
++        // CraftBukkit end
+         return stack.is(this.values);
+     }
+ 
+@@ -70,7 +_,7 @@
+ 
+     @Override
+     public boolean equals(Object other) {
+-        return other instanceof Ingredient ingredient && Objects.equals(this.values, ingredient.values);
++        return other instanceof Ingredient ingredient && Objects.equals(this.values, ingredient.values) && Objects.equals(this.itemStacks, ingredient.itemStacks); // CraftBukkit
+     }
+ 
+     public static Ingredient of(ItemLike item) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/Recipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch
similarity index 93%
rename from paper-server/patches/unapplied/net/minecraft/world/item/crafting/Recipe.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch
index ad97a94316..4d54b2d585 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/Recipe.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/Recipe.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/crafting/Recipe.java
 +++ b/net/minecraft/world/item/crafting/Recipe.java
-@@ -44,4 +44,6 @@
+@@ -44,4 +_,6 @@
      }
  
      RecipeBookCategory recipeBookCategory();
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch
new file mode 100644
index 0000000000..538297e2aa
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeHolder.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/item/crafting/RecipeHolder.java
++++ b/net/minecraft/world/item/crafting/RecipeHolder.java
+@@ -10,6 +_,12 @@
+         ResourceKey.streamCodec(Registries.RECIPE), RecipeHolder::id, Recipe.STREAM_CODEC, RecipeHolder::value, RecipeHolder::new
+     );
+ 
++    // CraftBukkit start
++    public final org.bukkit.inventory.Recipe toBukkitRecipe() {
++        return this.value.toBukkitRecipe(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.id.location()));
++    }
++    // CraftBukkit end
++
+     @Override
+     public boolean equals(Object other) {
+         return this == other || other instanceof RecipeHolder<?> recipeHolder && this.id == recipeHolder.id;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
new file mode 100644
index 0000000000..03dc6953c4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/item/crafting/RecipeManager.java
++++ b/net/minecraft/world/item/crafting/RecipeManager.java
+@@ -87,7 +_,26 @@
+         LOGGER.info("Loaded {} recipes", object.values().size());
+     }
+ 
++    // CraftBukkit start
++    public void addRecipe(RecipeHolder<?> irecipe) {
++        org.spigotmc.AsyncCatcher.catchOp("Recipe Add"); // Spigot
++        this.recipes.addRecipe(irecipe);
++        this.finalizeRecipeLoading();
++    }
++
++    private FeatureFlagSet featureflagset;
++
++    public void finalizeRecipeLoading() {
++        if (this.featureflagset != null) {
++            this.finalizeRecipeLoading(this.featureflagset);
++
++            net.minecraft.server.MinecraftServer.getServer().getPlayerList().reloadRecipes();
++        }
++    }
++
+     public void finalizeRecipeLoading(FeatureFlagSet enabledFeatures) {
++        this.featureflagset = enabledFeatures;
++        // CraftBukkit end
+         List<SelectableRecipe.SingleInputEntry<StonecutterRecipe>> list = new ArrayList<>();
+         List<RecipeManager.IngredientCollector> list1 = RECIPE_PROPERTY_SETS.entrySet()
+             .stream()
+@@ -147,7 +_,10 @@
+     }
+ 
+     public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> recipeType, I input, Level level) {
+-        return this.recipes.getRecipesFor(recipeType, input, level).findFirst();
++        // CraftBukkit start
++        List<RecipeHolder<T>> list = this.recipes.getRecipesFor(recipeType, input, level).toList();
++        return (list.isEmpty()) ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority
++        // CraftBukkit end
+     }
+ 
+     public Optional<RecipeHolder<?>> byKey(ResourceKey<Recipe<?>> key) {
+@@ -199,6 +_,22 @@
+         Recipe<?> recipe1 = Recipe.CODEC.parse(registries.createSerializationContext(JsonOps.INSTANCE), json).getOrThrow(JsonParseException::new);
+         return new RecipeHolder<>(recipe, recipe1);
+     }
++
++    // CraftBukkit start
++    public boolean removeRecipe(ResourceKey<Recipe<?>> mcKey) {
++        boolean removed = this.recipes.removeRecipe((ResourceKey<Recipe<RecipeInput>>) (ResourceKey) mcKey); // Paper - generic fix
++        if (removed) {
++            this.finalizeRecipeLoading();
++        }
++
++        return removed;
++    }
++
++    public void clearRecipes() {
++        this.recipes = RecipeMap.create(java.util.Collections.emptyList());
++        this.finalizeRecipeLoading();
++    }
++    // CraftBukkit end
+ 
+     public static <I extends RecipeInput, T extends Recipe<I>> RecipeManager.CachedCheck<I, T> createCheck(final RecipeType<T> recipeType) {
+         return new RecipeManager.CachedCheck<I, T>() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeMap.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
similarity index 69%
rename from paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeMap.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
index 171f3d8e88..ae4eba51bc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
@@ -1,25 +1,15 @@
 --- a/net/minecraft/world/item/crafting/RecipeMap.java
 +++ b/net/minecraft/world/item/crafting/RecipeMap.java
-@@ -11,6 +11,10 @@
- import javax.annotation.Nullable;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import com.google.common.collect.LinkedHashMultimap;
-+import com.google.common.collect.Maps;
-+// CraftBukkit end
- 
- public class RecipeMap {
- 
-@@ -35,11 +39,56 @@
-             com_google_common_collect_immutablemap_builder.put(recipeholder.id(), recipeholder);
+@@ -30,8 +_,53 @@
+             builder1.put(recipeHolder.id(), recipeHolder);
          }
  
--        return new RecipeMap(builder.build(), com_google_common_collect_immutablemap_builder.build());
+-        return new RecipeMap(builder.build(), builder1.build());
+-    }
 +        // CraftBukkit start - mutable
-+        return new RecipeMap(LinkedHashMultimap.create(builder.build()), Maps.newHashMap(com_google_common_collect_immutablemap_builder.build()));
-     }
- 
++        return new RecipeMap(com.google.common.collect.LinkedHashMultimap.create(builder.build()), com.google.common.collect.Maps.newHashMap(builder1.build()));
++    }
++
 +    public void addRecipe(RecipeHolder<?> irecipe) {
 +        Collection<RecipeHolder<?>> map = this.byType.get(irecipe.value().getType());
 +
@@ -63,10 +53,6 @@
 +        // Paper end - why are you using a loop???
 +    }
 +    // Paper end - replace removeRecipe implementation
-+
-     public <I extends RecipeInput, T extends Recipe<I>> Collection<RecipeHolder<T>> byType(RecipeType<T> type) {
--        return this.byType.get(type);
-+        return (Collection) this.byType.get(type); // CraftBukkit - decompile error
-     }
  
-     public Collection<RecipeHolder<?>> values() {
+     public <I extends RecipeInput, T extends Recipe<I>> Collection<RecipeHolder<T>> byType(RecipeType<T> type) {
+         return (Collection)this.byType.get(type);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapedRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
index 05fee5cb09..18e1fb0718 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
@@ -1,30 +1,16 @@
 --- a/net/minecraft/world/item/crafting/ShapedRecipe.java
 +++ b/net/minecraft/world/item/crafting/ShapedRecipe.java
-@@ -16,6 +16,13 @@
- import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay;
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
-+import org.bukkit.inventory.RecipeChoice;
-+// CraftBukkit end
- 
- public class ShapedRecipe implements CraftingRecipe {
- 
-@@ -39,7 +46,69 @@
-         this(group, category, raw, result, true);
+@@ -103,6 +_,68 @@
+         );
      }
  
 +    // CraftBukkit start
-     @Override
-+    public org.bukkit.inventory.ShapedRecipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
-+        CraftShapedRecipe recipe = new CraftShapedRecipe(id, result, this);
++    @Override
++    public org.bukkit.inventory.ShapedRecipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result);
++        org.bukkit.craftbukkit.inventory.CraftShapedRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftShapedRecipe(id, result, this);
 +        recipe.setGroup(this.group);
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
 +
 +        switch (this.pattern.height()) {
 +        case 1:
@@ -69,8 +55,8 @@
 +        }
 +        char c = 'a';
 +        for (Optional<Ingredient> list : this.pattern.ingredients()) {
-+            RecipeChoice choice = CraftRecipe.toBukkit(list);
-+            if (choice != RecipeChoice.empty()) { // Paper
++            org.bukkit.inventory.RecipeChoice choice = org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(list);
++            if (choice != org.bukkit.inventory.RecipeChoice.empty()) { // Paper
 +                recipe.setIngredient(c, choice);
 +            }
 +
@@ -80,7 +66,6 @@
 +    }
 +    // CraftBukkit end
 +
-+    @Override
-     public RecipeSerializer<? extends ShapedRecipe> getSerializer() {
-         return RecipeSerializer.SHAPED_RECIPE;
-     }
+     public static class Serializer implements RecipeSerializer<ShapedRecipe> {
+         public static final MapCodec<ShapedRecipe> CODEC = RecordCodecBuilder.mapCodec(
+             instance -> instance.group(
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
new file mode 100644
index 0000000000..e13cbaac8e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/item/crafting/ShapelessRecipe.java
++++ b/net/minecraft/world/item/crafting/ShapelessRecipe.java
+@@ -31,6 +_,21 @@
+         this.ingredients = ingredients;
+     }
+ 
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.ShapelessRecipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result);
++        org.bukkit.craftbukkit.inventory.CraftShapelessRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftShapelessRecipe(id, result, this);
++        recipe.setGroup(this.group);
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        for (Ingredient list : this.ingredients) {
++            recipe.addIngredient(org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(list));
++        }
++        return recipe;
++    }
++    // CraftBukkit end
++
+     @Override
+     public RecipeSerializer<ShapelessRecipe> getSerializer() {
+         return RecipeSerializer.SHAPELESS_RECIPE;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
new file mode 100644
index 0000000000..c174cfdcf8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/crafting/SmeltingRecipe.java
++++ b/net/minecraft/world/item/crafting/SmeltingRecipe.java
+@@ -32,4 +_,17 @@
+             case MISC -> RecipeBookCategories.FURNACE_MISC;
+         };
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result());
++
++        org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
++        recipe.setGroup(this.group());
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        return recipe;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
new file mode 100644
index 0000000000..5e4907953d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+@@ -21,8 +_,15 @@
+     final ItemStack result;
+     @Nullable
+     private PlacementInfo placementInfo;
++    final boolean copyDataComponents; // Paper - Option to prevent data components copy
+ 
+     public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result) {
++        // Paper start - Option to prevent data components copy
++        this(template, base, addition, result, true);
++    }
++    public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result, boolean copyDataComponents) {
++        this.copyDataComponents = copyDataComponents;
++        // Paper end - Option to prevent data components copy
+         this.template = template;
+         this.base = base;
+         this.addition = addition;
+@@ -32,7 +_,9 @@
+     @Override
+     public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
+         ItemStack itemStack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount());
++        if (this.copyDataComponents) { // Paper - Option to prevent data components copy
+         itemStack.applyComponents(this.result.getComponentsPatch());
++        } // Paper - Option to prevent data components copy
+         return itemStack;
+     }
+ 
+@@ -77,6 +_,17 @@
+             )
+         );
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result);
++
++        org.bukkit.craftbukkit.inventory.CraftSmithingTransformRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftSmithingTransformRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.template), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.base), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
++
++        return recipe;
++    }
++    // CraftBukkit end
+ 
+     public static class Serializer implements RecipeSerializer<SmithingTransformRecipe> {
+         private static final MapCodec<SmithingTransformRecipe> CODEC = RecordCodecBuilder.mapCodec(
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
new file mode 100644
index 0000000000..09cb0b71fe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+@@ -27,8 +_,15 @@
+     final Optional<Ingredient> addition;
+     @Nullable
+     private PlacementInfo placementInfo;
++    final boolean copyDataComponents; // Paper - Option to prevent data components copy
+ 
+     public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition) {
++        // Paper start - Option to prevent data components copy
++        this(template, base, addition, true);
++    }
++    public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, boolean copyDataComponents) {
++        this.copyDataComponents = copyDataComponents;
++        // Paper end - Option to prevent data components copy
+         this.template = template;
+         this.base = base;
+         this.addition = addition;
+@@ -36,10 +_,15 @@
+ 
+     @Override
+     public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
+-        return applyTrim(registries, input.base(), input.addition(), input.template());
++        return applyTrim(registries, input.base(), input.addition(), input.template(), this.copyDataComponents); // Paper - Option to prevent data components copy
+     }
+ 
+     public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template) {
++        // Paper start - Option to prevent data components copy
++        return applyTrim(registries, base, addition, template, true);
++    }
++    public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template, boolean copyDataComponents) {
++        // Paper end - Option to prevent data components copy
+         Optional<Holder.Reference<TrimMaterial>> fromIngredient = TrimMaterials.getFromIngredient(registries, addition);
+         Optional<Holder.Reference<TrimPattern>> fromTemplate = TrimPatterns.getFromTemplate(registries, template);
+         if (fromIngredient.isPresent() && fromTemplate.isPresent()) {
+@@ -47,7 +_,7 @@
+             if (armorTrim != null && armorTrim.hasPatternAndMaterial(fromTemplate.get(), fromIngredient.get())) {
+                 return ItemStack.EMPTY;
+             } else {
+-                ItemStack itemStack = base.copyWithCount(1);
++                ItemStack itemStack = copyDataComponents ? base.copyWithCount(1) : new ItemStack(base.getItem(), 1); // Paper - Option to prevent data components copy
+                 itemStack.set(DataComponents.TRIM, new ArmorTrim(fromIngredient.get(), fromTemplate.get()));
+                 return itemStack;
+             }
+@@ -100,6 +_,13 @@
+             )
+         );
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        return new org.bukkit.craftbukkit.inventory.CraftSmithingTrimRecipe(id, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.template), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.base), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
++    }
++    // CraftBukkit end
+ 
+     public static class Serializer implements RecipeSerializer<SmithingTrimRecipe> {
+         private static final MapCodec<SmithingTrimRecipe> CODEC = RecordCodecBuilder.mapCodec(
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
new file mode 100644
index 0000000000..e952bdf1b6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/crafting/SmokingRecipe.java
++++ b/net/minecraft/world/item/crafting/SmokingRecipe.java
+@@ -28,4 +_,17 @@
+     public RecipeBookCategory recipeBookCategory() {
+         return RecipeBookCategories.SMOKER_FOOD;
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result());
++
++        org.bukkit.craftbukkit.inventory.CraftSmokingRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftSmokingRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
++        recipe.setGroup(this.group());
++        recipe.setCategory(org.bukkit.craftbukkit.inventory.CraftRecipe.getCategory(this.category()));
++
++        return recipe;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
new file mode 100644
index 0000000000..daa51ba292
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/item/crafting/StonecutterRecipe.java
++++ b/net/minecraft/world/item/crafting/StonecutterRecipe.java
+@@ -35,4 +_,16 @@
+     public RecipeBookCategory recipeBookCategory() {
+         return RecipeBookCategories.STONECUTTER;
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        org.bukkit.craftbukkit.inventory.CraftItemStack result = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.result());
++
++        org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe recipe = new org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe(id, result, org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input()));
++        recipe.setGroup(this.group());
++
++        return recipe;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch
new file mode 100644
index 0000000000..bdfba17d6a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/item/crafting/TransmuteRecipe.java
++++ b/net/minecraft/world/item/crafting/TransmuteRecipe.java
+@@ -88,6 +_,13 @@
+         );
+     }
+ 
++    // CraftBukkit start
++    @Override
++    public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {
++        return new org.bukkit.craftbukkit.inventory.CraftTransmuteRecipe(id, org.bukkit.craftbukkit.inventory.CraftItemType.minecraftToBukkit(this.result.value()), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.input), org.bukkit.craftbukkit.inventory.CraftRecipe.toBukkit(this.material));
++    }
++    // CraftBukkit end
++
+     @Override
+     public RecipeSerializer<TransmuteRecipe> getSerializer() {
+         return RecipeSerializer.TRANSMUTE;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/BlastingRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
deleted file mode 100644
index 26a37bbc25..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/item/crafting/BlastingRecipe.java
-+++ b/net/minecraft/world/item/crafting/BlastingRecipe.java
-@@ -4,6 +4,14 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftBlastingRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class BlastingRecipe extends AbstractCookingRecipe {
- 
-     public BlastingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) {
-@@ -43,4 +51,17 @@
- 
-         return recipebookcategory;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result());
-+
-+        CraftBlastingRecipe recipe = new CraftBlastingRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
-+        recipe.setGroup(this.group());
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
deleted file mode 100644
index 039e214990..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
-+++ b/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
-@@ -4,6 +4,14 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftCampfireRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class CampfireCookingRecipe extends AbstractCookingRecipe {
- 
-     public CampfireCookingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) {
-@@ -29,4 +37,17 @@
-     public RecipeBookCategory recipeBookCategory() {
-         return RecipeBookCategories.CAMPFIRE;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result());
-+
-+        CraftCampfireRecipe recipe = new CraftCampfireRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
-+        recipe.setGroup(this.group());
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CustomRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CustomRecipe.java.patch
deleted file mode 100644
index 773c84fb8b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/CustomRecipe.java.patch
+++ /dev/null
@@ -1,54 +0,0 @@
---- a/net/minecraft/world/item/crafting/CustomRecipe.java
-+++ b/net/minecraft/world/item/crafting/CustomRecipe.java
-@@ -8,6 +8,15 @@
- import net.minecraft.network.RegistryFriendlyByteBuf;
- import net.minecraft.network.codec.StreamCodec;
- 
-+// CraftBukkit start
-+import net.minecraft.world.item.ItemStack;
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftComplexRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public abstract class CustomRecipe implements CraftingRecipe {
- 
-     private final CraftingBookCategory category;
-@@ -34,6 +43,19 @@
-     @Override
-     public abstract RecipeSerializer<? extends CustomRecipe> getSerializer();
- 
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(ItemStack.EMPTY);
-+
-+        CraftComplexRecipe recipe = new CraftComplexRecipe(id, result, this);
-+        recipe.setGroup(this.group());
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
-+
-     public static class Serializer<T extends CraftingRecipe> implements RecipeSerializer<T> {
- 
-         private final MapCodec<T> codec;
-@@ -41,13 +63,13 @@
- 
-         public Serializer(CustomRecipe.Serializer.Factory<T> factory) {
-             this.codec = RecordCodecBuilder.mapCodec((instance) -> {
--                P1 p1 = instance.group(CraftingBookCategory.CODEC.fieldOf("category").orElse(CraftingBookCategory.MISC).forGetter(CraftingRecipe::category));
-+                P1<RecordCodecBuilder.Mu<T>, CraftingBookCategory> p1 = instance.group(CraftingBookCategory.CODEC.fieldOf("category").orElse(CraftingBookCategory.MISC).forGetter(CraftingRecipe::category)); // CraftBukkit - decompile error
- 
-                 Objects.requireNonNull(factory);
-                 return p1.apply(instance, factory::create);
-             });
-             StreamCodec streamcodec = CraftingBookCategory.STREAM_CODEC;
--            Function function = CraftingRecipe::category;
-+            Function<CraftingRecipe, CraftingBookCategory> function = CraftingRecipe::category; // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(factory);
-             this.streamCodec = StreamCodec.composite(streamcodec, function, factory::create);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/Ingredient.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/Ingredient.java.patch
deleted file mode 100644
index 1d00386837..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/Ingredient.java.patch
+++ /dev/null
@@ -1,66 +0,0 @@
---- a/net/minecraft/world/item/crafting/Ingredient.java
-+++ b/net/minecraft/world/item/crafting/Ingredient.java
-@@ -20,6 +20,10 @@
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.level.ItemLike;
-+// CraftBukkit start
-+import java.util.List;
-+import javax.annotation.Nullable;
-+// CraftBukkit end
- 
- public final class Ingredient implements StackedContents.IngredientInfo<Holder<Item>>, Predicate<ItemStack> {
- 
-@@ -38,7 +42,25 @@
-         return recipeitemstack.values;
-     });
-     private final HolderSet<Item> values;
-+    // CraftBukkit start
-+    @Nullable
-+    private List<ItemStack> itemStacks;
- 
-+    public boolean isExact() {
-+        return this.itemStacks != null;
-+    }
-+
-+    public List<ItemStack> itemStacks() {
-+        return this.itemStacks;
-+    }
-+
-+    public static Ingredient ofStacks(List<ItemStack> stacks) {
-+        Ingredient recipe = Ingredient.of(stacks.stream().map(ItemStack::getItem));
-+        recipe.itemStacks = stacks;
-+        return recipe;
-+    }
-+    // CraftBukkit end
-+
-     private Ingredient(HolderSet<Item> entries) {
-         entries.unwrap().ifRight((list) -> {
-             if (list.isEmpty()) {
-@@ -70,6 +92,17 @@
-     }
- 
-     public boolean test(ItemStack itemstack) {
-+        // CraftBukkit start
-+        if (this.isExact()) {
-+            for (ItemStack itemstack1 : this.itemStacks()) {
-+                if (itemstack1.getItem() == itemstack.getItem() && ItemStack.isSameItemSameComponents(itemstack, itemstack1)) {
-+                    return true;
-+                }
-+            }
-+
-+            return false;
-+        }
-+        // CraftBukkit end
-         return itemstack.is(this.values);
-     }
- 
-@@ -79,7 +112,7 @@
- 
-     public boolean equals(Object object) {
-         if (object instanceof Ingredient recipeitemstack) {
--            return Objects.equals(this.values, recipeitemstack.values);
-+            return Objects.equals(this.values, recipeitemstack.values) && Objects.equals(this.itemStacks, recipeitemstack.itemStacks); // CraftBukkit
-         } else {
-             return false;
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeHolder.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeHolder.java.patch
deleted file mode 100644
index 818a4d266e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeHolder.java.patch
+++ /dev/null
@@ -1,26 +0,0 @@
---- a/net/minecraft/world/item/crafting/RecipeHolder.java
-+++ b/net/minecraft/world/item/crafting/RecipeHolder.java
-@@ -5,10 +5,21 @@
- import net.minecraft.network.codec.StreamCodec;
- import net.minecraft.resources.ResourceKey;
- 
--public record RecipeHolder<T extends Recipe<?>>(ResourceKey<Recipe<?>> id, T value) {
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
- 
--    public static final StreamCodec<RegistryFriendlyByteBuf, RecipeHolder<?>> STREAM_CODEC = StreamCodec.composite(ResourceKey.streamCodec(Registries.RECIPE), RecipeHolder::id, Recipe.STREAM_CODEC, RecipeHolder::value, RecipeHolder::new);
-+public record RecipeHolder<T extends net.minecraft.world.item.crafting.Recipe<?>>(ResourceKey<net.minecraft.world.item.crafting.Recipe<?>> id, T value) {
- 
-+    // CraftBukkit start
-+    public final Recipe toBukkitRecipe() {
-+        return this.value.toBukkitRecipe(CraftNamespacedKey.fromMinecraft(this.id.location()));
-+    }
-+    // CraftBukkit end
-+
-+    public static final StreamCodec<RegistryFriendlyByteBuf, RecipeHolder<?>> STREAM_CODEC = StreamCodec.composite(ResourceKey.streamCodec(Registries.RECIPE), RecipeHolder::id, net.minecraft.world.item.crafting.Recipe.STREAM_CODEC, RecipeHolder::value, RecipeHolder::new);
-+
-     public boolean equals(Object object) {
-         if (this == object) {
-             return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeManager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeManager.java.patch
deleted file mode 100644
index 83a2caa10c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/RecipeManager.java.patch
+++ /dev/null
@@ -1,111 +0,0 @@
---- a/net/minecraft/world/item/crafting/RecipeManager.java
-+++ b/net/minecraft/world/item/crafting/RecipeManager.java
-@@ -26,11 +26,6 @@
- import net.minecraft.resources.FileToIdConverter;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.resources.ResourceLocation;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.server.packs.resources.ResourceManager;
--import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
--import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
--import net.minecraft.util.profiling.ProfilerFiller;
- import net.minecraft.world.flag.FeatureFlagSet;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.crafting.display.RecipeDisplay;
-@@ -39,6 +34,16 @@
- import net.minecraft.world.level.Level;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.Collections;
-+import net.minecraft.server.MinecraftServer;
-+// CraftBukkit end
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.packs.resources.ResourceManager;
-+import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
-+import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
-+import net.minecraft.util.profiling.ProfilerFiller;
-+
- public class RecipeManager extends SimplePreparableReloadListener<RecipeMap> implements RecipeAccess {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -111,7 +116,26 @@
-         RecipeManager.LOGGER.info("Loaded {} recipes", prepared.values().size());
-     }
- 
-+    // CraftBukkit start
-+    public void addRecipe(RecipeHolder<?> irecipe) {
-+        org.spigotmc.AsyncCatcher.catchOp("Recipe Add"); // Spigot
-+        this.recipes.addRecipe(irecipe);
-+        this.finalizeRecipeLoading();
-+    }
-+
-+    private FeatureFlagSet featureflagset;
-+
-+    public void finalizeRecipeLoading() {
-+        if (this.featureflagset != null) {
-+            this.finalizeRecipeLoading(this.featureflagset);
-+
-+            MinecraftServer.getServer().getPlayerList().reloadRecipes();
-+        }
-+    }
-+
-     public void finalizeRecipeLoading(FeatureFlagSet features) {
-+        this.featureflagset = features;
-+        // CraftBukkit end
-         List<SelectableRecipe.SingleInputEntry<StonecutterRecipe>> list = new ArrayList();
-         List<RecipeManager.IngredientCollector> list1 = RecipeManager.RECIPE_PROPERTY_SETS.entrySet().stream().map((entry) -> {
-             return new RecipeManager.IngredientCollector((ResourceKey) entry.getKey(), (RecipeManager.IngredientExtractor) entry.getValue());
-@@ -130,7 +154,7 @@
-                     StonecutterRecipe recipestonecutting = (StonecutterRecipe) irecipe;
- 
-                     if (RecipeManager.isIngredientEnabled(features, recipestonecutting.input()) && recipestonecutting.resultDisplay().isEnabled(features)) {
--                        list.add(new SelectableRecipe.SingleInputEntry<>(recipestonecutting.input(), new SelectableRecipe<>(recipestonecutting.resultDisplay(), Optional.of(recipeholder))));
-+                        list.add(new SelectableRecipe.SingleInputEntry<StonecutterRecipe>(recipestonecutting.input(), new SelectableRecipe<>(recipestonecutting.resultDisplay(), Optional.of((RecipeHolder<StonecutterRecipe>) recipeholder)))); // CraftBukkit - decompile error
-                     }
-                 }
- 
-@@ -172,7 +196,10 @@
-     }
- 
-     public <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> type, I input, Level world) {
--        return this.recipes.getRecipesFor(type, input, world).findFirst();
-+        // CraftBukkit start
-+        List<RecipeHolder<T>> list = this.recipes.getRecipesFor(type, input, world).toList();
-+        return (list.isEmpty()) ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority
-+        // CraftBukkit end
-     }
- 
-     public Optional<RecipeHolder<?>> byKey(ResourceKey<Recipe<?>> key) {
-@@ -183,7 +210,7 @@
-     private <T extends Recipe<?>> RecipeHolder<T> byKeyTyped(RecipeType<T> type, ResourceKey<Recipe<?>> key) {
-         RecipeHolder<?> recipeholder = this.recipes.byKey(key);
- 
--        return recipeholder != null && recipeholder.value().getType().equals(type) ? recipeholder : null;
-+        return recipeholder != null && recipeholder.value().getType().equals(type) ? (RecipeHolder) recipeholder : null; // CraftBukkit - decompile error
-     }
- 
-     public Map<ResourceKey<RecipePropertySet>, RecipePropertySet> getSynchronizedItemProperties() {
-@@ -231,6 +258,22 @@
-         return new RecipeHolder<>(key, irecipe);
-     }
- 
-+    // CraftBukkit start
-+    public boolean removeRecipe(ResourceKey<Recipe<?>> mcKey) {
-+        boolean removed = this.recipes.removeRecipe((ResourceKey<Recipe<RecipeInput>>) (ResourceKey) mcKey); // Paper - generic fix
-+        if (removed) {
-+            this.finalizeRecipeLoading();
-+        }
-+
-+        return removed;
-+    }
-+
-+    public void clearRecipes() {
-+        this.recipes = RecipeMap.create(Collections.emptyList());
-+        this.finalizeRecipeLoading();
-+    }
-+    // CraftBukkit end
-+
-     public static <I extends RecipeInput, T extends Recipe<I>> RecipeManager.CachedCheck<I, T> createCheck(final RecipeType<T> type) {
-         return new RecipeManager.CachedCheck<I, T>() {
-             @Nullable
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
deleted file mode 100644
index 167de63903..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/item/crafting/ShapelessRecipe.java
-+++ b/net/minecraft/world/item/crafting/ShapelessRecipe.java
-@@ -16,6 +16,12 @@
- import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay;
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
-+// CraftBukkit end
- 
- public class ShapelessRecipe implements CraftingRecipe {
- 
-@@ -33,7 +39,23 @@
-         this.ingredients = ingredients;
-     }
- 
-+    // CraftBukkit start
-+    @SuppressWarnings("unchecked")
-     @Override
-+    public org.bukkit.inventory.ShapelessRecipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
-+        CraftShapelessRecipe recipe = new CraftShapelessRecipe(id, result, this);
-+        recipe.setGroup(this.group);
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        for (Ingredient list : this.ingredients) {
-+            recipe.addIngredient(CraftRecipe.toBukkit(list));
-+        }
-+        return recipe;
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public RecipeSerializer<ShapelessRecipe> getSerializer() {
-         return RecipeSerializer.SHAPELESS_RECIPE;
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
deleted file mode 100644
index 4b26fa911a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/item/crafting/SmeltingRecipe.java
-+++ b/net/minecraft/world/item/crafting/SmeltingRecipe.java
-@@ -4,6 +4,14 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class SmeltingRecipe extends AbstractCookingRecipe {
- 
-     public SmeltingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) {
-@@ -45,4 +53,17 @@
- 
-         return recipebookcategory;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result());
-+
-+        CraftFurnaceRecipe recipe = new CraftFurnaceRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
-+        recipe.setGroup(this.group());
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
deleted file mode 100644
index 173f836106..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
+++ /dev/null
@@ -1,61 +0,0 @@
---- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
-+++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
-@@ -14,6 +14,14 @@
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.item.crafting.display.SmithingRecipeDisplay;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftSmithingTransformRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class SmithingTransformRecipe implements SmithingRecipe {
- 
-     final Optional<Ingredient> template;
-@@ -22,8 +30,15 @@
-     final ItemStack result;
-     @Nullable
-     private PlacementInfo placementInfo;
-+    final boolean copyDataComponents; // Paper - Option to prevent data components copy
- 
-     public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result) {
-+        // Paper start - Option to prevent data components copy
-+        this(template, base, addition, result, true);
-+    }
-+    public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result, boolean copyDataComponents) {
-+        this.copyDataComponents = copyDataComponents;
-+        // Paper end - Option to prevent data components copy
-         this.template = template;
-         this.base = base;
-         this.addition = addition;
-@@ -33,7 +48,9 @@
-     public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
-         ItemStack itemstack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount());
- 
-+        if (this.copyDataComponents) { // Paper - Option to prevent data components copy
-         itemstack.applyComponents(this.result.getComponentsPatch());
-+        } // Paper - Option to prevent data components copy
-         return itemstack;
-     }
- 
-@@ -71,6 +88,17 @@
-         return List.of(new SmithingRecipeDisplay(Ingredient.optionalIngredientToDisplay(this.template), Ingredient.optionalIngredientToDisplay(this.base), Ingredient.optionalIngredientToDisplay(this.addition), new SlotDisplay.ItemStackSlotDisplay(this.result), new SlotDisplay.ItemSlotDisplay(Items.SMITHING_TABLE)));
-     }
- 
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
-+
-+        CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
-+
-     public static class Serializer implements RecipeSerializer<SmithingTransformRecipe> {
- 
-         private static final MapCodec<SmithingTransformRecipe> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
deleted file mode 100644
index a5e660382a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
+++ /dev/null
@@ -1,69 +0,0 @@
---- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
-+++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
-@@ -21,6 +21,13 @@
- import net.minecraft.world.item.equipment.trim.TrimPattern;
- import net.minecraft.world.item.equipment.trim.TrimPatterns;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftSmithingTrimRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class SmithingTrimRecipe implements SmithingRecipe {
- 
-     final Optional<Ingredient> template;
-@@ -28,18 +35,28 @@
-     final Optional<Ingredient> addition;
-     @Nullable
-     private PlacementInfo placementInfo;
-+    final boolean copyDataComponents; // Paper - Option to prevent data components copy
- 
-     public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition) {
-+        // Paper start - Option to prevent data components copy
-+        this(template, base, addition, true);
-+    }
-+    public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, boolean copyDataComponents) {
-+        this.copyDataComponents = copyDataComponents;
-+        // Paper end - Option to prevent data components copy
-         this.template = template;
-         this.base = base;
-         this.addition = addition;
-     }
- 
-     public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
--        return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template());
-+        return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template(), this.copyDataComponents);
-     }
- 
-     public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template) {
-+        return applyTrim(registries, base, addition, template, true);
-+    }
-+    public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template, boolean copyDataComponents) {
-         Optional<Holder.Reference<TrimMaterial>> optional = TrimMaterials.getFromIngredient(registries, addition);
-         Optional<Holder.Reference<TrimPattern>> optional1 = TrimPatterns.getFromTemplate(registries, template);
- 
-@@ -49,7 +66,7 @@
-             if (armortrim != null && armortrim.hasPatternAndMaterial((Holder) optional1.get(), (Holder) optional.get())) {
-                 return ItemStack.EMPTY;
-             } else {
--                ItemStack itemstack3 = base.copyWithCount(1);
-+                ItemStack itemstack3 = copyDataComponents ? base.copyWithCount(1) : new ItemStack(base.getItem(), 1); // Paper - Option to prevent data components copy
- 
-                 itemstack3.set(DataComponents.TRIM, new ArmorTrim((Holder) optional.get(), (Holder) optional1.get()));
-                 return itemstack3;
-@@ -97,6 +114,13 @@
-         return List.of(new SmithingRecipeDisplay(slotdisplay2, slotdisplay, slotdisplay1, new SlotDisplay.SmithingTrimDemoSlotDisplay(slotdisplay, slotdisplay1, slotdisplay2), new SlotDisplay.ItemSlotDisplay(Items.SMITHING_TABLE)));
-     }
- 
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
-+    }
-+    // CraftBukkit end
-+
-     public static class Serializer implements RecipeSerializer<SmithingTrimRecipe> {
- 
-         private static final MapCodec<SmithingTrimRecipe> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmokingRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
deleted file mode 100644
index 7c099a0fca..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/item/crafting/SmokingRecipe.java
-+++ b/net/minecraft/world/item/crafting/SmokingRecipe.java
-@@ -4,6 +4,14 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftSmokingRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class SmokingRecipe extends AbstractCookingRecipe {
- 
-     public SmokingRecipe(String group, CookingBookCategory category, Ingredient ingredient, ItemStack result, float experience, int cookingTime) {
-@@ -29,4 +37,17 @@
-     public RecipeBookCategory recipeBookCategory() {
-         return RecipeBookCategories.SMOKER_FOOD;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result());
-+
-+        CraftSmokingRecipe recipe = new CraftSmokingRecipe(id, result, CraftRecipe.toBukkit(this.input()), this.experience(), this.cookingTime());
-+        recipe.setGroup(this.group());
-+        recipe.setCategory(CraftRecipe.getCategory(this.category()));
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
deleted file mode 100644
index fd7dc9145c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/world/item/crafting/StonecutterRecipe.java
-+++ b/net/minecraft/world/item/crafting/StonecutterRecipe.java
-@@ -7,6 +7,14 @@
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.item.crafting.display.StonecutterRecipeDisplay;
- 
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
-+
- public class StonecutterRecipe extends SingleItemRecipe {
- 
-     public StonecutterRecipe(String group, Ingredient ingredient, ItemStack result) {
-@@ -36,4 +44,16 @@
-     public RecipeBookCategory recipeBookCategory() {
-         return RecipeBookCategories.STONECUTTER;
-     }
-+
-+    // CraftBukkit start
-+    @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        CraftItemStack result = CraftItemStack.asCraftMirror(this.result());
-+
-+        CraftStonecuttingRecipe recipe = new CraftStonecuttingRecipe(id, result, CraftRecipe.toBukkit(this.input()));
-+        recipe.setGroup(this.group());
-+
-+        return recipe;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch
deleted file mode 100644
index 7cc6f6f6d9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/crafting/TransmuteRecipe.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/item/crafting/TransmuteRecipe.java
-+++ b/net/minecraft/world/item/crafting/TransmuteRecipe.java
-@@ -19,6 +19,13 @@
- import net.minecraft.world.item.crafting.display.SlotDisplay;
- import net.minecraft.world.level.ItemLike;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.craftbukkit.inventory.CraftItemType;
-+import org.bukkit.craftbukkit.inventory.CraftRecipe;
-+import org.bukkit.craftbukkit.inventory.CraftTransmuteRecipe;
-+import org.bukkit.inventory.Recipe;
-+// CraftBukkit end
- 
- public class TransmuteRecipe implements CraftingRecipe {
- 
-@@ -84,7 +91,14 @@
-         return List.of(new ShapelessCraftingRecipeDisplay(List.of(this.input.display(), this.material.display()), new SlotDisplay.ItemSlotDisplay(this.result), new SlotDisplay.ItemSlotDisplay(Items.CRAFTING_TABLE)));
-     }
- 
-+    // CraftBukkit start
-     @Override
-+    public Recipe toBukkitRecipe(NamespacedKey id) {
-+        return new CraftTransmuteRecipe(id, CraftItemType.minecraftToBukkit(this.result.value()), CraftRecipe.toBukkit(this.input), CraftRecipe.toBukkit(this.material));
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public RecipeSerializer<TransmuteRecipe> getSerializer() {
-         return RecipeSerializer.TRANSMUTE;
-     }

From ff9cf280c2989c55c4c8383c1d8773886c2d7f11 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 19:16:30 +0100
Subject: [PATCH 083/285] 
 net.minecraft.world.entity.animal.{armadillo|camel|sniffer}

---
 .../animal/armadillo/Armadillo.java.patch     | 46 +++++++++++
 .../entity/animal/camel/Camel.java.patch      | 46 +++--------
 .../entity/animal/sniffer/Sniffer.java.patch  | 36 +++++++++
 .../animal/armadillo/Armadillo.java.patch     | 81 -------------------
 .../entity/animal/sniffer/Sniffer.java.patch  | 56 -------------
 5 files changed, 94 insertions(+), 171 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/animal/camel/Camel.java.patch (62%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
new file mode 100644
index 0000000000..95199daa53
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java
++++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
+@@ -141,10 +_,12 @@
+         ArmadilloAi.updateActivity(this);
+         profilerFiller.pop();
+         if (this.isAlive() && !this.isBaby() && --this.scuteTime <= 0) {
++            this.forceDrops = true; // CraftBukkit
+             if (this.dropFromGiftLootTable(level, BuiltInLootTables.ARMADILLO_SHED, this::spawnAtLocation)) {
+                 this.playSound(SoundEvents.ARMADILLO_SCUTE_DROP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+                 this.gameEvent(GameEvent.ENTITY_PLACE);
+             }
++            this.forceDrops = false; // CraftBukkit
+ 
+             this.scuteTime = this.pickNextScuteDropTime();
+         }
+@@ -283,7 +_,11 @@
+     }
+ 
+     @Override
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
++    // CraftBukkit start - void -> boolean
++    public boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) {
++        boolean damageResult = super.actuallyHurt(level, damageSource, amount, event);
++        if (!damageResult) return false;
++        // CraftBukkit end
+         super.actuallyHurt(level, damageSource, amount);
+         if (!this.isNoAi() && !this.isDeadOrDying()) {
+             if (damageSource.getEntity() instanceof LivingEntity) {
+@@ -295,6 +_,7 @@
+                 this.rollOut();
+             }
+         }
++        return true; // CraftBukkit
+     }
+ 
+     @Override
+@@ -313,7 +_,9 @@
+             return false;
+         } else {
+             if (this.level() instanceof ServerLevel serverLevel) {
++                this.forceDrops = true; // CraftBukkit
+                 this.spawnAtLocation(serverLevel, new ItemStack(Items.ARMADILLO_SCUTE));
++                this.forceDrops = false; // CraftBukkit
+                 this.gameEvent(GameEvent.ENTITY_INTERACT);
+                 this.playSound(SoundEvents.ARMADILLO_BRUSH);
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/camel/Camel.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/animal/camel/Camel.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
index e2b9543713..2ca2c4bbf8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/camel/Camel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
@@ -1,59 +1,37 @@
 --- a/net/minecraft/world/entity/animal/camel/Camel.java
 +++ b/net/minecraft/world/entity/animal/camel/Camel.java
-@@ -49,6 +49,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityDamageEvent;
-+// CraftBukkit end
- 
- public class Camel extends AbstractHorse {
- 
-@@ -143,7 +146,7 @@
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("camelBrain");
--        Brain<?> behaviorcontroller = this.getBrain();
-+        Brain<Camel> behaviorcontroller = (Brain<Camel>) this.getBrain(); // CraftBukkit - decompile error
- 
-         behaviorcontroller.tick(world, this);
-         gameprofilerfiller.pop();
-@@ -386,13 +389,13 @@
+@@ -386,12 +_,12 @@
+         } else {
              boolean flag = this.getHealth() < this.getMaxHealth();
- 
              if (flag) {
 -                this.heal(2.0F);
 +                this.heal(2.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason
              }
  
              boolean flag1 = this.isTamed() && this.getAge() == 0 && this.canFallInLove();
- 
              if (flag1) {
 -                this.setInLove(player);
 +                this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying
              }
  
-             boolean flag2 = this.isBaby();
-@@ -454,9 +457,15 @@
+             boolean isBaby = this.isBaby();
+@@ -451,9 +_,13 @@
      }
  
      @Override
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
 +    // CraftBukkit start - void -> boolean
-+    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) {
-+        boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event);
-+        if (!damageResult) {
-+            return false;
-+        }
++    public boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) {
++        boolean damageResult = super.actuallyHurt(level, damageSource, amount, event);
++        if (!damageResult) return false;
 +        // CraftBukkit end
          this.standUpInstantly();
--        super.actuallyHurt(world, source, amount);
+-        super.actuallyHurt(level, damageSource, amount);
 +        return true; // CraftBukkit
      }
  
      @Override
-@@ -563,7 +572,7 @@
+@@ -554,7 +_,7 @@
      }
  
      public void sitDown() {
@@ -62,7 +40,7 @@
              this.makeSound(SoundEvents.CAMEL_SIT);
              this.setPose(Pose.SITTING);
              this.gameEvent(GameEvent.ENTITY_ACTION);
-@@ -572,7 +581,7 @@
+@@ -563,7 +_,7 @@
      }
  
      public void standUp() {
@@ -71,7 +49,7 @@
              this.makeSound(SoundEvents.CAMEL_STAND);
              this.setPose(Pose.STANDING);
              this.gameEvent(GameEvent.ENTITY_ACTION);
-@@ -581,6 +590,7 @@
+@@ -572,6 +_,7 @@
      }
  
      public void standUpInstantly() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
new file mode 100644
index 0000000000..0e059e7e5c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+@@ -266,6 +_,13 @@
+             BlockPos headBlock = this.getHeadBlock();
+             this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.SNIFFER_DIGGING, (serverLevel1, itemStack) -> {
+                 ItemEntity itemEntity = new ItemEntity(this.level(), headBlock.getX(), headBlock.getY(), headBlock.getZ(), itemStack);
++                // CraftBukkit start - handle EntityDropItemEvent
++                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
++                org.bukkit.Bukkit.getPluginManager().callEvent(event);
++                if (event.isCancelled()) {
++                    return;
++                }
++                // CraftBukkit end
+                 itemEntity.setDefaultPickUpDelay();
+                 serverLevel1.addFreshEntity(itemEntity);
+             });
+@@ -325,12 +_,17 @@
+ 
+     @Override
+     public void spawnChildFromBreeding(ServerLevel level, Animal mate) {
++        // Paper start - Add EntityFertilizeEggEvent event
++        final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, mate);
++        if (result.isCancelled()) return;
++        // Paper end - Add EntityFertilizeEggEvent event
+         ItemStack itemStack = new ItemStack(Items.SNIFFER_EGG);
+         ItemEntity itemEntity = new ItemEntity(level, this.position().x(), this.position().y(), this.position().z(), itemStack);
+         itemEntity.setDefaultPickUpDelay();
+-        this.finalizeSpawnChildFromBreeding(level, mate, null);
++        this.finalizeSpawnChildFromBreeding(level, mate, null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event
++        if (this.spawnAtLocation(level, itemEntity) != null) { // Paper - Call EntityDropItemEvent
+         this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F);
+-        level.addFreshEntity(itemEntity);
++        } // Paper - Call EntityDropItemEvent
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
deleted file mode 100644
index 2e72adb5ea..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
+++ /dev/null
@@ -1,81 +0,0 @@
---- a/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-+++ b/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-@@ -47,6 +47,9 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityDamageEvent;
-+// CraftBukkit end
- 
- public class Armadillo extends Animal {
- 
-@@ -135,16 +138,18 @@
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("armadilloBrain");
--        this.brain.tick(world, this);
-+        ((Brain<Armadillo>) this.brain).tick(world, this); // CraftBukkit - decompile error
-         gameprofilerfiller.pop();
-         gameprofilerfiller.push("armadilloActivityUpdate");
-         ArmadilloAi.updateActivity(this);
-         gameprofilerfiller.pop();
-         if (this.isAlive() && !this.isBaby() && --this.scuteTime <= 0) {
-+            this.forceDrops = true; // CraftBukkit
-             if (this.dropFromGiftLootTable(world, BuiltInLootTables.ARMADILLO_SHED, this::spawnAtLocation)) {
-                 this.playSound(SoundEvents.ARMADILLO_SCUTE_DROP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
-                 this.gameEvent(GameEvent.ENTITY_PLACE);
-             }
-+            this.forceDrops = false; // CraftBukkit
- 
-             this.scuteTime = this.pickNextScuteDropTime();
-         }
-@@ -291,19 +296,25 @@
-     }
- 
-     @Override
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
--        super.actuallyHurt(world, source, amount);
-+    // CraftBukkit start - void -> boolean
-+    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) {
-+        boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event);
-+        if (!damageResult) {
-+            return false;
-+        }
-+        // CraftBukkit end
-         if (!this.isNoAi() && !this.isDeadOrDying()) {
--            if (source.getEntity() instanceof LivingEntity) {
-+            if (damagesource.getEntity() instanceof LivingEntity) {
-                 this.getBrain().setMemoryWithExpiry(MemoryModuleType.DANGER_DETECTED_RECENTLY, true, 80L);
-                 if (this.canStayRolledUp()) {
-                     this.rollUp();
-                 }
--            } else if (source.is(DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)) {
-+            } else if (damagesource.is(DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)) {
-                 this.rollOut();
-             }
- 
-         }
-+        return true; // CraftBukkit
-     }
- 
-     @Override
-@@ -327,7 +338,9 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
-+                this.forceDrops = true; // CraftBukkit
-                 this.spawnAtLocation(worldserver, new ItemStack(Items.ARMADILLO_SCUTE));
-+                this.forceDrops = false; // CraftBukkit
-                 this.gameEvent(GameEvent.ENTITY_INTERACT);
-                 this.playSound(SoundEvents.ARMADILLO_BRUSH);
-             }
-@@ -431,7 +444,7 @@
-         }
- 
-         public static Armadillo.ArmadilloState fromName(String name) {
--            return (Armadillo.ArmadilloState) Armadillo.ArmadilloState.CODEC.byName(name, (Enum) Armadillo.ArmadilloState.IDLE);
-+            return (Armadillo.ArmadilloState) Armadillo.ArmadilloState.CODEC.byName(name, Armadillo.ArmadilloState.IDLE); // CraftBukkit - decompile error
-         }
- 
-         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
deleted file mode 100644
index a6567df470..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
-+++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
-@@ -267,6 +267,13 @@
-                 this.dropFromGiftLootTable(worldserver, BuiltInLootTables.SNIFFER_DIGGING, (worldserver1, itemstack) -> {
-                     ItemEntity entityitem = new ItemEntity(this.level(), (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack);
- 
-+                    // CraftBukkit start - handle EntityDropItemEvent
-+                    org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
-+                    org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+                    if (event.isCancelled()) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     entityitem.setDefaultPickUpDelay();
-                     worldserver1.addFreshEntity(entityitem);
-                 });
-@@ -308,7 +315,7 @@
-         List<GlobalPos> list = (List) this.getExploredPositions().limit(20L).collect(Collectors.toList());
- 
-         list.add(0, GlobalPos.of(this.level().dimension(), pos));
--        this.getBrain().setMemory(MemoryModuleType.SNIFFER_EXPLORED_POSITIONS, (Object) list);
-+        this.getBrain().setMemory(MemoryModuleType.SNIFFER_EXPLORED_POSITIONS, list); // CraftBukkit - decompile error
-         return this;
-     }
- 
-@@ -333,13 +340,19 @@
- 
-     @Override
-     public void spawnChildFromBreeding(ServerLevel world, Animal other) {
-+        // Paper start - Add EntityFertilizeEggEvent event
-+        final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other);
-+        if (result.isCancelled()) return;
-+        // Paper end - Add EntityFertilizeEggEvent event
-+
-         ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG);
-         ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack);
- 
-         entityitem.setDefaultPickUpDelay();
--        this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null);
-+        this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event
-+        if (this.spawnAtLocation(world, entityitem) != null) { // Paper - Call EntityDropItemEvent
-         this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F);
--        world.addFreshEntity(entityitem);
-+        } // Paper - Call EntityDropItemEvent
-     }
- 
-     @Override
-@@ -444,7 +457,7 @@
- 
-     @Override
-     public Brain<Sniffer> getBrain() {
--        return super.getBrain();
-+        return (Brain<Sniffer>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override

From fa792cf719c1ebb5ce21d3315a1863aade43220a Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 13:21:16 -0500
Subject: [PATCH 084/285] /net/minecraft/core/cauldron/CauldronInteraction

---
 .../cauldron/CauldronInteraction.java.patch   | 302 ++++++++++++++++
 .../cauldron/CauldronInteraction.java.patch   | 330 ------------------
 2 files changed, 302 insertions(+), 330 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/core/cauldron/CauldronInteraction.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
new file mode 100644
index 0000000000..ddd596f865
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -0,0 +1,302 @@
+--- a/net/minecraft/core/cauldron/CauldronInteraction.java
++++ b/net/minecraft/core/cauldron/CauldronInteraction.java
+@@ -42,26 +_,31 @@
+ 
+     static CauldronInteraction.InteractionMap newInteractionMap(String name) {
+         Object2ObjectOpenHashMap<Item, CauldronInteraction> map = new Object2ObjectOpenHashMap<>();
+-        map.defaultReturnValue((state, level, pos, player, hand, stack) -> InteractionResult.TRY_WITH_EMPTY_HAND);
++        map.defaultReturnValue((state, level, pos, player, hand, stack, hitDirection) -> InteractionResult.TRY_WITH_EMPTY_HAND); // Paper - add hitDirection
+         CauldronInteraction.InteractionMap interactionMap = new CauldronInteraction.InteractionMap(name, map);
+         INTERACTIONS.put(name, interactionMap);
+         return interactionMap;
+     }
+ 
+-    InteractionResult interact(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack);
++    InteractionResult interact(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection); // Paper - add hitDirection
+ 
+     static void bootStrap() {
+         Map<Item, CauldronInteraction> map = EMPTY.map();
+         addDefaultInteractions(map);
+-        map.put(Items.POTION, (state, level, pos, player, hand, stack) -> {
++        map.put(Items.POTION, (state, level, pos, player, hand, stack, hitDirection) -> { // Paper - add hitDirection
+             PotionContents potionContents = stack.get(DataComponents.POTION_CONTENTS);
+             if (potionContents != null && potionContents.is(Potions.WATER)) {
+                 if (!level.isClientSide) {
++                    // CraftBukkit start
++                    if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++                        return InteractionResult.SUCCESS;
++                    }
++                    // CraftBukkit end
+                     Item item = stack.getItem();
+                     player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.GLASS_BOTTLE)));
+                     player.awardStat(Stats.USE_CAULDRON);
+                     player.awardStat(Stats.ITEM_USED.get(item));
+-                    level.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState());
++                    // world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit
+                     level.playSound(null, pos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+                     level.gameEvent(null, GameEvent.FLUID_PLACE, pos);
+                 }
+@@ -75,7 +_,7 @@
+         addDefaultInteractions(map1);
+         map1.put(
+             Items.BUCKET,
+-            (state, level, pos, player, hand, stack) -> fillBucket(
++            (state, level, pos, player, hand, stack, hitDirection) -> fillBucket( // Paper - add hitDirection
+                 state,
+                 level,
+                 pos,
+@@ -84,33 +_,44 @@
+                 stack,
+                 new ItemStack(Items.WATER_BUCKET),
+                 blockState -> blockState.getValue(LayeredCauldronBlock.LEVEL) == 3,
+-                SoundEvents.BUCKET_FILL
++                SoundEvents.BUCKET_FILL,
++                hitDirection
+             )
+         );
+-        map1.put(Items.GLASS_BOTTLE, (state, level, pos, player, hand, stack) -> {
++        map1.put(Items.GLASS_BOTTLE, (state, level, pos, player, hand, stack, hitDirection) -> { // Paper - add hitDirection
+             if (!level.isClientSide) {
++                // CraftBukkit start
++                if (!LayeredCauldronBlock.lowerFillLevel(state, level, pos, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) {
++                    return InteractionResult.SUCCESS;
++                }
++                // CraftBukkit end
+                 Item item = stack.getItem();
+                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, PotionContents.createItemStack(Items.POTION, Potions.WATER)));
+                 player.awardStat(Stats.USE_CAULDRON);
+                 player.awardStat(Stats.ITEM_USED.get(item));
+-                LayeredCauldronBlock.lowerFillLevel(state, level, pos);
++                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
+                 level.playSound(null, pos, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
+                 level.gameEvent(null, GameEvent.FLUID_PICKUP, pos);
+             }
+ 
+             return InteractionResult.SUCCESS;
+         });
+-        map1.put(Items.POTION, (state, level, pos, player, hand, stack) -> {
++        map1.put(Items.POTION, (state, level, pos, player, hand, stack, hitDirection) -> { // Paper - add hitDirection
+             if (state.getValue(LayeredCauldronBlock.LEVEL) == 3) {
+                 return InteractionResult.TRY_WITH_EMPTY_HAND;
+             } else {
+                 PotionContents potionContents = stack.get(DataComponents.POTION_CONTENTS);
+                 if (potionContents != null && potionContents.is(Potions.WATER)) {
+                     if (!level.isClientSide) {
++                        // CraftBukkit start
++                        if (!LayeredCauldronBlock.changeLevel(state, level, pos, state.cycle(LayeredCauldronBlock.LEVEL), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++                            return InteractionResult.SUCCESS;
++                        }
++                        // CraftBukkit end
+                         player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.GLASS_BOTTLE)));
+                         player.awardStat(Stats.USE_CAULDRON);
+                         player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+-                        level.setBlockAndUpdate(pos, state.cycle(LayeredCauldronBlock.LEVEL));
++                        // level.setBlockAndUpdate(pos, state.cycle(LayeredCauldronBlock.LEVEL)); // CraftBukkit
+                         level.playSound(null, pos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+                         level.gameEvent(null, GameEvent.FLUID_PLACE, pos);
+                     }
+@@ -162,15 +_,15 @@
+         Map<Item, CauldronInteraction> map2 = LAVA.map();
+         map2.put(
+             Items.BUCKET,
+-            (state, level, pos, player, hand, stack) -> fillBucket(
+-                state, level, pos, player, hand, stack, new ItemStack(Items.LAVA_BUCKET), blockState -> true, SoundEvents.BUCKET_FILL_LAVA
++            (state, level, pos, player, hand, stack, hitDirection) -> fillBucket( // Paper - add hitDirection
++                state, level, pos, player, hand, stack, new ItemStack(Items.LAVA_BUCKET), blockState -> true, SoundEvents.BUCKET_FILL_LAVA, hitDirection // Paper - add hitDirection
+             )
+         );
+         addDefaultInteractions(map2);
+         Map<Item, CauldronInteraction> map3 = POWDER_SNOW.map();
+         map3.put(
+             Items.BUCKET,
+-            (state, level, pos, player, hand, stack) -> fillBucket(
++            (state, level, pos, player, hand, stack, hitDirection) -> fillBucket( // Paper - add hitDirection
+                 state,
+                 level,
+                 pos,
+@@ -179,7 +_,7 @@
+                 stack,
+                 new ItemStack(Items.POWDER_SNOW_BUCKET),
+                 blockState -> blockState.getValue(LayeredCauldronBlock.LEVEL) == 3,
+-                SoundEvents.BUCKET_FILL_POWDER_SNOW
++                SoundEvents.BUCKET_FILL_POWDER_SNOW, hitDirection // Paper - add hitDirection
+             )
+         );
+         addDefaultInteractions(map3);
+@@ -202,15 +_,34 @@
+         Predicate<BlockState> statePredicate,
+         SoundEvent fillSound
+     ) {
++        // Paper start - add hitDirection
++        return fillBucket(state, level, pos, player, hand, emptyStack, filledStack, statePredicate, fillSound, null); // Paper - add hitDirection
++    }
++    static InteractionResult fillBucket(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack emptyStack, ItemStack filledStack, Predicate<BlockState> statePredicate, SoundEvent fillSound, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
++        // Paper end - add hitDirection
+         if (!statePredicate.test(state)) {
+             return InteractionResult.TRY_WITH_EMPTY_HAND;
+         } else {
+             if (!level.isClientSide) {
++                // Paper start - fire PlayerBucketFillEvent
++                if (hitDirection != null) {
++                    org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((net.minecraft.server.level.ServerLevel) level, player, pos, pos, hitDirection, emptyStack, filledStack.getItem(), hand);
++                    if (event.isCancelled()) {
++                        return InteractionResult.PASS;
++                    }
++                    filledStack = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
++                }
++                // Paper end - fire PlayerBucketFillEvent
++                // CraftBukkit start
++                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
++                    return InteractionResult.SUCCESS;
++                }
++                // CraftBukkit end
+                 Item item = emptyStack.getItem();
+                 player.setItemInHand(hand, ItemUtils.createFilledResult(emptyStack, player, filledStack));
+                 player.awardStat(Stats.USE_CAULDRON);
+                 player.awardStat(Stats.ITEM_USED.get(item));
+-                level.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState());
++                // level.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState()); // CraftBukkit
+                 level.playSound(null, pos, fillSound, SoundSource.BLOCKS, 1.0F, 1.0F);
+                 level.gameEvent(null, GameEvent.FLUID_PICKUP, pos);
+             }
+@@ -222,12 +_,32 @@
+     static InteractionResult emptyBucket(
+         Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStackl, BlockState state, SoundEvent emptySound
+     ) {
++        // Paper start - add hitDirection
++        return emptyBucket(level, pos, player, hand, filledStackl, state, emptySound, null);
++    }
++    static InteractionResult emptyBucket(Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStackl, BlockState state, SoundEvent emptySound, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
++        // Paper end - add hitDirection
+         if (!level.isClientSide) {
++            // Paper start - fire PlayerBucketEmptyEvent
++            ItemStack output = new ItemStack(Items.BUCKET);
++            if (hitDirection != null) {
++                org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent((net.minecraft.server.level.ServerLevel) level, player, pos, pos, hitDirection, filledStackl, hand);
++                if (event.isCancelled()) {
++                    return InteractionResult.PASS;
++                }
++                output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
++            }
++            // Paper end - fire PlayerBucketEmptyEvent
++            // CraftBukkit start
++            if (!LayeredCauldronBlock.changeLevel(state, level, pos, state, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++                return InteractionResult.SUCCESS;
++            }
++            // CraftBukkit end
+             Item item = filledStackl.getItem();
+-            player.setItemInHand(hand, ItemUtils.createFilledResult(filledStackl, player, new ItemStack(Items.BUCKET)));
++            player.setItemInHand(hand, ItemUtils.createFilledResult(filledStackl, player, output)); // Paper
+             player.awardStat(Stats.FILL_CAULDRON);
+             player.awardStat(Stats.ITEM_USED.get(item));
+-            level.setBlockAndUpdate(pos, state);
++            // level.setBlockAndUpdate(pos, state); // CraftBukkit
+             level.playSound(null, pos, emptySound, SoundSource.BLOCKS, 1.0F, 1.0F);
+             level.gameEvent(null, GameEvent.FLUID_PLACE, pos);
+         }
+@@ -236,7 +_,7 @@
+     }
+ 
+     private static InteractionResult fillWaterInteraction(
+-        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack
++        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack, final net.minecraft.core.Direction hitDirection // Paper - add hitDirection
+     ) {
+         return emptyBucket(
+             level,
+@@ -245,20 +_,20 @@
+             hand,
+             filledStack,
+             Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, Integer.valueOf(3)),
+-            SoundEvents.BUCKET_EMPTY
++            SoundEvents.BUCKET_EMPTY, hitDirection // Paper - add hitDirection
+         );
+     }
+ 
+     private static InteractionResult fillLavaInteraction(
+-        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack
++        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack, final net.minecraft.core.Direction hitDirection // Paper - add hitDirection
+     ) {
+         return (InteractionResult)(isUnderWater(level, pos)
+             ? InteractionResult.CONSUME
+-            : emptyBucket(level, pos, player, hand, filledStack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA));
++            : emptyBucket(level, pos, player, hand, filledStack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA, hitDirection)); // Paper - add hitDirection
+     }
+ 
+     private static InteractionResult fillPowderSnowInteraction(
+-        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack
++        BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack, final net.minecraft.core.Direction hitDirection // Paper - add hitDirection
+     ) {
+         return (InteractionResult)(isUnderWater(level, pos)
+             ? InteractionResult.CONSUME
+@@ -269,50 +_,65 @@
+                 hand,
+                 filledStack,
+                 Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, Integer.valueOf(3)),
+-                SoundEvents.BUCKET_EMPTY_POWDER_SNOW
++                SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection // Paper - add hitDirection
+             ));
+     }
+ 
+-    private static InteractionResult shulkerBoxInteraction(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
++    private static InteractionResult shulkerBoxInteraction(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
+         Block block = Block.byItem(stack.getItem());
+         if (!(block instanceof ShulkerBoxBlock)) {
+             return InteractionResult.TRY_WITH_EMPTY_HAND;
+         } else {
+             if (!level.isClientSide) {
++                // CraftBukkit start
++                if (!LayeredCauldronBlock.lowerFillLevel(state, level, pos, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.SHULKER_WASH)) {
++                    return InteractionResult.SUCCESS;
++                }
++                // CraftBukkit end
+                 ItemStack itemStack = stack.transmuteCopy(Blocks.SHULKER_BOX, 1);
+                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemStack, false));
+                 player.awardStat(Stats.CLEAN_SHULKER_BOX);
+-                LayeredCauldronBlock.lowerFillLevel(state, level, pos);
++                // LayeredCauldronBlock.lowerFillLevel(state, level, pos); // CraftBukkit
+             }
+ 
+             return InteractionResult.SUCCESS;
+         }
+     }
+ 
+-    private static InteractionResult bannerInteraction(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
++    private static InteractionResult bannerInteraction(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
+         BannerPatternLayers bannerPatternLayers = stack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
+         if (bannerPatternLayers.layers().isEmpty()) {
+             return InteractionResult.TRY_WITH_EMPTY_HAND;
+         } else {
+             if (!level.isClientSide) {
++                // CraftBukkit start
++                if (!LayeredCauldronBlock.lowerFillLevel(state, level, pos, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BANNER_WASH)) {
++                    return InteractionResult.SUCCESS;
++                }
++                // CraftBukkit end
+                 ItemStack itemStack = stack.copyWithCount(1);
+                 itemStack.set(DataComponents.BANNER_PATTERNS, bannerPatternLayers.removeLast());
+                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemStack, false));
+                 player.awardStat(Stats.CLEAN_BANNER);
+-                LayeredCauldronBlock.lowerFillLevel(state, level, pos);
++                // LayeredCauldronBlock.lowerFillLevel(state, level, pos); // CraftBukkit
+             }
+ 
+             return InteractionResult.SUCCESS;
+         }
+     }
+ 
+-    private static InteractionResult dyedItemIteration(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
++    private static InteractionResult dyedItemIteration(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
+         if (!stack.is(ItemTags.DYEABLE)) {
+             return InteractionResult.TRY_WITH_EMPTY_HAND;
+         } else if (!stack.has(DataComponents.DYED_COLOR)) {
+             return InteractionResult.TRY_WITH_EMPTY_HAND;
+         } else {
+             if (!level.isClientSide) {
++                // CraftBukkit start
++                if (!LayeredCauldronBlock.lowerFillLevel(state, level, pos, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.ARMOR_WASH)) {
++                    return InteractionResult.SUCCESS;
++                }
++                // CraftBukkit end
+                 stack.remove(DataComponents.DYED_COLOR);
+                 player.awardStat(Stats.CLEAN_ARMOR);
+                 LayeredCauldronBlock.lowerFillLevel(state, level, pos);
diff --git a/paper-server/patches/unapplied/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/unapplied/net/minecraft/core/cauldron/CauldronInteraction.java.patch
deleted file mode 100644
index f0db760867..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/core/cauldron/CauldronInteraction.java.patch
+++ /dev/null
@@ -1,330 +0,0 @@
---- a/net/minecraft/core/cauldron/CauldronInteraction.java
-+++ b/net/minecraft/core/cauldron/CauldronInteraction.java
-@@ -35,20 +35,25 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.material.FluidState;
-+// CraftBukkit start
-+import org.bukkit.event.block.CauldronLevelChangeEvent;
-+// CraftBukkit end
- 
- public interface CauldronInteraction {
- 
-     Map<String, CauldronInteraction.InteractionMap> INTERACTIONS = new Object2ObjectArrayMap();
--    Codec<CauldronInteraction.InteractionMap> CODEC;
--    CauldronInteraction.InteractionMap EMPTY;
--    CauldronInteraction.InteractionMap WATER;
--    CauldronInteraction.InteractionMap LAVA;
--    CauldronInteraction.InteractionMap POWDER_SNOW;
-+    // CraftBukkit start - decompile errors
-+    Codec<CauldronInteraction.InteractionMap> CODEC = Codec.stringResolver(CauldronInteraction.InteractionMap::name, CauldronInteraction.INTERACTIONS::get);
-+    CauldronInteraction.InteractionMap EMPTY = CauldronInteraction.newInteractionMap("empty");
-+    CauldronInteraction.InteractionMap WATER = CauldronInteraction.newInteractionMap("water");
-+    CauldronInteraction.InteractionMap LAVA = CauldronInteraction.newInteractionMap("lava");
-+    CauldronInteraction.InteractionMap POWDER_SNOW = CauldronInteraction.newInteractionMap("powder_snow");
-+    // CraftBukkit end
- 
-     static CauldronInteraction.InteractionMap newInteractionMap(String name) {
-         Object2ObjectOpenHashMap<Item, CauldronInteraction> object2objectopenhashmap = new Object2ObjectOpenHashMap();
- 
--        object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        object2objectopenhashmap.defaultReturnValue((iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         });
-         CauldronInteraction.InteractionMap cauldroninteraction_a = new CauldronInteraction.InteractionMap(name, object2objectopenhashmap);
-@@ -57,23 +62,28 @@
-         return cauldroninteraction_a;
-     }
- 
--    InteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack);
-+    InteractionResult interact(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection); // Paper - add hitDirection
- 
-     static void bootStrap() {
-         Map<Item, CauldronInteraction> map = CauldronInteraction.EMPTY.map();
- 
-         CauldronInteraction.addDefaultInteractions(map);
--        map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS);
- 
-             if (potioncontents != null && potioncontents.is(Potions.WATER)) {
-                 if (!world.isClientSide) {
-+                    // CraftBukkit start
-+                    if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
-+                        return InteractionResult.SUCCESS;
-+                    }
-+                    // CraftBukkit end
-                     Item item = itemstack.getItem();
- 
-                     entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE)));
-                     entityhuman.awardStat(Stats.USE_CAULDRON);
-                     entityhuman.awardStat(Stats.ITEM_USED.get(item));
--                    world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState());
-+                    // world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit
-                     world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
-                     world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, blockposition);
-                 }
-@@ -86,26 +96,31 @@
-         Map<Item, CauldronInteraction> map1 = CauldronInteraction.WATER.map();
- 
-         CauldronInteraction.addDefaultInteractions(map1);
--        map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.WATER_BUCKET), (iblockdata1) -> {
-                 return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3;
--            }, SoundEvents.BUCKET_FILL);
-+            }, SoundEvents.BUCKET_FILL, hitDirection); // Paper - add hitDirection
-         });
--        map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             if (!world.isClientSide) {
-+                // CraftBukkit start
-+                if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) {
-+                    return InteractionResult.SUCCESS;
-+                }
-+                // CraftBukkit end
-                 Item item = itemstack.getItem();
- 
-                 entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, PotionContents.createItemStack(Items.POTION, Potions.WATER)));
-                 entityhuman.awardStat(Stats.USE_CAULDRON);
-                 entityhuman.awardStat(Stats.ITEM_USED.get(item));
--                LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition);
-+                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
-                 world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
-                 world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, blockposition);
-             }
- 
-             return InteractionResult.SUCCESS;
-         });
--        map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             if ((Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) == 3) {
-                 return InteractionResult.TRY_WITH_EMPTY_HAND;
-             } else {
-@@ -113,10 +128,15 @@
- 
-                 if (potioncontents != null && potioncontents.is(Potions.WATER)) {
-                     if (!world.isClientSide) {
-+                        // CraftBukkit start
-+                        if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
-+                            return InteractionResult.SUCCESS;
-+                        }
-+                        // CraftBukkit end
-                         entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE)));
-                         entityhuman.awardStat(Stats.USE_CAULDRON);
-                         entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
--                        world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.cycle(LayeredCauldronBlock.LEVEL));
-+                        // world.setBlockAndUpdate(blockposition, (IBlockData) iblockdata.cycle(LayeredCauldronBlock.LEVEL)); // CraftBukkit
-                         world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
-                         world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, blockposition);
-                     }
-@@ -167,18 +187,18 @@
-         map1.put(Items.YELLOW_SHULKER_BOX, CauldronInteraction::shulkerBoxInteraction);
-         Map<Item, CauldronInteraction> map2 = CauldronInteraction.LAVA.map();
- 
--        map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map2.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.LAVA_BUCKET), (iblockdata1) -> {
-                 return true;
--            }, SoundEvents.BUCKET_FILL_LAVA);
-+            }, SoundEvents.BUCKET_FILL_LAVA, hitDirection); // Paper - add hitDirection
-         });
-         CauldronInteraction.addDefaultInteractions(map2);
-         Map<Item, CauldronInteraction> map3 = CauldronInteraction.POWDER_SNOW.map();
- 
--        map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
-+        map3.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack, hitDirection) -> { // Paper - add hitDirection
-             return CauldronInteraction.fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.POWDER_SNOW_BUCKET), (iblockdata1) -> {
-                 return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3;
--            }, SoundEvents.BUCKET_FILL_POWDER_SNOW);
-+            }, SoundEvents.BUCKET_FILL_POWDER_SNOW, hitDirection); // Paper - add hitDirection
-         });
-         CauldronInteraction.addDefaultInteractions(map3);
-     }
-@@ -190,16 +210,35 @@
-     }
- 
-     static InteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate<BlockState> fullPredicate, SoundEvent soundEvent) {
-+        // Paper start - add hitDirection
-+        return fillBucket(state, world, pos, player, hand, stack, output, fullPredicate, soundEvent, null); // Paper - add hitDirection
-+    }
-+    static InteractionResult fillBucket(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, ItemStack output, Predicate<BlockState> fullPredicate, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
-+        // Paper end - add hitDirection
-         if (!fullPredicate.test(state)) {
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         } else {
-             if (!world.isClientSide) {
-+                // Paper start - fire PlayerBucketFillEvent
-+                if (hitDirection != null) {
-+                    org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, output.getItem(), hand);
-+                    if (event.isCancelled()) {
-+                        return InteractionResult.PASS;
-+                    }
-+                    output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
-+                }
-+                // Paper end - fire PlayerBucketFillEvent
-+                // CraftBukkit start
-+                if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
-+                    return InteractionResult.SUCCESS;
-+                }
-+                // CraftBukkit end
-                 Item item = stack.getItem();
- 
-                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, output));
-                 player.awardStat(Stats.USE_CAULDRON);
-                 player.awardStat(Stats.ITEM_USED.get(item));
--                world.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState());
-+                // world.setBlockAndUpdate(blockposition, Blocks.CAULDRON.defaultBlockState()); // CraftBukkit
-                 world.playSound((Player) null, pos, soundEvent, SoundSource.BLOCKS, 1.0F, 1.0F);
-                 world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PICKUP, pos);
-             }
-@@ -209,13 +248,33 @@
-     }
- 
-     static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) {
-+        // Paper start - add hitDirection
-+        return emptyBucket(world, pos, player, hand, stack, state, soundEvent, null);
-+    }
-+    static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent, @javax.annotation.Nullable net.minecraft.core.Direction hitDirection) {
-+        // Paper end - add hitDirection
-         if (!world.isClientSide) {
-+            // Paper start - fire PlayerBucketEmptyEvent
-+            ItemStack output = new ItemStack(Items.BUCKET);
-+            if (hitDirection != null) {
-+                org.bukkit.event.player.PlayerBucketEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent((net.minecraft.server.level.ServerLevel) world, player, pos, pos, hitDirection, stack, hand);
-+                if (event.isCancelled()) {
-+                    return InteractionResult.PASS;
-+                }
-+                output = event.getItemStack() != null ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY;
-+            }
-+            // Paper end - fire PlayerBucketEmptyEvent
-+            // CraftBukkit start
-+            if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
-+                return InteractionResult.SUCCESS;
-+            }
-+            // CraftBukkit end
-             Item item = stack.getItem();
- 
--            player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.BUCKET)));
-+            player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, output)); // Paper
-             player.awardStat(Stats.FILL_CAULDRON);
-             player.awardStat(Stats.ITEM_USED.get(item));
--            world.setBlockAndUpdate(pos, state);
-+            // world.setBlockAndUpdate(blockposition, iblockdata); // CraftBukkit
-             world.playSound((Player) null, pos, soundEvent, SoundSource.BLOCKS, 1.0F, 1.0F);
-             world.gameEvent((Entity) null, (Holder) GameEvent.FLUID_PLACE, pos);
-         }
-@@ -223,65 +282,79 @@
-         return InteractionResult.SUCCESS;
-     }
- 
--    private static InteractionResult fillWaterInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
--        return CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY);
-+    private static InteractionResult fillWaterInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-+        return CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY, hitDirection); // Paper - add hitDirection
-     }
- 
--    private static InteractionResult fillLavaInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
--        return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA));
-+    private static InteractionResult fillLavaInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-+        return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA, hitDirection)); // Paper - add hitDirection
-     }
- 
--    private static InteractionResult fillPowderSnowInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
--        return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW));
-+    private static InteractionResult fillPowderSnowInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-+        return (InteractionResult) (CauldronInteraction.isUnderWater(world, pos) ? InteractionResult.CONSUME : CauldronInteraction.emptyBucket(world, pos, player, hand, stack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW, hitDirection)); // Paper - add hitDirection
-     }
- 
--    private static InteractionResult shulkerBoxInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
-+    private static InteractionResult shulkerBoxInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-         Block block = Block.byItem(stack.getItem());
- 
-         if (!(block instanceof ShulkerBoxBlock)) {
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         } else {
-             if (!world.isClientSide) {
-+                // CraftBukkit start
-+                if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.SHULKER_WASH)) {
-+                    return InteractionResult.SUCCESS;
-+                }
-+                // CraftBukkit end
-                 ItemStack itemstack1 = stack.transmuteCopy(Blocks.SHULKER_BOX, 1);
- 
-                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemstack1, false));
-                 player.awardStat(Stats.CLEAN_SHULKER_BOX);
--                LayeredCauldronBlock.lowerFillLevel(state, world, pos);
-+                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
-             }
--
-             return InteractionResult.SUCCESS;
-         }
-     }
- 
--    private static InteractionResult bannerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
-+    private static InteractionResult bannerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-         BannerPatternLayers bannerpatternlayers = (BannerPatternLayers) stack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
- 
-         if (bannerpatternlayers.layers().isEmpty()) {
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         } else {
-             if (!world.isClientSide) {
-+                // CraftBukkit start
-+                if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.BANNER_WASH)) {
-+                    return InteractionResult.SUCCESS;
-+                }
-+                // CraftBukkit end
-                 ItemStack itemstack1 = stack.copyWithCount(1);
- 
-                 itemstack1.set(DataComponents.BANNER_PATTERNS, bannerpatternlayers.removeLast());
-                 player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, itemstack1, false));
-                 player.awardStat(Stats.CLEAN_BANNER);
--                LayeredCauldronBlock.lowerFillLevel(state, world, pos);
-+                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
-             }
- 
-             return InteractionResult.SUCCESS;
-         }
-     }
- 
--    private static InteractionResult dyedItemIteration(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack) {
-+    private static InteractionResult dyedItemIteration(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, final net.minecraft.core.Direction hitDirection) { // Paper - add hitDirection
-         if (!stack.is(ItemTags.DYEABLE)) {
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         } else if (!stack.has(DataComponents.DYED_COLOR)) {
-             return InteractionResult.TRY_WITH_EMPTY_HAND;
-         } else {
-             if (!world.isClientSide) {
-+                // CraftBukkit start
-+                if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, player, CauldronLevelChangeEvent.ChangeReason.ARMOR_WASH)) {
-+                    return InteractionResult.SUCCESS;
-+                }
-+                // CraftBukkit end
-                 stack.remove(DataComponents.DYED_COLOR);
-                 player.awardStat(Stats.CLEAN_ARMOR);
--                LayeredCauldronBlock.lowerFillLevel(state, world, pos);
-+                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
-             }
- 
-             return InteractionResult.SUCCESS;
-@@ -294,8 +367,10 @@
-         return fluid.is(FluidTags.WATER);
-     }
- 
-+    // CraftBukkit start - decompile errors
-+    /*
-     static {
--        Function function = CauldronInteraction.InteractionMap::name;
-+        Function function = CauldronInteraction.a::name;
-         Map map = CauldronInteraction.INTERACTIONS;
- 
-         Objects.requireNonNull(map);
-@@ -305,6 +380,8 @@
-         LAVA = newInteractionMap("lava");
-         POWDER_SNOW = newInteractionMap("powder_snow");
-     }
-+     */
-+    // CraftBukkit end
- 
-     public static record InteractionMap(String name, Map<Item, CauldronInteraction> map) {
- 

From c8d3ed44306e633930acb276e03d50ccad62e5ed Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 13:23:24 -0500
Subject: [PATCH 085/285] /net/minecraft/core/cauldron/CauldronInteraction fix

---
 .../minecraft/core/cauldron/CauldronInteraction.java.patch   | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
index ddd596f865..3cadc7f318 100644
--- a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -45,13 +45,12 @@
                  state,
                  level,
                  pos,
-@@ -84,33 +_,44 @@
+@@ -84,33 +_,43 @@
                  stack,
                  new ItemStack(Items.WATER_BUCKET),
                  blockState -> blockState.getValue(LayeredCauldronBlock.LEVEL) == 3,
 -                SoundEvents.BUCKET_FILL
-+                SoundEvents.BUCKET_FILL,
-+                hitDirection
++                SoundEvents.BUCKET_FILL, hitDirection // Paper - add hitDirection
              )
          );
 -        map1.put(Items.GLASS_BOTTLE, (state, level, pos, player, hand, stack) -> {

From 33efd7ee123431e251d283ee7dc4542734795f69 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 19:27:16 +0100
Subject: [PATCH 086/285] net.minecraft.util.worldupdate

---
 .../util/worldupdate/WorldUpgrader.java.patch | 22 +++++++++++++
 .../util/worldupdate/WorldUpgrader.java.patch | 32 -------------------
 2 files changed, 22 insertions(+), 32 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/util/worldupdate/WorldUpgrader.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
new file mode 100644
index 0000000000..5f0dc70ca1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/util/worldupdate/WorldUpgrader.java
++++ b/net/minecraft/util/worldupdate/WorldUpgrader.java
+@@ -79,7 +_,7 @@
+         LevelStorageSource.LevelStorageAccess levelStorage, DataFixer dataFixer, RegistryAccess registryAccess, boolean eraseCache, boolean recreateRegionFiles
+     ) {
+         this.dimensions = registryAccess.lookupOrThrow(Registries.LEVEL_STEM);
+-        this.levels = this.dimensions.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet());
++        this.levels = java.util.stream.Stream.of(levelStorage.dimensionType).map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); // CraftBukkit
+         this.eraseCache = eraseCache;
+         this.dataFixer = dataFixer;
+         this.levelStorage = levelStorage;
+@@ -357,9 +_,7 @@
+             if (compoundTag != null) {
+                 int version = ChunkStorage.getVersion(compoundTag);
+                 ChunkGenerator chunkGenerator = WorldUpgrader.this.dimensions.getValueOrThrow(Registries.levelToLevelStem(dimension)).generator();
+-                CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(
+-                    dimension, () -> WorldUpgrader.this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer()
+-                );
++                CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(Registries.levelToLevelStem(dimension), () -> WorldUpgrader.this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer(), chunkPos, null); // CraftBukkit
+                 ChunkPos chunkPos1 = new ChunkPos(compoundTag1.getInt("xPos"), compoundTag1.getInt("zPos"));
+                 if (!chunkPos1.equals(chunkPos)) {
+                     WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", chunkPos, chunkPos1);
diff --git a/paper-server/patches/unapplied/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/paper-server/patches/unapplied/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
deleted file mode 100644
index de3432baee..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/util/worldupdate/WorldUpgrader.java
-+++ b/net/minecraft/util/worldupdate/WorldUpgrader.java
-@@ -80,7 +80,7 @@
- 
-     public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, RegistryAccess dynamicRegistryManager, boolean eraseCache, boolean recreateRegionFiles) {
-         this.dimensions = dynamicRegistryManager.lookupOrThrow(Registries.LEVEL_STEM);
--        this.levels = (Set) this.dimensions.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet());
-+        this.levels = (Set) java.util.stream.Stream.of(session.dimensionType).map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); // CraftBukkit
-         this.eraseCache = eraseCache;
-         this.dataFixer = dataFixer;
-         this.levelStorage = session;
-@@ -197,9 +197,9 @@
-             if (nbttagcompound != null) {
-                 int i = ChunkStorage.getVersion(nbttagcompound);
-                 ChunkGenerator chunkgenerator = ((LevelStem) WorldUpgrader.this.dimensions.getValueOrThrow(Registries.levelToLevelStem(worldKey))).generator();
--                CompoundTag nbttagcompound1 = storage.upgradeChunkTag(worldKey, () -> {
-+                CompoundTag nbttagcompound1 = storage.upgradeChunkTag(Registries.levelToLevelStem(worldKey), () -> { // CraftBukkit
-                     return WorldUpgrader.this.overworldDataStorage;
--                }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer());
-+                }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer(), chunkPos, null); // CraftBukkit
-                 ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos"));
- 
-                 if (!chunkcoordintpair1.equals(chunkPos)) {
-@@ -321,7 +321,7 @@
-                         WorldUpgrader.DimensionToUpgrade<T> worldupgrader_c = (WorldUpgrader.DimensionToUpgrade) iterator.next();
-                         ResourceKey<Level> resourcekey = worldupgrader_c.dimensionKey;
-                         ListIterator<WorldUpgrader.FileToUpgrade> listiterator = worldupgrader_c.files;
--                        T t0 = (AutoCloseable) worldupgrader_c.storage;
-+                        T t0 = (T) worldupgrader_c.storage; // CraftBukkit - decompile error
- 
-                         if (listiterator.hasNext()) {
-                             WorldUpgrader.FileToUpgrade worldupgrader_e = (WorldUpgrader.FileToUpgrade) listiterator.next();

From 27a47b77c754e178184058b771e36fbeb694dc15 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 19:31:59 +0100
Subject: [PATCH 087/285] net/minecraft/network/protocol/login

---
 .../ClientboundCustomQueryPacket.java.patch   |  7 +++----
 ...lientboundLoginDisconnectPacket.java.patch | 21 +++++++++++++++++++
 ...verboundCustomQueryAnswerPacket.java.patch | 20 ++++++++----------
 ...lientboundLoginDisconnectPacket.java.patch | 21 -------------------
 4 files changed, 33 insertions(+), 36 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch (81%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch (70%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch
similarity index 81%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch
index 60ed80c610..e1eb0709fe 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch
@@ -1,13 +1,12 @@
 --- a/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
 +++ b/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
-@@ -47,4 +47,14 @@
-     public void handle(ClientLoginPacketListener listener) {
-         listener.handleCustomQuery(this);
+@@ -47,4 +_,13 @@
+     public void handle(ClientLoginPacketListener handler) {
+         handler.handleCustomQuery(this);
      }
 +
 +    // Paper start - MC Utils - default query payloads
 +    public static record PlayerInfoChannelPayload(ResourceLocation id, FriendlyByteBuf buffer) implements CustomQueryPayload {
-+
 +        @Override
 +        public void write(final FriendlyByteBuf buf) {
 +            buf.writeBytes(this.buffer.copy());
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch
new file mode 100644
index 0000000000..3bc9092eb9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java
++++ b/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java
+@@ -18,11 +_,16 @@
+     }
+ 
+     private ClientboundLoginDisconnectPacket(FriendlyByteBuf buffer) {
+-        this.reason = Component.Serializer.fromJsonLenient(buffer.readUtf(262144), RegistryAccess.EMPTY);
++        this.reason = Component.Serializer.fromJsonLenient(buffer.readUtf(FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH), RegistryAccess.EMPTY); // Paper - diff on change
+     }
+ 
+     private void write(FriendlyByteBuf buffer) {
+-        buffer.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY));
++        // Paper start - Adventure
++        // buffer.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY));
++        // In the login phase, buffer.adventure$locale field is most likely null, but plugins may use internals to set it via the channel attribute
++        java.util.Locale bufLocale = buffer.adventure$locale;
++        buffer.writeJsonWithCodec(net.minecraft.network.chat.ComponentSerialization.localizedCodec(bufLocale == null ? java.util.Locale.US : bufLocale), this.reason, FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH);
++        // Paper end - Adventure
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
rename to paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
index 663ca9ede2..9596254f44 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
 +++ b/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
-@@ -20,7 +20,17 @@
+@@ -20,7 +_,17 @@
      }
  
-     private static CustomQueryAnswerPayload readPayload(int queryId, FriendlyByteBuf buf) {
--        return readUnknownPayload(buf);
+     private static CustomQueryAnswerPayload readPayload(int transactionId, FriendlyByteBuf buffer) {
+-        return readUnknownPayload(buffer);
 +        // Paper start - MC Utils - default query payloads
-+        FriendlyByteBuf buffer = buf.readNullable((buf2) -> {
++        FriendlyByteBuf buf = buffer.readNullable((buf2) -> {
 +            int i = buf2.readableBytes();
 +            if (i >= 0 && i <= MAX_PAYLOAD_SIZE) {
 +                return new FriendlyByteBuf(buf2.readBytes(i));
@@ -14,19 +14,18 @@
 +                throw new IllegalArgumentException("Payload may not be larger than " + MAX_PAYLOAD_SIZE + " bytes");
 +            }
 +        });
-+        return buffer == null ? null : new net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket.QueryAnswerPayload(buffer);
++        return buf == null ? null : new net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket.QueryAnswerPayload(buf);
 +        // Paper end - MC Utils - default query payloads
      }
  
-     private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf buf) {
-@@ -47,4 +57,21 @@
-     public void handle(ServerLoginPacketListener listener) {
-         listener.handleCustomQueryPacket(this);
+     private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf buffer) {
+@@ -47,4 +_,19 @@
+     public void handle(ServerLoginPacketListener handler) {
+         handler.handleCustomQueryPacket(this);
      }
 +
 +    // Paper start - MC Utils - default query payloads
 +    public static final class QueryAnswerPayload implements CustomQueryAnswerPayload {
-+
 +        public final FriendlyByteBuf buffer;
 +
 +        public QueryAnswerPayload(final net.minecraft.network.FriendlyByteBuf buffer) {
@@ -39,5 +38,4 @@
 +        }
 +    }
 +    // Paper end - MC Utils - default query payloads
-+
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch
deleted file mode 100644
index 7ad1ef4a1c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java
-+++ b/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java
-@@ -18,11 +18,16 @@
-     }
- 
-     private ClientboundLoginDisconnectPacket(FriendlyByteBuf buf) {
--        this.reason = Component.Serializer.fromJsonLenient(buf.readUtf(262144), RegistryAccess.EMPTY);
-+        this.reason = Component.Serializer.fromJsonLenient(buf.readUtf(FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH), RegistryAccess.EMPTY); // Paper - diff on change
-     }
- 
-     private void write(FriendlyByteBuf buf) {
--        buf.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY));
-+        // Paper start - Adventure
-+        // buf.writeUtf(Component.Serializer.toJson(this.reason, RegistryAccess.EMPTY));
-+        // In the login phase, buf.adventure$locale field is most likely null, but plugins may use internals to set it via the channel attribute
-+        java.util.Locale bufLocale = buf.adventure$locale;
-+        buf.writeJsonWithCodec(net.minecraft.network.chat.ComponentSerialization.localizedCodec(bufLocale == null ? java.util.Locale.US : bufLocale), this.reason, FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH);
-+        // Paper end - Adventure
-     }
- 
-     @Override

From 6a84e88e76973f1e139559ac13528765abd64b62 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 19:37:31 +0100
Subject: [PATCH 088/285] net/minecraft/world/level/block/entity/vault

---
 .../entity/vault/VaultBlockEntity.java.patch  | 26 ++++++
 .../entity/vault/VaultBlockEntity.java.patch  | 83 -------------------
 2 files changed, 26 insertions(+), 83 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch
new file mode 100644
index 0000000000..055a081be5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java
+@@ -272,6 +_,11 @@
+                     if (!list.isEmpty()) {
+                         player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+                         stack.consume(config.keyItem().getCount(), player);
++                        // CraftBukkit start
++                        org.bukkit.event.block.BlockDispenseLootEvent vaultDispenseLootEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDispenseLootEvent(level, pos, player, list);
++                        if (vaultDispenseLootEvent.isCancelled()) return;
++                        list = vaultDispenseLootEvent.getDispensedLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).toList();
++                        // CraftBukkit end
+                         unlock(level, state, pos, config, serverData, sharedData, list);
+                         serverData.addToRewardedPlayers(player);
+                         sharedData.updateConnectedPlayersWithinRange(level, pos, serverData, config, config.deactivationRange());
+@@ -294,6 +_,11 @@
+                 ItemStack randomDisplayItemFromLootTable = getRandomDisplayItemFromLootTable(
+                     level, pos, config.overrideLootTableToDisplay().orElse(config.lootTable())
+                 );
++                // CraftBukkit start
++                org.bukkit.event.block.VaultDisplayItemEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callVaultDisplayItemEvent(level, pos, randomDisplayItemFromLootTable);
++                if (event.isCancelled()) return;
++                randomDisplayItemFromLootTable = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDisplayItem());
++                // CraftBukkit end
+                 sharedData.setDisplayItem(randomDisplayItemFromLootTable);
+             }
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch
deleted file mode 100644
index 928e901a76..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java.patch
+++ /dev/null
@@ -1,83 +0,0 @@
---- a/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/vault/VaultBlockEntity.java
-@@ -46,6 +46,13 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.BlockDispenseLootEvent;
-+import org.bukkit.event.block.VaultDisplayItemEvent;
-+// CraftBukkit end
-+
- public class VaultBlockEntity extends BlockEntity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -96,18 +103,18 @@
-             dataresult = VaultServerData.CODEC.parse(dynamicops, nbt.get("server_data"));
-             logger = VaultBlockEntity.LOGGER;
-             Objects.requireNonNull(logger);
--            optional = dataresult.resultOrPartial(logger::error);
-+            optional = ((DataResult<VaultServerData>) dataresult).resultOrPartial(logger::error); // CraftBukkit - decompile error
-             VaultServerData vaultserverdata = this.serverData;
- 
-             Objects.requireNonNull(this.serverData);
--            optional.ifPresent(vaultserverdata::set);
-+            ((Optional<VaultServerData>) optional).ifPresent(vaultserverdata::set); // CraftBukkit - decompile error
-         }
- 
-         if (nbt.contains("config")) {
-             dataresult = VaultConfig.CODEC.parse(dynamicops, nbt.get("config"));
-             logger = VaultBlockEntity.LOGGER;
-             Objects.requireNonNull(logger);
--            dataresult.resultOrPartial(logger::error).ifPresent((vaultconfig) -> {
-+            ((DataResult<VaultConfig>) dataresult).resultOrPartial(logger::error).ifPresent((vaultconfig) -> { // CraftBukkit - decompile error
-                 this.config = vaultconfig;
-             });
-         }
-@@ -116,11 +123,11 @@
-             dataresult = VaultSharedData.CODEC.parse(dynamicops, nbt.get("shared_data"));
-             logger = VaultBlockEntity.LOGGER;
-             Objects.requireNonNull(logger);
--            optional = dataresult.resultOrPartial(logger::error);
-+            optional = ((DataResult<VaultSharedData>) dataresult).resultOrPartial(logger::error); // CraftBukkit - decompile error
-             VaultSharedData vaultshareddata = this.sharedData;
- 
-             Objects.requireNonNull(this.sharedData);
--            optional.ifPresent(vaultshareddata::set);
-+            ((Optional<VaultSharedData>) optional).ifPresent(vaultshareddata::set); // CraftBukkit - decompile error
-         }
- 
-     }
-@@ -320,6 +327,14 @@
-                     if (!list.isEmpty()) {
-                         player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
-                         stack.consume(config.keyItem().getCount(), player);
-+                        // CraftBukkit start
-+                        BlockDispenseLootEvent vaultDispenseLootEvent = CraftEventFactory.callBlockDispenseLootEvent(world, pos, player, list);
-+                        if (vaultDispenseLootEvent.isCancelled()) {
-+                            return;
-+                        }
-+
-+                        list = vaultDispenseLootEvent.getDispensedLoot().stream().map(CraftItemStack::asNMSCopy).toList();
-+                        // CraftBukkit end
-                         Server.unlock(world, state, pos, config, serverData, sharedData, list);
-                         serverData.addToRewardedPlayers(player);
-                         sharedData.updateConnectedPlayersWithinRange(world, pos, serverData, config, config.deactivationRange());
-@@ -341,7 +356,15 @@
-                 sharedData.setDisplayItem(ItemStack.EMPTY);
-             } else {
-                 ItemStack itemstack = Server.getRandomDisplayItemFromLootTable(world, pos, (ResourceKey) config.overrideLootTableToDisplay().orElse(config.lootTable()));
-+                // CraftBukkit start
-+                VaultDisplayItemEvent event = CraftEventFactory.callVaultDisplayItemEvent(world, pos, itemstack);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
- 
-+                itemstack = CraftItemStack.asNMSCopy(event.getDisplayItem());
-+                // CraftBukkit end
-+
-                 sharedData.setDisplayItem(itemstack);
-             }
-         }

From e90ba22ef767de6420a6cc9b34f878013a041d7b Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 18:40:10 +0000
Subject: [PATCH 089/285] net/minecraft/world/entity/ai/sensing

---
 .../net/minecraft/world/entity/ai/sensing/Sensor.java.patch       | 0
 .../minecraft/world/entity/ai/sensing/TemptingSensor.java.patch   | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/sensing/Sensor.java.patch (100%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch (100%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/sensing/Sensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
similarity index 100%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
similarity index 100%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch

From a890e322f104865c85f25f924b7c9a4a7ad7de42 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 18:42:28 +0000
Subject: [PATCH 090/285] net/minecraft/world/entity/ai/sensing (but, actually)

---
 .../world/entity/ai/sensing/Sensor.java.patch | 21 +++++++-------
 .../ai/sensing/TemptingSensor.java.patch      | 29 +++++++------------
 2 files changed, 20 insertions(+), 30 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
index 7d220911a0..a052e1e5a2 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/sensing/Sensor.java
 +++ b/net/minecraft/world/entity/ai/sensing/Sensor.java
-@@ -29,8 +29,19 @@
+@@ -29,8 +_,19 @@
          .ignoreInvisibilityTesting();
      private final int scanRate;
      private long timeToTick;
@@ -8,7 +8,7 @@
 +    private final String configKey;
 +    // Paper end
  
-     public Sensor(int senseInterval) {
+     public Sensor(int scanRate) {
 +        // Paper start - configurable sensor tick rate and timings
 +        String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName();
 +        int lastSeparator = key.lastIndexOf('.');
@@ -17,18 +17,17 @@
 +        }
 +        this.configKey = key.toLowerCase(java.util.Locale.ROOT);
 +        // Paper end
-         this.scanRate = senseInterval;
-         this.timeToTick = (long)RANDOM.nextInt(senseInterval);
+         this.scanRate = scanRate;
+         this.timeToTick = RANDOM.nextInt(scanRate);
      }
-@@ -41,8 +52,10 @@
+@@ -41,7 +_,9 @@
  
-     public final void tick(ServerLevel world, E entity) {
+     public final void tick(ServerLevel level, E entity) {
          if (--this.timeToTick <= 0L) {
--            this.timeToTick = (long)this.scanRate;
+-            this.timeToTick = this.scanRate;
 +            // Paper start - configurable sensor tick rate and timings
-+            this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate);
-             this.updateTargetingConditionRanges(entity);
++            this.timeToTick = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate);
 +            // Paper end
-             this.doTick(world, entity);
+             this.updateTargetingConditionRanges(entity);
+             this.doTick(level, entity);
          }
-     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
index 2bd53d40cd..7ec237343c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
 +++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
-@@ -19,6 +19,14 @@
+@@ -16,6 +_,14 @@
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.item.ItemStack;
  
@@ -13,33 +13,24 @@
 +// CraftBukkit end
 +
  public class TemptingSensor extends Sensor<PathfinderMob> {
- 
      private static final TargetingConditions TEMPT_TARGETING = TargetingConditions.forNonCombat().ignoreLineOfSight();
-@@ -31,7 +39,7 @@
-     protected void doTick(ServerLevel world, PathfinderMob entity) {
-         Brain<?> behaviorcontroller = entity.getBrain();
-         TargetingConditions pathfindertargetcondition = TemptingSensor.TEMPT_TARGETING.copy().range((double) ((float) entity.getAttributeValue(Attributes.TEMPT_RANGE)));
--        Stream stream = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((entityplayer) -> {
-+        Stream<net.minecraft.server.level.ServerPlayer> stream = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((entityplayer) -> { // CraftBukkit - decompile error
-             return pathfindertargetcondition.test(world, entity, entityplayer);
-         }).filter(this::playerHoldingTemptation).filter((entityplayer) -> {
-             return !entity.hasPassenger((Entity) entityplayer);
-@@ -43,7 +51,17 @@
+     private final Predicate<ItemStack> temptations;
+@@ -38,7 +_,17 @@
+             .collect(Collectors.toList());
          if (!list.isEmpty()) {
-             Player entityhuman = (Player) list.get(0);
- 
--            behaviorcontroller.setMemory(MemoryModuleType.TEMPTING_PLAYER, (Object) entityhuman);
+             Player player = list.get(0);
+-            brain.setMemory(MemoryModuleType.TEMPTING_PLAYER, player);
 +            // CraftBukkit start
-+            EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, entityhuman, EntityTargetEvent.TargetReason.TEMPT);
++            EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, player, EntityTargetEvent.TargetReason.TEMPT);
 +            if (event.isCancelled()) {
 +                return;
 +            }
 +            if (event.getTarget() instanceof HumanEntity) {
-+                behaviorcontroller.setMemory(MemoryModuleType.TEMPTING_PLAYER, ((CraftHumanEntity) event.getTarget()).getHandle());
++                brain.setMemory(MemoryModuleType.TEMPTING_PLAYER, ((CraftHumanEntity) event.getTarget()).getHandle());
 +            } else {
-+                behaviorcontroller.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
++                brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
 +            }
 +            // CraftBukkit end
          } else {
-             behaviorcontroller.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
+             brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
          }

From f1ee7a02621374b3431fd8c81b4a825c54b0292e Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 20:30:29 +0100
Subject: [PATCH 091/285] net.minecraft.commands.arguments(.{blocks|item})

---
 .../arguments/EntityArgument.java.patch       | 32 ++++++++++++
 .../arguments/MessageArgument.java.patch      | 32 ++++++------
 .../blocks/BlockStateParser.java.patch        | 11 ++++
 .../arguments/EntityArgument.java.patch       | 52 -------------------
 .../blocks/BlockStateParser.java.patch        | 38 --------------
 .../arguments/item/ItemInput.java.patch       | 10 ----
 6 files changed, 59 insertions(+), 116 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/commands/arguments/MessageArgument.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/commands/arguments/EntityArgument.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/commands/arguments/item/ItemInput.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
new file mode 100644
index 0000000000..30696dc07f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/commands/arguments/EntityArgument.java
++++ b/net/minecraft/commands/arguments/EntityArgument.java
+@@ -105,9 +_,14 @@
+     }
+ 
+     private EntitySelector parse(StringReader reader, boolean allowSelectors) throws CommandSyntaxException {
++        // CraftBukkit start
++        return this.parse(reader, allowSelectors, true);
++    }
++    private EntitySelector parse(StringReader reader, boolean allowSelectors, boolean overridePermissions) throws CommandSyntaxException {
++        // CraftBukkit end
+         int i = 0;
+         EntitySelectorParser entitySelectorParser = new EntitySelectorParser(reader, allowSelectors);
+-        EntitySelector entitySelector = entitySelectorParser.parse();
++        EntitySelector entitySelector = entitySelectorParser.parse(overridePermissions); // CraftBukkit
+         if (entitySelector.getMaxResults() > 1 && this.single) {
+             if (this.playersOnly) {
+                 reader.setCursor(0);
+@@ -129,7 +_,12 @@
+         if (context.getSource() instanceof SharedSuggestionProvider sharedSuggestionProvider) {
+             StringReader stringReader = new StringReader(builder.getInput());
+             stringReader.setCursor(builder.getStart());
+-            EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, EntitySelectorParser.allowSelectors(sharedSuggestionProvider));
++            // Paper start - Fix EntityArgument permissions
++            final boolean permission = sharedSuggestionProvider instanceof CommandSourceStack stack
++                ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector")
++                : sharedSuggestionProvider.hasPermission(2);
++            EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, permission);
++            // Paper end - Fix EntityArgument permissions
+ 
+             try {
+                 entitySelectorParser.parse();
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/MessageArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/commands/arguments/MessageArgument.java.patch
rename to paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
index ba048d7f68..9c9cb202be 100644
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/MessageArgument.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
@@ -1,30 +1,30 @@
 --- a/net/minecraft/commands/arguments/MessageArgument.java
 +++ b/net/minecraft/commands/arguments/MessageArgument.java
-@@ -40,6 +40,11 @@
+@@ -40,6 +_,11 @@
  
-     public static void resolveChatMessage(CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
-         MessageArgument.Message message = context.getArgument(name, MessageArgument.Message.class);
-+    // Paper start
-+        resolveChatMessage(message, context, name, callback);
+     public static void resolveChatMessage(CommandContext<CommandSourceStack> context, String key, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
+         MessageArgument.Message message = context.getArgument(key, MessageArgument.Message.class);
++        // Paper start
++        resolveChatMessage(message, context, key, callback);
 +    }
-+    public static void resolveChatMessage(MessageArgument.Message message, CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
-+    // Paper end
++    public static void resolveChatMessage(MessageArgument.Message message, CommandContext<CommandSourceStack> context, String key, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
++        // Paper end
          CommandSourceStack commandSourceStack = context.getSource();
          Component component = message.resolveComponent(commandSourceStack);
-         CommandSigningContext commandSigningContext = commandSourceStack.getSigningContext();
-@@ -54,17 +59,21 @@
+         CommandSigningContext signingContext = commandSourceStack.getSigningContext();
+@@ -54,17 +_,21 @@
      private static void resolveSignedMessage(Consumer<PlayerChatMessage> callback, CommandSourceStack source, PlayerChatMessage message) {
-         MinecraftServer minecraftServer = source.getServer();
+         MinecraftServer server = source.getServer();
          CompletableFuture<FilteredText> completableFuture = filterPlainText(source, message);
--        Component component = minecraftServer.getChatDecorator().decorate(source.getPlayer(), message.decoratedContent());
--        source.getChatMessageChainer().append(completableFuture, filtered -> {
--            PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(component).filter(filtered.mask());
+-        Component component = server.getChatDecorator().decorate(source.getPlayer(), message.decoratedContent());
+-        source.getChatMessageChainer().append(completableFuture, filteredText -> {
+-            PlayerChatMessage playerChatMessage = message.withUnsignedContent(component).filter(filteredText.mask());
 +        // Paper start - support asynchronous chat decoration
-+        CompletableFuture<Component> componentFuture = minecraftServer.getChatDecorator().decorate(source.getPlayer(), source, message.decoratedContent());
++        CompletableFuture<Component> componentFuture = server.getChatDecorator().decorate(source.getPlayer(), source, message.decoratedContent());
 +        source.getChatMessageChainer().append(CompletableFuture.allOf(completableFuture, componentFuture), filtered -> {
-+            PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask());
++            PlayerChatMessage playerChatMessage = message.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask());
 +            // Paper end - support asynchronous chat decoration
-             callback.accept(playerChatMessage2);
+             callback.accept(playerChatMessage);
          });
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
new file mode 100644
index 0000000000..5d7a0a0c8b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/commands/arguments/blocks/BlockStateParser.java
++++ b/net/minecraft/commands/arguments/blocks/BlockStateParser.java
+@@ -69,7 +_,7 @@
+     private final StringReader reader;
+     private final boolean forTesting;
+     private final boolean allowNbt;
+-    private final Map<Property<?>, Comparable<?>> properties = Maps.newHashMap();
++    private final Map<Property<?>, Comparable<?>> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable
+     private final Map<String, String> vagueProperties = Maps.newHashMap();
+     private ResourceLocation id = ResourceLocation.withDefaultNamespace("");
+     @Nullable
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/EntityArgument.java.patch b/paper-server/patches/unapplied/net/minecraft/commands/arguments/EntityArgument.java.patch
deleted file mode 100644
index 9c45cfda8f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/EntityArgument.java.patch
+++ /dev/null
@@ -1,52 +0,0 @@
---- a/net/minecraft/commands/arguments/EntityArgument.java
-+++ b/net/minecraft/commands/arguments/EntityArgument.java
-@@ -102,21 +102,27 @@
-     }
- 
-     private EntitySelector parse(StringReader reader, boolean allowAtSelectors) throws CommandSyntaxException {
-+        // CraftBukkit start
-+        return this.parse(reader, allowAtSelectors, false);
-+    }
-+
-+    public EntitySelector parse(StringReader stringreader, boolean flag, boolean overridePermissions) throws CommandSyntaxException {
-+        // CraftBukkit end
-         boolean flag1 = false;
--        EntitySelectorParser argumentparserselector = new EntitySelectorParser(reader, allowAtSelectors);
--        EntitySelector entityselector = argumentparserselector.parse();
-+        EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, flag);
-+        EntitySelector entityselector = argumentparserselector.parse(overridePermissions); // CraftBukkit
- 
-         if (entityselector.getMaxResults() > 1 && this.single) {
-             if (this.playersOnly) {
--                reader.setCursor(0);
--                throw EntityArgument.ERROR_NOT_SINGLE_PLAYER.createWithContext(reader);
-+                stringreader.setCursor(0);
-+                throw EntityArgument.ERROR_NOT_SINGLE_PLAYER.createWithContext(stringreader);
-             } else {
--                reader.setCursor(0);
--                throw EntityArgument.ERROR_NOT_SINGLE_ENTITY.createWithContext(reader);
-+                stringreader.setCursor(0);
-+                throw EntityArgument.ERROR_NOT_SINGLE_ENTITY.createWithContext(stringreader);
-             }
-         } else if (entityselector.includesEntities() && this.playersOnly && !entityselector.isSelfSelector()) {
--            reader.setCursor(0);
--            throw EntityArgument.ERROR_ONLY_PLAYERS_ALLOWED.createWithContext(reader);
-+            stringreader.setCursor(0);
-+            throw EntityArgument.ERROR_ONLY_PLAYERS_ALLOWED.createWithContext(stringreader);
-         } else {
-             return entityselector;
-         }
-@@ -129,7 +135,12 @@
-             StringReader stringreader = new StringReader(suggestionsbuilder.getInput());
- 
-             stringreader.setCursor(suggestionsbuilder.getStart());
--            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, EntitySelectorParser.allowSelectors(icompletionprovider));
-+            // Paper start - Fix EntityArgument permissions
-+            final boolean permission = object instanceof CommandSourceStack stack
-+                    ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector")
-+                    : icompletionprovider.hasPermission(2);
-+            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission);
-+            // Paper end - Fix EntityArgument permissions
- 
-             try {
-                 argumentparserselector.parse();
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch b/paper-server/patches/unapplied/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
deleted file mode 100644
index 46961c4f64..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/commands/arguments/blocks/BlockStateParser.java
-+++ b/net/minecraft/commands/arguments/blocks/BlockStateParser.java
-@@ -67,7 +67,7 @@
-     private final StringReader reader;
-     private final boolean forTesting;
-     private final boolean allowNbt;
--    private final Map<Property<?>, Comparable<?>> properties = Maps.newHashMap();
-+    private final Map<Property<?>, Comparable<?>> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable
-     private final Map<String, String> vagueProperties = Maps.newHashMap();
-     private ResourceLocation id = ResourceLocation.withDefaultNamespace("");
-     @Nullable
-@@ -275,7 +275,7 @@
-         Iterator iterator = property.getPossibleValues().iterator();
- 
-         while (iterator.hasNext()) {
--            T t0 = (Comparable) iterator.next();
-+            T t0 = (T) iterator.next(); // CraftBukkit - decompile error
- 
-             if (t0 instanceof Integer integer) {
-                 builder.suggest(integer);
-@@ -545,7 +545,7 @@
-         Optional<T> optional = property.getValue(value);
- 
-         if (optional.isPresent()) {
--            this.state = (BlockState) this.state.setValue(property, (Comparable) optional.get());
-+            this.state = (BlockState) this.state.setValue(property, (T) optional.get()); // CraftBukkit - decompile error
-             this.properties.put(property, (Comparable) optional.get());
-         } else {
-             this.reader.setCursor(cursor);
-@@ -581,7 +581,7 @@
-     private static <T extends Comparable<T>> void appendProperty(StringBuilder builder, Property<T> property, Comparable<?> value) {
-         builder.append(property.getName());
-         builder.append('=');
--        builder.append(property.getName(value));
-+        builder.append(property.getName((T) value)); // CraftBukkit - decompile error
-     }
- 
-     public static record BlockResult(BlockState blockState, Map<Property<?>, Comparable<?>> properties, @Nullable CompoundTag nbt) {
diff --git a/paper-server/patches/unapplied/net/minecraft/commands/arguments/item/ItemInput.java.patch b/paper-server/patches/unapplied/net/minecraft/commands/arguments/item/ItemInput.java.patch
deleted file mode 100644
index a20c412208..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/commands/arguments/item/ItemInput.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/commands/arguments/item/ItemInput.java
-+++ b/net/minecraft/commands/arguments/item/ItemInput.java
-@@ -78,6 +78,6 @@
-     }
- 
-     private String getItemName() {
--        return this.item.unwrapKey().map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString();
-+        return this.item.unwrapKey().<Object>map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString(); // Paper - decompile fix
-     }
- }

From ea5c599077e62da6ed3ab6f6e9853c790815b719 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 14:39:36 -0500
Subject: [PATCH 092/285] /net/minecraft/world/entity/monster

---
 .../monster/AbstractSkeleton.java.patch       |  79 +++++
 .../world/entity/monster/Bogged.java.patch    |  51 ++-
 .../entity/monster/CaveSpider.java.patch      |  11 +
 .../world/entity/monster/Creeper.java.patch   | 114 +++++++
 .../world/entity/monster/Drowned.java.patch   |   6 +-
 .../entity/monster/ElderGuardian.java.patch   |  11 +
 .../world/entity/monster/EnderMan.java.patch  | 144 +++++++++
 .../world/entity/monster/Endermite.java.patch |  11 +
 .../world/entity/monster/Evoker.java.patch    |  11 +
 .../world/entity/monster/Ghast.java.patch     |  23 ++
 .../world/entity/monster/Guardian.java.patch  |  16 +-
 .../world/entity/monster/Husk.java.patch      |  11 +
 .../entity/monster/Illusioner.java.patch      |  36 ++-
 .../world/entity/monster/Monster.java.patch   |   6 +-
 .../world/entity/monster/Phantom.java.patch   |  69 ++++
 .../world/entity/monster/Pillager.java.patch  |  11 +
 .../world/entity/monster/Ravager.java.patch   |  31 ++
 .../world/entity/monster/Shulker.java.patch   |  46 +++
 .../entity/monster/Silverfish.java.patch      |  40 +++
 .../world/entity/monster/Skeleton.java.patch  |  13 +-
 .../world/entity/monster/Slime.java.patch     | 219 +++++++++++++
 .../monster/SpellcasterIllager.java.patch     |  16 +-
 .../world/entity/monster/Spider.java.patch    |  20 +-
 .../world/entity/monster/Strider.java.patch   |  12 +-
 .../world/entity/monster/Vex.java.patch       |  26 +-
 .../entity/monster/Vindicator.java.patch      |   2 +-
 .../world/entity/monster/Witch.java.patch     |  63 ++--
 .../entity/monster/WitherSkeleton.java.patch  |  19 ++
 .../world/entity/monster/Zombie.java.patch    | 239 ++++++++++++++
 .../entity/monster/ZombieVillager.java.patch  | 107 ++++++
 .../entity/monster/ZombifiedPiglin.java.patch |  62 ++++
 .../monster/AbstractSkeleton.java.patch       |  74 -----
 .../entity/monster/CaveSpider.java.patch      |  11 -
 .../world/entity/monster/Creeper.java.patch   | 132 --------
 .../entity/monster/ElderGuardian.java.patch   |  11 -
 .../world/entity/monster/EnderMan.java.patch  | 157 ---------
 .../world/entity/monster/Endermite.java.patch |  21 --
 .../world/entity/monster/Evoker.java.patch    |  11 -
 .../world/entity/monster/Ghast.java.patch     |  25 --
 .../world/entity/monster/Husk.java.patch      |  11 -
 .../world/entity/monster/Phantom.java.patch   |  78 -----
 .../world/entity/monster/Pillager.java.patch  |  21 --
 .../world/entity/monster/Ravager.java.patch   |  41 ---
 .../world/entity/monster/Shulker.java.patch   |  59 ----
 .../entity/monster/Silverfish.java.patch      |  51 ---
 .../world/entity/monster/Slime.java.patch     | 239 --------------
 .../entity/monster/WitherSkeleton.java.patch  |  19 --
 .../world/entity/monster/Zombie.java.patch    | 304 ------------------
 .../entity/monster/ZombieVillager.java.patch  | 149 ---------
 .../entity/monster/ZombifiedPiglin.java.patch |  67 ----
 50 files changed, 1386 insertions(+), 1620 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Bogged.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Drowned.java.patch (81%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Guardian.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Illusioner.java.patch (69%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Monster.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Skeleton.java.patch (76%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch (50%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Spider.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Strider.java.patch (57%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Vex.java.patch (52%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Vindicator.java.patch (97%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/monster/Witch.java.patch (64%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/CaveSpider.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Creeper.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/ElderGuardian.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/EnderMan.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Endermite.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Evoker.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ghast.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Husk.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Phantom.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Pillager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ravager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Shulker.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Silverfish.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Slime.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/Zombie.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombieVillager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
new file mode 100644
index 0000000000..35bee18fdb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
++++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
+@@ -64,6 +_,11 @@
+             AbstractSkeleton.this.setAggressive(true);
+         }
+     };
++    // Paper start - shouldBurnInDay API
++    private boolean shouldBurnInDay = true;
++    public boolean shouldBurnInDay() { return shouldBurnInDay; }
++    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
++    // Paper end - shouldBurnInDay API
+ 
+     protected AbstractSkeleton(EntityType<? extends AbstractSkeleton> entityType, Level level) {
+         super(entityType, level);
+@@ -97,7 +_,7 @@
+ 
+     @Override
+     public void aiStep() {
+-        boolean isSunBurnTick = this.isSunBurnTick();
++        boolean isSunBurnTick = this.shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API
+         if (isSunBurnTick) {
+             ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
+             if (!itemBySlot.isEmpty()) {
+@@ -145,7 +_,7 @@
+         this.populateDefaultEquipmentSlots(random, difficulty);
+         this.populateDefaultEquipmentEnchantments(level, random, difficulty);
+         this.reassessWeaponGoal();
+-        this.setCanPickUpLoot(random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier());
++        this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot
+         if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+             LocalDate localDate = LocalDate.now();
+             int i = localDate.get(ChronoField.DAY_OF_MONTH);
+@@ -196,9 +_,19 @@
+         double d2 = target.getZ() - this.getZ();
+         double squareRoot = Math.sqrt(d * d + d2 * d2);
+         if (this.level() instanceof ServerLevel serverLevel) {
++            // CraftBukkit start
++            org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), arrow.getPickupItem(), arrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper - improve entity shhot bow event - add arrow stack to event
++            if (event.isCancelled()) {
++                event.getProjectile().remove();
++                return;
++            }
++
++            if (event.getProjectile() == arrow.getBukkitEntity()) {
++            // CraftBukkit end
+             Projectile.spawnProjectileUsingShoot(
+                 arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
+             );
++            } // CraftBukkit
+         }
+ 
+         this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
+@@ -222,11 +_,23 @@
+     public void readAdditionalSaveData(CompoundTag compound) {
+         super.readAdditionalSaveData(compound);
+         this.reassessWeaponGoal();
+-    }
++        // Paper start - shouldBurnInDay API
++        if (compound.contains("Paper.ShouldBurnInDay")) {
++            this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
++        }
++        // Paper end - shouldBurnInDay API
++    }
++    // Paper start - shouldBurnInDay API
++    @Override
++    public void addAdditionalSaveData(final net.minecraft.nbt.CompoundTag nbt) {
++        super.addAdditionalSaveData(nbt);
++        nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
++    }
++    // Paper end - shouldBurnInDay API
+ 
+     @Override
+-    public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
+-        super.setItemSlot(slot, stack);
++    public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change
++        super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change
+         if (!this.level().isClientSide) {
+             this.reassessWeaponGoal();
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Bogged.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Bogged.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
index 1cd68614bd..a9624e7f90 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Bogged.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
@@ -1,22 +1,14 @@
 --- a/net/minecraft/world/entity/monster/Bogged.java
 +++ b/net/minecraft/world/entity/monster/Bogged.java
-@@ -27,6 +27,7 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.storage.loot.BuiltInLootTables;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
- 
- public class Bogged extends AbstractSkeleton implements Shearable {
- 
-@@ -79,7 +80,20 @@
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.shear(worldserver, SoundSource.PLAYERS, itemstack);
+@@ -72,7 +_,20 @@
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
+             if (this.level() instanceof ServerLevel serverLevel) {
+-                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand);
 +                // CraftBukkit start
 +                // Paper start - custom shear drops
-+                java.util.List<ItemStack> drops = this.generateDefaultDrops(worldserver, itemstack);
-+                org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
++                java.util.List<ItemStack> drops = this.generateDefaultDrops(serverLevel, itemInHand);
++                org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
 +                if (event != null) {
 +                    if (event.isCancelled()) {
 +                        // this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients // Paper - no longer needed
@@ -26,16 +18,16 @@
 +                // Paper end - custom shear drops
 +                }
 +                // CraftBukkit end
-+                this.shear(worldserver, SoundSource.PLAYERS, itemstack, drops); // Paper - custom shear drops
++                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand, drops); // Paper - custom shear drops
                  this.gameEvent(GameEvent.SHEAR, player);
-                 itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
+                 itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
              }
-@@ -133,15 +147,36 @@
+@@ -125,15 +_,34 @@
  
      @Override
-     public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears) {
+     public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
 +    // Paper start - custom shear drops
-+        this.shear(world, shearedSoundCategory, shears, this.generateDefaultDrops(world, shears));
++        this.shear(level, soundSource, shears, this.generateDefaultDrops(level, shears));
 +    }
 +
 +    @Override
@@ -48,25 +40,24 @@
 +    }
 +
 +    @Override
-+    public void shear(ServerLevel world, SoundSource shearedSoundCategory, ItemStack shears, java.util.List<ItemStack> drops) {
++    public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<ItemStack> drops) {
 +    // Paper end - custom shear drops
-         world.playSound((Player) null, (Entity) this, SoundEvents.BOGGED_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
--        this.spawnShearedMushrooms(world, shears);
-+        this.spawnShearedMushrooms(world, shears, drops); // Paper - custom shear drops
+         level.playSound(null, this, SoundEvents.BOGGED_SHEAR, soundSource, 1.0F, 1.0F);
+         this.spawnShearedMushrooms(level, shears);
          this.setSheared(true);
      }
  
--    private void spawnShearedMushrooms(ServerLevel world, ItemStack shears) {
--        this.dropFromShearingLootTable(world, BuiltInLootTables.BOGGED_SHEAR, shears, (worldserver1, itemstack1) -> {
+-    private void spawnShearedMushrooms(ServerLevel level, ItemStack stack) {
+-        this.dropFromShearingLootTable(
+-            level, BuiltInLootTables.BOGGED_SHEAR, stack, (serverLevel, itemStack) -> this.spawnAtLocation(serverLevel, itemStack, this.getBbHeight())
+-        );
 +    // Paper start - custom shear drops
 +    private void spawnShearedMushrooms(ServerLevel world, ItemStack shears, java.util.List<ItemStack> drops) {
 +        final ServerLevel worldserver1 = world; // Named for lambda consumption
 +        this.forceDrops = true; // Paper - Add missing forceDrop toggles
-+        drops.forEach(itemstack1 -> {
-+    // Paper end - custom shear drops
-             this.spawnAtLocation(worldserver1, itemstack1, this.getBbHeight());
-         });
++        drops.forEach(itemstack1 -> this.spawnAtLocation(world, shears, this.getBbHeight()));
 +        this.forceDrops = false; // Paper - Add missing forceDrop toggles
++        // Paper end - custom shear drops
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch
new file mode 100644
index 0000000000..2b7ce5164e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/CaveSpider.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/CaveSpider.java
++++ b/net/minecraft/world/entity/monster/CaveSpider.java
+@@ -38,7 +_,7 @@
+                 }
+ 
+                 if (i > 0) {
+-                    ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this);
++                    ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
new file mode 100644
index 0000000000..30d6bab42b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
@@ -0,0 +1,114 @@
+--- a/net/minecraft/world/entity/monster/Creeper.java
++++ b/net/minecraft/world/entity/monster/Creeper.java
+@@ -49,6 +_,7 @@
+     public int maxSwell = 30;
+     public int explosionRadius = 3;
+     private int droppedSkulls;
++    public Entity entityIgniter; // CraftBukkit
+ 
+     public Creeper(EntityType<? extends Creeper> entityType, Level level) {
+         super(entityType, level);
+@@ -121,7 +_,7 @@
+         }
+ 
+         if (compound.getBoolean("ignited")) {
+-            this.ignite();
++            this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event
+         }
+     }
+ 
+@@ -204,8 +_,19 @@
+     @Override
+     public void thunderHit(ServerLevel level, LightningBolt lightning) {
+         super.thunderHit(level, lightning);
++        // CraftBukkit start
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.callCreeperPowerEvent(this, lightning, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) {
++            return;
++        }
++        // CraftBukkit end
+         this.entityData.set(DATA_IS_POWERED, true);
+     }
++    // CraftBukkit start
++    public void setPowered(boolean powered) {
++        this.entityData.set(Creeper.DATA_IS_POWERED, powered);
++    }
++    // CraftBukkit end
++
+ 
+     @Override
+     protected InteractionResult mobInteract(Player player, InteractionHand hand) {
+@@ -215,8 +_,9 @@
+             this.level()
+                 .playSound(player, this.getX(), this.getY(), this.getZ(), soundEvent, this.getSoundSource(), 1.0F, this.random.nextFloat() * 0.4F + 0.8F);
+             if (!this.level().isClientSide) {
++                this.entityIgniter = player; // CraftBukkit
+                 this.ignite();
+-                if (!itemInHand.isDamageableItem()) {
++                if (itemInHand.getMaxDamage() == 0) { // CraftBukkit - fix MC-264285: unbreakable flint and steels are completely consumed when igniting a creeper
+                     itemInHand.shrink(1);
+                 } else {
+                     itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
+@@ -232,18 +_,29 @@
+     public void explodeCreeper() {
+         if (this.level() instanceof ServerLevel serverLevel) {
+             float f = this.isPowered() ? 2.0F : 1.0F;
++            // CraftBukkit start
++            org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false);
++            if (!event.isCancelled()) {
++            // CraftBukkit end
+             this.dead = true;
+-            serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), this.explosionRadius * f, Level.ExplosionInteraction.MOB);
++            serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this)
+             this.spawnLingeringCloud();
+             this.triggerOnDeathMobEffects(serverLevel, Entity.RemovalReason.KILLED);
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
++            // CraftBukkit start
++            } else {
++                this.swell = 0;
++                this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper
++            }
++            // CraftBukkit end
+         }
+     }
+ 
+     private void spawnLingeringCloud() {
+         Collection<MobEffectInstance> activeEffects = this.getActiveEffects();
+-        if (!activeEffects.isEmpty()) {
++        if (!activeEffects.isEmpty() && !this.level().paperConfig().entities.behavior.disableCreeperLingeringEffect) { // Paper - Option to disable creeper lingering effect
+             AreaEffectCloud areaEffectCloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
++            areaEffectCloud.setOwner(this); // CraftBukkit
+             areaEffectCloud.setRadius(2.5F);
+             areaEffectCloud.setRadiusOnUse(-0.5F);
+             areaEffectCloud.setWaitTime(10);
+@@ -254,16 +_,26 @@
+                 areaEffectCloud.addEffect(new MobEffectInstance(mobEffectInstance));
+             }
+ 
+-            this.level().addFreshEntity(areaEffectCloud);
+-        }
+-    }
++            this.level().addFreshEntity(areaEffectCloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit
++        }
++    }
++    // Paper start - CreeperIgniteEvent
++    public void setIgnited(boolean ignited) {
++        if (isIgnited() != ignited) {
++            com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
++            if (event.callEvent()) {
++                this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited());
++            }
++        }
++    }
++    // Paper end - CreeperIgniteEvent
+ 
+     public boolean isIgnited() {
+         return this.entityData.get(DATA_IS_IGNITED);
+     }
+ 
+     public void ignite() {
+-        this.entityData.set(DATA_IS_IGNITED, true);
++        setIgnited(true); // Paper - CreeperIgniteEvent
+     }
+ 
+     public boolean canDropMobsSkull() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Drowned.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch
similarity index 81%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Drowned.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch
index 938442254b..f28da7213d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Drowned.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Drowned.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/monster/Drowned.java
 +++ b/net/minecraft/world/entity/monster/Drowned.java
-@@ -85,7 +85,7 @@
+@@ -85,7 +_,7 @@
          this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0));
          this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class));
-         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target)));
+         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> this.okTarget(entity)));
 -        this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
-+        if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));  // Paper - Check drowned for villager aggression config
++        if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config
          this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
          this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false));
          this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch
new file mode 100644
index 0000000000..c15aa5deb4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ElderGuardian.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/ElderGuardian.java
++++ b/net/minecraft/world/entity/monster/ElderGuardian.java
+@@ -65,7 +_,7 @@
+         super.customServerAiStep(level);
+         if ((this.tickCount + this.getId()) % 1200 == 0) {
+             MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2);
+-            List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(level, this, this.position(), 50.0, mobEffectInstance, 1200);
++            List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(level, this, this.position(), 50.0, mobEffectInstance, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent
+             list.forEach(
+                 serverPlayer -> serverPlayer.connection
+                     .send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F))
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
new file mode 100644
index 0000000000..908813f231
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
@@ -0,0 +1,144 @@
+--- a/net/minecraft/world/entity/monster/EnderMan.java
++++ b/net/minecraft/world/entity/monster/EnderMan.java
+@@ -116,10 +_,26 @@
+     }
+ 
+     @Override
+-    public void setTarget(@Nullable LivingEntity livingEntity) {
+-        super.setTarget(livingEntity);
++    public void setTarget(@Nullable LivingEntity target) {
++        // CraftBukkit start - fire event
++        this.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, true);
++    }
++
++    // Paper start - EndermanEscapeEvent
++    private boolean tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason reason) {
++        return new com.destroystokyo.paper.event.entity.EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent();
++    }
++    // Paper end - EndermanEscapeEvent
++
++    @Override
++    public boolean setTarget(LivingEntity target, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++        if (!super.setTarget(target, reason, fireEvent)) {
++            return false;
++        }
++        target = this.getTarget();
++        // CraftBukkit end
+         AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
+-        if (livingEntity == null) {
++        if (target == null) {
+             this.targetChangeTime = 0;
+             this.entityData.set(DATA_CREEPY, false);
+             this.entityData.set(DATA_STARED_AT, false);
+@@ -131,6 +_,7 @@
+                 attribute.addTransientModifier(SPEED_MODIFIER_ATTACKING);
+             }
+         }
++        return true; // CraftBukkit
+     }
+ 
+     @Override
+@@ -212,6 +_,14 @@
+     }
+ 
+     boolean isBeingStaredBy(Player player) {
++        // Paper start - EndermanAttackPlayerEvent
++        final boolean shouldAttack = isBeingStaredBy0(player);
++        final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity());
++        event.setCancelled(!shouldAttack);
++        return event.callEvent();
++    }
++    private boolean isBeingStaredBy0(Player player) {
++        // Paper end - EndermanAttackPlayerEvent
+         return LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM.test(player) && this.isLookingAtMe(player, 0.025, true, false, new double[]{this.getEyeY()});
+     }
+ 
+@@ -251,7 +_,7 @@
+             float lightLevelDependentMagicValue = this.getLightLevelDependentMagicValue();
+             if (lightLevelDependentMagicValue > 0.5F
+                 && level.canSeeSky(this.blockPosition())
+-                && this.random.nextFloat() * 30.0F < (lightLevelDependentMagicValue - 0.4F) * 2.0F) {
++                && this.random.nextFloat() * 30.0F < (lightLevelDependentMagicValue - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent
+                 this.setTarget(null);
+                 this.teleport();
+             }
+@@ -372,11 +_,13 @@
+             } else {
+                 boolean flag1 = flag && this.hurtWithCleanWater(level, damageSource, (ThrownPotion)damageSource.getDirectEntity(), amount);
+ 
++                if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent
+                 for (int i = 0; i < 64; i++) {
+                     if (this.teleport()) {
+                         return true;
+                     }
+                 }
++                } // Paper - EndermanEscapeEvent
+ 
+                 return flag1;
+             }
+@@ -400,6 +_,15 @@
+     public void setBeingStaredAt() {
+         this.entityData.set(DATA_STARED_AT, true);
+     }
++    // Paper start
++    public void setCreepy(boolean creepy) {
++        this.entityData.set(EnderMan.DATA_CREEPY, creepy);
++    }
++
++    public void setHasBeenStaredAt(boolean hasBeenStaredAt) {
++        this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt);
++    }
++    // Paper end
+ 
+     @Override
+     public boolean requiresCustomPersistence() {
+@@ -460,16 +_,19 @@
+             int floor1 = Mth.floor(this.enderman.getY() + random.nextDouble() * 2.0);
+             int floor2 = Mth.floor(this.enderman.getZ() - 1.0 + random.nextDouble() * 2.0);
+             BlockPos blockPos = new BlockPos(floor, floor1, floor2);
+-            BlockState blockState = level.getBlockState(blockPos);
++            BlockState blockState = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent endermen from loading chunks
++            if (blockState == null) return; // Paper - Prevent endermen from loading chunks
+             BlockPos blockPos1 = blockPos.below();
+             BlockState blockState1 = level.getBlockState(blockPos1);
+             BlockState carriedBlock = this.enderman.getCarriedBlock();
+             if (carriedBlock != null) {
+                 carriedBlock = Block.updateFromNeighbourShapes(carriedBlock, this.enderman.level(), blockPos);
+                 if (this.canPlaceBlock(level, blockPos, carriedBlock, blockState, blockState1, blockPos1)) {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockPos, carriedBlock)) { // CraftBukkit - Place event
+                     level.setBlock(blockPos, carriedBlock, 3);
+                     level.gameEvent(GameEvent.BLOCK_PLACE, blockPos, GameEvent.Context.of(this.enderman, carriedBlock));
+                     this.enderman.setCarriedBlock(null);
++                    } // CraftBukkit
+                 }
+             }
+         }
+@@ -567,7 +_,7 @@
+             } else {
+                 if (this.target != null && !this.enderman.isPassenger()) {
+                     if (this.enderman.isBeingStaredBy((Player)this.target)) {
+-                        if (this.target.distanceToSqr(this.enderman) < 16.0) {
++                        if (this.target.distanceToSqr(this.enderman) < 16.0 && this.enderman.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.STARE)) { // Paper - EndermanEscapeEvent
+                             this.enderman.teleport();
+                         }
+ 
+@@ -606,15 +_,18 @@
+             int floor1 = Mth.floor(this.enderman.getY() + random.nextDouble() * 3.0);
+             int floor2 = Mth.floor(this.enderman.getZ() - 2.0 + random.nextDouble() * 4.0);
+             BlockPos blockPos = new BlockPos(floor, floor1, floor2);
+-            BlockState blockState = level.getBlockState(blockPos);
++            BlockState blockState = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent endermen from loading chunks
++            if (blockState == null) return; // Paper - Prevent endermen from loading chunks
+             Vec3 vec3 = new Vec3(this.enderman.getBlockX() + 0.5, floor1 + 0.5, this.enderman.getBlockZ() + 0.5);
+             Vec3 vec31 = new Vec3(floor + 0.5, floor1 + 0.5, floor2 + 0.5);
+             BlockHitResult blockHitResult = level.clip(new ClipContext(vec3, vec31, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
+             boolean flag = blockHitResult.getBlockPos().equals(blockPos);
+             if (blockState.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockPos, blockState.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state
+                 level.removeBlock(blockPos, false);
+                 level.gameEvent(GameEvent.BLOCK_DESTROY, blockPos, GameEvent.Context.of(this.enderman, blockState));
+                 this.enderman.setCarriedBlock(blockState.getBlock().defaultBlockState());
++                } // CraftBukkit
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch
new file mode 100644
index 0000000000..4677099a10
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Endermite.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Endermite.java
++++ b/net/minecraft/world/entity/monster/Endermite.java
+@@ -121,7 +_,7 @@
+             }
+ 
+             if (this.life >= 2400) {
+-                this.discard();
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch
new file mode 100644
index 0000000000..5329db11c5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Evoker.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Evoker.java
++++ b/net/minecraft/world/entity/monster/Evoker.java
+@@ -264,7 +_,7 @@
+                         serverLevel.getScoreboard().addPlayerToTeam(vex.getScoreboardName(), team);
+                     }
+ 
+-                    serverLevel.addFreshEntityWithPassengers(vex);
++                    serverLevel.addFreshEntityWithPassengers(vex, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPELL); // CraftBukkit - Add SpawnReason
+                     serverLevel.gameEvent(GameEvent.ENTITY_PLACE, blockPos, GameEvent.Context.of(Evoker.this));
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
new file mode 100644
index 0000000000..9f53b5d8a3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/monster/Ghast.java
++++ b/net/minecraft/world/entity/monster/Ghast.java
+@@ -63,6 +_,12 @@
+         return this.explosionPower;
+     }
+ 
++    // Paper start
++    public void setExplosionPower(int explosionPower) {
++        this.explosionPower = explosionPower;
++    }
++    // Paper end
++
+     @Override
+     protected boolean shouldDespawnInPeaceful() {
+         return true;
+@@ -277,6 +_,7 @@
+                         }
+ 
+                         LargeFireball largeFireball = new LargeFireball(level, this.ghast, vec3.normalize(), this.ghast.getExplosionPower());
++                        largeFireball.bukkitYield = largeFireball.explosionPower = this.ghast.getExplosionPower(); // CraftBukkit - set bukkitYield when setting explosionpower
+                         largeFireball.setPos(this.ghast.getX() + viewVector.x * 4.0, this.ghast.getY(0.5) + 0.5, largeFireball.getZ() + viewVector.z * 4.0);
+                         level.addFreshEntity(largeFireball);
+                         this.chargeTime = -40;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Guardian.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Guardian.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch
index 078be667c0..5dbcc39af7 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Guardian.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Guardian.java.patch
@@ -1,19 +1,19 @@
 --- a/net/minecraft/world/entity/monster/Guardian.java
 +++ b/net/minecraft/world/entity/monster/Guardian.java
-@@ -60,6 +60,7 @@
+@@ -59,6 +_,7 @@
      private boolean clientSideTouchedGround;
      @Nullable
      public RandomStrollGoal randomStrollGoal;
 +    public Guardian.GuardianAttackGoal guardianAttackGoal; // CraftBukkit - add field
  
-     public Guardian(EntityType<? extends Guardian> type, Level world) {
-         super(type, world);
-@@ -75,7 +76,7 @@
-         MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D);
- 
-         this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80);
+     public Guardian(EntityType<? extends Guardian> entityType, Level level) {
+         super(entityType, level);
+@@ -73,7 +_,7 @@
+     protected void registerGoals() {
+         MoveTowardsRestrictionGoal moveTowardsRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0);
+         this.randomStrollGoal = new RandomStrollGoal(this, 1.0, 80);
 -        this.goalSelector.addGoal(4, new Guardian.GuardianAttackGoal(this));
 +        this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
-         this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction);
+         this.goalSelector.addGoal(5, moveTowardsRestrictionGoal);
          this.goalSelector.addGoal(7, this.randomStrollGoal);
          this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch
new file mode 100644
index 0000000000..78b5ff1c16
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Husk.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Husk.java
++++ b/net/minecraft/world/entity/monster/Husk.java
+@@ -57,7 +_,7 @@
+         boolean flag = super.doHurtTarget(level, source);
+         if (flag && this.getMainHandItem().isEmpty() && source instanceof LivingEntity) {
+             float effectiveDifficulty = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+-            ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int)effectiveDifficulty), this);
++            ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int)effectiveDifficulty), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+         }
+ 
+         return flag;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Illusioner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch
similarity index 69%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Illusioner.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch
index b839923b98..288baed666 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Illusioner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch
@@ -1,33 +1,26 @@
 --- a/net/minecraft/world/entity/monster/Illusioner.java
 +++ b/net/minecraft/world/entity/monster/Illusioner.java
-@@ -184,7 +184,17 @@
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
+@@ -180,9 +_,19 @@
+         double d2 = target.getZ() - this.getZ();
+         double squareRoot = Math.sqrt(d * d + d2 * d2);
+         if (this.level() instanceof ServerLevel serverLevel) {
 +            // Paper start - EntityShootBowEvent
-+            org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, target.getUsedItemHand(), 0.8F, true);
++            org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), mobArrow.getPickupItem(), mobArrow, target.getUsedItemHand(), 0.8F, true);
 +            if (event.isCancelled()) {
 +                event.getProjectile().remove();
 +                return;
 +            }
 +
-+            if (event.getProjectile() == entityarrow.getBukkitEntity()) {
-             Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4));
++            if (event.getProjectile() == mobArrow.getBukkitEntity()) {
+             Projectile.spawnProjectileUsingShoot(
+                 mobArrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
+             );
 +            }
 +            // Paper end - EntityShootBowEvent
          }
  
          this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
-@@ -218,7 +228,7 @@
- 
-         @Override
-         protected void performSpellCasting() {
--            Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200));
-+            Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit
-         }
- 
-         @Nullable
-@@ -269,7 +279,7 @@
+@@ -229,7 +_,7 @@
  
          @Override
          protected void performSpellCasting() {
@@ -36,3 +29,12 @@
          }
  
          @Override
+@@ -261,7 +_,7 @@
+ 
+         @Override
+         protected void performSpellCasting() {
+-            Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200));
++            Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit
+         }
+ 
+         @Nullable
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Monster.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Monster.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
index bae67fa57b..842c4f8588 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Monster.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/monster/Monster.java
 +++ b/net/minecraft/world/entity/monster/Monster.java
-@@ -92,7 +92,7 @@
+@@ -92,7 +_,7 @@
              return false;
          } else {
-             DimensionType dimensionType = world.dimensionType();
+             DimensionType dimensionType = level.dimensionType();
 -            int i = dimensionType.monsterSpawnBlockLightLimit();
 +            int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning
-             if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) {
+             if (i < 15 && level.getBrightness(LightLayer.BLOCK, pos) > i) {
                  return false;
              } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
new file mode 100644
index 0000000000..ed1eed8680
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/entity/monster/Phantom.java
++++ b/net/minecraft/world/entity/monster/Phantom.java
+@@ -141,7 +_,7 @@
+ 
+     @Override
+     public void aiStep() {
+-        if (this.isAlive() && this.isSunBurnTick()) {
++        if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API
+             this.igniteForSeconds(8.0F);
+         }
+ 
+@@ -165,6 +_,14 @@
+         }
+ 
+         this.setPhantomSize(compound.getInt("Size"));
++        // Paper start
++        if (compound.hasUUID("Paper.SpawningEntity")) {
++            this.spawningEntity = compound.getUUID("Paper.SpawningEntity");
++        }
++        if (compound.contains("Paper.ShouldBurnInDay")) {
++            this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
++        }
++        // Paper end
+     }
+ 
+     @Override
+@@ -174,6 +_,12 @@
+         compound.putInt("AY", this.anchorPoint.getY());
+         compound.putInt("AZ", this.anchorPoint.getZ());
+         compound.putInt("Size", this.getPhantomSize());
++        // Paper start
++        if (this.spawningEntity != null) {
++            compound.putUUID("Paper.SpawningEntity", this.spawningEntity);
++        }
++        compound.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
++        // Paper end
+     }
+ 
+     @Override
+@@ -222,6 +_,19 @@
+         return targetingConditions.test(level, this, entity);
+     }
+ 
++    // Paper start
++    @Nullable
++    java.util.UUID spawningEntity;
++
++    @Nullable
++    public java.util.UUID getSpawningEntity() {
++        return this.spawningEntity;
++    }
++    public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
++    private boolean shouldBurnInDay = true;
++    public boolean shouldBurnInDay() { return shouldBurnInDay; }
++    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
++    // Paper end
+     static enum AttackPhase {
+         CIRCLE,
+         SWOOP;
+@@ -247,7 +_,8 @@
+ 
+                     for (Player player : nearbyPlayers) {
+                         if (Phantom.this.canAttack(serverLevel, player, TargetingConditions.DEFAULT)) {
+-                            Phantom.this.setTarget(player);
++                            if (!level().paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.IS_INSOMNIAC.test(player)) // Paper - Add phantom creative and insomniac controls
++                            Phantom.this.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason
+                             return true;
+                         }
+                     }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch
new file mode 100644
index 0000000000..baac9f090b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Pillager.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Pillager.java
++++ b/net/minecraft/world/entity/monster/Pillager.java
+@@ -214,7 +_,7 @@
+             this.onItemPickup(entity);
+             ItemStack itemStack = this.inventory.addItem(item);
+             if (itemStack.isEmpty()) {
+-                entity.discard();
++                entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+             } else {
+                 item.setCount(itemStack.getCount());
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch
new file mode 100644
index 0000000000..00100011ed
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ravager.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/entity/monster/Ravager.java
++++ b/net/minecraft/world/entity/monster/Ravager.java
+@@ -151,12 +_,19 @@
+                     BlockState blockState = serverLevel.getBlockState(blockPos);
+                     Block block = blockState.getBlock();
+                     if (block instanceof LeavesBlock) {
++                        // CraftBukkit start
++                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                            continue;
++                        }
++                        // CraftBukkit end
+                         flag = serverLevel.destroyBlock(blockPos, true, this) || flag;
+                     }
+                 }
+ 
+                 if (!flag && this.onGround()) {
++                    if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
+                     this.jumpFromGround();
++                    } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
+                 }
+             }
+ 
+@@ -257,7 +_,7 @@
+         double d = entity.getX() - this.getX();
+         double d1 = entity.getZ() - this.getZ();
+         double max = Math.max(d * d + d1 * d1, 0.001);
+-        entity.push(d / max * 4.0, 0.2, d1 / max * 4.0);
++        entity.push(d / max * 4.0, 0.2, d1 / max * 4.0, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch
new file mode 100644
index 0000000000..8bd9da3693
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Shulker.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/monster/Shulker.java
++++ b/net/minecraft/world/entity/monster/Shulker.java
+@@ -274,7 +_,13 @@
+ 
+     @Override
+     public void stopRiding() {
+-        super.stopRiding();
++        // Paper start - Force entity dismount during teleportation
++        this.stopRiding(false);
++    }
++    @Override
++    public void stopRiding(boolean suppressCancellation) {
++        super.stopRiding(suppressCancellation);
++        // Paper end - Force entity dismount during teleportation
+         if (this.level().isClientSide) {
+             this.clientOldAttachPosition = this.blockPosition();
+         }
+@@ -387,6 +_,14 @@
+                     && this.level().getWorldBorder().isWithinBounds(blockPos1)
+                     && this.level().noCollision(this, new AABB(blockPos1).deflate(1.0E-6))) {
+                     Direction direction = this.findAttachableSurface(blockPos1);
++                    // CraftBukkit start
++                    org.bukkit.event.entity.EntityTeleportEvent teleportEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, blockPos1.getX(), blockPos1.getY(), blockPos1.getZ());
++                    if (teleportEvent.isCancelled() || teleportEvent.getTo() == null) { // Paper
++                        return false;
++                    } else {
++                        blockPos1 = org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(teleportEvent.getTo());
++                    }
++                    // CraftBukkit end
+                     if (direction != null) {
+                         this.unRide();
+                         this.setAttachFace(direction);
+@@ -453,7 +_,12 @@
+                 if (shulker != null) {
+                     shulker.setVariant(this.getVariant());
+                     shulker.moveTo(vec3);
+-                    this.level().addFreshEntity(shulker);
++                    // Paper start - Shulker duplicate event
++                    if (!new io.papermc.paper.event.entity.ShulkerDuplicateEvent((org.bukkit.entity.Shulker) shulker.getBukkitEntity(), (org.bukkit.entity.Shulker) this.getBukkitEntity()).callEvent()) {
++                        return;
++                    }
++                    // Paper end - Shulker duplicate event
++                    this.level().addFreshEntity(shulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - the mysteries of life
+                 }
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch
new file mode 100644
index 0000000000..a8c9b55110
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Silverfish.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/monster/Silverfish.java
++++ b/net/minecraft/world/entity/monster/Silverfish.java
+@@ -119,7 +_,7 @@
+             return true;
+         } else {
+             Player nearestPlayer = level.getNearestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 5.0, true);
+-            return nearestPlayer == null;
++            return !(nearestPlayer != null && !nearestPlayer.affectsSpawning) && nearestPlayer == null; // Paper - Affects Spawning API
+         }
+     }
+ 
+@@ -170,9 +_,14 @@
+                 BlockPos blockPos = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5, this.mob.getZ()).relative(this.selectedDirection);
+                 BlockState blockState = levelAccessor.getBlockState(blockPos);
+                 if (InfestedBlock.isCompatibleHostBlock(blockState)) {
++                    // CraftBukkit start
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, InfestedBlock.infestedStateByHost(blockState))) {
++                        return;
++                    }
++                    // CraftBukkit end
+                     levelAccessor.setBlock(blockPos, InfestedBlock.infestedStateByHost(blockState), 3);
+                     this.mob.spawnAnim();
+-                    this.mob.discard();
++                    this.mob.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause
+                 }
+             }
+         }
+@@ -212,6 +_,12 @@
+                             BlockState blockState = level.getBlockState(blockPos1);
+                             Block block = blockState.getBlock();
+                             if (block instanceof InfestedBlock) {
++                                // CraftBukkit start
++                                BlockState afterState = getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? blockState.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockPos1)); // Paper - fix wrong block state
++                                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockPos1, afterState)) { // Paper - fix wrong block state
++                                    continue;
++                                }
++                                // CraftBukkit end
+                                 if (getServerLevel(level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+                                     level.destroyBlock(blockPos1, true, this.silverfish);
+                                 } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Skeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
similarity index 76%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Skeleton.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
index d17fea360f..e847a3c8fd 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Skeleton.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
@@ -1,24 +1,23 @@
 --- a/net/minecraft/world/entity/monster/Skeleton.java
 +++ b/net/minecraft/world/entity/monster/Skeleton.java
-@@ -94,12 +94,19 @@
+@@ -89,11 +_,18 @@
      }
  
      protected void doFreezeConversion() {
--        this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> {
+-        this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), mob -> {
 +        final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam
              if (!this.isSilent()) {
-                 this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0);
+                 this.level().levelEvent(null, 1048, this.blockPosition(), 0);
              }
- 
 -        });
-+        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);// CraftBukkit - add spawn and transform reasons
-+
++        // CraftBukkit start - add spawn and transform reasons
++        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);
 +        // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam
 +        if (stray == null) {
 +            this.conversionTime = 300;
 +        }
 +        // Paper end - Fix issues with mob conversion
-+
++        // CraftBukkit end - add spawn and transform reasons
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
new file mode 100644
index 0000000000..2d78bee973
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
@@ -0,0 +1,219 @@
+--- a/net/minecraft/world/entity/monster/Slime.java
++++ b/net/minecraft/world/entity/monster/Slime.java
+@@ -110,6 +_,7 @@
+         super.addAdditionalSaveData(compound);
+         compound.putInt("Size", this.getSize() - 1);
+         compound.putBoolean("wasOnGround", this.wasOnGround);
++        compound.putBoolean("Paper.canWander", this.canWander); // Paper
+     }
+ 
+     @Override
+@@ -117,6 +_,11 @@
+         this.setSize(compound.getInt("Size") + 1, false);
+         super.readAdditionalSaveData(compound);
+         this.wasOnGround = compound.getBoolean("wasOnGround");
++        // Paper start
++        if (compound.contains("Paper.canWander")) {
++            this.canWander = compound.getBoolean("Paper.canWander");
++        }
++        // Paper end
+     }
+ 
+     public boolean isTiny() {
+@@ -197,6 +_,13 @@
+ 
+     @Override
+     public void remove(Entity.RemovalReason reason) {
++        // CraftBukkit start - add Bukkit remove cause
++        this.remove(reason, null);
++    }
++
++    @Override
++    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        // CraftBukkit end
+         int size = this.getSize();
+         if (!this.level().isClientSide && size > 1 && this.isDeadOrDying()) {
+             float width = this.getDimensions(this.getPose()).width();
+@@ -204,18 +_,45 @@
+             int i = size / 2;
+             int i1 = 2 + this.random.nextInt(3);
+             PlayerTeam team = this.getTeam();
++            // CraftBukkit start
++            org.bukkit.event.entity.SlimeSplitEvent event = new org.bukkit.event.entity.SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), i1);
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled() && event.getCount() > 0) {
++                i1 = event.getCount();
++            } else {
++                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++                return;
++            }
++            java.util.List<net.minecraft.world.entity.LivingEntity> slimes = new java.util.ArrayList<>(i1);
++            // CraftBukkit end
+ 
+             for (int i2 = 0; i2 < i1; i2++) {
+                 float f1 = (i2 % 2 - 0.5F) * f;
+                 float f2 = (i2 / 2 - 0.5F) * f;
+-                this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, team), EntitySpawnReason.TRIGGERED, mob -> {
++                Slime converted = this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, team), EntitySpawnReason.TRIGGERED, (mob) -> { // CraftBukkit
++                    mob.aware = this.aware; // Paper - Fix nerfed slime when splitting
+                     mob.setSize(i, true);
+                     mob.moveTo(this.getX() + f1, this.getY() + 0.5, this.getZ() + f2, this.random.nextFloat() * 360.0F, 0.0F);
+-                });
+-            }
++                // CraftBukkit start
++                }, null, null);
++                if (converted != null) {
++                    slimes.add(converted);
++                }
++                // CraftBukkit end
++            }
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, slimes, org.bukkit.event.entity.EntityTransformEvent.TransformReason.SPLIT).isCancelled()) {
++                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++                return;
++            }
++            for (LivingEntity living : slimes) {
++                this.level().addFreshEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason
++            }
++            // CraftBukkit end
+         }
+ 
+-        super.remove(reason);
++        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
+@@ -281,9 +_,13 @@
+                 return checkMobSpawnRules(entityType, level, spawnReason, pos, random);
+             }
+ 
++            // Paper start - Replace rules for Height in Swamp Biome
++            final double maxHeightSwamp = level.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum;
++            final double minHeightSwamp = level.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum;
++            // Paper end
+             if (level.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS)
+-                && pos.getY() > 50
+-                && pos.getY() < 70
++                && pos.getY() > minHeightSwamp // Paper - Replace rules for Height in Swamp Biome
++                && pos.getY() < maxHeightSwamp // Paper - Replace rules for Height in Swamp Biome
+                 && random.nextFloat() < 0.5F
+                 && random.nextFloat() < level.getMoonBrightness()
+                 && level.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
+@@ -295,8 +_,11 @@
+             }
+ 
+             ChunkPos chunkPos = new ChunkPos(pos);
+-            boolean flag = WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel)level).getSeed(), 987234911L).nextInt(10) == 0;
+-            if (random.nextInt(10) == 0 && flag && pos.getY() < 40) {
++            boolean flag = level.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkPos.x, chunkPos.z, ((WorldGenLevel) level).getSeed(), level.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper
++                // Paper start - Replace rules for Height in Slime Chunks
++                final double maxHeightSlimeChunk = level.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum;
++                if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) {
++                // Paper end - Replace rules for Height in Slime Chunks
+                 return checkMobSpawnRules(entityType, level, spawnReason, pos, random);
+             }
+         }
+@@ -367,7 +_,16 @@
+         @Override
+         public boolean canUse() {
+             LivingEntity target = this.slime.getTarget();
+-            return target != null && this.slime.canAttack(target) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
++
++            // Paper start - Slime pathfinder events
++            if (target == null || !target.isAlive()) {
++                return false;
++            }
++            if (!this.slime.canAttack(target)) {
++                return false;
++            }
++            return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity()).callEvent();
++            // Paper end - Slime pathfinder events
+         }
+ 
+         @Override
+@@ -379,7 +_,16 @@
+         @Override
+         public boolean canContinueToUse() {
+             LivingEntity target = this.slime.getTarget();
+-            return target != null && this.slime.canAttack(target) && --this.growTiredTimer > 0;
++
++            // Paper start - Slime pathfinder events
++            if (target == null || !target.isAlive()) {
++                return false;
++            }
++            if (!this.slime.canAttack(target)) {
++                return false;
++            }
++            return --this.growTiredTimer > 0 && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity()).callEvent();
++            // Paper end - Slime pathfinder events
+         }
+ 
+         @Override
+@@ -398,6 +_,12 @@
+                 slimeMoveControl.setDirection(this.slime.getYRot(), this.slime.isDealsDamage());
+             }
+         }
++        // Paper start - Slime pathfinder events; clear timer and target when goal resets
++        public void stop() {
++            this.growTiredTimer = 0;
++            this.slime.setTarget(null);
++        }
++        // Paper end - Slime pathfinder events
+     }
+ 
+     static class SlimeFloatGoal extends Goal {
+@@ -411,7 +_,7 @@
+ 
+         @Override
+         public boolean canUse() {
+-            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
++            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events
+         }
+ 
+         @Override
+@@ -441,7 +_,7 @@
+ 
+         @Override
+         public boolean canUse() {
+-            return !this.slime.isPassenger();
++            return !this.slime.isPassenger() && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events
+         }
+ 
+         @Override
+@@ -519,7 +_,7 @@
+ 
+         @Override
+         public boolean canUse() {
+-            return this.slime.getTarget() == null
++            return this.slime.getTarget() == null && this.slime.canWander // Paper - Slime pathfinder events
+                 && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION))
+                 && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
+         }
+@@ -529,6 +_,11 @@
+             if (--this.nextRandomizeTime <= 0) {
+                 this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60));
+                 this.chosenDegrees = this.slime.getRandom().nextInt(360);
++                // Paper start - Slime pathfinder events
++                com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent event = new com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees);
++                if (!this.slime.canWander || !event.callEvent()) return;
++                this.chosenDegrees = event.getNewYaw();
++                // Paper end - Slime pathfinder events
+             }
+ 
+             if (this.slime.getMoveControl() instanceof Slime.SlimeMoveControl slimeMoveControl) {
+@@ -536,4 +_,14 @@
+             }
+         }
+     }
++    // Paper start - Slime pathfinder events
++    private boolean canWander = true;
++    public boolean canWander() {
++        return canWander;
++    }
++
++    public void setWander(boolean canWander) {
++        this.canWander = canWander;
++    }
++    // Paper end - Slime pathfinder events
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
index 7e02fcf3a8..24fa757b5d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
@@ -1,21 +1,11 @@
 --- a/net/minecraft/world/entity/monster/SpellcasterIllager.java
 +++ b/net/minecraft/world/entity/monster/SpellcasterIllager.java
-@@ -17,6 +17,9 @@
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.ai.goal.Goal;
- import net.minecraft.world.level.Level;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public abstract class SpellcasterIllager extends AbstractIllager {
- 
-@@ -159,6 +162,11 @@
+@@ -208,6 +_,11 @@
          public void tick() {
-             --this.attackWarmupDelay;
+             this.attackWarmupDelay--;
              if (this.attackWarmupDelay == 0) {
 +                // CraftBukkit start
-+                if (!CraftEventFactory.handleEntitySpellCastEvent(SpellcasterIllager.this, this.getSpell())) {
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleEntitySpellCastEvent(SpellcasterIllager.this, this.getSpell())) {
 +                    return;
 +                }
 +                // CraftBukkit end
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Spider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Spider.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
index 1dae9b0f57..8bf70bc126 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Spider.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
@@ -1,29 +1,29 @@
 --- a/net/minecraft/world/entity/monster/Spider.java
 +++ b/net/minecraft/world/entity/monster/Spider.java
-@@ -82,7 +82,7 @@
+@@ -79,7 +_,7 @@
      public void tick() {
          super.tick();
          if (!this.level().isClientSide) {
 -            this.setClimbing(this.horizontalCollision);
 +            this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border)
          }
- 
      }
-@@ -126,7 +126,7 @@
+ 
+@@ -121,7 +_,7 @@
  
      @Override
-     public boolean canBeAffected(MobEffectInstance effect) {
--        return effect.is(MobEffects.POISON) ? false : super.canBeAffected(effect);
-+        return effect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects
+     public boolean canBeAffected(MobEffectInstance potioneffect) {
+-        return !potioneffect.is(MobEffects.POISON) && super.canBeAffected(potioneffect);
++        return potioneffect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(potioneffect); // Paper - Add config for mobs immune to default effects
      }
  
      public boolean isClimbing() {
-@@ -172,7 +172,7 @@
-             Holder<MobEffect> holder = entityspider_groupdataspider.effect;
- 
+@@ -165,7 +_,7 @@
+         if (spawnGroupData instanceof Spider.SpiderEffectsGroupData spiderEffectsGroupData) {
+             Holder<MobEffect> holder = spiderEffectsGroupData.effect;
              if (holder != null) {
 -                this.addEffect(new MobEffectInstance(holder, -1));
-+                this.addEffect(new MobEffectInstance(holder, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit
++                this.addEffect(new MobEffectInstance(holder, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, level instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit
              }
          }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Strider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Strider.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch
index fbc85b5bbe..63f135a7cc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Strider.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Strider.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/entity/monster/Strider.java
 +++ b/net/minecraft/world/entity/monster/Strider.java
-@@ -350,7 +350,14 @@
- 
-             boolean flag2 = flag1;
- 
--            this.setSuffocating(!flag || flag2);
+@@ -309,7 +_,14 @@
+                 || blockStateOnLegacy.is(BlockTags.STRIDER_WARM_BLOCKS)
+                 || this.getFluidHeight(FluidTags.LAVA) > 0.0;
+             boolean flag1 = this.getVehicle() instanceof Strider strider && strider.isSuffocating();
+-            this.setSuffocating(!flag || flag1);
 +            // CraftBukkit start
-+            boolean suffocating = !flag || flag2;
++            boolean suffocating = !flag || flag1;
 +            if (suffocating ^ this.isSuffocating()) {
 +                if (org.bukkit.craftbukkit.event.CraftEventFactory.callStriderTemperatureChangeEvent(this, suffocating)) {
 +                    this.setSuffocating(suffocating);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vex.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vex.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch
index c2c4e27616..415806e46f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vex.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vex.java.patch
@@ -1,18 +1,6 @@
 --- a/net/minecraft/world/entity/monster/Vex.java
 +++ b/net/minecraft/world/entity/monster/Vex.java
-@@ -354,7 +354,10 @@
-             for (int i = 0; i < 3; ++i) {
-                 BlockPos blockposition1 = blockposition.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7);
- 
--                if (Vex.this.level().isEmptyBlock(blockposition1)) {
-+                // Paper start - Don't load chunks
-+                final net.minecraft.world.level.block.state.BlockState blockState = Vex.this.level().getBlockStateIfLoaded(blockposition1);
-+                if (blockState != null && blockState.isAir()) {
-+                    // Paper end - Don't load chunks
-                     Vex.this.moveControl.setWantedPosition((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 0.25D);
-                     if (Vex.this.getTarget() == null) {
-                         Vex.this.getLookControl().setLookAt((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.5D, (double) blockposition1.getZ() + 0.5D, 180.0F, 20.0F);
-@@ -381,7 +384,7 @@
+@@ -296,7 +_,7 @@
  
          @Override
          public void start() {
@@ -21,3 +9,15 @@
              super.start();
          }
      }
+@@ -355,7 +_,10 @@
+ 
+             for (int i = 0; i < 3; i++) {
+                 BlockPos blockPos = boundOrigin.offset(Vex.this.random.nextInt(15) - 7, Vex.this.random.nextInt(11) - 5, Vex.this.random.nextInt(15) - 7);
+-                if (Vex.this.level().isEmptyBlock(blockPos)) {
++                // Paper start - Don't load chunks
++                final net.minecraft.world.level.block.state.BlockState blockState = Vex.this.level().getBlockStateIfLoaded(blockPos);
++                if (blockState != null && blockState.isAir()) {
++                    // Paper end - Don't load chunks
+                     Vex.this.moveControl.setWantedPosition(blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5, 0.25);
+                     if (Vex.this.getTarget() == null) {
+                         Vex.this.getLookControl().setLookAt(blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5, 180.0F, 20.0F);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vindicator.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vindicator.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch
index e652ce7a7e..70543fd76e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Vindicator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Vindicator.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/monster/Vindicator.java
 +++ b/net/minecraft/world/entity/monster/Vindicator.java
-@@ -184,7 +184,7 @@
+@@ -184,7 +_,7 @@
  
      static class VindicatorBreakDoorGoal extends BreakDoorGoal {
          public VindicatorBreakDoorGoal(Mob mob) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Witch.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/monster/Witch.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
index 6939fca599..e1dbaaaa93 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Witch.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
@@ -1,23 +1,22 @@
 --- a/net/minecraft/world/entity/monster/Witch.java
 +++ b/net/minecraft/world/entity/monster/Witch.java
-@@ -124,9 +124,15 @@
- 
+@@ -122,8 +_,14 @@
+                     ItemStack mainHandItem = this.getMainHandItem();
                      this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
-                     PotionContents potioncontents = (PotionContents) itemstack.get(DataComponents.POTION_CONTENTS);
+                     PotionContents potionContents = mainHandItem.get(DataComponents.POTION_CONTENTS);
 +                    // Paper start - WitchConsumePotionEvent
-+                    if (itemstack.is(Items.POTION)) {
++                    if (mainHandItem.is(Items.POTION)) {
 +                        com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack));
-+                        potioncontents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null;
++                        potionContents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null;
 +                    }
 +                    // Paper end - WitchConsumePotionEvent
- 
-                     if (itemstack.is(Items.POTION) && potioncontents != null) {
--                        potioncontents.forEachEffect(this::addEffect);
-+                        potioncontents.forEachEffect((effect) -> this.addEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK)); // CraftBukkit
+                     if (mainHandItem.is(Items.POTION) && potionContents != null) {
+-                        potionContents.forEachEffect(this::addEffect);
++                        potionContents.forEachEffect((effect) -> this.addEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK)); // CraftBukkit
                      }
  
                      this.gameEvent(GameEvent.DRINK);
-@@ -146,17 +152,7 @@
+@@ -147,26 +_,7 @@
                  }
  
                  if (holder != null) {
@@ -25,23 +24,30 @@
 -                    this.usingTime = this.getMainHandItem().getUseDuration(this);
 -                    this.setUsingItem(true);
 -                    if (!this.isSilent()) {
--                        this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F);
+-                        this.level()
+-                            .playSound(
+-                                null,
+-                                this.getX(),
+-                                this.getY(),
+-                                this.getZ(),
+-                                SoundEvents.WITCH_DRINK,
+-                                this.getSoundSource(),
+-                                1.0F,
+-                                0.8F + this.random.nextFloat() * 0.4F
+-                            );
 -                    }
 -
--                    AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
--
--                    attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID);
--                    attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING);
+-                    AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
+-                    attribute.removeModifier(SPEED_MODIFIER_DRINKING_ID);
+-                    attribute.addTransientModifier(SPEED_MODIFIER_DRINKING);
 +                    this.setDrinkingPotion(PotionContents.createItemStack(Items.POTION, holder)); // Paper - logic moved into setDrinkingPotion, copy exact impl into the method and then comment out
                  }
              }
  
-@@ -166,7 +162,24 @@
-         }
- 
+@@ -178,6 +_,23 @@
          super.aiStep();
-+    }
-+
+     }
+ 
 +    // Paper start - moved to its own method
 +    public void setDrinkingPotion(ItemStack potion) {
 +        potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion);
@@ -56,22 +62,23 @@
 +
 +        attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID);
 +        attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING);
-     }
++    }
 +    // Paper end
- 
++
      @Override
      public SoundEvent getCelebrateSound() {
-@@ -231,6 +244,13 @@
-                 ServerLevel worldserver = (ServerLevel) world;
-                 ItemStack itemstack = PotionContents.createItemStack(Items.SPLASH_POTION, holder);
+         return SoundEvents.WITCH_CELEBRATE;
+@@ -244,6 +_,13 @@
  
+             if (this.level() instanceof ServerLevel serverLevel) {
+                 ItemStack itemStack = PotionContents.createItemStack(Items.SPLASH_POTION, holder);
 +                // Paper start - WitchThrowPotionEvent
 +                com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack));
 +                if (!event.callEvent()) {
 +                    return;
 +                }
-+                itemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion());
-+                // Paper end - WitchThrowPotionEvent
-                 Projectile.spawnProjectileUsingShoot(ThrownPotion::new, worldserver, itemstack, this, d0, d1 + d3 * 0.2D, d2, 0.75F, 8.0F);
++                itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion());
++                // Paper end - WitchThrowPotionEven
+                 Projectile.spawnProjectileUsingShoot(ThrownPotion::new, serverLevel, itemStack, this, d, d1 + squareRoot * 0.2, d2, 0.75F, 8.0F);
              }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
new file mode 100644
index 0000000000..9780923155
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/monster/WitherSkeleton.java
++++ b/net/minecraft/world/entity/monster/WitherSkeleton.java
+@@ -105,7 +_,7 @@
+             return false;
+         } else {
+             if (source instanceof LivingEntity) {
+-                ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this);
++                ((LivingEntity)source).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             }
+ 
+             return true;
+@@ -121,6 +_,6 @@
+ 
+     @Override
+     public boolean canBeAffected(MobEffectInstance potioneffect) {
+-        return !potioneffect.is(MobEffects.WITHER) && super.canBeAffected(potioneffect);
++        return potioneffect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(potioneffect); // Paper - Add config for mobs immune to default effects
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
new file mode 100644
index 0000000000..1b2d06c538
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
@@ -0,0 +1,239 @@
+--- a/net/minecraft/world/entity/monster/Zombie.java
++++ b/net/minecraft/world/entity/monster/Zombie.java
+@@ -68,9 +_,7 @@
+ 
+ public class Zombie extends Monster {
+     private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby");
+-    private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(
+-        SPEED_MODIFIER_BABY_ID, 0.5, AttributeModifier.Operation.ADD_MULTIPLIED_BASE
+-    );
++    private final AttributeModifier babyModifier = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, this.level().paperConfig().entities.behavior.babyZombieMovementModifier, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); // Paper - Make baby speed configurable
+     private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
+     private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(
+         ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05F, AttributeModifier.Operation.ADD_VALUE
+@@ -87,13 +_,16 @@
+     private static final EntityDimensions BABY_DIMENSIONS = EntityType.ZOMBIE.getDimensions().scale(0.5F).withEyeHeight(0.93F);
+     private static final float BREAK_DOOR_CHANCE = 0.1F;
+     public static final Predicate<Difficulty> DOOR_BREAKING_PREDICATE = difficulty -> difficulty == Difficulty.HARD;
+-    private final BreakDoorGoal breakDoorGoal = new BreakDoorGoal(this, DOOR_BREAKING_PREDICATE);
++    private final BreakDoorGoal breakDoorGoal; // Paper - move down
+     private boolean canBreakDoors;
+     private int inWaterTime;
+     public int conversionTime;
++    // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time
++    private boolean shouldBurnInDay = true; // Paper - Add more Zombie API
+ 
+     public Zombie(EntityType<? extends Zombie> entityType, Level level) {
+         super(entityType, level);
++        this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(entityType, level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty
+     }
+ 
+     public Zombie(Level level) {
+@@ -102,7 +_,7 @@
+ 
+     @Override
+     protected void registerGoals() {
+-        this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3));
++        if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3)); // Paper - Add zombie targets turtle egg config
+         this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
+         this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+         this.addBehaviourGoals();
+@@ -114,7 +_,7 @@
+         this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
+         this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
+         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
+-        this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
++        if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
+         this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
+         this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
+     }
+@@ -168,11 +_,16 @@
+ 
+     @Override
+     protected int getBaseExperienceReward(ServerLevel level) {
++        final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward
+         if (this.isBaby()) {
+             this.xpReward = (int)(this.xpReward * 2.5);
+         }
+ 
+-        return super.getBaseExperienceReward(level);
++        // Paper start - store previous value to reset after calculating XP reward
++        int reward = super.getBaseExperienceReward(level);
++        this.xpReward = previousReward;
++        return reward;
++        // Paper end - store previous value to reset after calculating XP reward
+     }
+ 
+     @Override
+@@ -180,9 +_,9 @@
+         this.getEntityData().set(DATA_BABY_ID, childZombie);
+         if (this.level() != null && !this.level().isClientSide) {
+             AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
+-            attribute.removeModifier(SPEED_MODIFIER_BABY_ID);
++            attribute.removeModifier(this.babyModifier.id()); // Paper - Make baby speed configurable
+             if (childZombie) {
+-                attribute.addTransientModifier(SPEED_MODIFIER_BABY);
++                attribute.addTransientModifier(this.babyModifier); // Paper - Make baby speed configurable
+             }
+         }
+     }
+@@ -221,6 +_,7 @@
+         }
+ 
+         super.tick();
++        // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time
+     }
+ 
+     @Override
+@@ -251,7 +_,14 @@
+         super.aiStep();
+     }
+ 
++    // Paper start - Add more Zombie API
++    public void stopDrowning() {
++        this.conversionTime = -1;
++        this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false);
++    }
++    // Paper end - Add more Zombie API
+     public void startUnderWaterConversion(int conversionTime) {
++        // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall tim
+         this.conversionTime = conversionTime;
+         this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, true);
+     }
+@@ -264,31 +_,49 @@
+     }
+ 
+     protected void convertToZombieType(EntityType<? extends Zombie> entityType) {
+-        this.convertTo(
++        Zombie converted = this.convertTo( // CraftBukkit
+             entityType,
+             ConversionParams.single(this, true, true),
+-            zombie -> zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier())
+-        );
++            // CraftBukkit start
++            zombie -> {zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier());},
++            org.bukkit.event.entity.EntityTransformEvent.TransformReason.DROWNED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DROWNED);
++        if (converted == null) {
++            ((org.bukkit.entity.Zombie) this.getBukkitEntity()).setConversionTime(-1); // CraftBukkit - SPIGOT-5208: End conversion to stop event spam
++        }
++        // CraftBukkit end
+     }
+ 
+     @VisibleForTesting
+     public boolean convertVillagerToZombieVillager(ServerLevel level, Villager villager) {
++        // CraftBukkit start
++        return Zombie.convertVillagerToZombieVillager(level, villager, this.blockPosition(), this.isSilent(), org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.INFECTION) != null;
++    }
++
++    public static ZombieVillager convertVillagerToZombieVillager(ServerLevel level, Villager villager, net.minecraft.core.BlockPos blockPosition, boolean silent, org.bukkit.event.entity.EntityTransformEvent.TransformReason transformReason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++        // CraftBukkit end
+         ZombieVillager zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), mob -> {
+             mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
+             mob.setVillagerData(villager.getVillagerData());
+             mob.setGossips(villager.getGossips().store(NbtOps.INSTANCE));
+             mob.setTradeOffers(villager.getOffers().copy());
+             mob.setVillagerXp(villager.getVillagerXp());
+-            if (!this.isSilent()) {
+-                level.levelEvent(null, 1026, this.blockPosition(), 0);
++            // CraftBukkit start
++            if (!silent) {
++                level.levelEvent(null, 1026, blockPosition, 0);
+             }
++            // CraftBukkit end
+         });
+-        return zombieVillager != null;
++        return zombieVillager;
+     }
+ 
+     public boolean isSunSensitive() {
+-        return true;
+-    }
++        return this.shouldBurnInDay; // Paper - Add more Zombie API
++    }
++    // Paper start - Add more Zombie API
++    public void setShouldBurnInDay(boolean shouldBurnInDay) {
++        this.shouldBurnInDay = shouldBurnInDay;
++    }
++    // Paper end - Add more Zombie API
+ 
+     @Override
+     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
+@@ -321,13 +_,13 @@
+                     if (SpawnPlacements.isSpawnPositionOk(type, level, blockPos)
+                         && SpawnPlacements.checkSpawnRules(type, level, EntitySpawnReason.REINFORCEMENT, blockPos, level.random)) {
+                         zombie.setPos(i1, i2, i3);
+-                        if (!level.hasNearbyAlivePlayer(i1, i2, i3, 7.0)
++                        if (!level.hasNearbyAlivePlayerThatAffectsSpawning(i1, i2, i3, 7.0) // Paper - affects spawning api
+                             && level.isUnobstructed(zombie)
+                             && level.noCollision(zombie)
+                             && (zombie.canSpawnInLiquids() || !level.containsAnyLiquid(zombie.getBoundingBox()))) {
+-                            zombie.setTarget(target);
++                            zombie.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit
+                             zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, null);
+-                            level.addFreshEntityWithPassengers(zombie);
++                            level.addFreshEntityWithPassengers(zombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
+                             AttributeInstance attribute = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE);
+                             AttributeModifier modifier = attribute.getModifier(REINFORCEMENT_CALLER_CHARGE_ID);
+                             double d = modifier != null ? modifier.amount() : 0.0;
+@@ -352,7 +_,14 @@
+         if (flag) {
+             float effectiveDifficulty = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+             if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < effectiveDifficulty * 0.3F) {
+-                source.igniteForSeconds(2 * (int)effectiveDifficulty);
++                // CraftBukkit start
++                org.bukkit.event.entity.EntityCombustByEntityEvent event = new org.bukkit.event.entity.EntityCombustByEntityEvent(this.getBukkitEntity(), source.getBukkitEntity(), (float) (2 * (int)effectiveDifficulty));
++                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (!event.isCancelled()) {
++                    source.igniteForSeconds(event.getDuration(), false);
++                }
++                // CraftBukkit end
+             }
+         }
+ 
+@@ -412,6 +_,7 @@
+         compound.putBoolean("CanBreakDoors", this.canBreakDoors());
+         compound.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
+         compound.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
++        compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API
+     }
+ 
+     @Override
+@@ -423,12 +_,18 @@
+         if (compound.contains("DrownedConversionTime", 99) && compound.getInt("DrownedConversionTime") > -1) {
+             this.startUnderWaterConversion(compound.getInt("DrownedConversionTime"));
+         }
++        // Paper start - Add more Zombie API
++        if (compound.contains("Paper.ShouldBurnInDay")) {
++            this.shouldBurnInDay = compound.getBoolean("Paper.ShouldBurnInDay");
++        }
++        // Paper end - Add more Zombie API
+     }
+ 
+     @Override
+     public boolean killedEntity(ServerLevel level, LivingEntity entity) {
+         boolean flag = super.killedEntity(level, entity);
+-        if ((level.getDifficulty() == Difficulty.NORMAL || level.getDifficulty() == Difficulty.HARD) && entity instanceof Villager villager) {
++        final double fallbackChance = level.getDifficulty() == Difficulty.HARD ? 100d : level.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection
++        if (this.random.nextDouble() * 100 < level.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && entity instanceof Villager villager) { // Paper - Configurable chance of villager zombie infection
+             if (level.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
+                 return flag;
+             }
+@@ -465,7 +_,7 @@
+         spawnGroupData = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
+         float specialMultiplier = difficulty.getSpecialMultiplier();
+         if (spawnReason != EntitySpawnReason.CONVERSION) {
+-            this.setCanPickUpLoot(random.nextFloat() < 0.55F * specialMultiplier);
++            this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || random.nextFloat() < 0.55F * specialMultiplier); // Paper - Add world settings for mobs picking up loot
+         }
+ 
+         if (spawnGroupData == null) {
+@@ -492,7 +_,7 @@
+                             chicken1.finalizeSpawn(level, difficulty, EntitySpawnReason.JOCKEY, null);
+                             chicken1.setChickenJockey(true);
+                             this.startRiding(chicken1);
+-                            level.addFreshEntity(chicken1);
++                            level.addFreshEntity(chicken1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
+                         }
+                     }
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
new file mode 100644
index 0000000000..53b5b1fca0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
@@ -0,0 +1,107 @@
+--- a/net/minecraft/world/entity/monster/ZombieVillager.java
++++ b/net/minecraft/world/entity/monster/ZombieVillager.java
+@@ -33,6 +_,7 @@
+ import net.minecraft.world.entity.SlotAccess;
+ import net.minecraft.world.entity.SpawnGroupData;
+ import net.minecraft.world.entity.ai.village.ReputationEventType;
++import net.minecraft.world.entity.npc.Villager;
+ import net.minecraft.world.entity.npc.VillagerData;
+ import net.minecraft.world.entity.npc.VillagerDataHolder;
+ import net.minecraft.world.entity.npc.VillagerProfession;
+@@ -68,6 +_,7 @@
+     @Nullable
+     private MerchantOffers tradeOffers;
+     private int villagerXp;
++    private int lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - add field
+ 
+     public ZombieVillager(EntityType<? extends ZombieVillager> entityType, Level level) {
+         super(entityType, level);
+@@ -140,6 +_,11 @@
+     public void tick() {
+         if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
+             int conversionProgress = this.getConversionProgress();
++            // CraftBukkit start - Use wall time instead of ticks for villager conversion
++            // TODO: WE WANT TO REMOVE THIS? I THOUGHT WE REMOVED IT.
++            int elapsedTicks = net.minecraft.server.MinecraftServer.currentTick - this.lastTick;
++            conversionProgress *= elapsedTicks;
++            // CraftBukkit end
+             this.villagerConversionTime -= conversionProgress;
+             if (this.villagerConversionTime <= 0) {
+                 this.finishConversion((ServerLevel)this.level());
+@@ -147,6 +_,7 @@
+         }
+ 
+         super.tick();
++        this.lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
+     }
+ 
+     @Override
+@@ -183,12 +_,20 @@
+     }
+ 
+     public void startConverting(@Nullable UUID conversionStarter, int villagerConversionTime) {
++    // Paper start - missing entity behaviour api - converting without entity event
++        this.startConverting(conversionStarter, villagerConversionTime, true);
++    }
++
++    public void startConverting(@Nullable UUID conversionStarter, int villagerConversionTime, boolean broadcastEntityEvent) {
++    // Paper end - missing entity behaviour api - converting without entity event
+         this.conversionStarter = conversionStarter;
+         this.villagerConversionTime = villagerConversionTime;
+         this.getEntityData().set(DATA_CONVERTING_ID, true);
+-        this.removeEffect(MobEffects.WEAKNESS);
+-        this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, villagerConversionTime, Math.min(this.level().getDifficulty().getId() - 1, 0)));
+-        this.level().broadcastEntityEvent(this, (byte)16);
++        // CraftBukkit start
++        this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++        this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, villagerConversionTime, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++        // CraftBukkit end
++        if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte)16); // Paper - missing entity behaviour api - converting without entity event
+     }
+ 
+     @Override
+@@ -213,7 +_,7 @@
+     }
+ 
+     private void finishConversion(ServerLevel serverLevel) {
+-        this.convertTo(
++        Villager converted = this.convertTo( // CraftBukkit
+             EntityType.VILLAGER,
+             ConversionParams.single(this, false, false),
+             villager -> {
+@@ -223,6 +_,7 @@
+                     SlotAccess slot = villager.getSlot(equipmentSlot.getIndex() + 300);
+                     slot.set(this.getItemBySlot(equipmentSlot));
+                 }
++                this.forceDrops = false; // CraftBukkit
+ 
+                 villager.setVillagerData(this.getVillagerData());
+                 if (this.gossips != null) {
+@@ -237,19 +_,24 @@
+                 villager.finalizeSpawn(serverLevel, serverLevel.getCurrentDifficultyAt(villager.blockPosition()), EntitySpawnReason.CONVERSION, null);
+                 villager.refreshBrain(serverLevel);
+                 if (this.conversionStarter != null) {
+-                    Player playerByUuid = serverLevel.getPlayerByUUID(this.conversionStarter);
++                    Player playerByUuid = serverLevel.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate
+                     if (playerByUuid instanceof ServerPlayer) {
+                         CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer)playerByUuid, this, villager);
+                         serverLevel.onReputationEvent(ReputationEventType.ZOMBIE_VILLAGER_CURED, playerByUuid, villager);
+                     }
+                 }
+ 
+-                villager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
++                villager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); // CraftBukkit
+                 if (!this.isSilent()) {
+                     serverLevel.levelEvent(null, 1027, this.blockPosition(), 0);
+                 }
+-            }
++            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.CURED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED // CraftBukkit
+         );
++        // CraftBukkit start
++        if (converted == null) {
++            ((org.bukkit.entity.ZombieVillager) this.getBukkitEntity()).setConversionTime(-1); // SPIGOT-5208: End conversion to stop event spam
++        }
++        // CraftBukkit end
+     }
+ 
+     @VisibleForTesting
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
new file mode 100644
index 0000000000..dca58ab24d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+@@ -56,6 +_,7 @@
+     private static final int ALERT_RANGE_Y = 10;
+     private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6);
+     private int ticksUntilNextAlert;
++    private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation
+ 
+     public ZombifiedPiglin(EntityType<? extends ZombifiedPiglin> entityType, Level level) {
+         super(entityType, level);
+@@ -71,7 +_,7 @@
+     protected void addBehaviourGoals() {
+         this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0, false));
+         this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
+-        this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
++        this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this)).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation
+         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
+         this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
+     }
+@@ -148,7 +_,7 @@
+             .filter(zombifiedPiglin -> zombifiedPiglin != this)
+             .filter(zombifiedPiglin -> zombifiedPiglin.getTarget() == null)
+             .filter(zombifiedPiglin -> !zombifiedPiglin.isAlliedTo(this.getTarget()))
+-            .forEach(zombifiedPiglin -> zombifiedPiglin.setTarget(this.getTarget()));
++            .forEach(zombifiedPiglin -> zombifiedPiglin.setTarget(this.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true)); // CraftBukkit
+     }
+ 
+     private void playAngerSound() {
+@@ -156,7 +_,7 @@
+     }
+ 
+     @Override
+-    public void setTarget(@Nullable LivingEntity livingEntity) {
++    public boolean setTarget(@Nullable LivingEntity livingEntity, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { // CraftBukkit - signature
+         if (this.getTarget() == null && livingEntity != null) {
+             this.playFirstAngerSoundIn = FIRST_ANGER_SOUND_DELAY.sample(this.random);
+             this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random);
+@@ -166,12 +_,22 @@
+             this.setLastHurtByPlayer((Player)livingEntity);
+         }
+ 
+-        super.setTarget(livingEntity);
++        return super.setTarget(livingEntity, reason, fireEvent); // CraftBukkit
+     }
+ 
+     @Override
+     public void startPersistentAngerTimer() {
+-        this.setRemainingPersistentAngerTime(PERSISTENT_ANGER_TIME.sample(this.random));
++        // CraftBukkit start
++        net.minecraft.world.entity.Entity entity = ((ServerLevel) this.level()).getEntity(this.getPersistentAngerTarget());
++        org.bukkit.event.entity.PigZombieAngerEvent event = new org.bukkit.event.entity.PigZombieAngerEvent((org.bukkit.entity.PigZombie) this.getBukkitEntity(), (entity == null) ? null : entity.getBukkitEntity(), ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random));
++        this.level().getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            this.setPersistentAngerTarget(null);
++            pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation
++            return;
++        }
++        this.setRemainingPersistentAngerTime(event.getNewAnger());
++        // CraftBukkit end
+     }
+ 
+     public static boolean checkZombifiedPiglinSpawnRules(
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
deleted file mode 100644
index d726409faf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
+++ /dev/null
@@ -1,74 +0,0 @@
---- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
-+++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
-@@ -97,9 +97,15 @@
- 
-     abstract SoundEvent getStepSound();
- 
-+    // Paper start - shouldBurnInDay API
-+    private boolean shouldBurnInDay = true;
-+    public boolean shouldBurnInDay() { return shouldBurnInDay; }
-+    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
-+    // Paper end - shouldBurnInDay API
-+
-     @Override
-     public void aiStep() {
--        boolean flag = this.isSunBurnTick();
-+        boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API
- 
-         if (flag) {
-             ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
-@@ -152,7 +158,7 @@
-         this.populateDefaultEquipmentSlots(randomsource, difficulty);
-         this.populateDefaultEquipmentEnchantments(world, randomsource, difficulty);
-         this.reassessWeaponGoal();
--        this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier());
-+        this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot
-         if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
-             LocalDate localdate = LocalDate.now();
-             int i = localdate.get(ChronoField.DAY_OF_MONTH);
-@@ -209,7 +215,17 @@
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
--            Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4));
-+            // CraftBukkit start
-+            org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper - improve entity shhot bow event - add arrow stack to event
-+            if (event.isCancelled()) {
-+                event.getProjectile().remove();
-+                return;
-+            }
-+
-+            if (event.getProjectile() == entityarrow.getBukkitEntity()) {
-+                Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4));
-+            }
-+            // CraftBukkit end
-         }
- 
-         this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
-@@ -233,11 +249,24 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         super.readAdditionalSaveData(nbt);
-         this.reassessWeaponGoal();
-+        // Paper start - shouldBurnInDay API
-+        if (nbt.contains("Paper.ShouldBurnInDay")) {
-+            this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
-+        }
-+        // Paper end - shouldBurnInDay API
-     }
- 
-+    // Paper start - shouldBurnInDay API
-     @Override
--    public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
--        super.setItemSlot(slot, stack);
-+    public void addAdditionalSaveData(CompoundTag nbt) {
-+        super.addAdditionalSaveData(nbt);
-+        nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
-+    }
-+    // Paper end - shouldBurnInDay API
-+
-+    @Override
-+    public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change
-+        super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change
-         if (!this.level().isClientSide) {
-             this.reassessWeaponGoal();
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/CaveSpider.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/CaveSpider.java.patch
deleted file mode 100644
index c2449e2257..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/CaveSpider.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/CaveSpider.java
-+++ b/net/minecraft/world/entity/monster/CaveSpider.java
-@@ -40,7 +40,7 @@
-                 }
- 
-                 if (b0 > 0) {
--                    ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this);
-+                    ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-                 }
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Creeper.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Creeper.java.patch
deleted file mode 100644
index ea4e74957f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Creeper.java.patch
+++ /dev/null
@@ -1,132 +0,0 @@
---- a/net/minecraft/world/entity/monster/Creeper.java
-+++ b/net/minecraft/world/entity/monster/Creeper.java
-@@ -42,6 +42,13 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
-+
- public class Creeper extends Monster {
- 
-     private static final EntityDataAccessor<Integer> DATA_SWELL_DIR = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.INT);
-@@ -52,6 +59,7 @@
-     public int maxSwell = 30;
-     public int explosionRadius = 3;
-     private int droppedSkulls;
-+    public Entity entityIgniter; // CraftBukkit
- 
-     public Creeper(EntityType<? extends Creeper> type, Level world) {
-         super(type, world);
-@@ -125,7 +133,7 @@
-         }
- 
-         if (nbt.getBoolean("ignited")) {
--            this.ignite();
-+            this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event
-         }
- 
-     }
-@@ -214,9 +222,20 @@
-     @Override
-     public void thunderHit(ServerLevel world, LightningBolt lightning) {
-         super.thunderHit(world, lightning);
-+        // CraftBukkit start
-+        if (CraftEventFactory.callCreeperPowerEvent(this, lightning, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) {
-+            return;
-+        }
-+        // CraftBukkit end
-         this.entityData.set(Creeper.DATA_IS_POWERED, true);
-     }
- 
-+    // CraftBukkit start
-+    public void setPowered(boolean powered) {
-+        this.entityData.set(Creeper.DATA_IS_POWERED, powered);
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     protected InteractionResult mobInteract(Player player, InteractionHand hand) {
-         ItemStack itemstack = player.getItemInHand(hand);
-@@ -226,8 +245,9 @@
- 
-             this.level().playSound(player, this.getX(), this.getY(), this.getZ(), soundeffect, this.getSoundSource(), 1.0F, this.random.nextFloat() * 0.4F + 0.8F);
-             if (!this.level().isClientSide) {
-+                this.entityIgniter = player; // CraftBukkit
-                 this.ignite();
--                if (!itemstack.isDamageableItem()) {
-+                if (itemstack.getMaxDamage() == 0) { // CraftBukkit - fix MC-264285: unbreakable flint and steels are completely consumed when igniting a creeper
-                     itemstack.shrink(1);
-                 } else {
-                     itemstack.hurtAndBreak(1, player, getSlotForHand(hand));
-@@ -246,11 +266,21 @@
-         if (world instanceof ServerLevel worldserver) {
-             float f = this.isPowered() ? 2.0F : 1.0F;
- 
-+            // CraftBukkit start
-+            ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false);
-+            if (!event.isCancelled()) {
-+            // CraftBukkit end
-             this.dead = true;
--            worldserver.explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionRadius * f, Level.ExplosionInteraction.MOB);
-+            worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this)
-             this.spawnLingeringCloud();
-             this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED);
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
-+            // CraftBukkit start
-+            } else {
-+                this.swell = 0;
-+                this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
-@@ -258,9 +288,10 @@
-     private void spawnLingeringCloud() {
-         Collection<MobEffectInstance> collection = this.getActiveEffects();
- 
--        if (!collection.isEmpty()) {
-+        if (!collection.isEmpty() && !this.level().paperConfig().entities.behavior.disableCreeperLingeringEffect) { // Paper - Option to disable creeper lingering effect
-             AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
- 
-+            entityareaeffectcloud.setOwner(this); // CraftBukkit
-             entityareaeffectcloud.setRadius(2.5F);
-             entityareaeffectcloud.setRadiusOnUse(-0.5F);
-             entityareaeffectcloud.setWaitTime(10);
-@@ -274,7 +305,7 @@
-                 entityareaeffectcloud.addEffect(new MobEffectInstance(mobeffect));
-             }
- 
--            this.level().addFreshEntity(entityareaeffectcloud);
-+            this.level().addFreshEntity(entityareaeffectcloud, CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit
-         }
- 
-     }
-@@ -284,9 +315,20 @@
-     }
- 
-     public void ignite() {
--        this.entityData.set(Creeper.DATA_IS_IGNITED, true);
-+        // Paper start - CreeperIgniteEvent
-+        setIgnited(true);
-     }
- 
-+    public void setIgnited(boolean ignited) {
-+        if (isIgnited() != ignited) {
-+            com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
-+            if (event.callEvent()) {
-+                this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited());
-+            }
-+        }
-+        // Paper end - CreeperIgniteEvent
-+    }
-+
-     public boolean canDropMobsSkull() {
-         return this.isPowered() && this.droppedSkulls < 1;
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ElderGuardian.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ElderGuardian.java.patch
deleted file mode 100644
index b270c246e4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ElderGuardian.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/ElderGuardian.java
-+++ b/net/minecraft/world/entity/monster/ElderGuardian.java
-@@ -67,7 +67,7 @@
-         super.customServerAiStep(world);
-         if ((this.tickCount + this.getId()) % 1200 == 0) {
-             MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2);
--            List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200);
-+            List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent
- 
-             list.forEach((entityplayer) -> {
-                 entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/EnderMan.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/EnderMan.java.patch
deleted file mode 100644
index 512e3e45cf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/EnderMan.java.patch
+++ /dev/null
@@ -1,157 +0,0 @@
---- a/net/minecraft/world/entity/monster/EnderMan.java
-+++ b/net/minecraft/world/entity/monster/EnderMan.java
-@@ -68,6 +68,10 @@
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public class EnderMan extends Monster implements NeutralMob {
- 
-@@ -112,10 +116,26 @@
- 
-     @Override
-     public void setTarget(@Nullable LivingEntity target) {
--        super.setTarget(target);
-+        // CraftBukkit start - fire event
-+        this.setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true);
-+    }
-+
-+    // Paper start - EndermanEscapeEvent
-+    private boolean tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason reason) {
-+        return new com.destroystokyo.paper.event.entity.EndermanEscapeEvent((org.bukkit.craftbukkit.entity.CraftEnderman) this.getBukkitEntity(), reason).callEvent();
-+    }
-+    // Paper end - EndermanEscapeEvent
-+
-+    @Override
-+    public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
-+        if (!super.setTarget(entityliving, reason, fireEvent)) {
-+            return false;
-+        }
-+        entityliving = this.getTarget();
-+        // CraftBukkit end
-         AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
- 
--        if (target == null) {
-+        if (entityliving == null) {
-             this.targetChangeTime = 0;
-             this.entityData.set(EnderMan.DATA_CREEPY, false);
-             this.entityData.set(EnderMan.DATA_STARED_AT, false);
-@@ -127,6 +147,7 @@
-                 attributemodifiable.addTransientModifier(EnderMan.SPEED_MODIFIER_ATTACKING);
-             }
-         }
-+        return true;
- 
-     }
- 
-@@ -212,6 +233,14 @@
-     }
- 
-     boolean isBeingStaredBy(Player player) {
-+        // Paper start - EndermanAttackPlayerEvent
-+        final boolean shouldAttack = isBeingStaredBy0(player);
-+        final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity());
-+        event.setCancelled(!shouldAttack);
-+        return event.callEvent();
-+    }
-+    private boolean isBeingStaredBy0(Player player) {
-+        // Paper end - EndermanAttackPlayerEvent
-         return !LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM.test(player) ? false : this.isLookingAtMe(player, 0.025D, true, false, new double[]{this.getEyeY()});
-     }
- 
-@@ -241,7 +270,7 @@
-         if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) {
-             float f = this.getLightLevelDependentMagicValue();
- 
--            if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) {
-+            if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent
-                 this.setTarget((LivingEntity) null);
-                 this.teleport();
-             }
-@@ -367,11 +396,13 @@
-             } else {
-                 flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount);
- 
-+                if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent
-                 for (int i = 0; i < 64; ++i) {
-                     if (this.teleport()) {
-                         return true;
-                     }
-                 }
-+                } // Paper - EndermanEscapeEvent
- 
-                 return flag1;
-             }
-@@ -397,6 +428,16 @@
-         this.entityData.set(EnderMan.DATA_STARED_AT, true);
-     }
- 
-+    // Paper start
-+    public void setCreepy(boolean creepy) {
-+        this.entityData.set(EnderMan.DATA_CREEPY, creepy);
-+    }
-+
-+    public void setHasBeenStaredAt(boolean hasBeenStaredAt) {
-+        this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt);
-+    }
-+    // Paper end
-+
-     @Override
-     public boolean requiresCustomPersistence() {
-         return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
-@@ -457,7 +498,8 @@
-             int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 2.0D);
-             int k = Mth.floor(this.enderman.getZ() - 1.0D + randomsource.nextDouble() * 2.0D);
-             BlockPos blockposition = new BlockPos(i, j, k);
--            BlockState iblockdata = world.getBlockState(blockposition);
-+            BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks
-+            if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks
-             BlockPos blockposition1 = blockposition.below();
-             BlockState iblockdata1 = world.getBlockState(blockposition1);
-             BlockState iblockdata2 = this.enderman.getCarriedBlock();
-@@ -465,9 +507,11 @@
-             if (iblockdata2 != null) {
-                 iblockdata2 = Block.updateFromNeighbourShapes(iblockdata2, this.enderman.level(), blockposition);
-                 if (this.canPlaceBlock(world, blockposition, iblockdata2, iblockdata, iblockdata1, blockposition1)) {
-+                    if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata2)) { // CraftBukkit - Place event
-                     world.setBlock(blockposition, iblockdata2, 3);
-                     world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this.enderman, iblockdata2));
-                     this.enderman.setCarriedBlock((BlockState) null);
-+                    } // CraftBukkit
-                 }
- 
-             }
-@@ -499,16 +543,19 @@
-             int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 3.0D);
-             int k = Mth.floor(this.enderman.getZ() - 2.0D + randomsource.nextDouble() * 4.0D);
-             BlockPos blockposition = new BlockPos(i, j, k);
--            BlockState iblockdata = world.getBlockState(blockposition);
-+            BlockState iblockdata = world.getBlockStateIfLoaded(blockposition); // Paper - Prevent endermen from loading chunks
-+            if (iblockdata == null) return; // Paper - Prevent endermen from loading chunks
-             Vec3 vec3d = new Vec3((double) this.enderman.getBlockX() + 0.5D, (double) j + 0.5D, (double) this.enderman.getBlockZ() + 0.5D);
-             Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D);
-             BlockHitResult movingobjectpositionblock = world.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
-             boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition);
- 
-             if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
-+                if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state
-                 world.removeBlock(blockposition, false);
-                 world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata));
-                 this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState());
-+                } // CraftBukkit
-             }
- 
-         }
-@@ -592,7 +639,7 @@
-             } else {
-                 if (this.target != null && !this.enderman.isPassenger()) {
-                     if (this.enderman.isBeingStaredBy((Player) this.target)) {
--                        if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D) {
-+                        if (this.target.distanceToSqr((Entity) this.enderman) < 16.0D && this.enderman.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.STARE)) { // Paper - EndermanEscapeEvent
-                             this.enderman.teleport();
-                         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Endermite.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Endermite.java.patch
deleted file mode 100644
index 9a32ade2bd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Endermite.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/entity/monster/Endermite.java
-+++ b/net/minecraft/world/entity/monster/Endermite.java
-@@ -24,6 +24,9 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Endermite extends Monster {
- 
-@@ -113,7 +116,7 @@
-             }
- 
-             if (this.life >= 2400) {
--                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-             }
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Evoker.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Evoker.java.patch
deleted file mode 100644
index d8921518a6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Evoker.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/Evoker.java
-+++ b/net/minecraft/world/entity/monster/Evoker.java
-@@ -212,7 +212,7 @@
-                         worldserver.getScoreboard().addPlayerToTeam(entityvex.getScoreboardName(), scoreboardteam);
-                     }
- 
--                    worldserver.addFreshEntityWithPassengers(entityvex);
-+                    worldserver.addFreshEntityWithPassengers(entityvex, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPELL); // CraftBukkit - Add SpawnReason
-                     worldserver.gameEvent((Holder) GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of((Entity) Evoker.this));
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ghast.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ghast.java.patch
deleted file mode 100644
index e91f483915..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ghast.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/entity/monster/Ghast.java
-+++ b/net/minecraft/world/entity/monster/Ghast.java
-@@ -64,7 +64,13 @@
- 
-     public int getExplosionPower() {
-         return this.explosionPower;
-+    }
-+
-+    // Paper start
-+    public void setExplosionPower(int explosionPower) {
-+        this.explosionPower = explosionPower;
-     }
-+    // Paper end
- 
-     @Override
-     protected boolean shouldDespawnInPeaceful() {
-@@ -333,6 +339,8 @@
- 
-                         LargeFireball entitylargefireball = new LargeFireball(world, this.ghast, vec3d1.normalize(), this.ghast.getExplosionPower());
- 
-+                        // CraftBukkit - set bukkitYield when setting explosionpower
-+                        entitylargefireball.bukkitYield = entitylargefireball.explosionPower = this.ghast.getExplosionPower();
-                         entitylargefireball.setPos(this.ghast.getX() + vec3d.x * 4.0D, this.ghast.getY(0.5D) + 0.5D, entitylargefireball.getZ() + vec3d.z * 4.0D);
-                         world.addFreshEntity(entitylargefireball);
-                         this.chargeTime = -40;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Husk.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Husk.java.patch
deleted file mode 100644
index 0df55d8f37..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Husk.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/Husk.java
-+++ b/net/minecraft/world/entity/monster/Husk.java
-@@ -59,7 +59,7 @@
-         if (flag && this.getMainHandItem().isEmpty() && target instanceof LivingEntity) {
-             float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
- 
--            ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this);
-+            ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-         }
- 
-         return flag;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Phantom.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Phantom.java.patch
deleted file mode 100644
index ca42f84c2e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Phantom.java.patch
+++ /dev/null
@@ -1,78 +0,0 @@
---- a/net/minecraft/world/entity/monster/Phantom.java
-+++ b/net/minecraft/world/entity/monster/Phantom.java
-@@ -139,7 +139,7 @@
- 
-     @Override
-     public void aiStep() {
--        if (this.isAlive() && this.isSunBurnTick()) {
-+        if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API
-             this.igniteForSeconds(8.0F);
-         }
- 
-@@ -161,6 +161,14 @@
-         }
- 
-         this.setPhantomSize(nbt.getInt("Size"));
-+        // Paper start
-+        if (nbt.hasUUID("Paper.SpawningEntity")) {
-+            this.spawningEntity = nbt.getUUID("Paper.SpawningEntity");
-+        }
-+        if (nbt.contains("Paper.ShouldBurnInDay")) {
-+            this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
-+        }
-+        // Paper end
-     }
- 
-     @Override
-@@ -170,6 +178,12 @@
-         nbt.putInt("AY", this.anchorPoint.getY());
-         nbt.putInt("AZ", this.anchorPoint.getZ());
-         nbt.putInt("Size", this.getPhantomSize());
-+        // Paper start
-+        if (this.spawningEntity != null) {
-+            nbt.putUUID("Paper.SpawningEntity", this.spawningEntity);
-+        }
-+        nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
-+        // Paper end
-     }
- 
-     @Override
-@@ -219,6 +233,20 @@
-         return predicate.test(world, this, target);
-     }
- 
-+    // Paper start
-+    @Nullable
-+    java.util.UUID spawningEntity;
-+
-+    @Nullable
-+    public java.util.UUID getSpawningEntity() {
-+        return this.spawningEntity;
-+    }
-+    public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
-+    private boolean shouldBurnInDay = true;
-+    public boolean shouldBurnInDay() { return shouldBurnInDay; }
-+    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
-+    // Paper end
-+
-     private static enum AttackPhase {
- 
-         CIRCLE, SWOOP;
-@@ -522,14 +550,15 @@
-                 List<Player> list = worldserver.getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D));
- 
-                 if (!list.isEmpty()) {
--                    list.sort(Comparator.comparing(Entity::getY).reversed());
-+                    list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error
-                     Iterator iterator = list.iterator();
- 
-                     while (iterator.hasNext()) {
-                         Player entityhuman = (Player) iterator.next();
- 
-                         if (Phantom.this.canAttack(worldserver, entityhuman, TargetingConditions.DEFAULT)) {
--                            Phantom.this.setTarget(entityhuman);
-+                            if (!level().paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.IS_INSOMNIAC.test(entityhuman)) // Paper - Add phantom creative and insomniac controls
-+                            Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason
-                             return true;
-                         }
-                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Pillager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Pillager.java.patch
deleted file mode 100644
index 43bd8b72e6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Pillager.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/entity/monster/Pillager.java
-+++ b/net/minecraft/world/entity/monster/Pillager.java
-@@ -52,6 +52,9 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelReader;
- import net.minecraft.world.level.ServerLevelAccessor;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Pillager extends AbstractIllager implements CrossbowAttackMob, InventoryCarrier {
- 
-@@ -206,7 +209,7 @@
-             ItemStack itemstack1 = this.inventory.addItem(itemstack);
- 
-             if (itemstack1.isEmpty()) {
--                itemEntity.discard();
-+                itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-             } else {
-                 itemstack.setCount(itemstack1.getCount());
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ravager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ravager.java.patch
deleted file mode 100644
index 1cbab8cd90..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Ravager.java.patch
+++ /dev/null
@@ -1,41 +0,0 @@
---- a/net/minecraft/world/entity/monster/Ravager.java
-+++ b/net/minecraft/world/entity/monster/Ravager.java
-@@ -42,6 +42,9 @@
- import net.minecraft.world.level.pathfinder.PathType;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class Ravager extends Raider {
- 
-@@ -158,12 +161,19 @@
-                         Block block = iblockdata.getBlock();
- 
-                         if (block instanceof LeavesBlock) {
-+                            // CraftBukkit start
-+                            if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                                continue;
-+                            }
-+                            // CraftBukkit end
-                             flag = worldserver.destroyBlock(blockposition, true, this) || flag;
-                         }
-                     }
- 
-                     if (!flag && this.onGround()) {
-+                        if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
-                         this.jumpFromGround();
-+                        } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
-                     }
-                 }
-             }
-@@ -281,7 +291,7 @@
-         double d1 = entity.getZ() - this.getZ();
-         double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D);
- 
--        entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D);
-+        entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Shulker.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Shulker.java.patch
deleted file mode 100644
index fb73125321..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Shulker.java.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- a/net/minecraft/world/entity/monster/Shulker.java
-+++ b/net/minecraft/world/entity/monster/Shulker.java
-@@ -59,6 +59,12 @@
- import net.minecraft.world.phys.Vec3;
- import org.joml.Vector3f;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.entity.EntityTeleportEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
-+
- public class Shulker extends AbstractGolem implements VariantHolder<Optional<DyeColor>>, Enemy {
- 
-     private static final ResourceLocation COVERED_ARMOR_MODIFIER_ID = ResourceLocation.withDefaultNamespace("covered");
-@@ -283,7 +289,13 @@
- 
-     @Override
-     public void stopRiding() {
--        super.stopRiding();
-+        // Paper start - Force entity dismount during teleportation
-+        this.stopRiding(false);
-+    }
-+    @Override
-+    public void stopRiding(boolean suppressCancellation) {
-+        super.stopRiding(suppressCancellation);
-+        // Paper end - Force entity dismount during teleportation
-         if (this.level().isClientSide) {
-             this.clientOldAttachPosition = this.blockPosition();
-         }
-@@ -402,6 +414,14 @@
-                     Direction enumdirection = this.findAttachableSurface(blockposition1);
- 
-                     if (enumdirection != null) {
-+                        // CraftBukkit start
-+                        EntityTeleportEvent teleportEvent = CraftEventFactory.callEntityTeleportEvent(this, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
-+                        if (teleportEvent.isCancelled() || teleportEvent.getTo() == null) { // Paper
-+                            return false;
-+                        } else {
-+                            blockposition1 = CraftLocation.toBlockPosition(teleportEvent.getTo());
-+                        }
-+                        // CraftBukkit end
-                         this.unRide();
-                         this.setAttachFace(enumdirection);
-                         this.playSound(SoundEvents.SHULKER_TELEPORT, 1.0F, 1.0F);
-@@ -472,7 +492,12 @@
-                 if (entityshulker != null) {
-                     entityshulker.setVariant(this.getVariant());
-                     entityshulker.moveTo(vec3d);
--                    this.level().addFreshEntity(entityshulker);
-+                    // Paper start - Shulker duplicate event
-+                    if (!new io.papermc.paper.event.entity.ShulkerDuplicateEvent((org.bukkit.entity.Shulker) entityshulker.getBukkitEntity(), (org.bukkit.entity.Shulker) this.getBukkitEntity()).callEvent()) {
-+                        return;
-+                    }
-+                    // Paper end - Shulker duplicate event
-+                    this.level().addFreshEntity(entityshulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - the mysteries of life
-                 }
- 
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Silverfish.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Silverfish.java.patch
deleted file mode 100644
index b031571b09..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Silverfish.java.patch
+++ /dev/null
@@ -1,51 +0,0 @@
---- a/net/minecraft/world/entity/monster/Silverfish.java
-+++ b/net/minecraft/world/entity/monster/Silverfish.java
-@@ -30,6 +30,10 @@
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.InfestedBlock;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class Silverfish extends Monster {
- 
-@@ -119,7 +123,7 @@
-         } else {
-             Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true);
- 
--            return entityhuman == null;
-+            return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API
-         }
-     }
- 
-@@ -160,6 +164,12 @@
-                             Block block = iblockdata.getBlock();
- 
-                             if (block instanceof InfestedBlock) {
-+                                // CraftBukkit start
-+                                BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state
-+                                if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state
-+                                    continue;
-+                                }
-+                                // CraftBukkit end
-                                 if (getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-                                     world.destroyBlock(blockposition1, true, this.silverfish);
-                                 } else {
-@@ -229,9 +239,14 @@
-                 BlockState iblockdata = world.getBlockState(blockposition);
- 
-                 if (InfestedBlock.isCompatibleHostBlock(iblockdata)) {
-+                    // CraftBukkit start
-+                    if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, InfestedBlock.infestedStateByHost(iblockdata))) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     world.setBlock(blockposition, InfestedBlock.infestedStateByHost(iblockdata), 3);
-                     this.mob.spawnAnim();
--                    this.mob.discard();
-+                    this.mob.discard(EntityRemoveEvent.Cause.ENTER_BLOCK); // CraftBukkit - add Bukkit remove cause
-                 }
- 
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Slime.java.patch
deleted file mode 100644
index 4ee5279ca5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Slime.java.patch
+++ /dev/null
@@ -1,239 +0,0 @@
---- a/net/minecraft/world/entity/monster/Slime.java
-+++ b/net/minecraft/world/entity/monster/Slime.java
-@@ -46,6 +46,14 @@
- import net.minecraft.world.level.levelgen.WorldgenRandom;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.scores.PlayerTeam;
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.List;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+import org.bukkit.event.entity.SlimeSplitEvent;
-+// CraftBukkit end
- 
- public class Slime extends Mob implements Enemy {
- 
-@@ -111,6 +119,7 @@
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
-+        nbt.putBoolean("Paper.canWander", this.canWander); // Paper
-         nbt.putInt("Size", this.getSize() - 1);
-         nbt.putBoolean("wasOnGround", this.wasOnGround);
-     }
-@@ -119,6 +128,11 @@
-     public void readAdditionalSaveData(CompoundTag nbt) {
-         this.setSize(nbt.getInt("Size") + 1, false);
-         super.readAdditionalSaveData(nbt);
-+        // Paper start
-+        if (nbt.contains("Paper.canWander")) {
-+            this.canWander = nbt.getBoolean("Paper.canWander");
-+        }
-+        // Paper end
-         this.wasOnGround = nbt.getBoolean("wasOnGround");
-     }
- 
-@@ -197,11 +211,18 @@
- 
-     @Override
-     public EntityType<? extends Slime> getType() {
--        return super.getType();
-+        return (EntityType<? extends Slime>) super.getType(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-     public void remove(Entity.RemovalReason reason) {
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.remove(reason, null);
-+    }
-+
-+    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
-+        // CraftBukkit end
-         int i = this.getSize();
- 
-         if (!this.level().isClientSide && i > 1 && this.isDeadOrDying()) {
-@@ -210,19 +231,47 @@
-             int j = i / 2;
-             int k = 2 + this.random.nextInt(3);
-             PlayerTeam scoreboardteam = this.getTeam();
-+
-+            // CraftBukkit start
-+            SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), k);
-+            this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled() && event.getCount() > 0) {
-+                k = event.getCount();
-+            } else {
-+                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
-+                return;
-+            }
-+            List<LivingEntity> slimes = new ArrayList<>(j);
-+            // CraftBukkit end
- 
-             for (int l = 0; l < k; ++l) {
-                 float f2 = ((float) (l % 2) - 0.5F) * f1;
-                 float f3 = ((float) (l / 2) - 0.5F) * f1;
- 
--                this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> {
-+                Slime converted = this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> { // CraftBukkit
-+                    entityslime.aware = this.aware; // Paper - Fix nerfed slime when splitting
-                     entityslime.setSize(j, true);
-                     entityslime.moveTo(this.getX() + (double) f2, this.getY() + 0.5D, this.getZ() + (double) f3, this.random.nextFloat() * 360.0F, 0.0F);
--                });
-+                // CraftBukkit start
-+                }, null, null);
-+                if (converted != null) {
-+                    slimes.add(converted);
-+                }
-+                // CraftBukkit end
-             }
-+            // CraftBukkit start
-+            if (CraftEventFactory.callEntityTransformEvent(this, slimes, EntityTransformEvent.TransformReason.SPLIT).isCancelled()) {
-+                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
-+                return;
-+            }
-+            for (LivingEntity living : slimes) {
-+                this.level().addFreshEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason
-+            }
-+            // CraftBukkit end
-         }
- 
--        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
-@@ -291,7 +340,11 @@
-                 return checkMobSpawnRules(type, world, spawnReason, pos, random);
-             }
- 
--            if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
-+                // Paper start - Replace rules for Height in Swamp Biome
-+                final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum;
-+                final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum;
-+                if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
-+                // Paper end - Replace rules for Height in Swamp Biome
-                 return checkMobSpawnRules(type, world, spawnReason, pos, random);
-             }
- 
-@@ -300,9 +353,12 @@
-             }
- 
-             ChunkPos chunkcoordintpair = new ChunkPos(pos);
--            boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), 987234911L).nextInt(10) == 0;
-+                boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper
- 
--            if (random.nextInt(10) == 0 && flag && pos.getY() < 40) {
-+                // Paper start - Replace rules for Height in Slime Chunks
-+                final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum;
-+                if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) {
-+                // Paper end - Replace rules for Height in Slime Chunks
-                 return checkMobSpawnRules(type, world, spawnReason, pos, random);
-             }
-         }
-@@ -432,7 +488,7 @@
- 
-         @Override
-         public boolean canUse() {
--            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
-+            return (this.slime.isInWater() || this.slime.isInLava()) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeSwimEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events
-         }
- 
-         @Override
-@@ -469,7 +525,15 @@
-         public boolean canUse() {
-             LivingEntity entityliving = this.slime.getTarget();
- 
--            return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : this.slime.getMoveControl() instanceof Slime.SlimeMoveControl);
-+            // Paper start - Slime pathfinder events
-+            if (entityliving == null || !entityliving.isAlive()) {
-+                return false;
-+            }
-+            if (!this.slime.canAttack(entityliving)) {
-+                return false;
-+            }
-+            return this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent();
-+            // Paper end - Slime pathfinder events
-         }
- 
-         @Override
-@@ -482,7 +546,15 @@
-         public boolean canContinueToUse() {
-             LivingEntity entityliving = this.slime.getTarget();
- 
--            return entityliving == null ? false : (!this.slime.canAttack(entityliving) ? false : --this.growTiredTimer > 0);
-+            // Paper start - Slime pathfinder events
-+            if (entityliving == null || !entityliving.isAlive()) {
-+                return false;
-+            }
-+            if (!this.slime.canAttack(entityliving)) {
-+                return false;
-+            }
-+            return --this.growTiredTimer > 0 && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeTargetLivingEntityEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), (org.bukkit.entity.LivingEntity) entityliving.getBukkitEntity()).callEvent();
-+            // Paper end - Slime pathfinder events
-         }
- 
-         @Override
-@@ -505,6 +577,13 @@
-             }
- 
-         }
-+
-+        // Paper start - Slime pathfinder events; clear timer and target when goal resets
-+        public void stop() {
-+            this.growTiredTimer = 0;
-+            this.slime.setTarget(null);
-+        }
-+        // Paper end - Slime pathfinder events
-     }
- 
-     private static class SlimeRandomDirectionGoal extends Goal {
-@@ -520,7 +599,7 @@
- 
-         @Override
-         public boolean canUse() {
--            return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl;
-+            return this.slime.getTarget() == null && (this.slime.onGround() || this.slime.isInWater() || this.slime.isInLava() || this.slime.hasEffect(MobEffects.LEVITATION)) && this.slime.getMoveControl() instanceof Slime.SlimeMoveControl && this.slime.canWander; // Paper - Slime pathfinder events
-         }
- 
-         @Override
-@@ -528,6 +607,11 @@
-             if (--this.nextRandomizeTime <= 0) {
-                 this.nextRandomizeTime = this.adjustedTickDelay(40 + this.slime.getRandom().nextInt(60));
-                 this.chosenDegrees = (float) this.slime.getRandom().nextInt(360);
-+                // Paper start - Slime pathfinder events
-+                com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent event = new com.destroystokyo.paper.event.entity.SlimeChangeDirectionEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity(), this.chosenDegrees);
-+                if (!this.slime.canWander || !event.callEvent()) return;
-+                this.chosenDegrees = event.getNewYaw();
-+                // Paper end - Slime pathfinder events
-             }
- 
-             MoveControl controllermove = this.slime.getMoveControl();
-@@ -550,7 +634,7 @@
- 
-         @Override
-         public boolean canUse() {
--            return !this.slime.isPassenger();
-+            return !this.slime.isPassenger() && this.slime.canWander && new com.destroystokyo.paper.event.entity.SlimeWanderEvent((org.bukkit.entity.Slime) this.slime.getBukkitEntity()).callEvent(); // Paper - Slime pathfinder events
-         }
- 
-         @Override
-@@ -563,4 +647,15 @@
- 
-         }
-     }
-+
-+    // Paper start - Slime pathfinder events
-+    private boolean canWander = true;
-+    public boolean canWander() {
-+        return canWander;
-+    }
-+
-+    public void setWander(boolean canWander) {
-+        this.canWander = canWander;
-+    }
-+    // Paper end - Slime pathfinder events
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/WitherSkeleton.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
deleted file mode 100644
index 61b7e05667..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/world/entity/monster/WitherSkeleton.java
-+++ b/net/minecraft/world/entity/monster/WitherSkeleton.java
-@@ -110,7 +110,7 @@
-             return false;
-         } else {
-             if (target instanceof LivingEntity) {
--                ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this);
-+                ((LivingEntity) target).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
-             }
- 
-             return true;
-@@ -127,6 +127,6 @@
- 
-     @Override
-     public boolean canBeAffected(MobEffectInstance effect) {
--        return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect);
-+        return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Zombie.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Zombie.java.patch
deleted file mode 100644
index 4b95368547..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/Zombie.java.patch
+++ /dev/null
@@ -1,304 +0,0 @@
---- a/net/minecraft/world/entity/monster/Zombie.java
-+++ b/net/minecraft/world/entity/monster/Zombie.java
-@@ -6,19 +6,6 @@
- import java.util.List;
- import java.util.function.Predicate;
- import javax.annotation.Nullable;
--import net.minecraft.core.BlockPos;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.nbt.NbtOps;
--import net.minecraft.nbt.Tag;
--import net.minecraft.network.syncher.EntityDataAccessor;
--import net.minecraft.network.syncher.EntityDataSerializers;
--import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.resources.ResourceLocation;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundEvents;
--import net.minecraft.sounds.SoundSource;
--import net.minecraft.tags.FluidTags;
- import net.minecraft.util.Mth;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.Difficulty;
-@@ -66,11 +53,31 @@
- import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.Tag;
-+import net.minecraft.network.syncher.EntityDataAccessor;
-+import net.minecraft.network.syncher.EntityDataSerializers;
-+import net.minecraft.network.syncher.SynchedEntityData;
-+import net.minecraft.resources.ResourceLocation;
-+// CraftBukkit start
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundEvents;
-+import net.minecraft.sounds.SoundSource;
-+import net.minecraft.tags.FluidTags;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.entity.EntityCombustByEntityEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+// CraftBukkit end
- 
- public class Zombie extends Monster {
- 
-     private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby");
--    private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, 0.5D, AttributeModifier.Operation.ADD_MULTIPLIED_BASE);
-+    private final AttributeModifier babyModifier = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, this.level().paperConfig().entities.behavior.babyZombieMovementModifier, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); // Paper - Make baby speed configurable
-     private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
-     private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05000000074505806D, AttributeModifier.Operation.ADD_VALUE);
-     private static final ResourceLocation LEADER_ZOMBIE_BONUS_ID = ResourceLocation.withDefaultNamespace("leader_zombie_bonus");
-@@ -91,10 +98,12 @@
-     private boolean canBreakDoors;
-     private int inWaterTime;
-     public int conversionTime;
-+    // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time
-+    private boolean shouldBurnInDay = true; // Paper - Add more Zombie API
- 
-     public Zombie(EntityType<? extends Zombie> type, Level world) {
-         super(type, world);
--        this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE);
-+        this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty
-     }
- 
-     public Zombie(Level world) {
-@@ -103,7 +112,7 @@
- 
-     @Override
-     protected void registerGoals() {
--        this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3));
-+        if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config
-         this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
-         this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-         this.addBehaviourGoals();
-@@ -115,7 +124,7 @@
-         this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
-         this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class));
-         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
--        this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
-+        if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
-         this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
-         this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
-     }
-@@ -165,11 +174,16 @@
- 
-     @Override
-     protected int getBaseExperienceReward(ServerLevel world) {
-+        final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward
-         if (this.isBaby()) {
-             this.xpReward = (int) ((double) this.xpReward * 2.5D);
-         }
- 
--        return super.getBaseExperienceReward(world);
-+        // Paper start - store previous value to reset after calculating XP reward
-+        int reward = super.getBaseExperienceReward(world);
-+        this.xpReward = previousReward;
-+        return reward;
-+        // Paper end - store previous value to reset after calculating XP reward
-     }
- 
-     @Override
-@@ -178,9 +192,9 @@
-         if (this.level() != null && !this.level().isClientSide) {
-             AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
- 
--            attributemodifiable.removeModifier(Zombie.SPEED_MODIFIER_BABY_ID);
-+            attributemodifiable.removeModifier(this.babyModifier.id()); // Paper - Make baby speed configurable
-             if (baby) {
--                attributemodifiable.addTransientModifier(Zombie.SPEED_MODIFIER_BABY);
-+                attributemodifiable.addTransientModifier(this.babyModifier); // Paper - Make baby speed configurable
-             }
-         }
- 
-@@ -203,7 +217,7 @@
-     public void tick() {
-         if (!this.level().isClientSide && this.isAlive() && !this.isNoAi()) {
-             if (this.isUnderWaterConverting()) {
--                --this.conversionTime;
-+                --this.conversionTime; // Paper - remove anti tick skipping measures / wall time
-                 if (this.conversionTime < 0) {
-                     this.doUnderWaterConversion();
-                 }
-@@ -220,6 +234,7 @@
-         }
- 
-         super.tick();
-+        // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time
-     }
- 
-     @Override
-@@ -253,7 +268,14 @@
-         super.aiStep();
-     }
- 
-+    // Paper start - Add more Zombie API
-+    public void stopDrowning() {
-+        this.conversionTime = -1;
-+        this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, false);
-+    }
-+    // Paper end - Add more Zombie API
-     public void startUnderWaterConversion(int ticksUntilWaterConversion) {
-+        // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time
-         this.conversionTime = ticksUntilWaterConversion;
-         this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, true);
-     }
-@@ -267,32 +289,51 @@
-     }
- 
-     protected void convertToZombieType(EntityType<? extends Zombie> entityType) {
--        this.convertTo(entityType, ConversionParams.single(this, true, true), (entityzombie) -> {
-+        Zombie converted = this.convertTo(entityType, ConversionParams.single(this, true, true), (entityzombie) -> { // CraftBukkit
-             entityzombie.handleAttributes(entityzombie.level().getCurrentDifficultyAt(entityzombie.blockPosition()).getSpecialMultiplier());
--        });
-+        // CraftBukkit start
-+        }, EntityTransformEvent.TransformReason.DROWNED, CreatureSpawnEvent.SpawnReason.DROWNED);
-+        if (converted == null) {
-+            ((org.bukkit.entity.Zombie) this.getBukkitEntity()).setConversionTime(-1); // CraftBukkit - SPIGOT-5208: End conversion to stop event spam
-+        }
-+        // CraftBukkit end
-     }
- 
-     @VisibleForTesting
-     public boolean convertVillagerToZombieVillager(ServerLevel world, Villager villager) {
--        ZombieVillager entityzombievillager = (ZombieVillager) villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), (entityzombievillager1) -> {
--            entityzombievillager1.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombievillager1.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
--            entityzombievillager1.setVillagerData(villager.getVillagerData());
--            entityzombievillager1.setGossips((Tag) villager.getGossips().store(NbtOps.INSTANCE));
--            entityzombievillager1.setTradeOffers(villager.getOffers().copy());
--            entityzombievillager1.setVillagerXp(villager.getVillagerXp());
--            if (!this.isSilent()) {
--                world.levelEvent((Player) null, 1026, this.blockPosition(), 0);
-+        // CraftBukkit start
-+        return Zombie.convertVillagerToZombieVillager(world, villager, this.blockPosition(), this.isSilent(), EntityTransformEvent.TransformReason.INFECTION, CreatureSpawnEvent.SpawnReason.INFECTION) != null;
-+    }
-+
-+    public static ZombieVillager convertVillagerToZombieVillager(ServerLevel worldserver, Villager entityvillager, net.minecraft.core.BlockPos blockPosition, boolean silent, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
-+        // CraftBukkit end
-+        ZombieVillager entityzombievillager = (ZombieVillager) entityvillager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(entityvillager, true, true), (entityzombievillager1) -> {
-+            entityzombievillager1.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityzombievillager1.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
-+            entityzombievillager1.setVillagerData(entityvillager.getVillagerData());
-+            entityzombievillager1.setGossips((Tag) entityvillager.getGossips().store(NbtOps.INSTANCE));
-+            entityzombievillager1.setTradeOffers(entityvillager.getOffers().copy());
-+            entityzombievillager1.setVillagerXp(entityvillager.getVillagerXp());
-+            // CraftBukkit start
-+            if (!silent) {
-+                worldserver.levelEvent((Player) null, 1026, blockPosition, 0);
-             }
- 
--        });
-+        }, transformReason, spawnReason);
- 
--        return entityzombievillager != null;
-+        return entityzombievillager;
-+        // CraftBukkit end
-     }
- 
-     public boolean isSunSensitive() {
--        return true;
-+        return this.shouldBurnInDay; // Paper - Add more Zombie API
-     }
- 
-+    // Paper start - Add more Zombie API
-+    public void setShouldBurnInDay(boolean shouldBurnInDay) {
-+        this.shouldBurnInDay = shouldBurnInDay;
-+    }
-+    // Paper end - Add more Zombie API
-+
-     @Override
-     public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
-         if (!super.hurtServer(world, source, amount)) {
-@@ -323,10 +364,10 @@
- 
-                     if (SpawnPlacements.isSpawnPositionOk(entitytypes, world, blockposition) && SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.REINFORCEMENT, blockposition, world.random)) {
-                         entityzombie.setPos((double) i1, (double) j1, (double) k1);
--                        if (!world.hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) {
--                            entityzombie.setTarget(entityliving);
-+                        if (!world.hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && world.isUnobstructed(entityzombie) && world.noCollision((Entity) entityzombie) && (entityzombie.canSpawnInLiquids() || !world.containsAnyLiquid(entityzombie.getBoundingBox()))) { // Paper - affects spawning api
-+                            entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit
-                             entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, (SpawnGroupData) null);
--                            world.addFreshEntityWithPassengers(entityzombie);
-+                            world.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
-                             AttributeInstance attributemodifiable = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE);
-                             AttributeModifier attributemodifier = attributemodifiable.getModifier(Zombie.REINFORCEMENT_CALLER_CHARGE_ID);
-                             double d0 = attributemodifier != null ? attributemodifier.amount() : 0.0D;
-@@ -352,7 +393,14 @@
-             float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
- 
-             if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < f * 0.3F) {
--                target.igniteForSeconds((float) (2 * (int) f));
-+                // CraftBukkit start
-+                EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), target.getBukkitEntity(), (float) (2 * (int) f)); // PAIL: fixme
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (!event.isCancelled()) {
-+                    target.igniteForSeconds(event.getDuration(), false);
-+                }
-+                // CraftBukkit end
-             }
-         }
- 
-@@ -385,7 +433,7 @@
- 
-     @Override
-     public EntityType<? extends Zombie> getType() {
--        return super.getType();
-+        return (EntityType<? extends Zombie>) super.getType(); // CraftBukkit - decompile error
-     }
- 
-     protected boolean canSpawnInLiquids() {
-@@ -414,6 +462,7 @@
-         nbt.putBoolean("CanBreakDoors", this.canBreakDoors());
-         nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
-         nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
-+        nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API
-     }
- 
-     @Override
-@@ -425,6 +474,11 @@
-         if (nbt.contains("DrownedConversionTime", 99) && nbt.getInt("DrownedConversionTime") > -1) {
-             this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime"));
-         }
-+        // Paper start - Add more Zombie API
-+        if (nbt.contains("Paper.ShouldBurnInDay")) {
-+            this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
-+        }
-+        // Paper end - Add more Zombie API
- 
-     }
- 
-@@ -432,10 +486,8 @@
-     public boolean killedEntity(ServerLevel world, LivingEntity other) {
-         boolean flag = super.killedEntity(world, other);
- 
--        if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager entityvillager) {
--            if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
--                return flag;
--            }
-+        final double fallbackChance = world.getDifficulty() == Difficulty.HARD ? 100d : world.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection
-+        if (this.random.nextDouble() * 100 < world.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && other instanceof Villager entityvillager) { // Paper - Configurable chance of villager zombie infection
- 
-             if (this.convertVillagerToZombieVillager(world, entityvillager)) {
-                 flag = false;
-@@ -468,7 +520,7 @@
-         float f = difficulty.getSpecialMultiplier();
- 
-         if (spawnReason != EntitySpawnReason.CONVERSION) {
--            this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f);
-+            this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - Add world settings for mobs picking up loot
-         }
- 
-         if (object == null) {
-@@ -496,7 +548,7 @@
-                             entitychicken1.finalizeSpawn(world, difficulty, EntitySpawnReason.JOCKEY, (SpawnGroupData) null);
-                             entitychicken1.setChickenJockey(true);
-                             this.startRiding(entitychicken1);
--                            world.addFreshEntity(entitychicken1);
-+                            world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
-                         }
-                     }
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombieVillager.java.patch
deleted file mode 100644
index d025644b62..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombieVillager.java.patch
+++ /dev/null
@@ -1,149 +0,0 @@
---- a/net/minecraft/world/entity/monster/ZombieVillager.java
-+++ b/net/minecraft/world/entity/monster/ZombieVillager.java
-@@ -18,10 +18,6 @@
- import net.minecraft.network.syncher.EntityDataAccessor;
- import net.minecraft.network.syncher.EntityDataSerializers;
- import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.server.level.ServerPlayer;
--import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundEvents;
- import net.minecraft.world.DifficultyInstance;
- import net.minecraft.world.InteractionHand;
- import net.minecraft.world.InteractionResult;
-@@ -35,6 +31,7 @@
- import net.minecraft.world.entity.SlotAccess;
- import net.minecraft.world.entity.SpawnGroupData;
- import net.minecraft.world.entity.ai.village.ReputationEventType;
-+import net.minecraft.world.entity.npc.Villager;
- import net.minecraft.world.entity.npc.VillagerData;
- import net.minecraft.world.entity.npc.VillagerDataHolder;
- import net.minecraft.world.entity.npc.VillagerProfession;
-@@ -52,6 +49,16 @@
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundEvents;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.entity.EntityTransformEvent;
-+// CraftBukkit end
-+
- public class ZombieVillager extends Zombie implements VillagerDataHolder {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -69,6 +76,7 @@
-     @Nullable
-     private MerchantOffers tradeOffers;
-     private int villagerXp;
-+    private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
- 
-     public ZombieVillager(EntityType<? extends ZombieVillager> type, Level world) {
-         super(type, world);
-@@ -87,7 +95,7 @@
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
--        DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData());
-+        DataResult<Tag> dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); // CraftBukkit - decompile error
-         Logger logger = ZombieVillager.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -122,7 +130,7 @@
-         }
- 
-         if (nbt.contains("Offers")) {
--            DataResult dataresult1 = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers"));
-+            DataResult<MerchantOffers> dataresult1 = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); // CraftBukkit - decompile error
-             Logger logger1 = ZombieVillager.LOGGER;
- 
-             Objects.requireNonNull(logger1);
-@@ -149,6 +157,10 @@
-     public void tick() {
-         if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
-             int i = this.getConversionProgress();
-+            // CraftBukkit start - Use wall time instead of ticks for villager conversion
-+            int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
-+            i *= elapsedTicks;
-+            // CraftBukkit end
- 
-             this.villagerConversionTime -= i;
-             if (this.villagerConversionTime <= 0) {
-@@ -157,6 +169,7 @@
-         }
- 
-         super.tick();
-+        this.lastTick = MinecraftServer.currentTick; // CraftBukkit
-     }
- 
-     @Override
-@@ -194,12 +207,20 @@
-     }
- 
-     public void startConverting(@Nullable UUID uuid, int delay) {
-+    // Paper start - missing entity behaviour api - converting without entity event
-+        this.startConverting(uuid, delay, true);
-+    }
-+
-+    public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) {
-+    // Paper end - missing entity behaviour api - converting without entity event
-         this.conversionStarter = uuid;
-         this.villagerConversionTime = delay;
-         this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true);
--        this.removeEffect(MobEffects.WEAKNESS);
--        this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)));
--        this.level().broadcastEntityEvent(this, (byte) 16);
-+        // CraftBukkit start
-+        this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
-+        this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
-+        // CraftBukkit end
-+        if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event
-     }
- 
-     @Override
-@@ -215,10 +236,11 @@
-     }
- 
-     private void finishConversion(ServerLevel world) {
--        this.convertTo(EntityType.VILLAGER, ConversionParams.single(this, false, false), (entityvillager) -> {
-+        Villager converted = this.convertTo(EntityType.VILLAGER, ConversionParams.single(this, false, false), (entityvillager) -> { // CraftBukkit
-             Iterator iterator = this.dropPreservedEquipment(world, (itemstack) -> {
-                 return !EnchantmentHelper.has(itemstack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE);
-             }).iterator();
-+            this.forceDrops = false; // CraftBukkit
- 
-             while (iterator.hasNext()) {
-                 EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next();
-@@ -240,7 +262,7 @@
-             entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null);
-             entityvillager.refreshBrain(world);
-             if (this.conversionStarter != null) {
--                Player entityhuman = world.getPlayerByUUID(this.conversionStarter);
-+            Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate
- 
-                 if (entityhuman instanceof ServerPlayer) {
-                     CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager);
-@@ -248,12 +270,16 @@
-                 }
-             }
- 
--            entityvillager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
-+            entityvillager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); // CraftBukkit
-             if (!this.isSilent()) {
-                 world.levelEvent((Player) null, 1027, this.blockPosition(), 0);
-             }
--
--        });
-+        // CraftBukkit start
-+        }, EntityTransformEvent.TransformReason.CURED, CreatureSpawnEvent.SpawnReason.CURED);
-+        if (converted == null) {
-+            ((org.bukkit.entity.ZombieVillager) this.getBukkitEntity()).setConversionTime(-1); // SPIGOT-5208: End conversion to stop event spam
-+        }
-+        // CraftBukkit end
-     }
- 
-     @VisibleForTesting
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
deleted file mode 100644
index 0b3d7e043c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
+++ /dev/null
@@ -1,67 +0,0 @@
---- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-+++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-@@ -56,6 +56,7 @@
-     private static final int ALERT_RANGE_Y = 10;
-     private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6);
-     private int ticksUntilNextAlert;
-+    private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation
- 
-     public ZombifiedPiglin(EntityType<? extends ZombifiedPiglin> type, Level world) {
-         super(type, world);
-@@ -71,7 +72,7 @@
-     protected void addBehaviourGoals() {
-         this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false));
-         this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
--        this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers());
-+        this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation
-         this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
-         this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
-     }
-@@ -149,7 +150,7 @@
-         }).filter((entitypigzombie) -> {
-             return !entitypigzombie.isAlliedTo((Entity) this.getTarget());
-         }).forEach((entitypigzombie) -> {
--            entitypigzombie.setTarget(this.getTarget());
-+            entitypigzombie.setTarget(this.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit
-         });
-     }
- 
-@@ -158,22 +159,32 @@
-     }
- 
-     @Override
--    public void setTarget(@Nullable LivingEntity target) {
--        if (this.getTarget() == null && target != null) {
-+    public boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { // CraftBukkit - signature
-+        if (this.getTarget() == null && entityliving != null) {
-             this.playFirstAngerSoundIn = ZombifiedPiglin.FIRST_ANGER_SOUND_DELAY.sample(this.random);
-             this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random);
-         }
- 
--        if (target instanceof Player) {
--            this.setLastHurtByPlayer((Player) target);
-+        if (entityliving instanceof Player) {
-+            this.setLastHurtByPlayer((Player) entityliving);
-         }
- 
--        super.setTarget(target);
-+        return super.setTarget(entityliving, reason, fireEvent); // CraftBukkit
-     }
- 
-     @Override
-     public void startPersistentAngerTimer() {
--        this.setRemainingPersistentAngerTime(ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random));
-+        // CraftBukkit start
-+        Entity entity = ((ServerLevel) this.level()).getEntity(this.getPersistentAngerTarget());
-+        org.bukkit.event.entity.PigZombieAngerEvent event = new org.bukkit.event.entity.PigZombieAngerEvent((org.bukkit.entity.PigZombie) this.getBukkitEntity(), (entity == null) ? null : entity.getBukkitEntity(), ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random));
-+        this.level().getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            this.setPersistentAngerTarget(null);
-+            pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation
-+            return;
-+        }
-+        this.setRemainingPersistentAngerTime(event.getNewAnger());
-+        // CraftBukkit end
-     }
- 
-     public static boolean checkZombifiedPiglinSpawnRules(EntityType<ZombifiedPiglin> type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {

From c31ab10475986f8d05cd69fe0d8b16d1d5622a14 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 14:43:19 -0500
Subject: [PATCH 093/285] Fixy

---
 .../world/entity/monster/EnderMan.java.patch  | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
index 908813f231..5989e4cd04 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
@@ -1,14 +1,12 @@
 --- a/net/minecraft/world/entity/monster/EnderMan.java
 +++ b/net/minecraft/world/entity/monster/EnderMan.java
-@@ -116,10 +_,26 @@
-     }
+@@ -117,7 +_,23 @@
  
      @Override
--    public void setTarget(@Nullable LivingEntity livingEntity) {
+     public void setTarget(@Nullable LivingEntity livingEntity) {
 -        super.setTarget(livingEntity);
-+    public void setTarget(@Nullable LivingEntity target) {
 +        // CraftBukkit start - fire event
-+        this.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, true);
++        this.setTarget(livingEntity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.UNKNOWN, true);
 +    }
 +
 +    // Paper start - EndermanEscapeEvent
@@ -18,18 +16,15 @@
 +    // Paper end - EndermanEscapeEvent
 +
 +    @Override
-+    public boolean setTarget(LivingEntity target, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) {
-+        if (!super.setTarget(target, reason, fireEvent)) {
++    public boolean setTarget(LivingEntity livingEntity, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++        if (!super.setTarget(livingEntity, reason, fireEvent)) {
 +            return false;
 +        }
-+        target = this.getTarget();
++        livingEntity = this.getTarget();
 +        // CraftBukkit end
          AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
--        if (livingEntity == null) {
-+        if (target == null) {
+         if (livingEntity == null) {
              this.targetChangeTime = 0;
-             this.entityData.set(DATA_CREEPY, false);
-             this.entityData.set(DATA_STARED_AT, false);
 @@ -131,6 +_,7 @@
                  attribute.addTransientModifier(SPEED_MODIFIER_ATTACKING);
              }

From a3bd0b2bbb7ab41986f98a9b88cfd589f17051cb Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:20:06 -0500
Subject: [PATCH 094/285] net/minecraft/world/level/chunk/

---
 .../world/level/chunk/ChunkAccess.java.patch  |  69 ++-
 .../level/chunk/ChunkGenerator.java.patch     | 148 +++++++
 .../ChunkGeneratorStructureState.java.patch   | 112 +++--
 .../world/level/chunk/DataLayer.java.patch    |   2 +-
 .../level/chunk/EmptyLevelChunk.java.patch    |   2 +-
 .../level/chunk/HashMapPalette.java.patch     |  30 ++
 .../world/level/chunk/LevelChunk.java.patch   | 307 +++++++++++++
 .../level/chunk/LevelChunkSection.java.patch  |  33 +-
 .../level/chunk/PalettedContainer.java.patch  |  74 ++++
 .../world/level/chunk/ProtoChunk.java.patch   |  15 +-
 .../world/level/chunk/UpgradeData.java.patch  |  19 +-
 .../level/chunk/ChunkGenerator.java.patch     | 224 ----------
 .../level/chunk/HashMapPalette.java.patch     |  30 --
 .../world/level/chunk/LevelChunk.java.patch   | 409 ------------------
 .../level/chunk/PalettedContainer.java.patch  |  74 ----
 15 files changed, 673 insertions(+), 875 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/ChunkAccess.java.patch (65%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/DataLayer.java.patch (92%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch (95%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/LevelChunkSection.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/ProtoChunk.java.patch (80%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/UpgradeData.java.patch (75%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/HashMapPalette.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunk.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/PalettedContainer.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkAccess.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
index 54c4c6342e..b70d49f1a0 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkAccess.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
@@ -1,43 +1,45 @@
 --- a/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -65,7 +65,7 @@
+@@ -64,7 +_,7 @@
      protected final ShortList[] postProcessing;
      private volatile boolean unsaved;
      private volatile boolean isLightCorrect;
 -    protected final ChunkPos chunkPos;
 +    protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
      private long inhabitedTime;
-     /** @deprecated */
      @Nullable
-@@ -85,8 +85,14 @@
+     @Deprecated
+@@ -82,6 +_,11 @@
+     public final Map<BlockPos, BlockEntity> blockEntities = new Object2ObjectOpenHashMap<>();
      protected final LevelHeightAccessor levelHeightAccessor;
      protected final LevelChunkSection[] sections;
- 
 +    // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading.
 +    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
 +    public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
 +    // CraftBukkit end
-+
-     public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
--        this.chunkPos = pos;
-+        this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
-+        this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
++    public final Registry<Biome> biomeRegistry; // CraftBukkit
+ 
+     public ChunkAccess(
+         ChunkPos chunkPos,
+@@ -92,7 +_,8 @@
+         @Nullable LevelChunkSection[] sections,
+         @Nullable BlendingData blendingData
+     ) {
+-        this.chunkPos = chunkPos;
++        this.locX = chunkPos.x; this.locZ = chunkPos.z; // Paper - reduce need for field lookups
++        this.chunkPos = chunkPos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
          this.upgradeData = upgradeData;
-         this.levelHeightAccessor = heightLimitView;
-         this.sections = new LevelChunkSection[heightLimitView.getSectionsCount()];
-@@ -103,7 +109,11 @@
+         this.levelHeightAccessor = levelHeightAccessor;
+         this.sections = new LevelChunkSection[levelHeightAccessor.getSectionsCount()];
+@@ -109,6 +_,7 @@
          }
  
-         ChunkAccess.replaceMissingSections(biomeRegistry, this.sections);
-+        // CraftBukkit start
-+        this.biomeRegistry = biomeRegistry;
+         replaceMissingSections(biomeRegistry, this.sections);
++        this.biomeRegistry = biomeRegistry; // Craftbukkit
      }
-+    public final Registry<Biome> biomeRegistry;
-+    // CraftBukkit end
  
-     private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) {
-         for (int i = 0; i < sectionArray.length; ++i) {
-@@ -275,6 +285,7 @@
+     private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
+@@ -273,6 +_,7 @@
      public boolean tryMarkSaved() {
          if (this.unsaved) {
              this.unsaved = false;
@@ -45,7 +47,7 @@
              return true;
          } else {
              return false;
-@@ -282,7 +293,7 @@
+@@ -280,7 +_,7 @@
      }
  
      public boolean isUnsaved() {
@@ -54,15 +56,10 @@
      }
  
      public abstract ChunkStatus getPersistedStatus();
-@@ -458,10 +469,31 @@
- 
-             crashreportsystemdetails.setDetail("Location", () -> {
-                 return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ);
-+            });
-+            throw new ReportedException(crashreport);
-+        }
-+    }
-+
+@@ -446,6 +_,26 @@
+             throw new ReportedException(crashReport);
+         }
+     }
 +    // CraftBukkit start
 +    public void setBiome(int i, int j, int k, Holder<Biome> biome) {
 +        try {
@@ -78,11 +75,11 @@
 +
 +            crashreportsystemdetails.setDetail("Location", () -> {
 +                return CrashReportCategory.formatLocation(this, i, j, k);
-             });
-             throw new ReportedException(crashreport);
-         }
-     }
++            });
++            throw new ReportedException(crashreport);
++        }
++    }
 +    // CraftBukkit end
  
-     public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler) {
-         ChunkPos chunkcoordintpair = this.getPos();
+     public void fillBiomesFromNoise(BiomeResolver resolver, Climate.Sampler sampler) {
+         ChunkPos pos = this.getPos();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
new file mode 100644
index 0000000000..bd04398695
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -0,0 +1,148 @@
+--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -104,8 +_,8 @@
+ 
+     protected abstract MapCodec<? extends ChunkGenerator> codec();
+ 
+-    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed) {
+-        return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup);
++    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed, org.spigotmc.SpigotWorldConfig conf) { // Spigot
++        return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup, conf); // Spigot
+     }
+ 
+     public Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> getTypeNameForDataFixer() {
+@@ -127,6 +_,24 @@
+     public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(
+         ServerLevel level, HolderSet<Structure> structure, BlockPos pos, int searchRadius, boolean skipKnownStructures
+     ) {
++        // Paper start - StructuresLocateEvent
++        final org.bukkit.World bukkitWorld = level.getWorld();
++        final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(level, pos);
++        final List<org.bukkit.generator.structure.Structure> apiStructures = structure.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
++        if (!apiStructures.isEmpty()) {
++            final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, searchRadius, skipKnownStructures);
++            if (!event.callEvent()) {
++                return null;
++            }
++            if (event.getResult() != null) {
++                return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
++            }
++            pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin());
++            searchRadius = event.getRadius();
++            skipKnownStructures = event.shouldFindUnexplored();
++            structure = HolderSet.direct(api -> level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
++        }
++        // Paper end
+         ChunkGeneratorStructureState generatorState = level.getChunkSource().getGeneratorState();
+         Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap<>();
+ 
+@@ -222,6 +_,7 @@
+             BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ 
+             for (ChunkPos chunkPos : ringPositionsFor) {
++                if (!level.paperConfig().environment.locateStructuresOutsideWorldBorder && !level.getWorldBorder().isChunkInBounds(chunkPos.x, chunkPos.z)) { continue; } // Paper - Bound treasure maps to world border
+                 mutableBlockPos.set(SectionPos.sectionToBlockCoord(chunkPos.x, 8), 32, SectionPos.sectionToBlockCoord(chunkPos.z, 8));
+                 double d1 = mutableBlockPos.distSqr(pos);
+                 boolean flag = pair == null || d1 < d;
+@@ -255,11 +_,15 @@
+         int spacing = spreadPlacement.spacing();
+ 
+         for (int i = -z; i <= z; i++) {
+-            boolean flag = i == -z || i == z;
++            // Paper start - Perf: iterate over border chunks instead of entire square chunk area
++            final int radius = z;
++            boolean flag = i == -z || i == z; final boolean onBorderAlongZAxis = flag;  // Paper - OBFHELPER
+ 
+-            for (int i1 = -z; i1 <= z; i1++) {
+-                boolean flag1 = i1 == -z || i1 == z;
+-                if (flag || flag1) {
++            for (int i1 = -radius; i1 <= radius; i1 += onBorderAlongZAxis ? 1 : radius * 2) {
++                // boolean flag1 = i1 == -z || i1 == z;
++                // if (flag || flag1) {
++                if (true) {
++            // Paper end - Perf: iterate over border chunks instead of entire square chunk area
+                     int i2 = x + spacing * i;
+                     int i3 = y + spacing * i1;
+                     ChunkPos potentialStructureChunk = spreadPlacement.getPotentialStructureChunk(seed, i2, i3);
+@@ -312,7 +_,7 @@
+         }
+     }
+ 
+-    public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
++    public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) { // CraftBukkit - rename
+         ChunkPos pos = chunk.getPos();
+         if (!SharedConstants.debugVoidTerrain(pos)) {
+             SectionPos sectionPos = SectionPos.of(pos, level.getMinSectionY());
+@@ -385,7 +_,14 @@
+                             int i3 = ints[i2];
+                             PlacedFeature placedFeature = stepFeatureData1.features().get(i3);
+                             Supplier<String> supplier1 = () -> registry1.getResourceKey(placedFeature).map(Object::toString).orElseGet(placedFeature::toString);
+-                            worldgenRandom.setFeatureSeed(l, i3, i);
++                            // Paper start - Configurable feature seeds; change populationSeed used in random
++                            long featurePopulationSeed = i;
++                            final long configFeatureSeed = level.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedFeature.feature());
++                            if (configFeatureSeed != -1) {
++                                featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See seededrandom.setDecorationSeed from above
++                            }
++                            worldgenRandom.setFeatureSeed(featurePopulationSeed, i3, i);
++                            // Paper end - Configurable feature seeds
+ 
+                             try {
+                                 level.setCurrentlyGenerating(supplier1);
+@@ -407,6 +_,32 @@
+             }
+         }
+     }
++   // CraftBukkit start
++    public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
++        this.applyBiomeDecoration(world, chunk, structureAccessor, true);
++    }
++
++    public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
++        if (vanilla) {
++            this.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
++        }
++
++        org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld();
++        // only call when a populator is present (prevents unnecessary entity conversion)
++        if (!world.getPopulators().isEmpty()) {
++            org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos());
++            int x = ichunkaccess.getPos().x;
++            int z = ichunkaccess.getPos().z;
++            for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
++                WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed()));
++                seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z);
++                populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
++            }
++            limitedRegion.saveEntities();
++            limitedRegion.breakLink();
++        }
++    }
++    // CraftBukkit end
+ 
+     private static BoundingBox getWritableArea(ChunkAccess chunk) {
+         ChunkPos pos = chunk.getPos();
+@@ -483,7 +_,7 @@
+                         }
+                     }
+ 
+-                    if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z)) {
++                    if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z, structurePlacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
+                         if (list.size() == 1) {
+                             this.tryGenerateStructure(
+                                 list.get(0),
+@@ -577,6 +_,14 @@
+             predicate
+         );
+         if (structureStart.isValid()) {
++            // CraftBukkit start
++            BoundingBox box = structureStart.getBoundingBox();
++            org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureStart.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunkPos.x, chunkPos.z);
++            org.bukkit.Bukkit.getPluginManager().callEvent(event);
++            if (event.isCancelled()) {
++                return true;
++            }
++            // CraftBukkit end
+             structureManager.setStartForStructure(sectionPos, structure, structureStart, chunk);
+             return true;
+         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
index f9ebf0e3b1..0b9890be26 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
@@ -1,53 +1,38 @@
 --- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
 +++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.world.level.chunk;
  
  import com.google.common.base.Stopwatch;
-@@ -33,6 +34,11 @@
- import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
- import org.slf4j.Logger;
- 
-+// Spigot start
-+import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
-+import org.spigotmc.SpigotWorldConfig;
-+// Spigot end
-+
- public class ChunkGeneratorStructureState {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -44,22 +50,109 @@
-     private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap();
+@@ -41,22 +_,109 @@
+     private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap<>();
      private boolean hasGeneratedPositions;
      private final List<Holder<StructureSet>> possibleStructureSets;
-+    public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs
++    public final org.spigotmc.SpigotWorldConfig conf; // Paper - Add missing structure set seed configs
  
--    public static ChunkGeneratorStructureState createForFlat(RandomState noiseConfig, long seed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets) {
--        List<Holder<StructureSet>> list = structureSets.filter((holder) -> {
--            return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), biomeSource);
-+    public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream<Holder<StructureSet>> stream, SpigotWorldConfig conf) { // Spigot
-+        List<Holder<StructureSet>> list = stream.filter((holder) -> {
-+            return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager);
-         }).toList();
- 
--        return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, 0L, list);
-+        return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+     public static ChunkGeneratorStructureState createForFlat(
+-        RandomState randomState, long levelSeed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets
++        RandomState randomState, long levelSeed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets, org.spigotmc.SpigotWorldConfig conf // Spigot
+     ) {
+         List<Holder<StructureSet>> list = structureSets.filter(structureSet -> hasBiomesForStructureSet(structureSet.value(), biomeSource)).toList();
+-        return new ChunkGeneratorStructureState(randomState, biomeSource, levelSeed, 0L, list);
++        return new ChunkGeneratorStructureState(randomState, biomeSource, levelSeed, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
      }
  
--    public static ChunkGeneratorStructureState createForNormal(RandomState noiseConfig, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetRegistry) {
--        List<Holder<StructureSet>> list = (List) structureSetRegistry.listElements().filter((holder_c) -> {
--            return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), biomeSource);
-+    public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup<StructureSet> holderlookup, SpigotWorldConfig conf) { // Spigot
-+        List<Holder<StructureSet>> list = (List) holderlookup.listElements().filter((holder_c) -> {
-+            return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager);
-         }).collect(Collectors.toUnmodifiableList());
- 
--        return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, seed, list);
-+        return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+     public static ChunkGeneratorStructureState createForNormal(
+-        RandomState randomState, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetLookup
++        RandomState randomState, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetLookup, org.spigotmc.SpigotWorldConfig conf // Spigot
+     ) {
+         List<Holder<StructureSet>> list = structureSetLookup.listElements()
+             .filter(structureSet -> hasBiomesForStructureSet(structureSet.value(), biomeSource))
+             .collect(Collectors.toUnmodifiableList());
+-        return new ChunkGeneratorStructureState(randomState, biomeSource, seed, seed, list);
+-    }
++        return new ChunkGeneratorStructureState(randomState, biomeSource, seed, seed, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
 +    }
 +    // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key
-+    public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement {
++    public static final class KeyedRandomSpreadStructurePlacement extends net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement {
 +        public final net.minecraft.resources.ResourceKey<StructureSet> key;
 +        public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey<StructureSet> key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional<StructurePlacement.ExclusionZone> exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) {
 +            super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType);
@@ -57,11 +42,11 @@
 +    // Paper end - Add missing structure set seed configs
 +
 +    // Spigot start
-+    private static List<Holder<StructureSet>> injectSpigot(List<Holder<StructureSet>> list, SpigotWorldConfig conf) {
++    private static List<Holder<StructureSet>> injectSpigot(List<Holder<StructureSet>> list, org.spigotmc.SpigotWorldConfig conf) {
 +        return list.stream().map((holder) -> {
 +            StructureSet structureset = holder.value();
 +            final Holder<StructureSet> newHolder; // Paper - Add missing structure set seed configs
-+            if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path
++            if (structureset.placement() instanceof net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path
 +                String name = holder.unwrapKey().orElseThrow().location().getPath();
 +                int seed = randomConfig.salt;
 +
@@ -130,46 +115,47 @@
 +            return newHolder;
 +            // Paper end - Add missing structure set seed configs
 +        }).collect(Collectors.toUnmodifiableList());
-     }
++    }
 +    // Spigot end
  
      private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) {
-         Stream<Holder<Biome>> stream = structureSet.structures().stream().flatMap((structureset_a) -> {
-@@ -73,12 +166,13 @@
-         return stream.anyMatch(set::contains);
+         Stream<Holder<Biome>> stream = structureSet.structures().stream().flatMap(structureEntry -> {
+@@ -67,13 +_,14 @@
      }
  
--    private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets) {
-+    private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs
-         this.randomState = noiseConfig;
-         this.levelSeed = structureSeed;
+     private ChunkGeneratorStructureState(
+-        RandomState randomState, BiomeSource biomeSource, long levelSeed, long cocentricRingsSeed, List<Holder<StructureSet>> possibleStructureSets
++        RandomState randomState, BiomeSource biomeSource, long levelSeed, long cocentricRingsSeed, List<Holder<StructureSet>> possibleStructureSets, org.spigotmc.SpigotWorldConfig conf // Paper - Add missing structure set seed configs
+     ) {
+         this.randomState = randomState;
+         this.levelSeed = levelSeed;
          this.biomeSource = biomeSource;
-         this.concentricRingsSeed = concentricRingSeed;
-         this.possibleStructureSets = structureSets;
+         this.concentricRingsSeed = cocentricRingsSeed;
+         this.possibleStructureSets = possibleStructureSets;
 +        this.conf = conf; // Paper - Add missing structure set seed configs
      }
  
      public List<Holder<StructureSet>> possibleStructureSets() {
-@@ -132,7 +226,13 @@
-             HolderSet<Biome> holderset = placement.preferredBiomes();
-             RandomSource randomsource = RandomSource.create();
- 
+@@ -118,7 +_,13 @@
+             int spread = placement.spread();
+             HolderSet<Biome> holderSet = placement.preferredBiomes();
+             RandomSource randomSource = RandomSource.create();
 +            // Paper start - Add missing structure set seed configs
-+            if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) {
-+                randomsource.setSeed(this.conf.strongholdSeed);
++            if (this.conf.strongholdSeed != null && structureSet.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) {
++                randomSource.setSeed(this.conf.strongholdSeed);
 +            } else {
 +            // Paper end - Add missing structure set seed configs
-             randomsource.setSeed(this.concentricRingsSeed);
+             randomSource.setSeed(this.concentricRingsSeed);
 +            } // Paper - Add missing structure set seed configs
-             double d0 = randomsource.nextDouble() * Math.PI * 2.0D;
-             int l = 0;
+             double d = randomSource.nextDouble() * Math.PI * 2.0;
+             int i = 0;
              int i1 = 0;
-@@ -209,7 +309,7 @@
+@@ -197,7 +_,7 @@
  
-         for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) {
-             for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) {
--                if (structureplacement.isStructureChunk(this, l, i1)) {
-+                if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
+         for (int i = x - range; i <= x + range; i++) {
+             for (int i1 = z - range; i1 <= z + range; i1++) {
+-                if (structurePlacement.isStructureChunk(this, i, i1)) {
++                if (structurePlacement.isStructureChunk(this, i, i1, structurePlacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
                      return true;
                  }
              }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/DataLayer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch
similarity index 92%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/DataLayer.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch
index e1be97122d..f3ce378056 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/DataLayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/DataLayer.java
 +++ b/net/minecraft/world/level/chunk/DataLayer.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.world.level.chunk;
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch
index 1ccf6729a6..a67013f5ff 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/EmptyLevelChunk.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 +++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
-@@ -25,6 +25,12 @@
+@@ -25,6 +_,12 @@
      public BlockState getBlockState(BlockPos pos) {
          return Blocks.VOID_AIR.defaultBlockState();
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch
new file mode 100644
index 0000000000..df3071147b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/HashMapPalette.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/chunk/HashMapPalette.java
++++ b/net/minecraft/world/level/chunk/HashMapPalette.java
+@@ -20,7 +_,7 @@
+     }
+ 
+     public HashMapPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler) {
+-        this(registry, bits, resizeHandler, CrudeIncrementalIntIdentityHashBiMap.create(1 << bits));
++        this(registry, bits, resizeHandler, CrudeIncrementalIntIdentityHashBiMap.create((1 << bits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap
+     }
+ 
+     private HashMapPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler, CrudeIncrementalIntIdentityHashBiMap<T> values) {
+@@ -38,10 +_,16 @@
+     public int idFor(T state) {
+         int id = this.values.getId(state);
+         if (id == -1) {
+-            id = this.values.add(state);
+-            if (id >= 1 << this.bits) {
++            // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
++            // We use size() instead of the result from add(K)
++            // This avoids adding another object unnecessarily
++            // Without this change, + 2 would be required in the constructor
++            if (this.values.size() >= 1 << this.bits) {
+                 id = this.resizeHandler.onResize(this.bits + 1, state);
++            } else {
++                id = this.values.add(state);
+             }
++            // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
+         }
+ 
+         return id;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
new file mode 100644
index 0000000000..7d3779bd7c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
@@ -0,0 +1,307 @@
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -76,7 +_,7 @@
+     };
+     private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
+     public boolean loaded;
+-    public final Level level;
++    public final ServerLevel level; // CraftBukkit - type
+     @Nullable
+     private Supplier<FullChunkStatus> fullStatus;
+     @Nullable
+@@ -85,6 +_,14 @@
+     private final LevelChunkTicks<Block> blockTicks;
+     private final LevelChunkTicks<Fluid> fluidTicks;
+     private LevelChunk.UnsavedListener unsavedListener = chunkPos -> {};
++    // CraftBukkit start
++    public boolean mustNotSave;
++    public boolean needsDecoration;
++    // CraftBukkit end
++
++    // Paper start
++    boolean loadedTicketLevel;
++    // Paper end
+ 
+     public LevelChunk(Level level, ChunkPos pos) {
+         this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
+@@ -102,7 +_,7 @@
+         @Nullable BlendingData blendingData
+     ) {
+         super(pos, data, level, level.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData);
+-        this.level = level;
++        this.level = (net.minecraft.server.level.ServerLevel) level; // CraftBukkit - type
+         this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
+ 
+         for (Heightmap.Types types : Heightmap.Types.values()) {
+@@ -154,6 +_,10 @@
+         this.skyLightSources = chunk.skyLightSources;
+         this.setLightCorrect(chunk.isLightCorrect());
+         this.markUnsaved();
++        this.needsDecoration = true; // CraftBukkit
++        // CraftBukkit start
++        this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
++        // CraftBukkit end
+     }
+ 
+     public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
+@@ -162,6 +_,12 @@
+             unsavedListener.setUnsaved(this.chunkPos);
+         }
+     }
++    // Paper start
++    @Override
++    public long getInhabitedTime() {
++        return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
++    }
++    // Paper end
+ 
+     @Override
+     public void markUnsaved() {
+@@ -195,8 +_,25 @@
+             : super.getListenerRegistry(sectionY);
+     }
+ 
++    // Paper start - Perf: Reduce instructions and provide final method
++    public BlockState getBlockState(final int x, final int y, final int z) {
++        return this.getBlockStateFinal(x, y, z);
++    }
++    public BlockState getBlockStateFinal(final int x, final int y, final int z) {
++        // Copied and modified from below
++        final int sectionIndex = this.getSectionIndex(y);
++        if (sectionIndex < 0 || sectionIndex >= this.sections.length
++            || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
++            return Blocks.AIR.defaultBlockState();
++        }
++        return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
++    }
+     @Override
+     public BlockState getBlockState(BlockPos pos) {
++        if (true) {
++            return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
++        }
++        // Paper end - Perf: Reduce instructions and provide final method
+         int x = pos.getX();
+         int y = pos.getY();
+         int z = pos.getZ();
+@@ -231,33 +_,54 @@
+         }
+     }
+ 
++    // Paper start - If loaded util
++    @Override
++    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
++        return this.getFluidState(blockposition);
++    }
++
++    @Override
++    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
++        return this.getBlockState(blockposition);
++    }
++    // Paper end
++
+     @Override
+     public FluidState getFluidState(BlockPos pos) {
+         return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
+     }
+ 
+     public FluidState getFluidState(int x, int y, int z) {
+-        try {
++        // try {  // Paper start - Perf: Optimise Chunk#getFluid
+             int sectionIndex = this.getSectionIndex(y);
+             if (sectionIndex >= 0 && sectionIndex < this.sections.length) {
+                 LevelChunkSection levelChunkSection = this.sections[sectionIndex];
+                 if (!levelChunkSection.hasOnlyAir()) {
+-                    return levelChunkSection.getFluidState(x & 15, y & 15, z & 15);
++                    return levelChunkSection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid
+                 }
+             }
+ 
+             return Fluids.EMPTY.defaultFluidState();
++        /* // Paper - Perf: Optimise Chunk#getFluid
+         } catch (Throwable var7) {
+             CrashReport crashReport = CrashReport.forThrowable(var7, "Getting fluid state");
+             CrashReportCategory crashReportCategory = crashReport.addCategory("Block being got");
+             crashReportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, x, y, z));
+             throw new ReportedException(crashReport);
+         }
++        */ // Paper - Perf: Optimise Chunk#getFluid
+     }
+ 
+     @Nullable
+     @Override
+     public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) {
++// CraftBukkit start
++        return this.setBlockState(pos, state, isMoving, true);
++    }
++
++    @Nullable
++    public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) {
++        // CraftBukkit end
+         int y = pos.getY();
+         LevelChunkSection section = this.getSection(this.getSectionIndex(y));
+         boolean hasOnlyAir = section.hasOnlyAir();
+@@ -292,7 +_,7 @@
+                 }
+ 
+                 boolean hasBlockEntity = blockState.hasBlockEntity();
+-                if (!this.level.isClientSide) {
++                if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+                     blockState.onRemove(this.level, pos, state, isMoving);
+                 } else if (!blockState.is(block) && hasBlockEntity) {
+                     this.removeBlockEntity(pos);
+@@ -301,7 +_,7 @@
+                 if (!section.getBlockState(i, i1, i2).is(block)) {
+                     return null;
+                 } else {
+-                    if (!this.level.isClientSide) {
++                    if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
+                         state.onPlace(this.level, pos, blockState, isMoving);
+                     }
+ 
+@@ -355,7 +_,12 @@
+ 
+     @Nullable
+     public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
+-        BlockEntity blockEntity = this.blockEntities.get(pos);
++        // CraftBukkit start
++        BlockEntity blockEntity = this.level.capturedTileEntities.get(pos);
++        if (blockEntity == null) {
++            blockEntity = this.blockEntities.get(pos);
++        }
++        // CraftBukkit end
+         if (blockEntity == null) {
+             CompoundTag compoundTag = this.pendingBlockEntities.remove(pos);
+             if (compoundTag != null) {
+@@ -409,7 +_,13 @@
+         BlockPos blockPos = blockEntity.getBlockPos();
+         BlockState blockState = this.getBlockState(blockPos);
+         if (!blockState.hasBlockEntity()) {
+-            LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", blockEntity, blockPos, blockState);
++            // Paper start - ServerExceptionEvent
++            com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException(
++                "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockPos, blockState)
++            );
++            e.printStackTrace();
++            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e);
++            // Paper end - ServerExceptionEvent
+         } else {
+             BlockState blockState1 = blockEntity.getBlockState();
+             if (blockState != blockState1) {
+@@ -457,6 +_,11 @@
+     public void removeBlockEntity(BlockPos pos) {
+         if (this.isInLevel()) {
+             BlockEntity blockEntity = this.blockEntities.remove(pos);
++            // CraftBukkit start - SPIGOT-5561: Also remove from pending map
++            if (!this.pendingBlockEntities.isEmpty()) {
++                this.pendingBlockEntities.remove(pos);
++            }
++            // CraftBukkit end
+             if (blockEntity != null) {
+                 if (this.level instanceof ServerLevel serverLevel) {
+                     this.removeGameEventListener(blockEntity, serverLevel);
+@@ -499,6 +_,65 @@
+         }
+     }
+ 
++    // CraftBukkit start
++    public void loadCallback() {
++        // Paper start
++        this.loadedTicketLevel = true;
++        // Paper end
++        org.bukkit.Server server = this.level.getCraftServer();
++        this.level.getChunkSource().addLoadedChunk(this); // Paper
++        if (server != null) {
++            /*
++             * If it's a new world, the first few chunks are generated inside
++             * the World constructor. We can't reliably alter that, so we have
++             * no way of creating a CraftWorld/CraftServer at that point.
++             */
++            org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
++            server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
++
++            if (this.needsDecoration) {
++                this.needsDecoration = false;
++                java.util.Random random = new java.util.Random();
++                random.setSeed(this.level.getSeed());
++                long xRand = random.nextLong() / 2L * 2L + 1L;
++                long zRand = random.nextLong() / 2L * 2L + 1L;
++                random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed());
++
++                org.bukkit.World world = this.level.getWorld();
++                if (world != null) {
++                    this.level.populating = true;
++                    try {
++                        for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
++                            populator.populate(world, random, bukkitChunk);
++                        }
++                    } finally {
++                        this.level.populating = false;
++                    }
++                }
++                server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
++            }
++        }
++    }
++
++    public void unloadCallback() {
++        org.bukkit.Server server = this.level.getCraftServer();
++        org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
++        org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved());
++        server.getPluginManager().callEvent(unloadEvent);
++        // note: saving can be prevented, but not forced if no saving is actually required
++        this.mustNotSave = !unloadEvent.isSaveChunk();
++        this.level.getChunkSource().removeLoadedChunk(this); // Paper
++        // Paper start
++        this.loadedTicketLevel = false;
++        // Paper end
++    }
++
++    @Override
++    public boolean isUnsaved() {
++        return super.isUnsaved() && !this.mustNotSave;
++    }
++    // CraftBukkit end
++
+     public boolean isEmpty() {
+         return false;
+     }
+@@ -711,23 +_,25 @@
+                         if (this.blockEntity.getType().isValid(blockState)) {
+                             this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity);
+                             this.loggedInvalidBlockState = false;
+-                        } else if (!this.loggedInvalidBlockState) {
+-                            this.loggedInvalidBlockState = true;
+-                            LevelChunk.LOGGER
+-                                .warn(
+-                                    "Block entity {} @ {} state {} invalid for ticking:",
+-                                    LogUtils.defer(this::getType),
+-                                    LogUtils.defer(this::getPos),
+-                                    blockState
+-                                );
+-                        }
++                        // Paper start - Remove the Block Entity if it's invalid
++                        } else {
++                            LevelChunk.this.removeBlockEntity(this.getPos());
++                            if (!this.loggedInvalidBlockState) {
++                                this.loggedInvalidBlockState = true;
++                                LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), blockState});
++                            }
++                            // Paper end - Remove the Block Entity if it's invalid
++                         }
++
+ 
+                         profilerFiller.pop();
+                     } catch (Throwable var5) {
+-                        CrashReport crashReport = CrashReport.forThrowable(var5, "Ticking block entity");
+-                        CrashReportCategory crashReportCategory = crashReport.addCategory("Block entity being ticked");
+-                        this.blockEntity.fillCrashReportCategory(crashReportCategory);
+-                        throw new ReportedException(crashReport);
++                        // Paper start - Prevent block entity and entity crashes
++                        final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
++                        net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
++                        net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var5))); // Paper - ServerExceptionEvent
++                        LevelChunk.this.removeBlockEntity(this.getPos());
++                        // Paper end - Prevent block entity and entity crashes
+                     }
+                 }
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunkSection.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
index f6007d4aea..f4a6ffa6fb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java
 +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -19,11 +19,11 @@
+@@ -18,11 +_,11 @@
      public static final int SECTION_HEIGHT = 16;
      public static final int SECTION_SIZE = 4096;
      public static final int BIOME_CONTAINER_BITS = 2;
@@ -14,38 +14,33 @@
  
      private LevelChunkSection(LevelChunkSection section) {
          this.nonEmptyBlockCount = section.nonEmptyBlockCount;
-@@ -33,9 +33,9 @@
+@@ -32,7 +_,7 @@
          this.biomes = section.biomes.copy();
      }
  
--    public LevelChunkSection(PalettedContainer<BlockState> blockStateContainer, PalettedContainerRO<Holder<Biome>> biomeContainer) {
--        this.states = blockStateContainer;
--        this.biomes = biomeContainer;
-+    public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) { // CraftBukkit - read/write
-+        this.states = datapaletteblock;
-+        this.biomes = palettedcontainerro;
+-    public LevelChunkSection(PalettedContainer<BlockState> states, PalettedContainerRO<Holder<Biome>> biomes) {
++    public LevelChunkSection(PalettedContainer<BlockState> states, PalettedContainer<Holder<Biome>> biomes) { // CraftBukkit - read/write
+         this.states = states;
+         this.biomes = biomes;
          this.recalcBlockCounts();
-     }
- 
-@@ -49,7 +49,7 @@
+@@ -48,7 +_,7 @@
      }
  
      public FluidState getFluidState(int x, int y, int z) {
--        return ((BlockState) this.states.get(x, y, z)).getFluidState();
+-        return this.states.get(x, y, z).getFluidState();
 +        return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid.
      }
  
      public void acquire() {
-@@ -196,6 +196,12 @@
-         return (Holder) this.biomes.get(x, y, z);
+@@ -185,6 +_,11 @@
+     public Holder<Biome> getNoiseBiome(int x, int y, int z) {
+         return this.biomes.get(x, y, z);
      }
- 
 +    // CraftBukkit start
 +    public void setBiome(int i, int j, int k, Holder<Biome> biome) {
 +        this.biomes.set(i, j, k, biome);
 +    }
 +    // CraftBukkit end
-+
-     public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int y, int z) {
-         PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
-         boolean flag = true;
+ 
+     public void fillBiomesFromNoise(BiomeResolver biomeResolver, Climate.Sampler climateSampler, int x, int y, int z) {
+         PalettedContainer<Holder<Biome>> palettedContainer = this.biomes.recreate();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch
new file mode 100644
index 0000000000..360faa2856
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/PalettedContainer.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -30,14 +_,14 @@
+     public final IdMap<T> registry;
+     private volatile PalettedContainer.Data<T> data;
+     private final PalettedContainer.Strategy strategy;
+-    private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
++    //private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+ 
+     public void acquire() {
+-        this.threadingDetector.checkAndLock();
++        // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization
+     }
+ 
+     public void release() {
+-        this.threadingDetector.checkAndUnlock();
++        // this.threadingDetector.checkAndUnlock(); // Paper - disable this - use proper synchronization
+     }
+ 
+     public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> registry, Codec<T> codec, PalettedContainer.Strategy strategy, T value) {
+@@ -99,7 +_,7 @@
+     }
+ 
+     @Override
+-    public int onResize(int bits, T objectAdded) {
++    public synchronized int onResize(int bits, T objectAdded) { // Paper - synchronize
+         PalettedContainer.Data<T> data = this.data;
+         PalettedContainer.Data<T> data1 = this.createOrReuseData(data, bits);
+         data1.copyFrom(data.palette, data.storage);
+@@ -107,7 +_,7 @@
+         return data1.palette.idFor(objectAdded);
+     }
+ 
+-    public T getAndSet(int x, int y, int z, T state) {
++    public synchronized T getAndSet(int x, int y, int z, T state) { // Paper - synchronize
+         this.acquire();
+ 
+         Object var5;
+@@ -130,7 +_,7 @@
+         return this.data.palette.valueFor(andSet);
+     }
+ 
+-    public void set(int x, int y, int z, T state) {
++    public synchronized void set(int x, int y, int z, T state) { // Paper - synchronize
+         this.acquire();
+ 
+         try {
+@@ -163,7 +_,7 @@
+         set.forEach(id -> consumer.accept(palette.valueFor(id)));
+     }
+ 
+-    public void read(FriendlyByteBuf buffer) {
++    public synchronized void read(FriendlyByteBuf buffer) { // Paper - synchronize
+         this.acquire();
+ 
+         try {
+@@ -178,7 +_,7 @@
+     }
+ 
+     @Override
+-    public void write(FriendlyByteBuf buffer) {
++    public synchronized void write(FriendlyByteBuf buffer) { // Paper - synchronize
+         this.acquire();
+ 
+         try {
+@@ -226,7 +_,7 @@
+     }
+ 
+     @Override
+-    public PalettedContainerRO.PackedData<T> pack(IdMap<T> registry, PalettedContainer.Strategy strategy) {
++    public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> registry, PalettedContainer.Strategy strategy) { // Paper - synchronize
+         this.acquire();
+ 
+         PalettedContainerRO.PackedData var12;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ProtoChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
similarity index 80%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/ProtoChunk.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
index 1f9c01eb79..9988645334 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ProtoChunk.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
@@ -1,11 +1,9 @@
 --- a/net/minecraft/world/level/chunk/ProtoChunk.java
 +++ b/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -81,7 +81,19 @@
-     @Override
-     public ChunkAccess.PackedTicks getTicksForSerialization(long time) {
-         return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time));
-+    }
-+
+@@ -85,6 +_,18 @@
+         return new ChunkAccess.PackedTicks(this.blockTicks.pack(gametime), this.fluidTicks.pack(gametime));
+     }
+ 
 +    // Paper start - If loaded util
 +    @Override
 +    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
@@ -15,8 +13,9 @@
 +    @Override
 +    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
 +        return this.getBlockState(blockposition);
-     }
++    }
 +    // Paper end
- 
++
      @Override
      public BlockState getBlockState(BlockPos pos) {
+         int y = pos.getY();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/UpgradeData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/UpgradeData.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch
index 902ea36be8..8628e64dd8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/UpgradeData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/UpgradeData.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/UpgradeData.java
 +++ b/net/minecraft/world/level/chunk/UpgradeData.java
-@@ -113,12 +113,36 @@
+@@ -113,6 +_,24 @@
          }
      }
  
@@ -22,26 +22,25 @@
 +        }
 +    }
 +    // Paper end - filter out relocated neighbour ticks
-+
      public void upgrade(LevelChunk chunk) {
          this.upgradeInside(chunk);
  
-         for (Direction8 direction8 : DIRECTIONS) {
+@@ -120,6 +_,10 @@
              upgradeSides(chunk, direction8);
          }
-+
+ 
 +        // Paper start - filter out relocated neighbour ticks
 +        filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks);
 +        filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks);
 +        // Paper end - filter out relocated neighbour ticks
- 
          Level level = chunk.getLevel();
-         this.neighborBlockTicks.forEach(tick -> {
-@@ -129,6 +153,7 @@
-             Fluid fluid = tick.type() == Fluids.EMPTY ? level.getFluidState(tick.pos()).getType() : tick.type();
-             level.scheduleTick(tick.pos(), fluid, tick.delay(), tick.priority());
+         this.neighborBlockTicks.forEach(blockTicker -> {
+             Block block = blockTicker.type() == Blocks.AIR ? level.getBlockState(blockTicker.pos()).getBlock() : blockTicker.type();
+@@ -129,6 +_,7 @@
+             Fluid fluid = fluidTicker.type() == Fluids.EMPTY ? level.getFluidState(fluidTicker.pos()).getType() : fluidTicker.type();
+             level.scheduleTick(fluidTicker.pos(), fluid, fluidTicker.delay(), fluidTicker.priority());
          });
 +        UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised
-         CHUNKY_FIXERS.forEach(logic -> logic.processChunk(level));
+         CHUNKY_FIXERS.forEach(fixers -> fixers.processChunk(level));
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
deleted file mode 100644
index 337768ffa6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
+++ /dev/null
@@ -1,224 +0,0 @@
---- a/net/minecraft/world/level/chunk/ChunkGenerator.java
-+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
-@@ -108,8 +108,8 @@
- 
-     protected abstract MapCodec<? extends ChunkGenerator> codec();
- 
--    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetRegistry, RandomState noiseConfig, long seed) {
--        return ChunkGeneratorStructureState.createForNormal(noiseConfig, seed, this.biomeSource, structureSetRegistry);
-+    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i, org.spigotmc.SpigotWorldConfig conf) { // Spigot
-+        return ChunkGeneratorStructureState.createForNormal(randomstate, i, this.biomeSource, holderlookup, conf); // Spigot
-     }
- 
-     public Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> getTypeNameForDataFixer() {
-@@ -127,6 +127,24 @@
- 
-     @Nullable
-     public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel world, HolderSet<Structure> structures, BlockPos center, int radius, boolean skipReferencedStructures) {
-+        // Paper start - StructuresLocateEvent
-+        final org.bukkit.World bukkitWorld = world.getWorld();
-+        final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center);
-+        final List<org.bukkit.generator.structure.Structure> apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
-+        if (!apiStructures.isEmpty()) {
-+            final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures);
-+            if (!event.callEvent()) {
-+                return null;
-+            }
-+            if (event.getResult() != null) {
-+                return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
-+            }
-+            center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin());
-+            radius = event.getRadius();
-+            skipReferencedStructures = event.shouldFindUnexplored();
-+            structures = HolderSet.direct(api -> world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
-+        }
-+        // Paper end
-         ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState();
-         Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap();
-         Iterator iterator = structures.iterator();
-@@ -223,6 +241,7 @@
- 
-             while (iterator.hasNext()) {
-                 ChunkPos chunkcoordintpair = (ChunkPos) iterator.next();
-+                if (!world.paperConfig().environment.locateStructuresOutsideWorldBorder && !world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper - Bound treasure maps to world border
- 
-                 blockposition_mutableblockposition.set(SectionPos.sectionToBlockCoord(chunkcoordintpair.x, 8), 32, SectionPos.sectionToBlockCoord(chunkcoordintpair.z, 8));
-                 double d1 = blockposition_mutableblockposition.distSqr(center);
-@@ -247,12 +266,15 @@
-         int i1 = placement.spacing();
- 
-         for (int j1 = -radius; j1 <= radius; ++j1) {
--            boolean flag1 = j1 == -radius || j1 == radius;
-+            // Paper start - Perf: iterate over border chunks instead of entire square chunk area
-+            boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER
- 
--            for (int k1 = -radius; k1 <= radius; ++k1) {
--                boolean flag2 = k1 == -radius || k1 == radius;
-+            for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) {
-+                // boolean flag2 = k1 == -radius || k1 == radius;
- 
--                if (flag1 || flag2) {
-+                // if (flag1 || flag2) {
-+                if (true) {
-+                    // Paper end - Perf: iterate over border chunks instead of entire square chunk area
-                     int l1 = centerChunkX + i1 * j1;
-                     int i2 = centerChunkZ + i1 * k1;
-                     ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2);
-@@ -312,29 +334,29 @@
-         }
-     }
- 
--    public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
--        ChunkPos chunkcoordintpair = chunk.getPos();
-+    public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { // CraftBukkit
-+        ChunkPos chunkcoordintpair = ichunkaccess.getPos();
- 
-         if (!SharedConstants.debugVoidTerrain(chunkcoordintpair)) {
--            SectionPos sectionposition = SectionPos.of(chunkcoordintpair, world.getMinSectionY());
-+            SectionPos sectionposition = SectionPos.of(chunkcoordintpair, generatoraccessseed.getMinSectionY());
-             BlockPos blockposition = sectionposition.origin();
--            Registry<Structure> iregistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE);
-+            Registry<Structure> iregistry = generatoraccessseed.registryAccess().lookupOrThrow(Registries.STRUCTURE);
-             Map<Integer, List<Structure>> map = (Map) iregistry.stream().collect(Collectors.groupingBy((structure) -> {
-                 return structure.step().ordinal();
-             }));
-             List<FeatureSorter.StepFeatureData> list = (List) this.featuresPerStep.get();
-             WorldgenRandom seededrandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
--            long i = seededrandom.setDecorationSeed(world.getSeed(), blockposition.getX(), blockposition.getZ());
-+            long i = seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), blockposition.getX(), blockposition.getZ());
-             Set<Holder<Biome>> set = new ObjectArraySet();
- 
-             ChunkPos.rangeClosed(sectionposition.chunk(), 1).forEach((chunkcoordintpair1) -> {
--                ChunkAccess ichunkaccess1 = world.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z);
-+                ChunkAccess ichunkaccess1 = generatoraccessseed.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z);
-                 LevelChunkSection[] achunksection = ichunkaccess1.getSections();
-                 int j = achunksection.length;
- 
-                 for (int k = 0; k < j; ++k) {
-                     LevelChunkSection chunksection = achunksection[k];
--                    PalettedContainerRO palettedcontainerro = chunksection.getBiomes();
-+                    PalettedContainerRO<Holder<Biome>> palettedcontainerro = chunksection.getBiomes(); // CraftBukkit - decompile error
- 
-                     Objects.requireNonNull(set);
-                     palettedcontainerro.getAll(set::add);
-@@ -345,7 +367,7 @@
-             int j = list.size();
- 
-             try {
--                Registry<PlacedFeature> iregistry1 = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
-+                Registry<PlacedFeature> iregistry1 = generatoraccessseed.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
-                 int k = Math.max(GenerationStep.Decoration.values().length, j);
- 
-                 for (int l = 0; l < k; ++l) {
-@@ -353,7 +375,7 @@
-                     Iterator iterator;
-                     CrashReportCategory crashreportsystemdetails;
- 
--                    if (structureAccessor.shouldGenerateStructures()) {
-+                    if (structuremanager.shouldGenerateStructures()) {
-                         List<Structure> list1 = (List) map.getOrDefault(l, Collections.emptyList());
- 
-                         for (iterator = list1.iterator(); iterator.hasNext(); ++i1) {
-@@ -368,9 +390,9 @@
-                             };
- 
-                             try {
--                                world.setCurrentlyGenerating(supplier);
--                                structureAccessor.startsForStructure(sectionposition, structure).forEach((structurestart) -> {
--                                    structurestart.placeInChunk(world, structureAccessor, this, seededrandom, ChunkGenerator.getWritableArea(chunk), chunkcoordintpair);
-+                                generatoraccessseed.setCurrentlyGenerating(supplier);
-+                                structuremanager.startsForStructure(sectionposition, structure).forEach((structurestart) -> {
-+                                    structurestart.placeInChunk(generatoraccessseed, structuremanager, this, seededrandom, ChunkGenerator.getWritableArea(ichunkaccess), chunkcoordintpair);
-                                 });
-                             } catch (Exception exception) {
-                                 CrashReport crashreport = CrashReport.forThrowable(exception, "Feature placement");
-@@ -418,11 +440,18 @@
-                                 return (String) optional.orElseGet(placedfeature::toString);
-                             };
- 
--                            seededrandom.setFeatureSeed(i, l1, l);
-+                            // Paper start - Configurable feature seeds; change populationSeed used in random
-+                            long featurePopulationSeed = i;
-+                            final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature());
-+                            if (configFeatureSeed != -1) {
-+                                featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above
-+                            }
-+                            seededrandom.setFeatureSeed(featurePopulationSeed, l1, l);
-+                            // Paper end - Configurable feature seeds
- 
-                             try {
--                                world.setCurrentlyGenerating(supplier1);
--                                placedfeature.placeWithBiomeCheck(world, this, seededrandom, blockposition);
-+                                generatoraccessseed.setCurrentlyGenerating(supplier1);
-+                                placedfeature.placeWithBiomeCheck(generatoraccessseed, this, seededrandom, blockposition);
-                             } catch (Exception exception1) {
-                                 CrashReport crashreport1 = CrashReport.forThrowable(exception1, "Feature placement");
- 
-@@ -435,15 +464,42 @@
-                     }
-                 }
- 
--                world.setCurrentlyGenerating((Supplier) null);
-+                generatoraccessseed.setCurrentlyGenerating((Supplier) null);
-             } catch (Exception exception2) {
-                 CrashReport crashreport2 = CrashReport.forThrowable(exception2, "Biome decoration");
- 
-                 crashreport2.addCategory("Generation").setDetail("CenterX", (Object) chunkcoordintpair.x).setDetail("CenterZ", (Object) chunkcoordintpair.z).setDetail("Decoration Seed", (Object) i);
-                 throw new ReportedException(crashreport2);
-+            }
-+        }
-+    }
-+
-+   // CraftBukkit start
-+    public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
-+        this.applyBiomeDecoration(world, chunk, structureAccessor, true);
-+    }
-+
-+    public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
-+        if (vanilla) {
-+            this.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
-+        }
-+
-+        org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld();
-+        // only call when a populator is present (prevents unnecessary entity conversion)
-+        if (!world.getPopulators().isEmpty()) {
-+            org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos());
-+            int x = ichunkaccess.getPos().x;
-+            int z = ichunkaccess.getPos().z;
-+            for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
-+                WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed()));
-+                seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z);
-+                populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
-             }
-+            limitedRegion.saveEntities();
-+            limitedRegion.breakLink();
-         }
-     }
-+    // CraftBukkit end
- 
-     private static BoundingBox getWritableArea(ChunkAccess chunk) {
-         ChunkPos chunkcoordintpair = chunk.getPos();
-@@ -521,7 +577,7 @@
-                 }
-             }
- 
--            if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) {
-+            if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
-                 if (list.size() == 1) {
-                     this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition, dimension);
-                 } else {
-@@ -582,6 +638,14 @@
-         StructureStart structurestart = structure.generate(weightedEntry.structure(), dimension, dynamicRegistryManager, this, this.biomeSource, noiseConfig, structureManager, seed, pos, j, chunk, predicate);
- 
-         if (structurestart.isValid()) {
-+            // CraftBukkit start
-+            BoundingBox box = structurestart.getBoundingBox();
-+            org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureAccessor.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), pos.x, pos.z);
-+            org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                return true;
-+            }
-+            // CraftBukkit end
-             structureAccessor.setStartForStructure(sectionPos, structure, structurestart, chunk);
-             return true;
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/HashMapPalette.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/HashMapPalette.java.patch
deleted file mode 100644
index ce7c9bcdcb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/HashMapPalette.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/level/chunk/HashMapPalette.java
-+++ b/net/minecraft/world/level/chunk/HashMapPalette.java
-@@ -20,7 +20,7 @@
-     }
- 
-     public HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener) {
--        this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits));
-+        this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap
-     }
- 
-     private HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener, CrudeIncrementalIntIdentityHashBiMap<T> map) {
-@@ -38,10 +38,16 @@
-     public int idFor(T object) {
-         int i = this.values.getId(object);
-         if (i == -1) {
--            i = this.values.add(object);
--            if (i >= 1 << this.bits) {
-+            // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
-+            // We use size() instead of the result from add(K)
-+            // This avoids adding another object unnecessarily
-+            // Without this change, + 2 would be required in the constructor
-+            if (this.values.size() >= 1 << this.bits) {
-                 i = this.resizeHandler.onResize(this.bits + 1, object);
-+            } else {
-+                i = this.values.add(object);
-             }
-+            // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
-         }
- 
-         return i;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunk.java.patch
deleted file mode 100644
index d33242d709..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/LevelChunk.java.patch
+++ /dev/null
@@ -1,409 +0,0 @@
---- a/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -79,7 +79,7 @@
-     };
-     private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel;
-     public boolean loaded;
--    public final Level level;
-+    public final ServerLevel level; // CraftBukkit - type
-     @Nullable
-     private Supplier<FullChunkStatus> fullStatus;
-     @Nullable
-@@ -98,7 +98,7 @@
-         this.tickersInLevel = Maps.newHashMap();
-         this.unsavedListener = (chunkcoordintpair1) -> {
-         };
--        this.level = world;
-+        this.level = (ServerLevel) world; // CraftBukkit - type
-         this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
-         Heightmap.Types[] aheightmap_type = Heightmap.Types.values();
-         int j = aheightmap_type.length;
-@@ -116,6 +116,15 @@
-         this.fluidTicks = fluidTickScheduler;
-     }
- 
-+    // CraftBukkit start
-+    public boolean mustNotSave;
-+    public boolean needsDecoration;
-+    // CraftBukkit end
-+
-+    // Paper start
-+    boolean loadedTicketLevel;
-+    // Paper end
-+
-     public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
-         this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
-         if (!Collections.disjoint(protoChunk.pendingBlockEntities.keySet(), protoChunk.blockEntities.keySet())) {
-@@ -151,6 +160,10 @@
-         this.skyLightSources = protoChunk.skyLightSources;
-         this.setLightCorrect(protoChunk.isLightCorrect());
-         this.markUnsaved();
-+        this.needsDecoration = true; // CraftBukkit
-+        // CraftBukkit start
-+        this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
-+        // CraftBukkit end
-     }
- 
-     public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
-@@ -187,7 +200,14 @@
-         return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time));
-     }
- 
-+    // Paper start
-     @Override
-+    public long getInhabitedTime() {
-+        return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
-+    }
-+    // Paper end
-+
-+    @Override
-     public GameEventListenerRegistry getListenerRegistry(int ySectionCoord) {
-         Level world = this.level;
- 
-@@ -200,8 +220,25 @@
-         }
-     }
- 
-+    // Paper start - Perf: Reduce instructions and provide final method
-+    public BlockState getBlockState(final int x, final int y, final int z) {
-+        return this.getBlockStateFinal(x, y, z);
-+    }
-+    public BlockState getBlockStateFinal(final int x, final int y, final int z) {
-+        // Copied and modified from below
-+        final int sectionIndex = this.getSectionIndex(y);
-+        if (sectionIndex < 0 || sectionIndex >= this.sections.length
-+            || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
-+            return Blocks.AIR.defaultBlockState();
-+        }
-+        return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
-+    }
-     @Override
-     public BlockState getBlockState(BlockPos pos) {
-+        if (true) {
-+            return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
-+        }
-+        // Paper end - Perf: Reduce instructions and provide final method
-         int i = pos.getX();
-         int j = pos.getY();
-         int k = pos.getZ();
-@@ -243,24 +280,38 @@
-         }
-     }
- 
-+    // Paper start - If loaded util
-     @Override
-+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
-+        return this.getFluidState(blockposition);
-+    }
-+
-+    @Override
-+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
-+        return this.getBlockState(blockposition);
-+    }
-+    // Paper end
-+
-+    @Override
-     public FluidState getFluidState(BlockPos pos) {
-         return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
-     }
- 
-     public FluidState getFluidState(int x, int y, int z) {
--        try {
--            int l = this.getSectionIndex(y);
-+        // Paper start - Perf: Optimise Chunk#getFluid
-+        // try {  // Remove try catch
-+        int index = this.getSectionIndex(y);
-+            if (index >= 0 && index < this.sections.length) {
-+                LevelChunkSection chunksection = this.sections[index];
- 
--            if (l >= 0 && l < this.sections.length) {
--                LevelChunkSection chunksection = this.sections[l];
--
-                 if (!chunksection.hasOnlyAir()) {
--                    return chunksection.getFluidState(x & 15, y & 15, z & 15);
-+                    return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState();
-+                    // Paper end - Perf: Optimise Chunk#getFluid
-                 }
-             }
- 
-             return Fluids.EMPTY.defaultFluidState();
-+        /* // Paper - Perf: Optimise Chunk#getFluid
-         } catch (Throwable throwable) {
-             CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state");
-             CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got");
-@@ -270,80 +321,89 @@
-             });
-             throw new ReportedException(crashreport);
-         }
-+         */  // Paper - Perf: Optimise Chunk#getFluid
-     }
- 
-+    // CraftBukkit start
-     @Nullable
-     @Override
-     public BlockState setBlockState(BlockPos pos, BlockState state, boolean moved) {
--        int i = pos.getY();
-+        return this.setBlockState(pos, state, moved, true);
-+    }
-+
-+    @Nullable
-+    public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) {
-+        // CraftBukkit end
-+        int i = blockposition.getY();
-         LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i));
-         boolean flag1 = chunksection.hasOnlyAir();
- 
--        if (flag1 && state.isAir()) {
-+        if (flag1 && iblockdata.isAir()) {
-             return null;
-         } else {
--            int j = pos.getX() & 15;
-+            int j = blockposition.getX() & 15;
-             int k = i & 15;
--            int l = pos.getZ() & 15;
--            BlockState iblockdata1 = chunksection.setBlockState(j, k, l, state);
-+            int l = blockposition.getZ() & 15;
-+            BlockState iblockdata1 = chunksection.setBlockState(j, k, l, iblockdata);
- 
--            if (iblockdata1 == state) {
-+            if (iblockdata1 == iblockdata) {
-                 return null;
-             } else {
--                Block block = state.getBlock();
-+                Block block = iblockdata.getBlock();
- 
--                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, state);
--                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, state);
--                ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, state);
--                ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, state);
-+                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, iblockdata);
-+                ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, iblockdata);
-+                ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, iblockdata);
-+                ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, iblockdata);
-                 boolean flag2 = chunksection.hasOnlyAir();
- 
-                 if (flag1 != flag2) {
--                    this.level.getChunkSource().getLightEngine().updateSectionStatus(pos, flag2);
-+                    this.level.getChunkSource().getLightEngine().updateSectionStatus(blockposition, flag2);
-                     this.level.getChunkSource().onSectionEmptinessChanged(this.chunkPos.x, SectionPos.blockToSectionCoord(i), this.chunkPos.z, flag2);
-                 }
- 
--                if (LightEngine.hasDifferentLightProperties(iblockdata1, state)) {
-+                if (LightEngine.hasDifferentLightProperties(iblockdata1, iblockdata)) {
-                     ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-                     gameprofilerfiller.push("updateSkyLightSources");
-                     this.skyLightSources.update(this, j, i, l);
-                     gameprofilerfiller.popPush("queueCheckLight");
--                    this.level.getChunkSource().getLightEngine().checkBlock(pos);
-+                    this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
-                     gameprofilerfiller.pop();
-                 }
- 
-                 boolean flag3 = iblockdata1.hasBlockEntity();
- 
--                if (!this.level.isClientSide) {
--                    iblockdata1.onRemove(this.level, pos, state, moved);
-+                if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
-+                    iblockdata1.onRemove(this.level, blockposition, iblockdata, flag);
-                 } else if (!iblockdata1.is(block) && flag3) {
--                    this.removeBlockEntity(pos);
-+                    this.removeBlockEntity(blockposition);
-                 }
- 
-                 if (!chunksection.getBlockState(j, k, l).is(block)) {
-                     return null;
-                 } else {
--                    if (!this.level.isClientSide) {
--                        state.onPlace(this.level, pos, iblockdata1, moved);
-+                    // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
-+                    if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) {
-+                        iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
-                     }
- 
--                    if (state.hasBlockEntity()) {
--                        BlockEntity tileentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK);
-+                    if (iblockdata.hasBlockEntity()) {
-+                        BlockEntity tileentity = this.getBlockEntity(blockposition, LevelChunk.EntityCreationType.CHECK);
- 
--                        if (tileentity != null && !tileentity.isValidBlockState(state)) {
--                            LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{pos, tileentity.getType().builtInRegistryHolder().key().location(), state});
--                            this.removeBlockEntity(pos);
-+                        if (tileentity != null && !tileentity.isValidBlockState(iblockdata)) {
-+                            LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{blockposition, tileentity.getType().builtInRegistryHolder().key().location(), iblockdata});
-+                            this.removeBlockEntity(blockposition);
-                             tileentity = null;
-                         }
- 
-                         if (tileentity == null) {
--                            tileentity = ((EntityBlock) block).newBlockEntity(pos, state);
-+                            tileentity = ((EntityBlock) block).newBlockEntity(blockposition, iblockdata);
-                             if (tileentity != null) {
-                                 this.addAndRegisterBlockEntity(tileentity);
-                             }
-                         } else {
--                            tileentity.setBlockState(state);
-+                            tileentity.setBlockState(iblockdata);
-                             this.updateBlockEntityTicker(tileentity);
-                         }
-                     }
-@@ -375,7 +435,12 @@
- 
-     @Nullable
-     public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
--        BlockEntity tileentity = (BlockEntity) this.blockEntities.get(pos);
-+        // CraftBukkit start
-+        BlockEntity tileentity = this.level.capturedTileEntities.get(pos);
-+        if (tileentity == null) {
-+            tileentity = (BlockEntity) this.blockEntities.get(pos);
-+        }
-+        // CraftBukkit end
- 
-         if (tileentity == null) {
-             CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos);
-@@ -446,7 +511,13 @@
-         BlockState iblockdata = this.getBlockState(blockposition);
- 
-         if (!iblockdata.hasBlockEntity()) {
--            LevelChunk.LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", new Object[]{blockEntity, blockposition, iblockdata});
-+            // Paper start - ServerExceptionEvent
-+            com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException(
-+                "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockposition, iblockdata)
-+            );
-+            e.printStackTrace();
-+            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e);
-+            // Paper end - ServerExceptionEvent
-         } else {
-             BlockState iblockdata1 = blockEntity.getBlockState();
- 
-@@ -500,6 +571,12 @@
-         if (this.isInLevel()) {
-             BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos);
- 
-+            // CraftBukkit start - SPIGOT-5561: Also remove from pending map
-+            if (!this.pendingBlockEntities.isEmpty()) {
-+                this.pendingBlockEntities.remove(pos);
-+            }
-+            // CraftBukkit end
-+
-             if (tileentity != null) {
-                 Level world = this.level;
- 
-@@ -553,6 +630,65 @@
- 
-     }
- 
-+    // CraftBukkit start
-+    public void loadCallback() {
-+        // Paper start
-+        this.loadedTicketLevel = true;
-+        // Paper end
-+        org.bukkit.Server server = this.level.getCraftServer();
-+        this.level.getChunkSource().addLoadedChunk(this); // Paper
-+        if (server != null) {
-+            /*
-+             * If it's a new world, the first few chunks are generated inside
-+             * the World constructor. We can't reliably alter that, so we have
-+             * no way of creating a CraftWorld/CraftServer at that point.
-+             */
-+            org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
-+            server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
-+
-+            if (this.needsDecoration) {
-+                this.needsDecoration = false;
-+                java.util.Random random = new java.util.Random();
-+                random.setSeed(this.level.getSeed());
-+                long xRand = random.nextLong() / 2L * 2L + 1L;
-+                long zRand = random.nextLong() / 2L * 2L + 1L;
-+                random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed());
-+
-+                org.bukkit.World world = this.level.getWorld();
-+                if (world != null) {
-+                    this.level.populating = true;
-+                    try {
-+                        for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
-+                            populator.populate(world, random, bukkitChunk);
-+                        }
-+                    } finally {
-+                        this.level.populating = false;
-+                    }
-+                }
-+                server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
-+            }
-+        }
-+    }
-+
-+    public void unloadCallback() {
-+        org.bukkit.Server server = this.level.getCraftServer();
-+        org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
-+        org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved());
-+        server.getPluginManager().callEvent(unloadEvent);
-+        // note: saving can be prevented, but not forced if no saving is actually required
-+        this.mustNotSave = !unloadEvent.isSaveChunk();
-+        this.level.getChunkSource().removeLoadedChunk(this); // Paper
-+        // Paper start
-+        this.loadedTicketLevel = false;
-+        // Paper end
-+    }
-+
-+    @Override
-+    public boolean isUnsaved() {
-+        return super.isUnsaved() && !this.mustNotSave;
-+    }
-+    // CraftBukkit end
-+
-     public boolean isEmpty() {
-         return false;
-     }
-@@ -750,7 +886,7 @@
- 
-     private <T extends BlockEntity> void updateBlockEntityTicker(T blockEntity) {
-         BlockState iblockdata = blockEntity.getBlockState();
--        BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, blockEntity.getType());
-+        BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, (BlockEntityType<T>) blockEntity.getType()); // CraftBukkit - decompile error
- 
-         if (blockentityticker == null) {
-             this.removeBlockEntityTicker(blockEntity.getBlockPos());
-@@ -841,7 +977,7 @@
-         private boolean loggedInvalidBlockState;
- 
-         BoundTickingBlockEntity(final BlockEntity tileentity, final BlockEntityTicker blockentityticker) {
--            this.blockEntity = tileentity;
-+            this.blockEntity = (T) tileentity; // CraftBukkit - decompile error
-             this.ticker = blockentityticker;
-         }
- 
-@@ -860,18 +996,25 @@
-                         if (this.blockEntity.getType().isValid(iblockdata)) {
-                             this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity);
-                             this.loggedInvalidBlockState = false;
--                        } else if (!this.loggedInvalidBlockState) {
--                            this.loggedInvalidBlockState = true;
--                            LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
-+                        // Paper start - Remove the Block Entity if it's invalid
-+                        } else {
-+                            LevelChunk.this.removeBlockEntity(this.getPos());
-+                            if (!this.loggedInvalidBlockState) {
-+                                this.loggedInvalidBlockState = true;
-+                                LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
-+                            }
-+                            // Paper end - Remove the Block Entity if it's invalid
-                         }
- 
-                         gameprofilerfiller.pop();
-                     } catch (Throwable throwable) {
--                        CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking block entity");
--                        CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block entity being ticked");
--
--                        this.blockEntity.fillCrashReportCategory(crashreportsystemdetails);
--                        throw new ReportedException(crashreport);
-+                        // Paper start - Prevent block entity and entity crashes
-+                        final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
-+                        net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
-+                        net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
-+                        LevelChunk.this.removeBlockEntity(this.getPos());
-+                        // Paper end - Prevent block entity and entity crashes
-+                        // Spigot start
-                     }
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/PalettedContainer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/PalettedContainer.java.patch
deleted file mode 100644
index aff5b81eae..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/PalettedContainer.java.patch
+++ /dev/null
@@ -1,74 +0,0 @@
---- a/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -30,14 +30,14 @@
-     public final IdMap<T> registry;
-     private volatile PalettedContainer.Data<T> data;
-     private final PalettedContainer.Strategy strategy;
--    private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
-+    // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
- 
-     public void acquire() {
--        this.threadingDetector.checkAndLock();
-+        // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization
-     }
- 
-     public void release() {
--        this.threadingDetector.checkAndUnlock();
-+        // this.threadingDetector.checkAndUnlock(); // Paper - disable this
-     }
- 
-     public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) {
-@@ -110,7 +110,7 @@
-     }
- 
-     @Override
--    public int onResize(int newBits, T object) {
-+    public synchronized int onResize(int newBits, T object) { // Paper - synchronize
-         PalettedContainer.Data<T> data = this.data;
-         PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits);
-         data2.copyFrom(data.palette, data.storage);
-@@ -135,7 +135,7 @@
-         return this.getAndSet(this.strategy.getIndex(x, y, z), value);
-     }
- 
--    private T getAndSet(int index, T value) {
-+    private synchronized T getAndSet(int index, T value) { // Paper - synchronize
-         int i = this.data.palette.idFor(value);
-         int j = this.data.storage.getAndSet(index, i);
-         return this.data.palette.valueFor(j);
-@@ -151,7 +151,7 @@
-         }
-     }
- 
--    private void set(int index, T value) {
-+    private synchronized void set(int index, T value) { // Paper - synchronize
-         int i = this.data.palette.idFor(value);
-         this.data.storage.set(index, i);
-     }
-@@ -174,7 +174,7 @@
-         intSet.forEach(id -> action.accept(palette.valueFor(id)));
-     }
- 
--    public void read(FriendlyByteBuf buf) {
-+    public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize
-         this.acquire();
- 
-         try {
-@@ -189,7 +189,7 @@
-     }
- 
-     @Override
--    public void write(FriendlyByteBuf buf) {
-+    public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
-         this.acquire();
- 
-         try {
-@@ -237,7 +237,7 @@
-     }
- 
-     @Override
--    public PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) {
-+    public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize
-         this.acquire();
- 
-         PalettedContainerRO.PackedData var12;

From 9aa5f1a9554ef8e27ac8357b5fdd7f6612a6bec0 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 21:20:52 +0100
Subject: [PATCH 095/285] LivingEntity

---
 .../world/entity/LivingEntity.java.patch      | 1497 +++++++----------
 .../world/entity/TamableAnimal.java.patch     |    9 -
 2 files changed, 609 insertions(+), 897 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/LivingEntity.java.patch (52%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/LivingEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index c1e602ad26..713fc86621 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -42,6 +42,8 @@
+@@ -42,6 +_,8 @@
  import net.minecraft.core.particles.ParticleOptions;
  import net.minecraft.core.particles.ParticleTypes;
  import net.minecraft.nbt.CompoundTag;
@@ -9,19 +9,10 @@
  import net.minecraft.nbt.ListTag;
  import net.minecraft.nbt.NbtOps;
  import net.minecraft.nbt.Tag;
-@@ -94,7 +96,6 @@
- import net.minecraft.world.entity.animal.Wolf;
- import net.minecraft.world.entity.boss.wither.WitherBoss;
- import net.minecraft.world.entity.item.ItemEntity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.projectile.AbstractArrow;
- import net.minecraft.world.entity.projectile.Projectile;
- import net.minecraft.world.item.AxeItem;
-@@ -135,6 +136,30 @@
- import net.minecraft.world.scores.PlayerTeam;
+@@ -136,6 +_,29 @@
  import net.minecraft.world.scores.Scoreboard;
  import org.slf4j.Logger;
-+
+ 
 +// CraftBukkit start
 +import java.util.ArrayList;
 +import java.util.HashSet;
@@ -33,7 +24,6 @@
 +import org.bukkit.craftbukkit.attribute.CraftAttributeMap;
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.entity.Player;
 +import org.bukkit.event.entity.ArrowBodyCountChangeEvent;
 +import org.bukkit.event.entity.EntityDamageEvent;
 +import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
@@ -45,31 +35,14 @@
 +import org.bukkit.event.entity.EntityTeleportEvent;
 +import org.bukkit.event.player.PlayerItemConsumeEvent;
 +// CraftBukkit end
- 
++
  public abstract class LivingEntity extends Entity implements Attackable {
- 
-@@ -174,7 +199,7 @@
-     public static final float DEFAULT_BABY_SCALE = 0.5F;
-     public static final String ATTRIBUTES_FIELD = "attributes";
-     public static final Predicate<LivingEntity> PLAYER_NOT_WEARING_DISGUISE_ITEM = (entityliving) -> {
--        if (entityliving instanceof Player entityhuman) {
-+        if (entityliving instanceof net.minecraft.world.entity.player.Player entityhuman) {
-             ItemStack itemstack = entityhuman.getItemBySlot(EquipmentSlot.HEAD);
- 
-             return !itemstack.is(ItemTags.GAZE_DISGUISE_EQUIPMENT);
-@@ -210,7 +235,7 @@
-     public float yHeadRotO;
-     public final ElytraAnimationState elytraAnimationState;
-     @Nullable
--    public Player lastHurtByPlayer;
-+    public net.minecraft.world.entity.player.Player lastHurtByPlayer;
-     public int lastHurtByPlayerTime;
-     protected boolean dead;
-     protected int noActionTime;
-@@ -260,7 +285,30 @@
-     protected boolean skipDropExperience;
-     private final EnumMap<EquipmentSlot, Reference2ObjectMap<Enchantment, Set<EnchantmentLocationBasedEffect>>> activeLocationDependentEnchantments;
-     protected float appliedScale;
+     private static final Logger LOGGER = LogUtils.getLogger();
+     private static final String TAG_ACTIVE_EFFECTS = "active_effects";
+@@ -266,11 +_,36 @@
+         EquipmentSlot.class
+     );
+     protected float appliedScale = 1.0F;
 +    // CraftBukkit start
 +    public int expToDrop;
 +    public ArrayList<DefaultDrop> drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior
@@ -80,7 +53,7 @@
 +    public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
 +    public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
 +    public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
- 
++
 +    @Override
 +    public float getBukkitYaw() {
 +        return this.getYHeadRot();
@@ -93,73 +66,42 @@
 +        ++this.noActionTime; // Above all the floats
 +    }
 +    // Spigot end
-+
-     protected LivingEntity(EntityType<? extends LivingEntity> type, Level world) {
-         super(type, world);
-         this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY);
-@@ -276,7 +324,9 @@
-         this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class);
-         this.appliedScale = 1.0F;
-         this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
+ 
+     protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
+         super(entityType, level);
+         this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType));
 -        this.setHealth(this.getMaxHealth());
 +        this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
-+        // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
++        // CraftBukkit - this.setHealth(this.getMaxHealth()) inlined and simplified to skip the instanceof check for Player, as getBukkitEntity() is not initialized in constructor
 +        this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
          this.blocksBuilding = true;
-         this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
+         this.rotA = (float)((Math.random() + 1.0) * 0.01F);
          this.reapplyPosition();
-@@ -356,7 +406,13 @@
-                     double d8 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
-                     int i = (int) (150.0D * d8);
- 
--                    worldserver.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
-+                    // CraftBukkit start - visiblity api
-+                    if (this instanceof ServerPlayer) {
-+                        worldserver.sendParticlesSource((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, state), false, false, d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
-+                    } else {
-+                        worldserver.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
-+                    }
-+                    // CraftBukkit end
-                 }
+@@ -360,7 +_,13 @@
+                 float f = Mth.ceil(this.fallDistance - attributeValue);
+                 double min = Math.min((double)(0.2F + f / 15.0F), 2.5);
+                 int i = (int)(150.0 * min);
+-                serverLevel.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
++                // CraftBukkit start - visiblity api
++                if (this instanceof ServerPlayer) {
++                    serverLevel.sendParticlesSource((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, state), false, false, x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
++                } else {
++                    serverLevel.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
++                }
++                // CraftBukkit end
              }
          }
-@@ -402,7 +458,7 @@
-         }
  
-         if (this.isAlive()) {
--            boolean flag = this instanceof Player;
-+            boolean flag = this instanceof net.minecraft.world.entity.player.Player;
-             Level world1 = this.level();
-             ServerLevel worldserver1;
-             double d0;
-@@ -424,7 +480,7 @@
-             }
- 
-             if (this.isEyeInFluid(FluidTags.WATER) && !this.level().getBlockState(BlockPos.containing(this.getX(), this.getEyeY(), this.getZ())).is(Blocks.BUBBLE_COLUMN)) {
--                boolean flag1 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!flag || !((Player) this).getAbilities().invulnerable);
-+                boolean flag1 = !this.canBreatheUnderwater() && !MobEffectUtil.hasWaterBreathing(this) && (!flag || !((net.minecraft.world.entity.player.Player) this).getAbilities().invulnerable);
- 
-                 if (flag1) {
-                     this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
-@@ -573,7 +629,7 @@
-         ++this.deathTime;
+@@ -566,7 +_,7 @@
+         this.deathTime++;
          if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) {
-             this.level().broadcastEntityEvent(this, (byte) 60);
+             this.level().broadcastEntityEvent(this, (byte)60);
 -            this.remove(Entity.RemovalReason.KILLED);
 +            this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
          }
- 
-     }
-@@ -629,7 +685,7 @@
-         return this.lastHurtByMobTimestamp;
      }
  
--    public void setLastHurtByPlayer(@Nullable Player attacking) {
-+    public void setLastHurtByPlayer(@Nullable net.minecraft.world.entity.player.Player attacking) {
-         this.lastHurtByPlayer = attacking;
-         this.lastHurtByPlayerTime = this.tickCount;
-     }
-@@ -667,7 +723,7 @@
+@@ -658,7 +_,7 @@
      }
  
      public boolean shouldDiscardFriction() {
@@ -167,69 +109,47 @@
 +        return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API
      }
  
-     public void setDiscardFriction(boolean noDrag) {
-@@ -679,17 +735,23 @@
+     public void setDiscardFriction(boolean discardFriction) {
+@@ -670,11 +_,16 @@
      }
  
-     public void onEquipItem(EquipmentSlot slot, ItemStack oldStack, ItemStack newStack) {
--        if (!this.level().isClientSide() && !this.isSpectator()) {
--            boolean flag = newStack.isEmpty() && oldStack.isEmpty();
--
--            if (!flag && !ItemStack.isSameItemSameComponents(oldStack, newStack) && !this.firstTick) {
--                Equippable equippable = (Equippable) newStack.get(DataComponents.EQUIPPABLE);
+     public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem) {
 +        // CraftBukkit start
-+        this.onEquipItem(slot, oldStack, newStack, false);
++        this.onEquipItem(slot, oldItem, newItem, false);
 +    }
- 
--                if (!this.isSilent() && equippable != null && slot == equippable.slot()) {
--                    this.level().playSeededSound((Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong());
-+    public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) {
++    public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem, boolean silent) {
 +        // CraftBukkit end
-+        if (!this.level().isClientSide() && !this.isSpectator()) {
-+            boolean flag = itemstack1.isEmpty() && itemstack.isEmpty();
-+
-+            if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) {
-+                Equippable equippable = (Equippable) itemstack1.get(DataComponents.EQUIPPABLE);
-+
-+                if (!this.isSilent() && equippable != null && enumitemslot == equippable.slot() && !silent) { // CraftBukkit
-+                    this.level().playSeededSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong());
-                 }
- 
--                if (this.doesEmitEquipEvent(slot)) {
-+                if (this.doesEmitEquipEvent(enumitemslot)) {
-                     this.gameEvent(equippable != null ? GameEvent.EQUIP : GameEvent.UNEQUIP);
-                 }
- 
-@@ -699,17 +761,24 @@
+         if (!this.level().isClientSide() && !this.isSpectator()) {
+             boolean flag = newItem.isEmpty() && oldItem.isEmpty();
+             if (!flag && !ItemStack.isSameItemSameComponents(oldItem, newItem) && !this.firstTick) {
+                 Equippable equippable = newItem.get(DataComponents.EQUIPPABLE);
+-                if (!this.isSilent() && equippable != null && slot == equippable.slot()) {
++                if (!this.isSilent() && equippable != null && slot == equippable.slot() && !silent) { // CraftBukkit
+                     this.level()
+                         .playSeededSound(
+                             null, this.getX(), this.getY(), this.getZ(), equippable.equipSound(), this.getSoundSource(), 1.0F, 1.0F, this.random.nextLong()
+@@ -690,11 +_,18 @@
  
      @Override
      public void remove(Entity.RemovalReason reason) {
--        if (reason == Entity.RemovalReason.KILLED || reason == Entity.RemovalReason.DISCARDED) {
 +        // CraftBukkit start - add Bukkit remove cause
 +        this.remove(reason, null);
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason reason, EntityRemoveEvent.Cause eventCaue) {
 +        // CraftBukkit end
-+        if (entity_removalreason == Entity.RemovalReason.KILLED || entity_removalreason == Entity.RemovalReason.DISCARDED) {
-             Level world = this.level();
- 
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.triggerOnDeathMobEffects(worldserver, reason);
-+                this.triggerOnDeathMobEffects(worldserver, entity_removalreason);
-             }
+         if ((reason == Entity.RemovalReason.KILLED || reason == Entity.RemovalReason.DISCARDED) && this.level() instanceof ServerLevel serverLevel) {
+             this.triggerOnDeathMobEffects(serverLevel, reason);
          }
  
 -        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit
++        super.remove(reason, eventCaue); // CraftBukkit
          this.brain.clearMemories();
      }
  
-@@ -722,11 +791,17 @@
-             mobeffect.onMobRemoved(world, this, reason);
+@@ -703,11 +_,17 @@
+             mobEffectInstance.onMobRemoved(level, this, removalReason);
          }
  
 +        this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); // CraftBukkit
@@ -237,82 +157,81 @@
      }
  
      @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
+     public void addAdditionalSaveData(CompoundTag compound) {
 +        // Paper start - Friction API
 +        if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
-+            nbt.putString("Paper.FrictionState", this.frictionState.toString());
++            compound.putString("Paper.FrictionState", this.frictionState.toString());
 +        }
 +        // Paper end - Friction API
-         nbt.putFloat("Health", this.getHealth());
-         nbt.putShort("HurtTime", (short) this.hurtTime);
-         nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp);
-@@ -763,7 +838,23 @@
+         compound.putFloat("Health", this.getHealth());
+         compound.putShort("HurtTime", (short)this.hurtTime);
+         compound.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp);
+@@ -736,7 +_,23 @@
  
      @Override
-     public void readAdditionalSaveData(CompoundTag nbt) {
--        this.internalSetAbsorptionAmount(nbt.getFloat("AbsorptionAmount"));
+     public void readAdditionalSaveData(CompoundTag compound) {
+-        this.internalSetAbsorptionAmount(compound.getFloat("AbsorptionAmount"));
 +        // Paper start - Check for NaN
-+        float absorptionAmount = nbt.getFloat("AbsorptionAmount");
++        float absorptionAmount = compound.getFloat("AbsorptionAmount");
 +        if (Float.isNaN(absorptionAmount)) {
 +            absorptionAmount = 0;
 +        }
 +        this.internalSetAbsorptionAmount(absorptionAmount);
 +        // Paper end - Check for NaN
 +        // Paper start - Friction API
-+        if (nbt.contains("Paper.FrictionState")) {
-+            String fs = nbt.getString("Paper.FrictionState");
++        if (compound.contains("Paper.FrictionState")) {
++            String frictionState = compound.getString("Paper.FrictionState");
 +            try {
-+                frictionState = net.kyori.adventure.util.TriState.valueOf(fs);
++                this.frictionState = net.kyori.adventure.util.TriState.valueOf(frictionState);
 +            } catch (Exception ignored) {
-+                LOGGER.error("Unknown friction state " + fs + " for " + this);
++                LOGGER.error("Unknown friction state " + frictionState + " for " + this);
 +            }
 +        }
 +        // Paper end - Friction API
-         if (nbt.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) {
-             this.getAttributes().load(nbt.getList("attributes", 10));
+         if (compound.contains("attributes", 9) && this.level() != null && !this.level().isClientSide) {
+             this.getAttributes().load(compound.getList("attributes", 10));
          }
-@@ -781,6 +872,17 @@
+@@ -753,6 +_,16 @@
              }
          }
  
 +        // CraftBukkit start
-+        if (nbt.contains("Bukkit.MaxHealth")) {
-+            Tag nbtbase = nbt.get("Bukkit.MaxHealth");
-+            if (nbtbase.getId() == 5) {
-+                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) nbtbase).getAsDouble());
-+            } else if (nbtbase.getId() == 3) {
-+                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) nbtbase).getAsDouble());
++        if (compound.contains("Bukkit.MaxHealth")) {
++            Tag maxHealthTag = compound.get("Bukkit.MaxHealth");
++            if (maxHealthTag.getId() == 5) {
++                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) maxHealthTag).getAsDouble());
++            } else if (maxHealthTag.getId() == 3) {
++                this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) maxHealthTag).getAsDouble());
 +            }
 +        }
 +        // CraftBukkit end
-+
-         if (nbt.contains("Health", 99)) {
-             this.setHealth(nbt.getFloat("Health"));
+         if (compound.contains("Health", 99)) {
+             this.setHealth(compound.getFloat("Health"));
          }
-@@ -792,6 +894,7 @@
-             String s = nbt.getString("Team");
+@@ -764,6 +_,7 @@
+             String string = compound.getString("Team");
              Scoreboard scoreboard = this.level().getScoreboard();
-             PlayerTeam scoreboardteam = scoreboard.getPlayerTeam(s);
-+            if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper - Perf: Disable Scoreboards for non players by default
-             boolean flag = scoreboardteam != null && scoreboard.addPlayerToTeam(this.getStringUUID(), scoreboardteam);
- 
+             PlayerTeam playerTeam = scoreboard.getPlayerTeam(string);
++            if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { playerTeam = null; } // Paper - Perf: Disable Scoreboards for non players by default
+             boolean flag = playerTeam != null && scoreboard.addPlayerToTeam(this.getStringUUID(), playerTeam);
              if (!flag) {
-@@ -806,11 +909,13 @@
-         if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) {
-             BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ"));
+                 LOGGER.warn("Unable to add mob to team \"{}\" (that team probably doesn't exist)", string);
+@@ -776,11 +_,13 @@
  
-+            if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong
-             this.setSleepingPos(blockposition);
-             this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING);
+         if (compound.contains("SleepingX", 99) && compound.contains("SleepingY", 99) && compound.contains("SleepingZ", 99)) {
+             BlockPos blockPos = new BlockPos(compound.getInt("SleepingX"), compound.getInt("SleepingY"), compound.getInt("SleepingZ"));
++            if (this.position().distanceToSqr(blockPos.getX(), blockPos.getY(), blockPos.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong
+             this.setSleepingPos(blockPos);
+             this.entityData.set(DATA_POSE, Pose.SLEEPING);
              if (!this.firstTick) {
-                 this.setPosToBed(blockposition);
+                 this.setPosToBed(blockPos);
              }
 +            } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong
          }
  
-         if (nbt.contains("Brain", 10)) {
-@@ -819,9 +924,32 @@
- 
+         if (compound.contains("Brain", 10)) {
+@@ -788,15 +_,44 @@
+         }
      }
  
 +    // CraftBukkit start
@@ -343,56 +262,44 @@
 +        this.isTickingEffects = true; // CraftBukkit
          try {
              while (iterator.hasNext()) {
-                 Holder<MobEffect> holder = (Holder) iterator.next();
-@@ -831,6 +959,12 @@
-                     this.onEffectUpdated(mobeffect, true, (Entity) null);
-                 })) {
+                 Holder<MobEffect> holder = iterator.next();
+                 MobEffectInstance mobEffectInstance = this.activeEffects.get(holder);
+                 if (!mobEffectInstance.tick(this, () -> this.onEffectUpdated(mobEffectInstance, true, null))) {
                      if (!this.level().isClientSide) {
 +                        // CraftBukkit start
-+                        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
++                        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobEffectInstance, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
 +                        if (event.isCancelled()) {
 +                            continue;
 +                        }
 +                        // CraftBukkit end
                          iterator.remove();
-                         this.onEffectsRemoved(List.of(mobeffect));
+                         this.onEffectsRemoved(List.of(mobEffectInstance));
                      }
-@@ -841,6 +975,17 @@
-         } catch (ConcurrentModificationException concurrentmodificationexception) {
-             ;
+@@ -807,6 +_,17 @@
+         } catch (ConcurrentModificationException var6) {
          }
+ 
 +        // CraftBukkit start
 +        this.isTickingEffects = false;
-+        for (ProcessableEffect e : this.effectsToProcess) {
-+            if (e.effect != null) {
-+                this.addEffect(e.effect, e.cause);
++        for (ProcessableEffect effect : this.effectsToProcess) {
++            if (effect.effect != null) {
++                this.addEffect(effect.effect, effect.cause);
 +            } else {
-+                this.removeEffect(e.type, e.cause);
++                this.removeEffect(effect.type, effect.cause);
 +            }
 +        }
 +        this.effectsToProcess.clear();
 +        // CraftBukkit end
- 
          if (this.effectsDirty) {
              if (!this.level().isClientSide) {
-@@ -921,7 +1066,7 @@
+                 this.updateInvisibilityStatus();
+@@ -912,15 +_,33 @@
      }
  
-     public boolean canAttack(LivingEntity target) {
--        return target instanceof Player && this.level().getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy();
-+        return target instanceof net.minecraft.world.entity.player.Player && this.level().getDifficulty() == Difficulty.PEACEFUL ? false : target.canBeSeenAsEnemy();
-     }
- 
-     public boolean canBeSeenAsEnemy() {
-@@ -952,17 +1097,36 @@
-         this.entityData.set(LivingEntity.DATA_EFFECT_PARTICLES, List.of());
-     }
- 
-+    // CraftBukkit start
      public boolean removeAllEffects() {
++        // CraftBukkit start
 +        return this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
 +    }
-+
 +    public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) {
 +        // CraftBukkit end
          if (this.level().isClientSide) {
@@ -401,13 +308,12 @@
              return false;
          } else {
 -            Map<Holder<MobEffect>, MobEffectInstance> map = Maps.newHashMap(this.activeEffects);
-+            // CraftBukkit start
-+            List<MobEffectInstance> toRemove = new LinkedList<>();
-+            Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
- 
 -            this.activeEffects.clear();
 -            this.onEffectsRemoved(map.values());
 -            return true;
++            // CraftBukkit start
++            List<MobEffectInstance> toRemove = new LinkedList<>();
++            Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
 +            while (iterator.hasNext()) {
 +                MobEffectInstance effect = iterator.next();
 +                EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
@@ -425,142 +331,125 @@
          }
      }
  
-@@ -987,24 +1151,63 @@
-         return this.addEffect(effect, (Entity) null);
+@@ -942,21 +_,57 @@
      }
  
+     public final boolean addEffect(MobEffectInstance effectInstance) {
+-        return this.addEffect(effectInstance, null);
++        return this.addEffect(effectInstance, (Entity) null); // CraftBukkit
++    }
++
 +    // CraftBukkit start
-+    public boolean addEffect(MobEffectInstance mobeffect, EntityPotionEffectEvent.Cause cause) {
-+        return this.addEffect(mobeffect, (Entity) null, cause);
++    public boolean addEffect(MobEffectInstance effectInstance, EntityPotionEffectEvent.Cause cause) {
++        return this.addEffect(effectInstance, (Entity) null, cause);
+     }
+ 
+     public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
++        return this.addEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
 +    }
 +
-     public boolean addEffect(MobEffectInstance effect, @Nullable Entity source) {
--        if (!this.canBeAffected(effect)) {
-+        return this.addEffect(effect, source, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
-+    }
-+
-+    public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
++    public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
 +        // Paper start - Don't fire sync event during generation
-+        return this.addEffect(mobeffect, entity, cause, true);
++        return this.addEffect(effectInstance, entity, cause, true);
 +    }
-+    public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
++    public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) {
 +        // Paper end - Don't fire sync event during generation
 +        // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API
 +        if (this.isTickingEffects) {
-+            this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
++            this.effectsToProcess.add(new ProcessableEffect(effectInstance, cause));
 +            return true;
 +        }
 +        // CraftBukkit end
-+
-+        if (!this.canBeAffected(mobeffect)) {
+         if (!this.canBeAffected(effectInstance)) {
              return false;
          } else {
--            MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(effect.getEffect());
-+            MobEffectInstance mobeffect1 = (MobEffectInstance) this.activeEffects.get(mobeffect.getEffect());
+             MobEffectInstance mobEffectInstance = this.activeEffects.get(effectInstance.getEffect());
              boolean flag = false;
- 
 +            // CraftBukkit start
 +            boolean override = false;
-+            if (mobeffect1 != null) {
-+                override = new MobEffectInstance(mobeffect1).update(mobeffect);
++            if (mobEffectInstance != null) {
++                override = new MobEffectInstance(mobEffectInstance).update(effectInstance);
 +            }
 +
 +            if (fireEvent) { // Paper - Don't fire sync event during generation
-+            EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override);
++            EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobEffectInstance, effectInstance, cause, override);
 +            override = event.isOverride(); // Paper - Don't fire sync event during generation
 +            if (event.isCancelled()) {
 +                return false;
 +            }
 +            } // Paper - Don't fire sync event during generation
 +            // CraftBukkit end
-+
-             if (mobeffect1 == null) {
--                this.activeEffects.put(effect.getEffect(), effect);
--                this.onEffectAdded(effect, source);
-+                this.activeEffects.put(mobeffect.getEffect(), mobeffect);
-+                this.onEffectAdded(mobeffect, entity);
+             if (mobEffectInstance == null) {
+                 this.activeEffects.put(effectInstance.getEffect(), effectInstance);
+                 this.onEffectAdded(effectInstance, entity);
                  flag = true;
--                effect.onEffectAdded(this);
--            } else if (mobeffect1.update(effect)) {
--                this.onEffectUpdated(mobeffect1, true, source);
-+                mobeffect.onEffectAdded(this);
+                 effectInstance.onEffectAdded(this);
+-            } else if (mobEffectInstance.update(effectInstance)) {
 +                // CraftBukkit start
 +            } else if (override) { // Paper - Don't fire sync event during generation
-+                mobeffect1.update(mobeffect);
-+                this.onEffectUpdated(mobeffect1, true, entity);
-+                // CraftBukkit end
++                mobEffectInstance.update(effectInstance);
+                 this.onEffectUpdated(mobEffectInstance, true, entity);
                  flag = true;
              }
+@@ -995,11 +_,37 @@
  
--            effect.onEffectStarted(this);
-+            mobeffect.onEffectStarted(this);
-             return flag;
-         }
-     }
-@@ -1031,14 +1234,40 @@
-         return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM);
-     }
- 
-+    // CraftBukkit start
      @Nullable
      public MobEffectInstance removeEffectNoUpdate(Holder<MobEffect> effect) {
--        return (MobEffectInstance) this.activeEffects.remove(effect);
+-        return this.activeEffects.remove(effect);
++        // CraftBukkit start
 +        return this.removeEffectNoUpdate(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
-     }
- 
++    }
++
 +    @Nullable
-+    public MobEffectInstance removeEffectNoUpdate(Holder<MobEffect> holder, EntityPotionEffectEvent.Cause cause) {
++    public MobEffectInstance removeEffectNoUpdate(Holder<MobEffect> effect, EntityPotionEffectEvent.Cause cause) {
 +        if (this.isTickingEffects) {
-+            this.effectsToProcess.add(new ProcessableEffect(holder, cause));
++            this.effectsToProcess.add(new ProcessableEffect(effect, cause));
 +            return null;
 +        }
 +
-+        MobEffectInstance effect = this.activeEffects.get(holder);
-+        if (effect == null) {
++        MobEffectInstance effectInstance = this.activeEffects.get(effect);
++        if (effectInstance == null) {
 +            return null;
 +        }
 +
-+        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause);
++        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effectInstance, null, cause);
 +        if (event.isCancelled()) {
 +            return null;
 +        }
 +
-+        return (MobEffectInstance) this.activeEffects.remove(holder);
-+    }
-+
-     public boolean removeEffect(Holder<MobEffect> effect) {
--        MobEffectInstance mobeffect = this.removeEffectNoUpdate(effect);
-+        return this.removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
-+    }
- 
-+    public boolean removeEffect(Holder<MobEffect> holder, EntityPotionEffectEvent.Cause cause) {
-+        MobEffectInstance mobeffect = this.removeEffectNoUpdate(holder, cause);
-+        // CraftBukkit end
-+
-         if (mobeffect != null) {
-             this.onEffectsRemoved(List.of(mobeffect));
-             return true;
-@@ -1142,20 +1371,65 @@
- 
++        return this.activeEffects.remove(effectInstance);
      }
  
-+    // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
-     public void heal(float amount) {
-+        this.heal(amount, EntityRegainHealthEvent.RegainReason.CUSTOM);
+     public boolean removeEffect(Holder<MobEffect> effect) {
+-        MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(effect);
++        return this.removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
 +    }
 +
-+    public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) {
-+        // Paper start - Forward
-+        heal(f, regainReason, false);
-+    }
-+
-+    public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) {
-+        // Paper end
-         float f1 = this.getHealth();
++    public boolean removeEffect(Holder<MobEffect> effect, EntityPotionEffectEvent.Cause cause) {
++        MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(effect, cause);
++        // CraftBukkit end
+         if (mobEffectInstance != null) {
+             this.onEffectsRemoved(List.of(mobEffectInstance));
+             return true;
+@@ -1080,17 +_,62 @@
+     }
  
-         if (f1 > 0.0F) {
--            this.setHealth(f1 + amount);
-+            EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper
+     public void heal(float healAmount) {
++        // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
++        this.heal(healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM);
++    }
++
++    public void heal(float healAmount, EntityRegainHealthEvent.RegainReason regainReason) {
++        // Paper start - Forward
++        this.heal(healAmount, regainReason, false);
++    }
++
++    public void heal(float healAmount, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) {
++        // Paper end - Forward
+         float health = this.getHealth();
+         if (health > 0.0F) {
+-            this.setHealth(health + healAmount);
++            EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), healAmount, regainReason, isFastRegen); // Paper
 +            // Suppress during worldgen
 +            if (this.valid) {
 +                this.level().getCraftServer().getPluginManager().callEvent(event);
@@ -571,16 +460,15 @@
 +            }
 +            // CraftBukkit end
          }
- 
      }
  
      public float getHealth() {
 +        // CraftBukkit start - Use unscaled health
-+        if (this instanceof ServerPlayer) {
-+            return (float) ((ServerPlayer) this).getBukkitEntity().getHealth();
++        if (this instanceof ServerPlayer serverPlayer) {
++            return (float) serverPlayer.getBukkitEntity().getHealth();
 +        }
 +        // CraftBukkit end
-         return (Float) this.entityData.get(LivingEntity.DATA_HEALTH_ID);
+         return this.entityData.get(DATA_HEALTH_ID);
      }
  
      public void setHealth(float health) {
@@ -604,76 +492,71 @@
 +            return;
 +        }
 +        // CraftBukkit end
-         this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
+         this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
      }
  
-@@ -1167,7 +1441,7 @@
-     public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
-         if (this.isInvulnerableTo(world, source)) {
+@@ -1102,7 +_,7 @@
+     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
+         if (this.isInvulnerableTo(level, damageSource)) {
              return false;
 -        } else if (this.isDeadOrDying()) {
 +        } else if (this.isRemoved() || this.dead || this.getHealth() <= 0.0F) { // CraftBukkit - Don't allow entities that got set to dead/killed elsewhere to get damaged and die
              return false;
-         } else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
+         } else if (damageSource.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
              return false;
-@@ -1181,11 +1455,12 @@
+@@ -1116,10 +_,11 @@
                  amount = 0.0F;
              }
  
--            float f1 = amount;
+-            float f = amount;
 -            boolean flag = false;
-+            float f1 = amount; final float originalAmount = f1; // Paper - revert to vanilla #hurt - OBFHELPER
-+            boolean flag = amount > 0.0F && this.isDamageSourceBlocked(source); // Copied from below
-             float f2 = 0.0F;
- 
--            if (amount > 0.0F && this.isDamageSourceBlocked(source)) {
++            float f = amount; final float originalAmount = amount; // Paper - revert to vanilla #hurt - OBFHELPER
++            boolean flag = amount > 0.0F && this.isDamageSourceBlocked(damageSource); // Copied from below;
+             float f1 = 0.0F;
+-            if (amount > 0.0F && this.isDamageSourceBlocked(damageSource)) {
 +            // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
-+            if (false && amount > 0.0F && this.isDamageSourceBlocked(source)) {
++            if (false && amount > 0.0F && this.isDamageSourceBlocked(damageSource)) {
                  this.hurtCurrentlyUsedShield(amount);
-                 f2 = amount;
+                 f1 = amount;
                  amount = 0.0F;
-@@ -1202,15 +1477,21 @@
+@@ -1130,33 +_,56 @@
                  flag = true;
              }
  
--            if (source.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
+-            if (damageSource.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
 +            // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f
-+            if (false && source.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
++            if (false && damageSource.is(DamageTypeTags.IS_FREEZING) && this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
                  amount *= 5.0F;
              }
  
--            if (source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+-            if (damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
 +            // CraftBukkit - Moved into handleEntityDamage(DamageSource, float) for get f and actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
-+            if (false && source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
-                 this.hurtHelmet(source, amount);
++            if (false && damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+                 this.hurtHelmet(damageSource, amount);
                  amount *= 0.75F;
              }
  
-+            // CraftBukkit start
-+            EntityDamageEvent event; // Paper - move this into the actual invuln check....
-+            // CraftBukkit end
-+
++            EntityDamageEvent event; // CraftBukkit // Paper - move this into the actual invuln check....
              this.walkAnimation.setSpeed(1.5F);
              if (Float.isNaN(amount) || Float.isInfinite(amount)) {
                  amount = Float.MAX_VALUE;
-@@ -1218,18 +1499,38 @@
+             }
  
              boolean flag1 = true;
- 
--            if ((float) this.invulnerableTime > 10.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
-+            if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) { // CraftBukkit - restore use of maxNoDamageTicks
+-            if (this.invulnerableTime > 10.0F && !damageSource.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
++            if (this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && !damageSource.is(DamageTypeTags.BYPASSES_COOLDOWN)) { // CraftBukkit - restore use of maxNoDamageTicks
                  if (amount <= this.lastHurt) {
                      return false;
                  }
  
--                this.actuallyHurt(world, source, amount - this.lastHurt);
+-                this.actuallyHurt(level, damageSource, amount - this.lastHurt);
 +                // Paper start - only call damage event when actuallyHurt will be called - move call logic down
-+                event = this.handleEntityDamage(source, amount, this.lastHurt); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction
++                event = this.handleEntityDamage(damageSource, amount, this.lastHurt); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction
 +                amount = computeAmountFromEntityDamageEvent(event);
 +                // Paper end - only call damage event when actuallyHurt will be called - move call logic down
 +
 +                // CraftBukkit start
-+                if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) { // Paper - fix invulnerability reduction in EntityDamageEvent - no longer subtract lastHurt, that is part of the damage event calc now
++                if (!this.actuallyHurt(level, damageSource, (float) event.getFinalDamage(), event)) { // Paper - fix invulnerability reduction in EntityDamageEvent - no longer subtract lastHurt, that is part of the damage event calc now
 +                    return false;
 +                }
 +                if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event.
@@ -682,45 +565,45 @@
                  flag1 = false;
              } else {
 +                // Paper start - only call damage event when actuallyHurt will be called - move call logic down
-+                event = this.handleEntityDamage(source, amount, 0); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction (none in this branch)
++                event = this.handleEntityDamage(damageSource, amount, 0); // Paper - fix invulnerability reduction in EntityDamageEvent - pass lastDamage reduction (none in this branch)
 +                amount = computeAmountFromEntityDamageEvent(event);
 +                // Paper end - only call damage event when actuallyHurt will be called - move call logic down
 +                // CraftBukkit start
-+                if (!this.actuallyHurt(world, source, (float) event.getFinalDamage(), event)) {
++                if (!this.actuallyHurt(level, damageSource, (float) event.getFinalDamage(), event)) {
 +                    return false;
 +                }
 +                if (this instanceof ServerPlayer && event.getDamage() == 0 && originalAmount == 0) return false; // Paper - revert to vanilla damage - players are not affected by damage that is 0 - skip damage if the vanilla damage is 0 and was not modified by plugins in the event.
                  this.lastHurt = amount;
 -                this.invulnerableTime = 20;
--                this.actuallyHurt(world, source, amount);
+-                this.actuallyHurt(level, damageSource, amount);
 +                this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks
-+                // this.actuallyHurt(worldserver, damagesource, f);
++                // this.actuallyHurt(level, damageSource, amount);
 +                // CraftBukkit end
                  this.hurtDuration = 10;
                  this.hurtTime = this.hurtDuration;
              }
-@@ -1243,7 +1544,7 @@
-                     world.broadcastDamageEvent(this, source);
+@@ -1170,7 +_,7 @@
+                     level.broadcastDamageEvent(this, damageSource);
                  }
  
--                if (!source.is(DamageTypeTags.NO_IMPACT) && (!flag || amount > 0.0F)) {
-+                if (!source.is(DamageTypeTags.NO_IMPACT) && !flag) { // CraftBukkit - Prevent marking hurt if the damage is blocked
+-                if (!damageSource.is(DamageTypeTags.NO_IMPACT) && (!flag || amount > 0.0F)) {
++                if (!damageSource.is(DamageTypeTags.NO_IMPACT) && !flag) { // CraftBukkit - Prevent marking hurt if the damage is blocked
                      this.markHurt();
                  }
  
-@@ -1263,7 +1564,7 @@
-                         d1 = source.getSourcePosition().z() - this.getZ();
+@@ -1186,7 +_,7 @@
+                         d1 = damageSource.getSourcePosition().z() - this.getZ();
                      }
  
--                    this.knockback(0.4000000059604645D, d0, d1);
-+                    this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
+-                    this.knockback(0.4F, d, d1);
++                    this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
                      if (!flag) {
-                         this.indicateDamage(d0, d1);
+                         this.indicateDamage(d, d1);
                      }
-@@ -1272,17 +1573,18 @@
+@@ -1195,17 +_,18 @@
  
              if (this.isDeadOrDying()) {
-                 if (!this.checkTotemDeathProtection(source)) {
+                 if (!this.checkTotemDeathProtection(damageSource)) {
 -                    if (flag1) {
 -                        this.makeSound(this.getDeathSound());
 -                    }
@@ -728,43 +611,19 @@
 +                    this.silentDeath = !flag1; // mark entity as dying silently
 +                    // Paper end
  
-                     this.die(source);
+                     this.die(damageSource);
 +                    this.silentDeath = false; // Paper - cancellable death event - reset to default
                  }
              } else if (flag1) {
-                 this.playHurtSound(source);
+                 this.playHurtSound(damageSource);
              }
  
 -            boolean flag2 = !flag || amount > 0.0F;
 +            boolean flag2 = !flag; // CraftBukkit - Ensure to return false if damage is blocked
- 
              if (flag2) {
-                 this.lastDamageSource = source;
-@@ -1329,10 +1631,10 @@
-     }
- 
-     @Nullable
--    protected Player resolvePlayerResponsibleForDamage(DamageSource damageSource) {
-+    protected net.minecraft.world.entity.player.Player resolvePlayerResponsibleForDamage(DamageSource damageSource) {
-         Entity entity = damageSource.getEntity();
- 
--        if (entity instanceof Player entityhuman) {
-+        if (entity instanceof net.minecraft.world.entity.player.Player entityhuman) {
-             this.lastHurtByPlayerTime = 100;
-             this.lastHurtByPlayer = entityhuman;
-             return entityhuman;
-@@ -1342,8 +1644,8 @@
-                     this.lastHurtByPlayerTime = 100;
-                     LivingEntity entityliving = entitywolf.getOwner();
- 
--                    if (entityliving instanceof Player) {
--                        Player entityhuman1 = (Player) entityliving;
-+                    if (entityliving instanceof net.minecraft.world.entity.player.Player) {
-+                        net.minecraft.world.entity.player.Player entityhuman1 = (net.minecraft.world.entity.player.Player) entityliving;
- 
-                         this.lastHurtByPlayer = entityhuman1;
-                     } else {
-@@ -1358,12 +1660,24 @@
+                 this.lastDamageSource = damageSource;
+                 this.lastDamageStamp = this.level().getGameTime();
+@@ -1259,12 +_,24 @@
          }
      }
  
@@ -784,69 +643,67 @@
          attacker.blockedByShield(this);
      }
  
-     protected void blockedByShield(LivingEntity target) {
--        target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ());
-+        target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events
+     protected void blockedByShield(LivingEntity defender) {
+-        defender.knockback(0.5, defender.getX() - this.getX(), defender.getZ() - this.getZ());
++        defender.knockback(0.5, defender.getX() - this.getX(), defender.getZ() - this.getZ(), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker & knockback events
      }
  
-     private boolean checkTotemDeathProtection(DamageSource source) {
-@@ -1375,20 +1689,39 @@
-             InteractionHand[] aenumhand = InteractionHand.values();
-             int i = aenumhand.length;
+     private boolean checkTotemDeathProtection(DamageSource damageSource) {
+@@ -1274,18 +_,37 @@
+             ItemStack itemStack = null;
+             DeathProtection deathProtection = null;
  
 +            // CraftBukkit start
 +            InteractionHand hand = null;
-+            ItemStack itemstack1 = ItemStack.EMPTY;
-             for (int j = 0; j < i; ++j) {
-                 InteractionHand enumhand = aenumhand[j];
--                ItemStack itemstack1 = this.getItemInHand(enumhand);
-+                itemstack1 = this.getItemInHand(enumhand);
- 
-                 deathprotection = (DeathProtection) itemstack1.get(DataComponents.DEATH_PROTECTION);
-                 if (deathprotection != null) {
-+                    hand = enumhand; // CraftBukkit
-                     itemstack = itemstack1.copy();
--                    itemstack1.shrink(1);
-+                    // itemstack1.subtract(1); // CraftBukkit
++            ItemStack itemInHand = ItemStack.EMPTY;
+             for (InteractionHand interactionHand : InteractionHand.values()) {
+-                ItemStack itemInHand = this.getItemInHand(interactionHand);
++                itemInHand = this.getItemInHand(interactionHand);
+                 deathProtection = itemInHand.get(DataComponents.DEATH_PROTECTION);
+                 if (deathProtection != null) {
++                    hand = interactionHand; // CraftBukkit
+                     itemStack = itemInHand.copy();
+-                    itemInHand.shrink(1);
++                    // itemInHand.shrink(1); // CraftBukkit
                      break;
                  }
              }
  
--            if (itemstack != null) {
--                if (this instanceof ServerPlayer) {
+-            if (itemStack != null) {
+-                if (this instanceof ServerPlayer serverPlayer) {
 +            org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
 +            EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
-+            event.setCancelled(itemstack == null);
++            event.setCancelled(itemStack == null);
 +            this.level().getCraftServer().getPluginManager().callEvent(event);
 +
 +            if (!event.isCancelled()) {
-+                if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found
-+                    itemstack1.shrink(1);
++                if (!itemStack.isEmpty() && itemStack != null) { // Paper - only reduce item if actual totem was found
++                    itemStack.shrink(1);
 +                }
 +                // Paper start - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled
 +                // restore the previous behavior in that case by defaulting to vanillas totem of undying efect
-+                if (deathprotection == null) {
-+                    deathprotection = DeathProtection.TOTEM_OF_UNDYING;
++                if (deathProtection == null) {
++                    deathProtection = DeathProtection.TOTEM_OF_UNDYING;
 +                }
 +                // Paper end - fix NPE when pre-cancelled EntityResurrectEvent is uncancelled
-+                if (itemstack != null && this instanceof ServerPlayer) {
++                if (itemStack != null && this instanceof ServerPlayer serverPlayer) {
 +                    // CraftBukkit end
-                     ServerPlayer entityplayer = (ServerPlayer) this;
- 
-                     entityplayer.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
-@@ -1468,6 +1801,7 @@
+                     serverPlayer.awardStat(Stats.ITEM_USED.get(itemStack.getItem()));
+                     CriteriaTriggers.USED_TOTEM.trigger(serverPlayer, itemStack);
+                     this.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+@@ -1364,6 +_,7 @@
+         if (!this.isRemoved() && !this.dead) {
              Entity entity = damageSource.getEntity();
-             LivingEntity entityliving = this.getKillCredit();
- 
+             LivingEntity killCredit = this.getKillCredit();
 +            /* // Paper - move down to make death event cancellable - this is the awardKillScore below
-             if (entityliving != null) {
-                 entityliving.awardKillScore(this, damageSource);
+             if (killCredit != null) {
+                 killCredit.awardKillScore(this, damageSource);
              }
-@@ -1477,26 +1811,61 @@
+@@ -1373,68 +_,145 @@
              }
  
              if (!this.level().isClientSide && this.hasCustomName()) {
--                LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString());
+-                LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString());
 +                if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
              }
 +            */ // Paper - move down to make death event cancellable - this is the awardKillScore below
@@ -854,15 +711,11 @@
              this.dead = true;
 -            this.getCombatTracker().recheckStatus();
 +            // Paper - moved into if below
-             Level world = this.level();
- 
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
+             if (this.level() instanceof ServerLevel serverLevel) {
+-                if (entity == null || entity.killedEntity(serverLevel, this)) {
 +                // Paper - move below into if for onKill
- 
--                if (entity == null || entity.killedEntity(worldserver, this)) {
 +                // Paper start
-+                org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(worldserver, damageSource);
++                org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(serverLevel, damageSource);
 +                if (deathEvent == null || !deathEvent.isCancelled()) {
 +                    //if (entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent
 +                    //    entityliving.awardKillScore(this, damageSource);
@@ -889,18 +742,18 @@
 +                        entity.killedEntity((ServerLevel) this.level(), this);
 +                    }
                      this.gameEvent(GameEvent.ENTITY_DIE);
--                    this.dropAllDeathLoot(worldserver, damageSource);
+-                    this.dropAllDeathLoot(serverLevel, damageSource);
 +                } else {
 +                    this.dead = false;
 +                    this.setHealth((float) deathEvent.getReviveHealth());
 +                }
 +                // Paper end
-                     this.createWitherRose(entityliving);
+                     this.createWitherRose(killCredit);
                  }
  
 +            // Paper start
 +            if (this.dead) { // Paper
-                 this.level().broadcastEntityEvent(this, (byte) 3);
+                 this.level().broadcastEntityEvent(this, (byte)3);
 -            }
  
              this.setPose(Pose.DYING);
@@ -909,63 +762,58 @@
          }
      }
  
-@@ -1506,20 +1875,28 @@
-         if (world instanceof ServerLevel worldserver) {
-             boolean flag = false;
- 
--            if (adversary instanceof WitherBoss) {
-+            if (this.dead && adversary instanceof WitherBoss) { // Paper
-                 if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-                     BlockPos blockposition = this.blockPosition();
-                     BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
- 
-                     if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
--                        this.level().setBlock(blockposition, iblockdata, 3);
--                        flag = true;
+     protected void createWitherRose(@Nullable LivingEntity entitySource) {
+         if (this.level() instanceof ServerLevel serverLevel) {
+             boolean var6 = false;
+-            if (entitySource instanceof WitherBoss) {
++            if (this.dead && entitySource instanceof WitherBoss) { // Paper
+                 if (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+                     BlockPos blockPos = this.blockPosition();
+                     BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState();
+                     if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
+                         this.level().setBlock(blockPos, blockState, 3);
+-                        var6 = true;
 +                        // CraftBukkit start - call EntityBlockFormEvent for Wither Rose
-+                        flag = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, 3, this);
++                        var6 = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockPos, blockState, 3, this);
 +                        // CraftBukkit end
                      }
                  }
  
-                 if (!flag) {
-                     ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
- 
+                 if (!var6) {
+                     ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
 +                    // CraftBukkit start
-+                    org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++                    org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
 +                    CraftEventFactory.callEvent(event);
 +                    if (event.isCancelled()) {
 +                        return;
 +                    }
 +                    // CraftBukkit end
-                     this.level().addFreshEntity(entityitem);
+                     this.level().addFreshEntity(itemEntity);
                  }
              }
-@@ -1527,27 +1904,60 @@
          }
      }
  
--    protected void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
+-    protected void dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
 +    // Paper start
 +    protected boolean clearEquipmentSlots = true;
 +    protected Set<EquipmentSlot> clearedEquipmentSlots = new java.util.HashSet<>();
-+    protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
++    protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel level, DamageSource damageSource) {
 +        // Paper end
          boolean flag = this.lastHurtByPlayerTime > 0;
- 
-+        this.dropEquipment(world); // CraftBukkit - from below
-         if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-             this.dropFromLootTable(world, damageSource, flag);
++        this.dropEquipment(level); // CraftBukkit - from below
+         if (this.shouldDropLoot() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+             this.dropFromLootTable(level, damageSource, flag);
 +            // Paper start
 +            final boolean prev = this.clearEquipmentSlots;
 +            this.clearEquipmentSlots = false;
 +            this.clearedEquipmentSlots.clear();
 +            // Paper end
-             this.dropCustomDeathLoot(world, damageSource, flag);
+             this.dropCustomDeathLoot(level, damageSource, flag);
 +            this.clearEquipmentSlots = prev; // Paper
          }
--
--        this.dropEquipment(world);
+ 
+-        this.dropEquipment(level);
 +        // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
 +        org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> {
 +            final LivingEntity entityliving = this.getKillCredit();
@@ -975,83 +823,82 @@
 +        }); // Paper end
 +        this.postDeathDropItems(deathEvent); // Paper
 +        this.drops = new ArrayList<>();
++        // this.dropEquipment(level); // CraftBukkit - moved up
 +        // CraftBukkit end
-+
-+        // this.dropEquipment(worldserver);// CraftBukkit - moved up
-         this.dropExperience(world, damageSource.getEntity());
+         this.dropExperience(level, damageSource.getEntity());
 +        return deathEvent; // Paper
      }
  
-     protected void dropEquipment(ServerLevel world) {}
+     protected void dropEquipment(ServerLevel level) {
+     }
 +    protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
  
--    protected void dropExperience(ServerLevel world, @Nullable Entity attacker) {
--        if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) {
--            ExperienceOrb.award(world, this.position(), this.getExperienceReward(world, attacker));
-+    public int getExpReward(ServerLevel worldserver, @Nullable Entity entity) { // CraftBukkit
-+        if (!this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) {
-+            return this.getExperienceReward(worldserver, entity); // CraftBukkit            }
-         }
- 
+-    protected void dropExperience(ServerLevel level, @Nullable Entity entity) {
++    protected int getExpReward(ServerLevel level, @Nullable Entity entity) { // CraftBukkit
+         if (!this.wasExperienceConsumed()
+             && (
+                 this.isAlwaysExperienceDropper()
+                     || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)
+             )) {
+-            ExperienceOrb.award(level, this.position(), this.getExperienceReward(level, entity));
+-        }
++            return this.getExperienceReward(level, entity); // CraftBukkit
++        }
 +        return 0; // CraftBukkit
-     }
- 
-+    protected void dropExperience(ServerLevel world, @Nullable Entity attacker) {
++    }
++
++    protected void dropExperience(ServerLevel level, @Nullable Entity entity) {
 +        // CraftBukkit start - Update getExpReward() above if the removed if() changes!
 +        if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
-+            ExperienceOrb.award(world, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper
++            ExperienceOrb.award(level, this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, entity, this); // Paper
 +            this.expToDrop = 0;
 +        }
 +        // CraftBukkit end
-+    }
-+
-     protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {}
+     }
  
-     public long getLootTableSeed() {
-@@ -1612,19 +2022,35 @@
+     protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
+@@ -1513,9 +_,14 @@
      }
  
      public void knockback(double strength, double x, double z) {
--        strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
--        if (strength > 0.0D) {
--            this.hasImpulse = true;
 +        // CraftBukkit start - EntityKnockbackEvent
 +        this.knockback(strength, x, z, null, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.UNKNOWN); // Paper - knockback events
 +    }
- 
-+    public void knockback(double d0, double d1, double d2, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events
-+        d0 *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
-+        if (true || d0 > 0.0D) { // CraftBukkit - Call event even when force is 0
-+            //this.hasImpulse = true; // CraftBukkit - Move down
 +
-             Vec3 vec3d;
++    public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause eventCause) { // Paper - knockback events
+         strength *= 1.0 - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
+-        if (!(strength <= 0.0)) {
+-            this.hasImpulse = true;
++        if (true || !(strength <= 0.0)) { // CraftBukkit - Call event even when force is 0
++            // this.hasImpulse = true; // CraftBukkit - Move down
+             Vec3 deltaMovement = this.getDeltaMovement();
  
--            for (vec3d = this.getDeltaMovement(); x * x + z * z < 9.999999747378752E-6D; z = (Math.random() - Math.random()) * 0.01D) {
--                x = (Math.random() - Math.random()) * 0.01D;
-+            for (vec3d = this.getDeltaMovement(); d1 * d1 + d2 * d2 < 9.999999747378752E-6D; d2 = (Math.random() - Math.random()) * 0.01D) {
-+                d1 = (Math.random() - Math.random()) * 0.01D;
+             while (x * x + z * z < 1.0E-5F) {
+@@ -1524,11 +_,22 @@
              }
  
--            Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength);
-+            Vec3 vec3d1 = (new Vec3(d1, 0.0D, d2)).normalize().scale(d0);
- 
--            this.setDeltaMovement(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z);
+             Vec3 vec3 = new Vec3(x, 0.0, z).normalize().scale(strength);
+-            this.setDeltaMovement(
 +            // Paper start - knockback events
-+            Vec3 finalVelocity = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + d0) : vec3d.y, vec3d.z / 2.0D - vec3d1.z);
-+            Vec3 diff = finalVelocity.subtract(vec3d);
-+            io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, cause, d0, diff);
++            Vec3 finalVelocity = new Vec3(
+                 deltaMovement.x / 2.0 - vec3.x,
+                 this.onGround() ? Math.min(0.4, deltaMovement.y / 2.0 + strength) : deltaMovement.y,
+                 deltaMovement.z / 2.0 - vec3.z
+             );
++            Vec3 diff = finalVelocity.subtract(deltaMovement);
++            io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) this.getBukkitEntity(), attacker, attacker, eventCause, strength, diff);
 +            // Paper end - knockback events
 +            if (event.isCancelled()) {
 +                return;
 +            }
 +
 +            this.hasImpulse = true;
-+            this.setDeltaMovement(vec3d.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events
++            this.setDeltaMovement(deltaMovement.add(event.getKnockback().getX(), event.getKnockback().getY(), event.getKnockback().getZ())); // Paper - knockback events
 +            // CraftBukkit end
          }
      }
  
-@@ -1683,6 +2109,20 @@
+@@ -1584,6 +_,20 @@
          return new LivingEntity.Fallsounds(SoundEvents.GENERIC_SMALL_FALL, SoundEvents.GENERIC_BIG_FALL);
      }
  
@@ -1072,7 +919,7 @@
      public Optional<BlockPos> getLastClimbablePos() {
          return this.lastClimbablePos;
      }
-@@ -1718,7 +2158,7 @@
+@@ -1617,7 +_,7 @@
  
      @Override
      public boolean isAlive() {
@@ -1080,54 +927,50 @@
 +        return !this.isRemoved() && this.getHealth() > 0.0F && !this.dead; // Paper - Check this.dead
      }
  
-     public boolean isLookingAtMe(LivingEntity entity, double d0, boolean flag, boolean visualShape, double... checkedYs) {
-@@ -1757,9 +2197,14 @@
-         int i = this.calculateFallDamage(fallDistance, damageMultiplier);
- 
+     public boolean isLookingAtMe(LivingEntity entity, double tolerance, boolean scaleByDistance, boolean visual, double... yValues) {
+@@ -1651,9 +_,14 @@
+         boolean flag = super.causeFallDamage(fallDistance, multiplier, source);
+         int i = this.calculateFallDamage(fallDistance, multiplier);
          if (i > 0) {
 +            // CraftBukkit start
-+            if (!this.hurtServer((ServerLevel) this.level(), damageSource, (float) i)) {
++            if (!this.hurtServer((ServerLevel) this.level(), source, (float) i)) {
 +                return true;
 +            }
 +            // CraftBukkit end
              this.playSound(this.getFallDamageSound(i), 1.0F, 1.0F);
              this.playBlockFallSound();
--            this.hurt(damageSource, (float) i);
-+            // this.damageEntity(damagesource, (float) i); // CraftBukkit - moved up
+-            this.hurt(source, i);
++            // this.hurt(source, i); // CraftBukkit - moved up
              return true;
          } else {
              return flag;
-@@ -1830,7 +2275,7 @@
+@@ -1718,7 +_,7 @@
  
-     protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) {
-         if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) {
--            this.hurtArmor(source, amount);
-+            // this.hurtArmor(damagesource, f); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage
-             amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
-         }
- 
-@@ -1841,7 +2286,8 @@
-         if (source.is(DamageTypeTags.BYPASSES_EFFECTS)) {
-             return amount;
+     protected float getDamageAfterArmorAbsorb(DamageSource damageSource, float damageAmount) {
+         if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
+-            this.hurtArmor(damageSource, damageAmount);
++            // this.hurtArmor(damageSource, damageAmount); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for damage handling
+             damageAmount = CombatRules.getDamageAfterAbsorb(
+                 this, damageAmount, damageSource, this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)
+             );
+@@ -1731,7 +_,8 @@
+         if (damageSource.is(DamageTypeTags.BYPASSES_EFFECTS)) {
+             return damageAmount;
          } else {
--            if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !source.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
+-            if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
 +            // CraftBukkit - Moved to handleEntityDamage(DamageSource, float)
-+            if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !source.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
++            if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
                  int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
-                 int j = 25 - i;
-                 float f1 = amount * (float) j;
-@@ -1884,18 +2330,170 @@
+                 int i1 = 25 - i;
+                 float f = damageAmount * i1;
+@@ -1768,24 +_,212 @@
          }
      }
  
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
--        if (!this.isInvulnerableTo(world, source)) {
--            amount = this.getDamageAfterArmorAbsorb(source, amount);
--            amount = this.getDamageAfterMagicAbsorb(source, amount);
--            float f1 = amount;
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
 +    // CraftBukkit start
-+    private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float f, final float invulnerabilityRelatedLastDamage) { // Paper - fix invulnerability reduction in EntityDamageEvent
-+        float originalDamage = f;
++    private EntityDamageEvent handleEntityDamage(final DamageSource damagesource, float amount, final float invulnerabilityRelatedLastDamage) { // Paper - fix invulnerability reduction in EntityDamageEvent
++        float originalDamage = amount;
 +        // Paper start - fix invulnerability reduction in EntityDamageEvent
 +        final com.google.common.base.Function<Double, Double> invulnerabilityReductionEquation = d -> {
 +            if (invulnerabilityRelatedLastDamage == 0) return 0D; // no last damage, no reduction
@@ -1136,13 +979,10 @@
 +            if (d < invulnerabilityRelatedLastDamage) return 0D;
 +            return (double) -invulnerabilityRelatedLastDamage;
 +        };
-+        final float originalInvulnerabilityReduction = invulnerabilityReductionEquation.apply((double) f).floatValue();
-+        f += originalInvulnerabilityReduction;
++        final float originalInvulnerabilityReduction = invulnerabilityReductionEquation.apply((double) amount).floatValue();
++        amount += originalInvulnerabilityReduction;
 +        // Paper end - fix invulnerability reduction in EntityDamageEvent
- 
--            amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
--            this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - amount));
--            float f2 = f1 - amount;
++
 +        com.google.common.base.Function<Double, Double> freezing = new com.google.common.base.Function<Double, Double>() {
 +            @Override
 +            public Double apply(Double f) {
@@ -1152,9 +992,9 @@
 +                return -0.0;
 +            }
 +        };
-+        float freezingModifier = freezing.apply((double) f).floatValue();
-+        f += freezingModifier;
- 
++        float freezingModifier = freezing.apply((double) amount).floatValue();
++        amount += freezingModifier;
++
 +        com.google.common.base.Function<Double, Double> hardHat = new com.google.common.base.Function<Double, Double>() {
 +            @Override
 +            public Double apply(Double f) {
@@ -1164,8 +1004,8 @@
 +                return -0.0;
 +            }
 +        };
-+        float hardHatModifier = hardHat.apply((double) f).floatValue();
-+        f += hardHatModifier;
++        float hardHatModifier = hardHat.apply((double) amount).floatValue();
++        amount += hardHatModifier;
 +
 +        com.google.common.base.Function<Double, Double> blocking = new com.google.common.base.Function<Double, Double>() {
 +            @Override
@@ -1173,8 +1013,8 @@
 +                return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0);
 +            }
 +        };
-+        float blockingModifier = blocking.apply((double) f).floatValue();
-+        f += blockingModifier;
++        float blockingModifier = blocking.apply((double) amount).floatValue();
++        amount += blockingModifier;
 +
 +        com.google.common.base.Function<Double, Double> armor = new com.google.common.base.Function<Double, Double>() {
 +            @Override
@@ -1182,8 +1022,8 @@
 +                return -(f - LivingEntity.this.getDamageAfterArmorAbsorb(damagesource, f.floatValue()));
 +            }
 +        };
-+        float armorModifier = armor.apply((double) f).floatValue();
-+        f += armorModifier;
++        float armorModifier = armor.apply((double) amount).floatValue();
++        amount += armorModifier;
 +
 +        com.google.common.base.Function<Double, Double> resistance = new com.google.common.base.Function<Double, Double>() {
 +            @Override
@@ -1198,8 +1038,8 @@
 +                return -0.0;
 +            }
 +        };
-+        float resistanceModifier = resistance.apply((double) f).floatValue();
-+        f += resistanceModifier;
++        float resistanceModifier = resistance.apply((double) amount).floatValue();
++        amount += resistanceModifier;
 +
 +        com.google.common.base.Function<Double, Double> magic = new com.google.common.base.Function<Double, Double>() {
 +            @Override
@@ -1207,8 +1047,8 @@
 +                return -(f - LivingEntity.this.getDamageAfterMagicAbsorb(damagesource, f.floatValue()));
 +            }
 +        };
-+        float magicModifier = magic.apply((double) f).floatValue();
-+        f += magicModifier;
++        float magicModifier = magic.apply((double) amount).floatValue();
++        amount += magicModifier;
 +
 +        com.google.common.base.Function<Double, Double> absorption = new com.google.common.base.Function<Double, Double>() {
 +            @Override
@@ -1216,7 +1056,7 @@
 +                return -(Math.max(f - Math.max(f - LivingEntity.this.getAbsorptionAmount(), 0.0F), 0.0F));
 +            }
 +        };
-+        float absorptionModifier = absorption.apply((double) f).floatValue();
++        float absorptionModifier = absorption.apply((double) amount).floatValue();
 +
 +        // Paper start - fix invulnerability reduction in EntityDamageEvent
 +        return CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, freezingModifier, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, freezing, hardHat, blocking, armor, resistance, magic, absorption, (damageModifierDoubleMap, damageModifierFunctionMap) -> {
@@ -1226,22 +1066,27 @@
 +        // Paper end - fix invulnerability reduction in EntityDamageEvent
 +    }
 +
-+    protected boolean actuallyHurt(ServerLevel worldserver, final DamageSource damagesource, float f, final EntityDamageEvent event) { // void -> boolean, add final
-+        if (!this.isInvulnerableTo(worldserver, damagesource)) {
++    protected boolean actuallyHurt(ServerLevel level, final DamageSource damageSource, float amount, final EntityDamageEvent event) { // void -> boolean, add final
+         if (!this.isInvulnerableTo(level, damageSource)) {
+-            amount = this.getDamageAfterArmorAbsorb(damageSource, amount);
+-            amount = this.getDamageAfterMagicAbsorb(damageSource, amount);
+-            float var10 = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
+-            this.setAbsorptionAmount(this.getAbsorptionAmount() - (amount - var10));
+-            float f1 = amount - var10;
 +            if (event.isCancelled()) {
 +                return false;
 +            }
 +
-+            if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
++            if (damageSource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
 +                // Paper start - PlayerAttackEntityCooldownResetEvent
 +                //((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired
-+                if (damagesource.getEntity() instanceof ServerPlayer) {
-+                    ServerPlayer player = (ServerPlayer) damagesource.getEntity();
++                if (damageSource.getEntity() instanceof ServerPlayer) {
++                    ServerPlayer player = (ServerPlayer) damageSource.getEntity();
 +                    if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) {
 +                        player.resetAttackStrengthTicker();
 +                    }
 +                } else {
-+                    ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker();
++                    ((net.minecraft.world.entity.player.Player) damageSource.getEntity()).resetAttackStrengthTicker();
 +                }
 +                // Paper end - PlayerAttackEntityCooldownResetEvent
 +            }
@@ -1252,29 +1097,29 @@
 +                if (f3 > 0.0F && f3 < 3.4028235E37F) {
 +                    if (this instanceof ServerPlayer) {
 +                        ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F));
-+                    } else if (damagesource.getEntity() instanceof ServerPlayer) {
-+                        ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
++                    } else if (damageSource.getEntity() instanceof ServerPlayer) {
++                        ((ServerPlayer) damageSource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
 +                    }
 +                }
 +            }
 +
 +            // Apply damage to helmet
-+            if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
-+                this.hurtHelmet(damagesource, f);
++            if (damageSource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
++                this.hurtHelmet(damageSource, amount);
 +            }
 +
 +            // Apply damage to armor
-+            if (!damagesource.is(DamageTypeTags.BYPASSES_ARMOR)) {
++            if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
 +                float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
-+                this.hurtArmor(damagesource, armorDamage);
++                this.hurtArmor(damageSource, armorDamage);
 +            }
 +
 +            // Apply blocking code // PAIL: steal from above
 +            if (event.getDamage(DamageModifier.BLOCKING) < 0) {
 +                this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
-+                Entity entity = damagesource.getDirectEntity();
++                Entity entity = damageSource.getDirectEntity();
 +
-+                if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
++                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
 +                    this.blockUsingShield((LivingEntity) entity);
 +                }
 +            }
@@ -1283,59 +1128,51 @@
 +            float originalDamage = (float) event.getDamage();
 +            float absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION);
 +            this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F));
-+            float f2 = absorptionModifier;
++            float f1 = absorptionModifier;
 +
-+            if (f2 > 0.0F && f2 < 3.4028235E37F && this instanceof net.minecraft.world.entity.player.Player) {
-+                ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F));
++            if (f1 > 0.0F && f1 < 3.4028235E37F && this instanceof Player player) {
++                player.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f1 * 10.0F));
 +            }
 +            // CraftBukkit end
-+
-             if (f2 > 0.0F && f2 < 3.4028235E37F) {
--                Entity entity = source.getEntity();
-+                Entity entity = damagesource.getEntity();
- 
-                 if (entity instanceof ServerPlayer) {
-                     ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -1904,13 +2502,48 @@
-                 }
+             if (f1 > 0.0F && f1 < 3.4028235E37F && damageSource.getEntity() instanceof ServerPlayer serverPlayer) {
+                 serverPlayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f1 * 10.0F));
              }
  
--            if (amount != 0.0F) {
--                this.getCombatTracker().recordDamage(source, amount);
--                this.setHealth(this.getHealth() - amount);
--                this.setAbsorptionAmount(this.getAbsorptionAmount() - amount);
+-            if (var10 != 0.0F) {
+-                this.getCombatTracker().recordDamage(damageSource, var10);
+-                this.setHealth(this.getHealth() - var10);
+-                this.setAbsorptionAmount(this.getAbsorptionAmount() - var10);
 +            // CraftBukkit start
-+            if (f > 0 || !human) {
++            if (amount > 0 || !human) {
 +                if (human) {
 +                    // PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
-+                    ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damagesource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
-+                    if (f < 3.4028235E37F) {
-+                        ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F));
++                    ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damageSource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
++                    if (amount < 3.4028235E37F) {
++                        ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(amount * 10.0F));
 +                    }
 +                }
 +                // CraftBukkit end
-+                this.getCombatTracker().recordDamage(damagesource, f);
-+                this.setHealth(this.getHealth() - f);
++                this.getCombatTracker().recordDamage(damageSource, amount);
++                this.setHealth(this.getHealth() - amount);
 +                // CraftBukkit start
 +                if (!human) {
-+                    this.setAbsorptionAmount(this.getAbsorptionAmount() - f);
++                    this.setAbsorptionAmount(this.getAbsorptionAmount() - amount);
 +                }
                  this.gameEvent(GameEvent.ENTITY_DAMAGE);
-+
 +                return true;
 +            } else {
 +                // Duplicate triggers if blocking
 +                if (event.getDamage(DamageModifier.BLOCKING) < 0) {
 +                    if (this instanceof ServerPlayer) {
-+                        CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order
-+                        f2 = (float) -event.getDamage(DamageModifier.BLOCKING);
-+                        if (f2 > 0.0F && f2 < 3.4028235E37F) {
++                        CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damageSource, originalDamage, amount, true); // Paper - fix taken/dealt param order
++                        f1 = (float) -event.getDamage(DamageModifier.BLOCKING);
++                        if (f1 > 0.0F && f1 < 3.4028235E37F) {
 +                            ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F));
 +                        }
 +                    }
 +
-+                    if (damagesource.getEntity() instanceof ServerPlayer) {
-+                        CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order
++                    if (damageSource.getEntity() instanceof ServerPlayer) {
++                        CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damageSource.getEntity(), this, damageSource, originalDamage, amount, true); // Paper - fix taken/dealt param order
 +                    }
 +
 +                    return !io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.skipVanillaDamageTickWhenShieldBlocked; // Paper - this should always return true, however expose an unsupported setting to flip this to false to enable "shield stunning".
@@ -1349,36 +1186,26 @@
      }
  
      public CombatTracker getCombatTracker() {
-@@ -1935,8 +2568,18 @@
+@@ -1814,7 +_,17 @@
      }
  
-     public final void setArrowCount(int stuckArrowCount) {
--        this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, stuckArrowCount);
+     public final void setArrowCount(int count) {
+-        this.entityData.set(DATA_ARROW_COUNT_ID, count);
 +        // CraftBukkit start
-+        this.setArrowCount(stuckArrowCount, false);
++        this.setArrowCount(count, false);
 +    }
 +
-+    public final void setArrowCount(int i, boolean flag) {
-+        ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, this.getArrowCount(), i, flag);
++    public final void setArrowCount(int count, boolean reset) {
++        ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, this.getArrowCount(), count, reset);
 +        if (event.isCancelled()) {
 +            return;
 +        }
-+        this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, event.getNewAmount());
++        this.entityData.set(DATA_ARROW_COUNT_ID, event.getNewAmount());
++        // CraftBukkit end
      }
-+    // CraftBukkit end
  
      public final int getStingerCount() {
-         return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID);
-@@ -1999,7 +2642,7 @@
-                     this.playSound(soundeffect, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
-                 }
- 
--                if (!(this instanceof Player)) {
-+                if (!(this instanceof net.minecraft.world.entity.player.Player)) {
-                     this.setHealth(0.0F);
-                     this.die(this.damageSources().generic());
-                 }
-@@ -2083,7 +2726,7 @@
+@@ -1957,7 +_,7 @@
  
      @Override
      protected void onBelowWorld() {
@@ -1387,7 +1214,7 @@
      }
  
      protected void updateSwingTime() {
-@@ -2182,6 +2825,12 @@
+@@ -2052,6 +_,12 @@
  
      public abstract ItemStack getItemBySlot(EquipmentSlot slot);
  
@@ -1400,17 +1227,16 @@
      public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
  
      public Iterable<ItemStack> getHandSlots() {
-@@ -2292,17 +2941,29 @@
-         return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F;
+@@ -2158,14 +_,27 @@
+         return this.hasEffect(MobEffects.JUMP) ? 0.1F * (this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F;
      }
  
 +    protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits
      @VisibleForTesting
      public void jumpFromGround() {
-         float f = this.getJumpPower();
- 
-         if (f > 1.0E-5F) {
-             Vec3 vec3d = this.getDeltaMovement();
+         float jumpPower = this.getJumpPower();
+         if (!(jumpPower <= 1.0E-5F)) {
+             Vec3 deltaMovement = this.getDeltaMovement();
 +            // Paper start - Prevent excessive velocity through repeated crits
 +            long time = System.nanoTime();
 +            boolean canCrit = true;
@@ -1422,51 +1248,14 @@
 +                }
 +            }
 +            // Paper end - Prevent excessive velocity through repeated crits
- 
-             this.setDeltaMovement(vec3d.x, Math.max((double) f, vec3d.y), vec3d.z);
+             this.setDeltaMovement(deltaMovement.x, Math.max((double)jumpPower, deltaMovement.y), deltaMovement.z);
              if (this.isSprinting()) {
-                 float f1 = this.getYRot() * 0.017453292F;
--
+                 float f = this.getYRot() * (float) (Math.PI / 180.0);
 +                if (canCrit) // Paper - Prevent excessive velocity through repeated crits
-                 this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D));
+                 this.addDeltaMovement(new Vec3(-Mth.sin(f) * 0.2, 0.0, Mth.cos(f) * 0.2));
              }
  
-@@ -2494,7 +3155,7 @@
- 
-     }
- 
--    private void travelRidden(Player controllingPlayer, Vec3 movementInput) {
-+    private void travelRidden(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) {
-         Vec3 vec3d1 = this.getRiddenInput(controllingPlayer, movementInput);
- 
-         this.tickRidden(controllingPlayer, vec3d1);
-@@ -2507,13 +3168,13 @@
- 
-     }
- 
--    protected void tickRidden(Player controllingPlayer, Vec3 movementInput) {}
-+    protected void tickRidden(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) {}
- 
--    protected Vec3 getRiddenInput(Player controllingPlayer, Vec3 movementInput) {
-+    protected Vec3 getRiddenInput(net.minecraft.world.entity.player.Player controllingPlayer, Vec3 movementInput) {
-         return movementInput;
-     }
- 
--    protected float getRiddenSpeed(Player controllingPlayer) {
-+    protected float getRiddenSpeed(net.minecraft.world.entity.player.Player controllingPlayer) {
-         return this.getSpeed();
-     }
- 
-@@ -2571,7 +3232,7 @@
-             double d1 = Mth.clamp(motion.z, -0.15000000596046448D, 0.15000000596046448D);
-             double d2 = Math.max(motion.y, -0.15000000596046448D);
- 
--            if (d2 < 0.0D && !this.getInBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof Player) {
-+            if (d2 < 0.0D && !this.getInBlockState().is(Blocks.SCAFFOLDING) && this.isSuppressingSlidingDownLadder() && this instanceof net.minecraft.world.entity.player.Player) {
-                 d2 = 0.0D;
-             }
- 
-@@ -2586,7 +3247,7 @@
+@@ -2425,7 +_,7 @@
      }
  
      protected float getFlyingSpeed() {
@@ -1475,7 +1264,7 @@
      }
  
      public float getSpeed() {
-@@ -2634,7 +3295,7 @@
+@@ -2471,7 +_,7 @@
                  }
              }
  
@@ -1484,26 +1273,22 @@
              if (this.tickCount % 20 == 0) {
                  this.getCombatTracker().recheckStatus();
              }
-@@ -2687,38 +3348,16 @@
-         gameprofilerfiller.pop();
-         gameprofilerfiller.push("rangeChecks");
+@@ -2519,37 +_,14 @@
+         profilerFiller.pop();
+         profilerFiller.push("rangeChecks");
  
 -        while (this.getYRot() - this.yRotO < -180.0F) {
 -            this.yRotO -= 360.0F;
 -        }
-+        // Paper start - stop large pitch and yaw changes from crashing the server
-+        this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F;
- 
+-
 -        while (this.getYRot() - this.yRotO >= 180.0F) {
 -            this.yRotO += 360.0F;
 -        }
-+        this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F;
- 
+-
 -        while (this.yBodyRot - this.yBodyRotO < -180.0F) {
 -            this.yBodyRotO -= 360.0F;
 -        }
-+        this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F;
- 
+-
 -        while (this.yBodyRot - this.yBodyRotO >= 180.0F) {
 -            this.yBodyRotO += 360.0F;
 -        }
@@ -1515,9 +1300,7 @@
 -        while (this.getXRot() - this.xRotO >= 180.0F) {
 -            this.xRotO += 360.0F;
 -        }
-+        this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
-+        // Paper end
- 
+-
 -        while (this.yHeadRot - this.yHeadRotO < -180.0F) {
 -            this.yHeadRotO -= 360.0F;
 -        }
@@ -1525,83 +1308,75 @@
 -        while (this.yHeadRot - this.yHeadRotO >= 180.0F) {
 -            this.yHeadRotO += 360.0F;
 -        }
--
-         gameprofilerfiller.pop();
++        // Paper start - stop large pitch and yaw changes from crashing the server
++        this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F;
++
++        this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F;
++
++        this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F;
++
++        this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
+ 
+         profilerFiller.pop();
          this.animStep += f2;
-         if (this.isFallFlying()) {
-@@ -2741,7 +3380,7 @@
+@@ -2573,7 +_,7 @@
          this.elytraAnimationState.tick();
      }
  
 -    public void detectEquipmentUpdates() {
 +    public void detectEquipmentUpdatesPublic() { // CraftBukkit
          Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
- 
          if (map != null) {
-@@ -2778,10 +3417,17 @@
-                     throw new MatchException((String) null, (Throwable) null);
-             }
- 
--            ItemStack itemstack2 = itemstack1;
-+            ItemStack itemstack2 = itemstack1; final ItemStack oldEquipment = itemstack2; // Paper - PlayerArmorChangeEvent - obfhelper 
- 
--            itemstack = this.getItemBySlot(enumitemslot);
-+            itemstack = this.getItemBySlot(enumitemslot); final ItemStack newEquipment = itemstack;// Paper - PlayerArmorChangeEvent - obfhelper
-             if (this.equipmentHasChanged(itemstack2, itemstack)) {
+             this.handleHandSwap(map);
+@@ -2595,6 +_,13 @@
+             };
+             ItemStack itemBySlot = this.getItemBySlot(equipmentSlot);
+             if (this.equipmentHasChanged(itemStack, itemBySlot)) {
 +                // Paper start - PlayerArmorChangeEvent
-+                if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
-+                    final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(oldEquipment);
-+                    final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(newEquipment);
-+                    new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent();
++                if (this instanceof ServerPlayer && equipmentSlot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
++                    final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemStack);
++                    final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemBySlot);
++                    new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((org.bukkit.entity.Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(equipmentSlot.name()), oldItem, newItem).callEvent();
 +                }
 +                // Paper end - PlayerArmorChangeEvent
                  if (map == null) {
                      map = Maps.newEnumMap(EquipmentSlot.class);
                  }
-@@ -2864,7 +3510,7 @@
+@@ -2664,7 +_,7 @@
+                     this.lastBodyItemStack = itemStack;
              }
- 
          });
--        ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
-+        ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization
+-        ((ServerLevel)this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
++        ((ServerLevel)this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization
      }
  
      private ItemStack getLastArmorItem(EquipmentSlot slot) {
-@@ -2974,8 +3620,10 @@
-             } else if (this.isInLava() && (!this.onGround() || d3 > d4)) {
-                 this.jumpInLiquid(FluidTags.LAVA);
-             } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) {
-+                if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
-                 this.jumpFromGround();
-                 this.noJumpDelay = 10;
-+                } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
-             }
-         } else {
-             this.noJumpDelay = 0;
-@@ -3000,7 +3648,7 @@
-         {
-             LivingEntity entityliving = this.getControllingPassenger();
- 
--            if (entityliving instanceof Player entityhuman) {
-+            if (entityliving instanceof net.minecraft.world.entity.player.Player entityhuman) {
-                 if (this.isAlive()) {
-                     this.travelRidden(entityhuman, vec3d1);
-                     break label112;
-@@ -3017,7 +3665,7 @@
+@@ -2765,8 +_,10 @@
+             if (!flag || this.onGround() && !(fluidHeight > fluidJumpThreshold)) {
+                 if (!this.isInLava() || this.onGround() && !(fluidHeight > fluidJumpThreshold)) {
+                     if ((this.onGround() || flag && fluidHeight <= fluidJumpThreshold) && this.noJumpDelay == 0) {
++                        if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API
+                         this.jumpFromGround();
+                         this.noJumpDelay = 10;
++                        } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop
+                     }
+                 } else {
+                     this.jumpInLiquid(FluidTags.LAVA);
+@@ -2805,7 +_,7 @@
          this.calculateEntityAnimation(this instanceof FlyingAnimal);
-         gameprofilerfiller.pop();
-         gameprofilerfiller.push("freezing");
+         profilerFiller.pop();
+         profilerFiller.push("freezing");
 -        if (!this.level().isClientSide && !this.isDeadOrDying()) {
 +        if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API
-             int i = this.getTicksFrozen();
- 
+             int ticksFrozen = this.getTicksFrozen();
              if (this.isInPowderSnow && this.canFreeze()) {
-@@ -3046,6 +3694,20 @@
+                 this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), ticksFrozen + 1));
+@@ -2829,6 +_,20 @@
  
          this.pushEntities();
-         gameprofilerfiller.pop();
+         profilerFiller.pop();
 +        // Paper start - Add EntityMoveEvent
-+        if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
++        if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) {
 +            if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
 +                Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
 +                Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
@@ -1614,10 +1389,10 @@
 +            }
 +        }
 +        // Paper end - Add EntityMoveEvent
-         world = this.level();
-         if (world instanceof ServerLevel worldserver) {
-             if (this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
-@@ -3063,6 +3725,7 @@
+         if (this.level() instanceof ServerLevel serverLevel && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
+             this.hurtServer(serverLevel, this.damageSources().drown(), 1.0F);
+         }
+@@ -2842,6 +_,7 @@
          this.checkSlowFallDistance();
          if (!this.level().isClientSide) {
              if (!this.canGlide()) {
@@ -1625,99 +1400,98 @@
                  this.setSharedFlag(7, false);
                  return;
              }
-@@ -3113,12 +3776,26 @@
-         Level world = this.level();
- 
-         if (!(world instanceof ServerLevel worldserver)) {
--            this.level().getEntities(EntityTypeTest.forClass(Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
-+            this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
+@@ -2881,9 +_,24 @@
+         if (!(this.level() instanceof ServerLevel serverLevel)) {
+             this.level().getEntities(EntityTypeTest.forClass(Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush);
          } else {
--            List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this));
+-            List<Entity> entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
 +            // Paper start - don't run getEntities if we're not going to use its result
 +            if (!this.isPushable()) {
 +                return;
 +            }
++
 +            net.minecraft.world.scores.Team team = this.getTeam();
 +            if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) {
 +                return;
 +            }
- 
-+            int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
-+            if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) {
++
++            int _int = serverLevel.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
++            if (_int <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) {
 +                return;
 +            }
 +            // Paper end - don't run getEntities if we're not going to use its result
-+            List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule
-+
-             if (!list.isEmpty()) {
--                int i = worldserver.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
++            List<Entity> entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule
+             if (!entities.isEmpty()) {
+-                int _int = serverLevel.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING);
 +                // Paper - don't run getEntities if we're not going to use its result; moved up
+                 if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) {
+                     int i = 0;
  
-                 if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) {
-                     int j = 0;
-@@ -3138,10 +3815,12 @@
+@@ -2898,7 +_,16 @@
+                     }
                  }
  
-                 Iterator iterator1 = list.iterator();
-+                this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions); // Paper - Cap entity collisions
- 
--                while (iterator1.hasNext()) {
-+                while (iterator1.hasNext() && this.numCollisions < this.level().paperConfig().collisions.maxEntityCollisions) { // Paper - Cap entity collisions
-                     Entity entity1 = (Entity) iterator1.next();
--
-+                    entity1.numCollisions++; // Paper - Cap entity collisions
-+                    this.numCollisions++; // Paper - Cap entity collisions
++                // Paper start - Cap entity collisions
++                this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions);
+                 for (Entity entity1 : entities) {
++                    if (this.numCollisions >= this.level().paperConfig().collisions.maxEntityCollisions) {
++                        break;
++                    }
++
++                    entity1.numCollisions++;
++                    this.numCollisions++;
++                    // Paper end - Cap entity collisions
                      this.doPush(entity1);
                  }
              }
-@@ -3190,10 +3869,16 @@
+@@ -2941,9 +_,16 @@
  
      @Override
      public void stopRiding() {
 +        // Paper start - Force entity dismount during teleportation
 +        this.stopRiding(false);
 +    }
++
 +    @Override
 +    public void stopRiding(boolean suppressCancellation) {
 +        // Paper end - Force entity dismount during teleportation
-         Entity entity = this.getVehicle();
- 
+         Entity vehicle = this.getVehicle();
 -        super.stopRiding();
--        if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) {
+-        if (vehicle != null && vehicle != this.getVehicle() && !this.level().isClientSide) {
 +        super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation
-+        if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen
-             this.dismountVehicle(entity);
++        if (vehicle != null && vehicle != this.getVehicle() && !this.level().isClientSide && vehicle.valid) { // Paper - don't process on world gen
+             this.dismountVehicle(vehicle);
          }
- 
-@@ -3258,7 +3943,7 @@
+     }
+@@ -3007,7 +_,7 @@
      }
  
-     public void onItemPickup(ItemEntity item) {
--        Entity entity = item.getOwner();
-+        Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate
- 
-         if (entity instanceof ServerPlayer) {
-             CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this);
-@@ -3268,7 +3953,7 @@
- 
-     public void take(Entity item, int count) {
-         if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) {
--            ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count));
-+            ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source
+     public void onItemPickup(ItemEntity itemEntity) {
+-        Entity owner = itemEntity.getOwner();
++        Entity owner = itemEntity.thrower != null ? this.level().getGlobalPlayerByUUID(itemEntity.thrower) : null; // Paper - check global player list where appropriate
+         if (owner instanceof ServerPlayer) {
+             CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer)owner, itemEntity.getItem(), this);
+         }
+@@ -3017,7 +_,7 @@
+         if (!entity.isRemoved()
+             && !this.level().isClientSide
+             && (entity instanceof ItemEntity || entity instanceof AbstractArrow || entity instanceof ExperienceOrb)) {
+-            ((ServerLevel)this.level()).getChunkSource().broadcast(entity, new ClientboundTakeItemEntityPacket(entity.getId(), this.getId(), amount));
++            ((ServerLevel)this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(entity.getId(), this.getId(), amount)); // Paper - broadcast with collector as source
          }
- 
      }
-@@ -3284,7 +3969,8 @@
-             Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ());
-             Vec3 vec3d1 = new Vec3(entity.getX(), entityY, entity.getZ());
  
--            return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS;
+@@ -3031,7 +_,8 @@
+         } else {
+             Vec3 vec3 = new Vec3(this.getX(), this.getEyeY(), this.getZ());
+             Vec3 vec31 = new Vec3(entity.getX(), y, entity.getZ());
+-            return !(vec31.distanceTo(vec3) > 128.0) && this.level().clip(new ClipContext(vec3, vec31, block, fluid, this)).getType() == HitResult.Type.MISS;
 +            // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
-+            return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared
++            return !(vec31.distanceToSqr(vec3) > 128.0D * 128.0D) && this.level().clip(new ClipContext(vec3, vec31, block, fluid, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared
          }
      }
  
-@@ -3305,13 +3991,27 @@
+@@ -3051,13 +_,27 @@
  
      @Override
      public boolean isPickable() {
@@ -1729,6 +1503,7 @@
      @Override
      public boolean isPushable() {
 -        return this.isAlive() && !this.isSpectator() && !this.onClimbable();
+-    }
 +        return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule);
 +    }
 +
@@ -1742,12 +1517,12 @@
 +    @Override
 +    public boolean canCollideWithBukkit(Entity entity) {
 +        return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID());
-     }
++    }
 +    // CraftBukkit end
  
      @Override
      public float getYHeadRot() {
-@@ -3342,7 +4042,7 @@
+@@ -3088,7 +_,7 @@
      }
  
      public final void setAbsorptionAmount(float absorptionAmount) {
@@ -1756,8 +1531,8 @@
      }
  
      protected void internalSetAbsorptionAmount(float absorptionAmount) {
-@@ -3367,6 +4067,11 @@
-         return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
+@@ -3115,6 +_,11 @@
+         return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
      }
  
 +    // Paper start - Properly cancel usable items
@@ -1768,7 +1543,7 @@
      private void updatingUsingItem() {
          if (this.isUsingItem()) {
              if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
-@@ -3410,9 +4115,14 @@
+@@ -3154,8 +_,13 @@
      }
  
      public void startUsingItem(InteractionHand hand) {
@@ -1777,54 +1552,52 @@
 +    }
 +    public void startUsingItem(InteractionHand hand, boolean forceUpdate) {
 +        // Paper end - Prevent consuming the wrong itemstack
-         ItemStack itemstack = this.getItemInHand(hand);
- 
--        if (!itemstack.isEmpty() && !this.isUsingItem()) {
-+        if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
-             this.useItem = itemstack;
-             this.useItemRemaining = itemstack.getUseDuration(this);
+         ItemStack itemInHand = this.getItemInHand(hand);
+-        if (!itemInHand.isEmpty() && !this.isUsingItem()) {
++        if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
+             this.useItem = itemInHand;
+             this.useItemRemaining = itemInHand.getUseDuration(this);
              if (!this.level().isClientSide) {
-@@ -3483,13 +4193,50 @@
+@@ -3220,12 +_,49 @@
                  this.releaseUsingItem();
              } else {
                  if (!this.useItem.isEmpty() && this.isUsingItem()) {
--                    ItemStack itemstack = this.useItem.finishUsingItem(this.level(), this);
+-                    ItemStack itemStack = this.useItem.finishUsingItem(this.level(), this);
 +                    this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack
 +                    // CraftBukkit start - fire PlayerItemConsumeEvent
-+                    ItemStack itemstack;
++                    ItemStack itemStack;
 +                    PlayerItemConsumeEvent event = null; // Paper
-+                    if (this instanceof ServerPlayer entityPlayer) {
++                    if (this instanceof ServerPlayer serverPlayer) {
 +                        org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem);
-+                        org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand);
-+                        event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper
++                        org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(usedItemHand);
++                        event = new PlayerItemConsumeEvent((org.bukkit.entity.Player) this.getBukkitEntity(), craftItem, hand); // Paper
 +                        this.level().getCraftServer().getPluginManager().callEvent(event);
 +
 +                        if (event.isCancelled()) {
 +                            // Update client
 +                            Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE);
 +                            if (consumable != null) {
-+                                consumable.cancelUsingItem(entityPlayer, this.useItem);
++                                consumable.cancelUsingItem(serverPlayer, this.useItem);
 +                            }
-+                            entityPlayer.getBukkitEntity().updateInventory();
-+                            entityPlayer.getBukkitEntity().updateScaledHealth();
++                            serverPlayer.getBukkitEntity().updateInventory();
++                            serverPlayer.getBukkitEntity().updateScaledHealth();
 +                            this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use
 +                            return;
 +                        }
- 
-+                        itemstack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level(), this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(this.level(), this);
++
++                        itemStack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level(), this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(this.level(), this);
 +                    } else {
-+                        itemstack = this.useItem.finishUsingItem(this.level(), this);
++                        itemStack = this.useItem.finishUsingItem(this.level(), this);
 +                    }
 +                    // Paper start - save the default replacement item and change it if necessary
-+                    final ItemStack defaultReplacement = itemstack;
++                    final ItemStack defaultReplacement = itemStack;
 +                    if (event != null && event.getReplacement() != null) {
-+                        itemstack = CraftItemStack.asNMSCopy(event.getReplacement());
++                        itemStack = CraftItemStack.asNMSCopy(event.getReplacement());
 +                    }
 +                    // Paper end
 +                    // CraftBukkit end
-+
-                     if (itemstack != this.useItem) {
-                         this.setItemInHand(enumhand, itemstack);
+                     if (itemStack != this.useItem) {
+                         this.setItemInHand(usedItemHand, itemStack);
                      }
  
                      this.stopUsingItem();
@@ -1834,26 +1607,27 @@
 +                    }
 +                    // Paper end
                  }
- 
              }
-@@ -3512,6 +4259,7 @@
+         }
+@@ -3248,6 +_,7 @@
  
      public void releaseUsingItem() {
          if (!this.useItem.isEmpty()) {
-+            if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent
++            if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((org.bukkit.entity.Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent
              this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks());
              if (this.useItem.useOnRelease()) {
                  this.updatingUsingItem();
-@@ -3544,12 +4292,69 @@
-         if (this.isUsingItem() && !this.useItem.isEmpty()) {
-             Item item = this.useItem.getItem();
- 
--            return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < 5 ? null : this.useItem);
-+            return item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK ? null : (item.getUseDuration(this.useItem, this) - this.useItemRemaining < getShieldBlockingDelay() ? null : this.useItem); // Paper - Make shield blocking delay configurable
+@@ -3281,12 +_,69 @@
+             if (item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK) {
+                 return null;
+             } else {
+-                return item.getUseDuration(this.useItem, this) - this.useItemRemaining < 5 ? null : this.useItem;
++                return item.getUseDuration(this.useItem, this) - this.useItemRemaining < this.getShieldBlockingDelay() ? null : this.useItem; // Paper - Make shield blocking delay configurable
+             }
          } else {
              return null;
          }
-+    }
+     }
 +
 +    // Paper start - Make shield blocking delay configurable
 +    public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) {
@@ -1905,56 +1679,45 @@
 +
 +    public int getShieldBlockingDelay() {
 +        return shieldBlockingDelay;
-     }
- 
++    }
++
 +    public void setShieldBlockingDelay(int shieldBlockingDelay) {
 +        this.shieldBlockingDelay = shieldBlockingDelay;
 +    }
 +    // Paper end - Make shield blocking delay configurable
-+
+ 
      public boolean isSuppressingSlidingDownLadder() {
          return this.isShiftKeyDown();
-     }
-@@ -3568,12 +4373,18 @@
+@@ -3306,6 +_,12 @@
      }
  
-     public boolean randomTeleport(double x, double y, double z, boolean particleEffects) {
+     public boolean randomTeleport(double x, double y, double z, boolean broadcastTeleport) {
 +        // CraftBukkit start
-+        return this.randomTeleport(x, y, z, particleEffects, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false);
++        return this.randomTeleport(x, y, z, broadcastTeleport, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false);
 +    }
 +
-+    public Optional<Boolean> randomTeleport(double d0, double d1, double d2, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++    public Optional<Boolean> randomTeleport(double x, double y, double z, boolean broadcastTeleport, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
 +        // CraftBukkit end
-         double d3 = this.getX();
-         double d4 = this.getY();
-         double d5 = this.getZ();
--        double d6 = y;
-+        double d6 = d1;
-         boolean flag1 = false;
--        BlockPos blockposition = BlockPos.containing(x, y, z);
-+        BlockPos blockposition = BlockPos.containing(d0, d1, d2);
-         Level world = this.level();
- 
-         if (world.hasChunkAt(blockposition)) {
-@@ -3592,18 +4403,43 @@
+         double x1 = this.getX();
+         double y1 = this.getY();
+         double z1 = this.getZ();
+@@ -3328,16 +_,39 @@
              }
  
-             if (flag2) {
--                this.teleportTo(x, d6, z);
+             if (flag1) {
+-                this.teleportTo(x, d, z);
 +                // CraftBukkit start - Teleport event
-+                // this.teleportTo(d0, d6, d2);
-+
 +                // first set position, to check if the place to teleport is valid
-+                this.setPos(d0, d6, d2);
-                 if (world.noCollision((Entity) this) && !world.containsAnyLiquid(this.getBoundingBox())) {
-                     flag1 = true;
++                this.setPos(x, d, z);
+                 if (level.noCollision(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
+                     flag = true;
                  }
 +                // now revert and call event if the teleport place is valid
-+                this.setPos(d3, d4, d5);
++                this.setPos(x1, y1, z1);
 +
-+                if (flag1) {
++                if (flag) {
 +                    if (!(this instanceof ServerPlayer)) {
-+                        EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2));
++                        EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), x1, y1, z1), new Location(this.level().getWorld(), x, d, z));
 +                        this.level().getCraftServer().getPluginManager().callEvent(teleport);
 +                        if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper
 +                            Location to = teleport.getTo();
@@ -1964,7 +1727,7 @@
 +                        }
 +                    } else {
 +                        // player teleport event is called in the underlining code
-+                        if (!((ServerPlayer) this).connection.teleport(d0, d6, d2, this.getYRot(), this.getXRot(), cause)) {
++                        if (!((ServerPlayer) this).connection.teleport(x, d, z, this.getYRot(), this.getXRot(), cause)) {
 +                            return Optional.empty();
 +                        }
 +                    }
@@ -1973,19 +1736,16 @@
              }
          }
  
-         if (!flag1) {
--            this.teleportTo(d3, d4, d5);
+         if (!flag) {
+-            this.teleportTo(x1, y1, z1);
 -            return false;
-+            // this.enderTeleportTo(d3, d4, d5); // CraftBukkit - already set the location back
++            // this.teleportTo(x1, y1, z1); // CraftBukkit - already set the location back
 +            return Optional.of(false); // CraftBukkit
          } else {
--            if (particleEffects) {
-+            if (flag) {
-                 world.broadcastEntityEvent(this, (byte) 46);
-             }
- 
-@@ -3613,7 +4449,7 @@
-                 entitycreature.getNavigation().stop();
+             if (broadcastTeleport) {
+                 level.broadcastEntityEvent(this, (byte)46);
+@@ -3347,7 +_,7 @@
+                 pathfinderMob.getNavigation().stop();
              }
  
 -            return true;
@@ -1993,42 +1753,3 @@
          }
      }
  
-@@ -3706,7 +4542,7 @@
-     }
- 
-     public void stopSleeping() {
--        Optional optional = this.getSleepingPos();
-+        Optional<BlockPos> optional = this.getSleepingPos(); // CraftBukkit - decompile error
-         Level world = this.level();
- 
-         java.util.Objects.requireNonNull(world);
-@@ -3718,9 +4554,9 @@
- 
-                 this.level().setBlock(blockposition, (BlockState) iblockdata.setValue(BedBlock.OCCUPIED, false), 3);
-                 Vec3 vec3d = (Vec3) BedBlock.findStandUpPosition(this.getType(), this.level(), blockposition, enumdirection, this.getYRot()).orElseGet(() -> {
--                    BlockPosition blockposition1 = blockposition.above();
-+                    BlockPos blockposition1 = blockposition.above();
- 
--                    return new Vec3D((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.1D, (double) blockposition1.getZ() + 0.5D);
-+                    return new Vec3((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.1D, (double) blockposition1.getZ() + 0.5D);
-                 });
-                 Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize();
-                 float f = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D);
-@@ -3740,7 +4576,7 @@
- 
-     @Nullable
-     public Direction getBedOrientation() {
--        BlockPos blockposition = (BlockPos) this.getSleepingPos().orElse((Object) null);
-+        BlockPos blockposition = (BlockPos) this.getSleepingPos().orElse(null); // CraftBukkit - decompile error
- 
-         return blockposition != null ? BedBlock.getBedOrientation(this.level(), blockposition) : null;
-     }
-@@ -3905,7 +4741,7 @@
-     public float maxUpStep() {
-         float f = (float) this.getAttributeValue(Attributes.STEP_HEIGHT);
- 
--        return this.getControllingPassenger() instanceof Player ? Math.max(f, 1.0F) : f;
-+        return this.getControllingPassenger() instanceof net.minecraft.world.entity.player.Player ? Math.max(f, 1.0F) : f;
-     }
- 
-     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
index ca11667ad6..878f738ed0 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
@@ -1,14 +1,5 @@
 --- a/net/minecraft/world/entity/TamableAnimal.java
 +++ b/net/minecraft/world/entity/TamableAnimal.java
-@@ -57,7 +_,7 @@
-             compound.putUUID("Owner", this.getOwnerUUID());
-         }
- 
--        compound.putBoolean("Sitting", this.orderedToSit);
-+        compound.putBoolean("Sitting", this.orderedToSit);f
-     }
- 
-     @Override
 @@ -84,7 +_,7 @@
          }
  

From 0aa15ea868d2f34331e7cad6311f846c6f10d8c9 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:26:08 -0500
Subject: [PATCH 096/285] net/minecraft/world/level/chunk/

---
 .../net/minecraft/world/level/chunk/ChunkGenerator.java.patch   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
index bd04398695..bdd2e9ec24 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -79,7 +79,7 @@
                              Supplier<String> supplier1 = () -> registry1.getResourceKey(placedFeature).map(Object::toString).orElseGet(placedFeature::toString);
 -                            worldgenRandom.setFeatureSeed(l, i3, i);
 +                            // Paper start - Configurable feature seeds; change populationSeed used in random
-+                            long featurePopulationSeed = i;
++                            long featurePopulationSeed = l;
 +                            final long configFeatureSeed = level.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedFeature.feature());
 +                            if (configFeatureSeed != -1) {
 +                                featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See seededrandom.setDecorationSeed from above

From 12e0268dab510a0ae204782ca96f06f1e9000dbf Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 20:27:07 +0000
Subject: [PATCH 097/285] net/minecraft/core

---
 .../net/minecraft/core/BlockPos.java.patch    | 84 ++++---------------
 .../net/minecraft/core/Direction.java.patch   | 22 ++---
 .../net/minecraft/core/Holder.java.patch      |  2 +-
 .../minecraft/core/MappedRegistry.java.patch  | 10 +--
 .../net/minecraft/core/Rotations.java.patch   |  4 +-
 .../net/minecraft/core/Vec3i.java.patch       | 16 ++--
 6 files changed, 44 insertions(+), 94 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/BlockPos.java.patch (57%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/Direction.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/Holder.java.patch (94%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/MappedRegistry.java.patch (94%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/Rotations.java.patch (84%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/Vec3i.java.patch (70%)

diff --git a/paper-server/patches/unapplied/net/minecraft/core/BlockPos.java.patch b/paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/core/BlockPos.java.patch
rename to paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch
index 7858f6cef1..13a67b0e34 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/BlockPos.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/BlockPos.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/BlockPos.java
 +++ b/net/minecraft/core/BlockPos.java
-@@ -158,67 +158,84 @@
+@@ -158,67 +_,84 @@
  
      @Override
      public BlockPos above() {
@@ -21,9 +21,9 @@
      }
  
      @Override
-     public BlockPos below(int i) {
--        return this.relative(Direction.DOWN, i);
-+        return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition
+     public BlockPos below(int distance) {
+-        return this.relative(Direction.DOWN, distance);
++        return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - distance, this.getZ()); // Paper - Perf: Optimize BlockPosition
      }
  
      @Override
@@ -76,6 +76,7 @@
  
      @Override
      public BlockPos relative(Direction direction) {
+-        return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ());
 +        // Paper start - Perf: Optimize BlockPosition
 +        switch(direction) {
 +            case UP:
@@ -91,64 +92,13 @@
 +            case EAST:
 +                return new BlockPos(this.getX() + 1, this.getY(), this.getZ());
 +            default:
-         return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ());
++                return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ());
 +        }
 +        // Paper end - Perf: Optimize BlockPosition
      }
  
      @Override
-@@ -324,9 +341,11 @@
- 
-     public static Iterable<BlockPos> withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) {
-         int i = rangeX + rangeY + rangeZ;
--        int j = center.getX();
--        int k = center.getY();
--        int l = center.getZ();
-+        // Paper start - rename variables to fix conflict with anonymous class (remap fix)
-+        int centerX = center.getX();
-+        int centerY = center.getY();
-+        int centerZ = center.getZ();
-+        // Paper end
-         return () -> new AbstractIterator<BlockPos>() {
-                 private final BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
-                 private int currentDepth;
-@@ -340,7 +359,7 @@
-                 protected BlockPos computeNext() {
-                     if (this.zMirror) {
-                         this.zMirror = false;
--                        this.cursor.setZ(l - (this.cursor.getZ() - l));
-+                        this.cursor.setZ(centerZ - (this.cursor.getZ() - centerZ)); // Paper - remap fix
-                         return this.cursor;
-                     } else {
-                         BlockPos blockPos;
-@@ -366,7 +385,7 @@
-                             int k = this.currentDepth - Math.abs(i) - Math.abs(j);
-                             if (k <= rangeZ) {
-                                 this.zMirror = k != 0;
--                                blockPos = this.cursor.set(j + i, k + j, l + k);
-+                                blockPos = this.cursor.set(centerX + i, centerY + j, centerZ + k); // Paper - remap fix
-                             }
-                         }
- 
-@@ -444,12 +463,12 @@
-                     if (this.index == l) {
-                         return this.endOfData();
-                     } else {
--                        int i = this.index % i;
--                        int j = this.index / i;
--                        int k = j % j;
--                        int l = j / j;
-+                        int offsetX = this.index % i; // Paper - decomp fix
-+                        int u = this.index / i; // Paper - decomp fix
-+                        int offsetY = u % j; // Paper - decomp fix
-+                        int offsetZ = u / j; // Paper - decomp fix
-                         this.index++;
--                        return this.cursor.set(startX + i, startY + k, startZ + l);
-+                        return this.cursor.set(startX + offsetX, startY + offsetY, startZ + offsetZ); // Paper - decomp fix
-                     }
-                 }
-             };
-@@ -569,9 +588,9 @@
+@@ -573,9 +_,9 @@
          }
  
          public BlockPos.MutableBlockPos set(int x, int y, int z) {
@@ -161,26 +111,26 @@
              return this;
          }
  
-@@ -636,19 +655,19 @@
+@@ -638,19 +_,19 @@
  
          @Override
-         public BlockPos.MutableBlockPos setX(int i) {
--            super.setX(i);
-+            this.x = i; // Paper - Perf: Manually inline methods in BlockPosition
+         public BlockPos.MutableBlockPos setX(int x) {
+-            super.setX(x);
++            this.x = x; // Paper - Perf: Manually inline methods in BlockPosition
              return this;
          }
  
          @Override
-         public BlockPos.MutableBlockPos setY(int i) {
--            super.setY(i);
-+            this.y = i; // Paper - Perf: Manually inline methods in BlockPosition
+         public BlockPos.MutableBlockPos setY(int y) {
+-            super.setY(y);
++            this.y = y; // Paper - Perf: Manually inline methods in BlockPosition
              return this;
          }
  
          @Override
-         public BlockPos.MutableBlockPos setZ(int i) {
--            super.setZ(i);
-+            this.z = i; // Paper - Perf: Manually inline methods in BlockPosition
+         public BlockPos.MutableBlockPos setZ(int z) {
+-            super.setZ(z);
++            this.z = z; // Paper - Perf: Manually inline methods in BlockPosition
              return this;
          }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/core/Direction.java.patch b/paper-server/patches/sources/net/minecraft/core/Direction.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/core/Direction.java.patch
rename to paper-server/patches/sources/net/minecraft/core/Direction.java.patch
index a0a77c5f10..1859b6bb9f 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/Direction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Direction.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/Direction.java
 +++ b/net/minecraft/core/Direction.java
-@@ -57,6 +57,12 @@
+@@ -57,6 +_,12 @@
          .sorted(Comparator.comparingInt(direction -> direction.data2d))
          .toArray(Direction[]::new);
  
@@ -11,21 +11,21 @@
 +    // Paper end - Perf: Inline shift direction fields
 +
      private Direction(
-         final int id,
-         final int idOpposite,
-@@ -74,6 +80,11 @@
-         this.axisDirection = direction;
-         this.normal = vector;
-         this.normalVec3 = Vec3.atLowerCornerOf(vector);
+         final int data3d,
+         final int oppositeIndex,
+@@ -74,6 +_,11 @@
+         this.axisDirection = axisDirection;
+         this.normal = normal;
+         this.normalVec3 = Vec3.atLowerCornerOf(normal);
 +        // Paper start - Perf: Inline shift direction fields
-+        this.adjX = vector.getX();
-+        this.adjY = vector.getY();
-+        this.adjZ = vector.getZ();
++        this.adjX = normal.getX();
++        this.adjY = normal.getY();
++        this.adjZ = normal.getZ();
 +        // Paper end - Perf: Inline shift direction fields
      }
  
      public static Direction[] orderedByNearest(Entity entity) {
-@@ -247,15 +258,15 @@
+@@ -247,15 +_,15 @@
      }
  
      public int getStepX() {
diff --git a/paper-server/patches/unapplied/net/minecraft/core/Holder.java.patch b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/net/minecraft/core/Holder.java.patch
rename to paper-server/patches/sources/net/minecraft/core/Holder.java.patch
index 634a2b2190..51a81bf5f3 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/Holder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/Holder.java
 +++ b/net/minecraft/core/Holder.java
-@@ -230,7 +230,7 @@
+@@ -230,7 +_,7 @@
          }
  
          void bindTags(Collection<TagKey<T>> tags) {
diff --git a/paper-server/patches/unapplied/net/minecraft/core/MappedRegistry.java.patch b/paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/net/minecraft/core/MappedRegistry.java.patch
rename to paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch
index 4d3c8b552e..400d714abe 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/MappedRegistry.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/MappedRegistry.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/MappedRegistry.java
 +++ b/net/minecraft/core/MappedRegistry.java
-@@ -33,11 +33,11 @@
+@@ -33,11 +_,11 @@
  public class MappedRegistry<T> implements WritableRegistry<T> {
      private final ResourceKey<? extends Registry<T>> key;
      private final ObjectList<Holder.Reference<T>> byId = new ObjectArrayList<>(256);
@@ -17,17 +17,17 @@
      private Lifecycle registryLifecycle;
      private final Map<TagKey<T>, HolderSet.Named<T>> frozenTags = new IdentityHashMap<>();
      MappedRegistry.TagSet<T> allTags = MappedRegistry.TagSet.unbound();
-@@ -508,5 +508,13 @@
-         void forEach(BiConsumer<? super TagKey<T>, ? super HolderSet.Named<T>> consumer);
+@@ -509,4 +_,13 @@
  
          Stream<HolderSet.Named<T>> getTags();
-+    }
+     }
++
 +    // Paper start
 +    // used to clear intrusive holders from GameEvent, Item, Block, EntityType, and Fluid from unused instances of those types
 +    public void clearIntrusiveHolder(final T instance) {
 +        if (this.unregisteredIntrusiveHolders != null) {
 +            this.unregisteredIntrusiveHolders.remove(instance);
 +        }
-     }
++    }
 +    // Paper end
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/core/Rotations.java.patch b/paper-server/patches/sources/net/minecraft/core/Rotations.java.patch
similarity index 84%
rename from paper-server/patches/unapplied/net/minecraft/core/Rotations.java.patch
rename to paper-server/patches/sources/net/minecraft/core/Rotations.java.patch
index 29c5bfc220..0b17d3ac2a 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/Rotations.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Rotations.java.patch
@@ -1,7 +1,7 @@
 --- a/net/minecraft/core/Rotations.java
 +++ b/net/minecraft/core/Rotations.java
-@@ -34,6 +34,18 @@
-         this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2));
+@@ -34,6 +_,18 @@
+         this(tag.getFloat(0), tag.getFloat(1), tag.getFloat(2));
      }
  
 +    // Paper start - faster alternative constructor
diff --git a/paper-server/patches/unapplied/net/minecraft/core/Vec3i.java.patch b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/core/Vec3i.java.patch
rename to paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
index 8c19b63ae6..cd374686c0 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/Vec3i.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
@@ -1,7 +1,7 @@
 --- a/net/minecraft/core/Vec3i.java
 +++ b/net/minecraft/core/Vec3i.java
-@@ -16,9 +16,9 @@
-             vec -> IntStream.of(vec.getX(), vec.getY(), vec.getZ())
+@@ -16,9 +_,9 @@
+             vec3i -> IntStream.of(vec3i.getX(), vec3i.getY(), vec3i.getZ())
          );
      public static final Vec3i ZERO = new Vec3i(0, 0, 0);
 -    private int x;
@@ -11,15 +11,15 @@
 +    protected int y; // Paper - Perf: Manually inline methods in BlockPosition; protected
 +    protected int z; // Paper - Perf: Manually inline methods in BlockPosition; protected
  
-     public static Codec<Vec3i> offsetCodec(int maxAbsValue) {
+     public static Codec<Vec3i> offsetCodec(int maxOffset) {
          return CODEC.validate(
-@@ -35,12 +35,12 @@
+@@ -35,12 +_,12 @@
      }
  
      @Override
--    public boolean equals(Object object) {
-+    public final boolean equals(Object object) { // Paper - Perf: Final for inline
-         return this == object || object instanceof Vec3i vec3i && this.getX() == vec3i.getX() && this.getY() == vec3i.getY() && this.getZ() == vec3i.getZ();
+-    public boolean equals(Object other) {
++    public final boolean equals(Object other) { // Paper - Perf: Final for inline
+         return this == other || other instanceof Vec3i vec3i && this.getX() == vec3i.getX() && this.getY() == vec3i.getY() && this.getZ() == vec3i.getZ();
      }
  
      @Override
@@ -28,7 +28,7 @@
          return (this.getY() + this.getZ() * 31) * 31 + this.getX();
      }
  
-@@ -53,15 +53,15 @@
+@@ -53,15 +_,15 @@
          }
      }
  

From a9d7ae7213d2587caaee23e63c4f7a6748a31a65 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:32:18 -0500
Subject: [PATCH 098/285] Missing part of
 net/minecraft/world/level/dimension/end/

---
 .../end/DragonRespawnAnimation.java.patch     | 11 ++++
 .../end/DragonRespawnAnimation.java.patch     | 57 -------------------
 2 files changed, 11 insertions(+), 57 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch
new file mode 100644
index 0000000000..495ef0300d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java
++++ b/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java
+@@ -90,7 +_,7 @@
+                 for (EndCrystal endCrystal : crystals) {
+                     endCrystal.setBeamTarget(null);
+                     level.explode(endCrystal, endCrystal.getX(), endCrystal.getY(), endCrystal.getZ(), 6.0F, Level.ExplosionInteraction.NONE);
+-                    endCrystal.discard();
++                    endCrystal.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
+                 }
+             } else if (ticks >= 80) {
+                 level.levelEvent(3001, new BlockPos(0, 128, 0), 0);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch
deleted file mode 100644
index bc18c1f95e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java.patch
+++ /dev/null
@@ -1,57 +0,0 @@
---- a/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java
-+++ b/net/minecraft/world/level/dimension/end/DragonRespawnAnimation.java
-@@ -12,6 +12,9 @@
- import net.minecraft.world.level.levelgen.feature.Feature;
- import net.minecraft.world.level.levelgen.feature.SpikeFeature;
- import net.minecraft.world.level.levelgen.feature.configurations.SpikeConfiguration;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public enum DragonRespawnAnimation {
- 
-@@ -27,7 +30,7 @@
-                 entityendercrystal.setBeamTarget(blockposition1);
-             }
- 
--            fight.setRespawnStage(null.PREPARING_TO_SUMMON_PILLARS);
-+            fight.setRespawnStage(PREPARING_TO_SUMMON_PILLARS); // CraftBukkit - decompile error
-         }
-     },
-     PREPARING_TO_SUMMON_PILLARS {
-@@ -38,7 +41,7 @@
-                     world.levelEvent(3001, new BlockPos(0, 128, 0), 0);
-                 }
-             } else {
--                fight.setRespawnStage(null.SUMMONING_PILLARS);
-+                fight.setRespawnStage(SUMMONING_PILLARS); // CraftBukkit - decompile error
-             }
- 
-         }
-@@ -81,7 +84,7 @@
-                         Feature.END_SPIKE.place(worldgenfeatureendspikeconfiguration, world, world.getChunkSource().getGenerator(), RandomSource.create(), new BlockPos(worldgenender_spike.getCenterX(), 45, worldgenender_spike.getCenterZ()));
-                     }
-                 } else if (flag1) {
--                    fight.setRespawnStage(null.SUMMONING_DRAGON);
-+                    fight.setRespawnStage(SUMMONING_DRAGON); // CraftBukkit - decompile error
-                 }
-             }
- 
-@@ -94,7 +97,7 @@
-             EndCrystal entityendercrystal;
- 
-             if (tick >= 100) {
--                fight.setRespawnStage(null.END);
-+                fight.setRespawnStage(END); // CraftBukkit - decompile error
-                 fight.resetSpikeCrystals();
-                 iterator = crystals.iterator();
- 
-@@ -102,7 +105,7 @@
-                     entityendercrystal = (EndCrystal) iterator.next();
-                     entityendercrystal.setBeamTarget((BlockPos) null);
-                     world.explode(entityendercrystal, entityendercrystal.getX(), entityendercrystal.getY(), entityendercrystal.getZ(), 6.0F, Level.ExplosionInteraction.NONE);
--                    entityendercrystal.discard();
-+                    entityendercrystal.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
-                 }
-             } else if (tick >= 80) {
-                 world.levelEvent(3001, new BlockPos(0, 128, 0), 0);

From a546b39e5804397f3dc99f228e35f0011bc5dd5b Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 20:46:42 +0000
Subject: [PATCH 099/285] net/minecraft/world/scores

---
 .../net/minecraft/world/scores/ScoreboardSaveData.java.patch  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/scores/ScoreboardSaveData.java.patch (89%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/scores/ScoreboardSaveData.java.patch b/paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch
similarity index 89%
rename from paper-server/patches/unapplied/net/minecraft/world/scores/ScoreboardSaveData.java.patch
rename to paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch
index d29c6da396..c8b1a7802f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/scores/ScoreboardSaveData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/scores/ScoreboardSaveData.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/scores/ScoreboardSaveData.java
 +++ b/net/minecraft/world/scores/ScoreboardSaveData.java
-@@ -148,6 +148,7 @@
+@@ -148,6 +_,7 @@
          ListTag listTag = new ListTag();
  
          for (PlayerTeam playerTeam : this.scoreboard.getPlayerTeams()) {
 +            if (!io.papermc.paper.configuration.GlobalConfiguration.get().scoreboards.saveEmptyScoreboardTeams && playerTeam.getPlayers().isEmpty()) continue; // Paper - Don't save empty scoreboard teams to scoreboard.dat
              CompoundTag compoundTag = new CompoundTag();
              compoundTag.putString("Name", playerTeam.getName());
-             compoundTag.putString("DisplayName", Component.Serializer.toJson(playerTeam.getDisplayName(), registries));
+             compoundTag.putString("DisplayName", Component.Serializer.toJson(playerTeam.getDisplayName(), levelRegistry));

From b0f627d7bb3af9d579d3d6787191a7aa21cca75c Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 14 Dec 2024 21:42:25 +0100
Subject: [PATCH 100/285] Small cleanup

---
 .../common/ServerboundCustomPayloadPacket.java.patch   | 10 ++++------
 .../net/minecraft/world/entity/LivingEntity.java.patch |  4 ++--
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
index b7477f930e..10835a1af3 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
@@ -1,13 +1,11 @@
 --- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
 +++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
-@@ -14,9 +_,7 @@
+@@ -14,7 +_,7 @@
      private static final int MAX_PAYLOAD_SIZE = 32767;
      public static final StreamCodec<FriendlyByteBuf, ServerboundCustomPayloadPacket> STREAM_CODEC = CustomPacketPayload.<FriendlyByteBuf>codec(
              id -> DiscardedPayload.codec(id, 32767),
 -            Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)), list -> {})
--        )
--        .map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload);
-+        java.util.Collections.emptyList()).map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload); // CraftBukkit - treat all packets the same
++            java.util.Collections.emptyList() // CraftBukkit - treat all packets the same
+         )
+         .map(ServerboundCustomPayloadPacket::new, ServerboundCustomPayloadPacket::payload);
  
-     @Override
-     public PacketType<ServerboundCustomPayloadPacket> type() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index 713fc86621..19054bee9f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -137,14 +137,14 @@
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason reason, EntityRemoveEvent.Cause eventCaue) {
++    public void remove(Entity.RemovalReason reason, EntityRemoveEvent.Cause eventCause) {
 +        // CraftBukkit end
          if ((reason == Entity.RemovalReason.KILLED || reason == Entity.RemovalReason.DISCARDED) && this.level() instanceof ServerLevel serverLevel) {
              this.triggerOnDeathMobEffects(serverLevel, reason);
          }
  
 -        super.remove(reason);
-+        super.remove(reason, eventCaue); // CraftBukkit
++        super.remove(reason, eventCause); // CraftBukkit
          this.brain.clearMemories();
      }
  

From 93c74cf2f4c2004dbf4d5935ecbd12021234f874 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:47:45 -0500
Subject: [PATCH 101/285] net/minecraft/network/

---
 .../minecraft/network/Connection.java.patch   | 178 ++++++++----------
 .../network/FriendlyByteBuf.java.patch        |  42 +++++
 .../network/PacketEncoder.java.patch          |   7 +-
 .../net/minecraft/network/VarInt.java.patch   |  40 ++--
 .../network/Varint21FrameDecoder.java.patch   |  14 +-
 .../network/FriendlyByteBuf.java.patch        | 105 -----------
 6 files changed, 149 insertions(+), 237 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/Connection.java.patch (70%)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/PacketEncoder.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/VarInt.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/network/Varint21FrameDecoder.java.patch (50%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/FriendlyByteBuf.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/network/Connection.java.patch b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/network/Connection.java.patch
rename to paper-server/patches/sources/net/minecraft/network/Connection.java.patch
index f1e414f6b1..f36ea815c5 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/Connection.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
@@ -1,23 +1,23 @@
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
-@@ -82,13 +82,13 @@
-         marker.add(Connection.PACKET_MARKER);
-     });
-     public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(() -> {
--        return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
-+        return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
-     });
-     public static final Supplier<EpollEventLoopGroup> NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> {
--        return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
-+        return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
-     });
-     public static final Supplier<DefaultEventLoopGroup> LOCAL_WORKER_GROUP = Suppliers.memoize(() -> {
--        return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
-+        return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
-     });
+@@ -74,13 +_,13 @@
+     public static final Marker PACKET_RECEIVED_MARKER = Util.make(MarkerFactory.getMarker("PACKET_RECEIVED"), marker -> marker.add(PACKET_MARKER));
+     public static final Marker PACKET_SENT_MARKER = Util.make(MarkerFactory.getMarker("PACKET_SENT"), marker -> marker.add(PACKET_MARKER));
+     public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(
+-        () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Client IO #%d").setDaemon(true).build())
++        () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
+     );
+     public static final Supplier<EpollEventLoopGroup> NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(
+-        () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build())
++        () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
+     );
+     public static final Supplier<DefaultEventLoopGroup> LOCAL_WORKER_GROUP = Suppliers.memoize(
+-        () -> new DefaultEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Local Client IO #%d").setDaemon(true).build())
++        () -> new DefaultEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
+     );
      private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
      private final PacketFlow receiving;
-@@ -96,6 +96,11 @@
+@@ -88,6 +_,11 @@
      private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
      public Channel channel;
      public SocketAddress address;
@@ -29,7 +29,7 @@
      @Nullable
      private volatile PacketListener disconnectListener;
      @Nullable
-@@ -114,7 +119,42 @@
+@@ -106,6 +_,40 @@
      private volatile DisconnectionDetails delayedDisconnect;
      @Nullable
      BandwidthDebugMonitor bandwidthDebugMonitor;
@@ -39,7 +39,6 @@
 +    public java.net.InetSocketAddress virtualHost;
 +    private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing
 +    // Paper end
- 
 +    // Paper start - add utility methods
 +    public final net.minecraft.server.level.ServerPlayer getPlayer() {
 +        if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) {
@@ -68,13 +67,12 @@
 +    }
 +    // Paper end - packet limiter
 +    @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address
-+
-     public Connection(PacketFlow side) {
-         this.receiving = side;
-     }
-@@ -123,6 +163,9 @@
-         super.channelActive(channelhandlercontext);
-         this.channel = channelhandlercontext.channel();
+ 
+     public Connection(PacketFlow receiving) {
+         this.receiving = receiving;
+@@ -116,6 +_,9 @@
+         super.channelActive(context);
+         this.channel = context.channel();
          this.address = this.channel.remoteAddress();
 +        // Spigot Start
 +        this.preparing = false;
@@ -82,62 +80,61 @@
          if (this.delayedDisconnect != null) {
              this.disconnect(this.delayedDisconnect);
          }
-@@ -134,6 +177,21 @@
-     }
+@@ -128,14 +_,31 @@
  
-     public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {
+     @Override
+     public void exceptionCaught(ChannelHandlerContext context, Throwable exception) {
 +        // Paper start - Handle large packets disconnecting client
-+        if (throwable instanceof io.netty.handler.codec.EncoderException && throwable.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) {
++        if (exception instanceof io.netty.handler.codec.EncoderException && exception.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) {
 +            final Packet<?> packet = packetTooLargeException.getPacket();
 +            if (packet.packetTooLarge(this)) {
-+                ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet);
++                ProtocolSwapHandler.handleOutboundTerminalPacket(context, packet);
 +                return;
 +            } else if (packet.isSkippable()) {
-+                Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause());
-+                ProtocolSwapHandler.handleOutboundTerminalPacket(channelhandlercontext, packet);
++                Connection.LOGGER.debug("Skipping packet due to errors", exception.getCause());
++                ProtocolSwapHandler.handleOutboundTerminalPacket(context, packet);
 +                return;
 +            } else {
-+                throwable = throwable.getCause();
++                exception = exception.getCause();
 +            }
 +        }
 +        // Paper end - Handle large packets disconnecting client
-         if (throwable instanceof SkipPacketException) {
-             Connection.LOGGER.debug("Skipping packet due to errors", throwable.getCause());
+         if (exception instanceof SkipPacketException) {
+             LOGGER.debug("Skipping packet due to errors", exception.getCause());
          } else {
-@@ -141,8 +199,10 @@
- 
+             boolean flag = !this.handlingFault;
              this.handlingFault = true;
              if (this.channel.isOpen()) {
 +                net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason
-                 if (throwable instanceof TimeoutException) {
-                     Connection.LOGGER.debug("Timeout", throwable);
+                 if (exception instanceof TimeoutException) {
+                     LOGGER.debug("Timeout", exception);
 +                    if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason
-                     this.disconnect((Component) Component.translatable("disconnect.timeout"));
+                     this.disconnect(Component.translatable("disconnect.timeout"));
                  } else {
-                     MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + String.valueOf(throwable));
-@@ -155,9 +215,11 @@
-                         disconnectiondetails = new DisconnectionDetails(ichatmutablecomponent);
+                     Component component = Component.translatable("disconnect.genericReason", "Internal Exception: " + exception);
+@@ -147,9 +_,11 @@
+                         disconnectionDetails = new DisconnectionDetails(component);
                      }
  
 +                    if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason
                      if (flag) {
-                         Connection.LOGGER.debug("Failed to sent packet", throwable);
+                         LOGGER.debug("Failed to sent packet", exception);
 -                        if (this.getSending() == PacketFlow.CLIENTBOUND) {
 +                        boolean doesDisconnectExist = this.packetListener.protocol() != ConnectionProtocol.STATUS && this.packetListener.protocol() != ConnectionProtocol.HANDSHAKING; // Paper
 +                        if (this.getSending() == PacketFlow.CLIENTBOUND && doesDisconnectExist) { // Paper
-                             Packet<?> packet = this.sendLoginDisconnect ? new ClientboundLoginDisconnectPacket(ichatmutablecomponent) : new ClientboundDisconnectPacket(ichatmutablecomponent);
- 
-                             this.send((Packet) packet, PacketSendListener.thenRun(() -> {
-@@ -176,6 +238,7 @@
- 
+                             Packet<?> packet = (Packet<?>)(this.sendLoginDisconnect
+                                 ? new ClientboundLoginDisconnectPacket(component)
+                                 : new ClientboundDisconnectPacket(component));
+@@ -166,6 +_,7 @@
+                 }
              }
          }
-+        if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper
++        if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Spigot // Paper
      }
  
-     protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
-@@ -185,11 +248,61 @@
-             if (packetlistener == null) {
+     @Override
+@@ -175,10 +_,60 @@
+             if (packetListener == null) {
                  throw new IllegalStateException("Received a packet before the packet listener was initialized");
              } else {
 +                // Paper start - packet limiter
@@ -189,25 +186,15 @@
 +                    }
 +                }
 +                // Paper end - packet limiter
-                 if (packetlistener.shouldHandleMessage(packet)) {
+                 if (packetListener.shouldHandleMessage(packet)) {
                      try {
-                         Connection.genericsFtw(packet, packetlistener);
-                     } catch (RunningOnDifferentThreadException cancelledpackethandleexception) {
-                         ;
+                         genericsFtw(packet, packetListener);
+                     } catch (RunningOnDifferentThreadException var5) {
 +                    } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop
-                     } catch (RejectedExecutionException rejectedexecutionexception) {
-                         this.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown"));
-                     } catch (ClassCastException classcastexception) {
-@@ -205,7 +318,7 @@
-     }
- 
-     private static <T extends PacketListener> void genericsFtw(Packet<T> packet, PacketListener listener) {
--        packet.handle(listener);
-+        packet.handle((T) listener); // CraftBukkit - decompile error
-     }
- 
-     private void validateListener(ProtocolInfo<?> state, PacketListener listener) {
-@@ -418,12 +531,26 @@
+                     } catch (RejectedExecutionException var6) {
+                         this.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
+                     } catch (ClassCastException var7) {
+@@ -385,10 +_,24 @@
          }
      }
  
@@ -222,19 +209,17 @@
 +            Connection.joinAttemptsThisTick = 0;
 +        }
 +        // Paper end - Buffer joins to world
-         PacketListener packetlistener = this.packetListener;
- 
-         if (packetlistener instanceof TickablePacketListener tickablepacketlistener) {
+         if (this.packetListener instanceof TickablePacketListener tickablePacketListener) {
 +            // Paper start - Buffer joins to world
 +            if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
 +                || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
 +                || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
-             tickablepacketlistener.tick();
+             tickablePacketListener.tick();
 +            } // Paper end - Buffer joins to world
          }
  
          if (!this.isConnected() && !this.disconnectionHandled) {
-@@ -431,7 +558,7 @@
+@@ -396,7 +_,7 @@
          }
  
          if (this.channel != null) {
@@ -243,34 +228,25 @@
          }
  
          if (this.tickCount++ % 20 == 0) {
-@@ -464,12 +591,15 @@
+@@ -432,12 +_,15 @@
      }
  
-     public void disconnect(DisconnectionDetails disconnectionInfo) {
+     public void disconnect(DisconnectionDetails disconnectionDetails) {
 +        // Spigot Start
 +        this.preparing = false;
 +        // Spigot End
          if (this.channel == null) {
-             this.delayedDisconnect = disconnectionInfo;
+             this.delayedDisconnect = disconnectionDetails;
          }
  
          if (this.isConnected()) {
 -            this.channel.close().awaitUninterruptibly();
 +            this.channel.close(); // We can't wait as this may be called from an event loop.
-             this.disconnectionDetails = disconnectionInfo;
+             this.disconnectionDetails = disconnectionDetails;
          }
- 
-@@ -537,7 +667,7 @@
      }
- 
-     public void configurePacketHandler(ChannelPipeline pipeline) {
--        pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter(this) {
-+        pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter() { // CraftBukkit - decompile error
-             public void write(ChannelHandlerContext channelhandlercontext, Object object, ChannelPromise channelpromise) throws Exception {
-                 super.write(channelhandlercontext, object, channelpromise);
-             }
-@@ -613,6 +743,14 @@
- 
+@@ -584,6 +_,13 @@
+         }
      }
  
 +    // Paper start - add proper async disconnect
@@ -280,33 +256,31 @@
 +        }
 +    }
 +    // Paper end - add proper async disconnect
-+
-     public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) {
-         if (compressionThreshold >= 0) {
-             ChannelHandler channelhandler = this.channel.pipeline().get("decompress");
-@@ -633,6 +771,7 @@
+     public void setupCompression(int threshold, boolean validateDecompressed) {
+         if (threshold >= 0) {
+             if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder compressionDecoder) {
+@@ -597,6 +_,7 @@
              } else {
-                 this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold));
+                 this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(threshold));
              }
 +            this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
          } else {
              if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
                  this.channel.pipeline().remove("decompress");
-@@ -641,6 +780,7 @@
+@@ -605,6 +_,7 @@
              if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) {
                  this.channel.pipeline().remove("compress");
              }
 +            this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners
          }
- 
      }
-@@ -661,6 +801,27 @@
  
-                     packetlistener1.onDisconnect(disconnectiondetails);
+@@ -622,6 +_,26 @@
+                     );
+                     packetListener1.onDisconnect(disconnectionDetails);
                  }
 +                this.pendingActions.clear(); // Free up packet queue.
 +                // Paper start - Add PlayerConnectionCloseEvent
-+                final PacketListener packetListener = this.getPacketListener();
 +                if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
 +                    /* Player was logged in, either game listener or configuration listener */
 +                    final com.mojang.authlib.GameProfile profile = commonPacketListener.getOwner();
@@ -325,6 +299,6 @@
 +                    }
 +                }
 +                // Paper end - Add PlayerConnectionCloseEvent
- 
              }
          }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch b/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch
new file mode 100644
index 0000000000..f06b6bc4a6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/FriendlyByteBuf.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/network/FriendlyByteBuf.java
++++ b/net/minecraft/network/FriendlyByteBuf.java
+@@ -70,6 +_,7 @@
+ public class FriendlyByteBuf extends ByteBuf {
+     public static final int DEFAULT_NBT_QUOTA = 2097152;
+     private final ByteBuf source;
++    @Nullable public final java.util.Locale adventure$locale; // Paper - track player's locale for server-side translations
+     public static final short MAX_STRING_LENGTH = 32767;
+     public static final int MAX_COMPONENT_STRING_LENGTH = 262144;
+     private static final int PUBLIC_KEY_SIZE = 256;
+@@ -78,6 +_,7 @@
+     private static final Gson GSON = new Gson();
+ 
+     public FriendlyByteBuf(ByteBuf source) {
++        this.adventure$locale = PacketEncoder.ADVENTURE_LOCALE.get(); // Paper - track player's locale for server-side translations
+         this.source = source;
+     }
+ 
+@@ -106,8 +_,13 @@
+     }
+ 
+     public <T> void writeJsonWithCodec(Codec<T> codec, T value) {
++        // Paper start - Adventure; add max length parameter
++        this.writeJsonWithCodec(codec, value, MAX_STRING_LENGTH);
++    }
++    public <T> void writeJsonWithCodec(Codec<T> codec, T value, int maxLength) {
++        // Paper end - Adventure; add max length parameter
+         DataResult<JsonElement> dataResult = codec.encodeStart(JsonOps.INSTANCE, value);
+-        this.writeUtf(GSON.toJson(dataResult.getOrThrow(exception -> new EncoderException("Failed to encode: " + exception + " " + value))));
++        this.writeUtf(GSON.toJson(dataResult.getOrThrow(exception -> new EncoderException("Failed to encode: " + exception + " " + value))), maxLength); // Paper - Adventure; add max length parameter
+     }
+ 
+     public static <T> IntFunction<T> limitValue(IntFunction<T> function, int limit) {
+@@ -527,7 +_,7 @@
+ 
+         try {
+             NbtIo.writeAnyTag(nbt, new ByteBufOutputStream(buffer));
+-        } catch (IOException var3) {
++        } catch (Exception var3) { // CraftBukkit - IOException -> Exception
+             throw new EncoderException(var3);
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/network/PacketEncoder.java.patch b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/network/PacketEncoder.java.patch
rename to paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
index ce1cafa04d..814472ca6a 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/PacketEncoder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
@@ -1,10 +1,11 @@
 --- a/net/minecraft/network/PacketEncoder.java
 +++ b/net/minecraft/network/PacketEncoder.java
-@@ -17,10 +17,12 @@
-         this.protocolInfo = state;
+@@ -17,11 +_,13 @@
+         this.protocolInfo = protocolInfo;
      }
  
 +    static final ThreadLocal<java.util.Locale> ADVENTURE_LOCALE = ThreadLocal.withInitial(() -> null); // Paper - adventure; set player's locale
+     @Override
      protected void encode(ChannelHandlerContext channelHandlerContext, Packet<T> packet, ByteBuf byteBuf) throws Exception {
          PacketType<? extends Packet<? super T>> packetType = packet.type();
  
@@ -13,7 +14,7 @@
              this.protocolInfo.codec().encode(byteBuf, packet);
              int i = byteBuf.readableBytes();
              if (LOGGER.isDebugEnabled()) {
-@@ -31,14 +33,40 @@
+@@ -32,14 +_,40 @@
  
              JvmProfiler.INSTANCE.onPacketSent(this.protocolInfo.id(), packetType, channelHandlerContext.channel().remoteAddress(), i);
          } catch (Throwable var9) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/VarInt.java.patch b/paper-server/patches/sources/net/minecraft/network/VarInt.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/network/VarInt.java.patch
rename to paper-server/patches/sources/net/minecraft/network/VarInt.java.patch
index e2a07664c5..0a23124969 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/VarInt.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/VarInt.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/network/VarInt.java
 +++ b/net/minecraft/network/VarInt.java
-@@ -9,6 +9,18 @@
+@@ -9,6 +_,18 @@
      private static final int DATA_BITS_PER_BYTE = 7;
  
-     public static int getByteSize(int i) {
+     public static int getByteSize(int data) {
 +    // Paper start - Optimize VarInts
-+        return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(i)];
++        return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(data)];
 +    }
 +    private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
 +    static {
@@ -14,30 +14,30 @@
 +        }
 +        VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0.
 +    }
-+    public static int getByteSizeOld(int i) {
++    public static int getByteSizeOld(int data) {
 +    // Paper end - Optimize VarInts
-         for (int j = 1; j < 5; j++) {
-             if ((i & -1 << j * 7) == 0) {
-                 return j;
-@@ -39,6 +51,21 @@
+         for (int i = 1; i < 5; i++) {
+             if ((data & -1 << i * 7) == 0) {
+                 return i;
+@@ -39,6 +_,21 @@
      }
  
-     public static ByteBuf write(ByteBuf buf, int i) {
+     public static ByteBuf write(ByteBuf buffer, int value) {
 +     // Paper start - Optimize VarInts
 +        // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
 +        // that the proxy will write, to improve inlining.
-+        if ((i & (0xFFFFFFFF << 7)) == 0) {
-+            buf.writeByte(i);
-+        } else if ((i & (0xFFFFFFFF << 14)) == 0) {
-+            int w = (i & 0x7F | 0x80) << 8 | (i >>> 7);
-+            buf.writeShort(w);
++        if ((value & (0xFFFFFFFF << 7)) == 0) {
++            buffer.writeByte(value);
++        } else if ((value & (0xFFFFFFFF << 14)) == 0) {
++            int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
++            buffer.writeShort(w);
 +        } else {
-+            writeOld(buf, i);
++            writeOld(buffer, value);
 +        }
-+        return buf;
++        return buffer;
 +    }
-+    public static ByteBuf writeOld(ByteBuf buf, int i) {
++    public static ByteBuf writeOld(ByteBuf buffer, int value) {
 +    // Paper end - Optimize VarInts
-         while ((i & -128) != 0) {
-             buf.writeByte(i & 127 | 128);
-             i >>>= 7;
+         while ((value & -128) != 0) {
+             buffer.writeByte(value & 127 | 128);
+             value >>>= 7;
diff --git a/paper-server/patches/unapplied/net/minecraft/network/Varint21FrameDecoder.java.patch b/paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/network/Varint21FrameDecoder.java.patch
rename to paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch
index e9aad632b1..5a1cea5c51 100644
--- a/paper-server/patches/unapplied/net/minecraft/network/Varint21FrameDecoder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/Varint21FrameDecoder.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/network/Varint21FrameDecoder.java
 +++ b/net/minecraft/network/Varint21FrameDecoder.java
-@@ -39,6 +39,12 @@
-     }
+@@ -41,6 +_,12 @@
  
-     protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
+     @Override
+     protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) {
 +        // Paper start - Perf: Optimize exception handling; if channel is not active just discard the packet
-+        if (!channelHandlerContext.channel().isActive()) {
-+            byteBuf.skipBytes(byteBuf.readableBytes());
++        if (!context.channel().isActive()) {
++            in.skipBytes(in.readableBytes());
 +            return;
 +        }
 +        // Paper end - Perf: Optimize exception handling
-         byteBuf.markReaderIndex();
+         in.markReaderIndex();
          this.helperBuf.clear();
-         if (!copyVarint(byteBuf, this.helperBuf)) {
+         if (!copyVarint(in, this.helperBuf)) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/FriendlyByteBuf.java.patch b/paper-server/patches/unapplied/net/minecraft/network/FriendlyByteBuf.java.patch
deleted file mode 100644
index c9d512f8d6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/FriendlyByteBuf.java.patch
+++ /dev/null
@@ -1,105 +0,0 @@
---- a/net/minecraft/network/FriendlyByteBuf.java
-+++ b/net/minecraft/network/FriendlyByteBuf.java
-@@ -72,6 +72,7 @@
- 
-     public static final int DEFAULT_NBT_QUOTA = 2097152;
-     private final ByteBuf source;
-+    @Nullable public final java.util.Locale adventure$locale; // Paper - track player's locale for server-side translations
-     public static final short MAX_STRING_LENGTH = Short.MAX_VALUE;
-     public static final int MAX_COMPONENT_STRING_LENGTH = 262144;
-     private static final int PUBLIC_KEY_SIZE = 256;
-@@ -80,6 +81,7 @@
-     private static final Gson GSON = new Gson();
- 
-     public FriendlyByteBuf(ByteBuf parent) {
-+        this.adventure$locale = PacketEncoder.ADVENTURE_LOCALE.get(); // Paper - track player's locale for server-side translations
-         this.source = parent;
-     }
- 
-@@ -120,11 +122,16 @@
-     }
- 
-     public <T> void writeJsonWithCodec(Codec<T> codec, T value) {
-+        // Paper start - Adventure; add max length parameter
-+        this.writeJsonWithCodec(codec, value, MAX_STRING_LENGTH);
-+    }
-+    public <T> void writeJsonWithCodec(Codec<T> codec, T value, int maxLength) {
-+        // Paper end - Adventure; add max length parameter
-         DataResult<JsonElement> dataresult = codec.encodeStart(JsonOps.INSTANCE, value);
- 
-         this.writeUtf(FriendlyByteBuf.GSON.toJson((JsonElement) dataresult.getOrThrow((s) -> {
-             return new EncoderException("Failed to encode: " + s + " " + String.valueOf(value));
--        })));
-+        })), maxLength); // Paper - Adventure; add max length parameter
-     }
- 
-     public static <T> IntFunction<T> limitValue(IntFunction<T> applier, int max) {
-@@ -139,7 +146,7 @@
- 
-     public <T, C extends Collection<T>> C readCollection(IntFunction<C> collectionFactory, StreamDecoder<? super FriendlyByteBuf, T> reader) {
-         int i = this.readVarInt();
--        C c0 = (Collection) collectionFactory.apply(i);
-+        C c0 = collectionFactory.apply(i); // CraftBukkit - decompile error
- 
-         for (int j = 0; j < i; ++j) {
-             c0.add(reader.decode(this));
-@@ -150,7 +157,7 @@
- 
-     public <T> void writeCollection(Collection<T> collection, StreamEncoder<? super FriendlyByteBuf, T> writer) {
-         this.writeVarInt(collection.size());
--        Iterator iterator = collection.iterator();
-+        Iterator<T> iterator = collection.iterator(); // CraftBukkit - decompile error
- 
-         while (iterator.hasNext()) {
-             T t0 = iterator.next();
-@@ -177,12 +184,12 @@
- 
-     public void writeIntIdList(IntList list) {
-         this.writeVarInt(list.size());
--        list.forEach(this::writeVarInt);
-+        list.forEach((java.util.function.IntConsumer) this::writeVarInt); // CraftBukkit - decompile error
-     }
- 
-     public <K, V, M extends Map<K, V>> M readMap(IntFunction<M> mapFactory, StreamDecoder<? super FriendlyByteBuf, K> keyReader, StreamDecoder<? super FriendlyByteBuf, V> valueReader) {
-         int i = this.readVarInt();
--        M m0 = (Map) mapFactory.apply(i);
-+        M m0 = mapFactory.apply(i); // CraftBukkit - decompile error
- 
-         for (int j = 0; j < i; ++j) {
-             K k0 = keyReader.decode(this);
-@@ -216,7 +223,7 @@
-     }
- 
-     public <E extends Enum<E>> void writeEnumSet(EnumSet<E> enumSet, Class<E> type) {
--        E[] ae = (Enum[]) type.getEnumConstants();
-+        E[] ae = type.getEnumConstants(); // CraftBukkit - decompile error
-         BitSet bitset = new BitSet(ae.length);
- 
-         for (int i = 0; i < ae.length; ++i) {
-@@ -227,7 +234,7 @@
-     }
- 
-     public <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> type) {
--        E[] ae = (Enum[]) type.getEnumConstants();
-+        E[] ae = type.getEnumConstants(); // CraftBukkit - decompile error
-         BitSet bitset = this.readFixedBitSet(ae.length);
-         EnumSet<E> enumset = EnumSet.noneOf(type);
- 
-@@ -498,7 +505,7 @@
-     }
- 
-     public <T extends Enum<T>> T readEnum(Class<T> enumClass) {
--        return ((Enum[]) enumClass.getEnumConstants())[this.readVarInt()];
-+        return ((T[]) enumClass.getEnumConstants())[this.readVarInt()]; // CraftBukkit - fix decompile error
-     }
- 
-     public FriendlyByteBuf writeEnum(Enum<?> instance) {
-@@ -565,7 +572,7 @@
- 
-         try {
-             NbtIo.writeAnyTag((Tag) nbt, new ByteBufOutputStream(buf));
--        } catch (IOException ioexception) {
-+        } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception
-             throw new EncoderException(ioexception);
-         }
-     }

From ce87e69d531c633eaafb12e9195a08514545a4ee Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 10:24:21 -0800
Subject: [PATCH 102/285] net.minecraft.world.level.storage.loot

---
 .../storage/loot/LootDataType.java.patch      | 20 +++++++++
 .../level/storage/loot/LootTable.java.patch   | 45 +++++++++++++++++++
 2 files changed, 65 insertions(+)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch
new file mode 100644
index 0000000000..87eb699603
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootDataType.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/storage/loot/LootDataType.java
++++ b/net/minecraft/world/level/storage/loot/LootDataType.java
+@@ -31,9 +_,14 @@
+     }
+ 
+     private static LootDataType.Validator<LootTable> createLootTableValidator() {
+-        return (context, key, value) -> value.validate(
+-            context.setContextKeySet(value.getParamSet()).enterElement("{" + key.registry() + "/" + key.location() + "}", key)
+-        );
++        // CraftBukkit start
++        return (context, key, value) -> {
++            value.validate(
++                context.setContextKeySet(value.getParamSet()).enterElement("{" + key.registry() + "/" + key.location() + "}", key)
++            );
++            value.craftLootTable = new org.bukkit.craftbukkit.CraftLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(key.location()), value);
++            // CraftBukkit end
++        };
+     }
+ 
+     @FunctionalInterface
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch
new file mode 100644
index 0000000000..21a8108d1a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/LootTable.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/level/storage/loot/LootTable.java
++++ b/net/minecraft/world/level/storage/loot/LootTable.java
+@@ -48,6 +_,7 @@
+     private final List<LootPool> pools;
+     private final List<LootItemFunction> functions;
+     private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
++    public org.bukkit.craftbukkit.CraftLootTable craftLootTable; // CraftBukkit
+ 
+     LootTable(ContextKeySet paramSet, Optional<ResourceLocation> randomSequence, List<LootPool> pools, List<LootItemFunction> functions) {
+         this.paramSet = paramSet;
+@@ -58,9 +_,10 @@
+     }
+ 
+     public static Consumer<ItemStack> createStackSplitter(ServerLevel level, Consumer<ItemStack> output) {
++        boolean skipSplitter = level != null && !level.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items
+         return itemStack -> {
+             if (itemStack.isItemEnabled(level.enabledFeatures())) {
+-                if (itemStack.getCount() < itemStack.getMaxStackSize()) {
++                if (skipSplitter || itemStack.getCount() < itemStack.getMaxStackSize()) { // Paper - preserve overstacked items
+                     output.accept(itemStack);
+                 } else {
+                     int count = itemStack.getCount();
+@@ -141,9 +_,22 @@
+     }
+ 
+     public void fill(Container container, LootParams params, long seed) {
++        // CraftBukkit start
++        this.fillInventory(container, params, seed, false);
++    }
++
++    public void fillInventory(Container container, LootParams params, long seed, boolean plugin) {
++        // CraftBukkit end
+         LootContext lootContext = new LootContext.Builder(params).withOptionalRandomSeed(seed).create(this.randomSequence);
+         ObjectArrayList<ItemStack> randomItems = this.getRandomItems(lootContext);
+         RandomSource random = lootContext.getRandom();
++        // CraftBukkit start
++        org.bukkit.event.world.LootGenerateEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLootGenerateEvent(container, this, lootContext, randomItems, plugin);
++        if (event.isCancelled()) {
++            return;
++        }
++        randomItems = event.getLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).collect(ObjectArrayList.toList());
++        // CraftBukkit end
+         List<Integer> availableSlots = this.getAvailableSlots(container, random);
+         this.shuffleAndSplitItems(randomItems, availableSlots.size(), random);
+ 

From ca35cc216efcfaf3421f061fd06f0b7b4e55e8ec Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 10:35:04 -0800
Subject: [PATCH 103/285] 
 net.minecraft.world.level.levelgen.structure.structures

---
 build.gradle.kts                              |  2 +-
 .../DesertPyramidStructure.java.patch         | 16 ++---
 .../structures/EndCityPieces.java.patch       | 14 ++++
 .../structures/IglooPieces.java.patch         | 29 ++++++++
 .../structures/MineshaftPieces.java.patch     | 28 ++++++++
 .../NetherFortressPieces.java.patch           | 20 ++++++
 .../structures/OceanRuinPieces.java.patch     | 40 +++++++++++
 .../structures/ShipwreckPieces.java.patch     | 14 ++++
 .../structures/StrongholdPieces.java.patch    | 28 ++++++++
 .../structures/SwampHutPiece.java.patch       | 20 ++++++
 .../structures/EndCityPieces.java.patch       | 16 -----
 .../structures/IglooPieces.java.patch         | 31 ---------
 .../structures/MineshaftPieces.java.patch     | 68 -------------------
 .../NetherFortressPieces.java.patch           | 45 ------------
 .../structures/OceanRuinPieces.java.patch     | 37 ----------
 .../structures/ShipwreckPieces.java.patch     | 16 -----
 .../structures/StrongholdPieces.java.patch    | 55 ---------------
 .../structures/SwampHutPiece.java.patch       | 20 ------
 18 files changed, 202 insertions(+), 297 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch (52%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch

diff --git a/build.gradle.kts b/build.gradle.kts
index dd83603682..2360d32e7a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -146,7 +146,7 @@ tasks.register("checkWork") {
         return Path.of(path.replaceFirst("^~".toRegex(), System.getProperty("user.home")))
     }
 
-    val input = layout.cache.resolve("last-updating-folder").readText()
+    val input = layout.cache.resolve("last-updating-folder").readText().trim()
     val patchFolder = layout.projectDirectory.file("paper-server/patches/sources").convertToPath().resolve(input)
     val sourceFolder = layout.projectDirectory.file("paper-server/src/vanilla/java/").convertToPath().resolve(input)
     val targetFolder = expandUserHome(
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
index 84696b809f..73853cb81e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
@@ -1,18 +1,18 @@
 --- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
 +++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
-@@ -68,6 +68,15 @@
+@@ -65,6 +_,15 @@
  
-     private static void placeSuspiciousSand(BoundingBox box, WorldGenLevel world, BlockPos pos) {
-         if (box.isInside(pos)) {
+     private static void placeSuspiciousSand(BoundingBox boundingBox, WorldGenLevel worldGenLevel, BlockPos pos) {
+         if (boundingBox.isInside(pos)) {
 +            // CraftBukkit start
-+            if (world instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
-+                org.bukkit.craftbukkit.block.CraftBrushableBlock brushableState = (org.bukkit.craftbukkit.block.CraftBrushableBlock) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), null);
++            if (worldGenLevel instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++                org.bukkit.craftbukkit.block.CraftBrushableBlock brushableState = (org.bukkit.craftbukkit.block.CraftBrushableBlock) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldGenLevel, pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), null);
 +                brushableState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY));
 +                brushableState.setSeed(pos.asLong());
 +                transformerAccess.setCraftBlock(pos, brushableState, 2);
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), 2);
-             world.getBlockEntity(pos, BlockEntityType.BRUSHABLE_BLOCK).ifPresent((brushableblockentity) -> {
-                 brushableblockentity.setLootTable(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY, pos.asLong());
+             worldGenLevel.setBlock(pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), 2);
+             worldGenLevel.getBlockEntity(pos, BlockEntityType.BRUSHABLE_BLOCK)
+                 .ifPresent(brushableBlockEntity -> brushableBlockEntity.setLootTable(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY, pos.asLong()));
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
new file mode 100644
index 0000000000..a024b0bfeb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
+@@ -390,7 +_,10 @@
+             if (name.startsWith("Chest")) {
+                 BlockPos blockPos = pos.below();
+                 if (box.isInside(blockPos)) {
+-                    RandomizableContainer.setBlockEntityLootTable(level, random, blockPos, BuiltInLootTables.END_CITY_TREASURE);
++                    // CraftBukkit start - ensure block transformation
++                    // RandomizableContainer.setBlockEntityLootTable(level, random, blockPos, BuiltInLootTables.END_CITY_TREASURE);
++                    this.setCraftLootTable(level, blockPos, random, BuiltInLootTables.END_CITY_TREASURE);
++                    // CraftBukkit end
+                 }
+             } else if (box.isInside(pos) && Level.isInSpawnableBounds(pos)) {
+                 if (name.startsWith("Sentry")) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
new file mode 100644
index 0000000000..b94822ff02
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
+@@ -13,8 +_,6 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.Mirror;
+ import net.minecraft.world.level.block.Rotation;
+-import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.block.entity.ChestBlockEntity;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.chunk.ChunkGenerator;
+ import net.minecraft.world.level.levelgen.Heightmap;
+@@ -102,10 +_,13 @@
+         protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+             if ("chest".equals(name)) {
+                 level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
+-                BlockEntity blockEntity = level.getBlockEntity(pos.below());
+-                if (blockEntity instanceof ChestBlockEntity) {
+-                    ((ChestBlockEntity)blockEntity).setLootTable(BuiltInLootTables.IGLOO_CHEST, random.nextLong());
+-                }
++                // CraftBukkit start - ensure block transformation
++                // BlockEntity blockEntity = level.getBlockEntity(pos.below());
++                // if (blockEntity instanceof ChestBlockEntity) {
++                //     ((ChestBlockEntity)blockEntity).setLootTable(BuiltInLootTables.IGLOO_CHEST, random.nextLong());
++                // }
++                this.setCraftLootTable(level, pos.below(), random, BuiltInLootTables.IGLOO_CHEST);
++                // CraftBukkit end
+             }
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
new file mode 100644
index 0000000000..50b84588ce
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
+@@ -8,6 +_,7 @@
+ import net.minecraft.core.Direction;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.nbt.NbtOps;
++import net.minecraft.nbt.Tag;
+ import net.minecraft.resources.ResourceKey;
+ import net.minecraft.tags.BiomeTags;
+ import net.minecraft.util.RandomSource;
+@@ -401,10 +_,13 @@
+                         BlockPos worldPos = this.getWorldPos(1, 0, i8);
+                         if (box.isInside(worldPos) && this.isInterior(level, 1, 0, i8, box)) {
+                             this.hasPlacedSpider = true;
+-                            level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
+-                            if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
+-                                spawnerBlockEntity.setEntityId(EntityType.CAVE_SPIDER, random);
+-                            }
++                            // CraftBukkit start
++                            // level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
++                            // if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
++                            //     spawnerBlockEntity.setEntityId(EntityType.CAVE_SPIDER, random);
++                            // }
++                            this.placeCraftSpawner(level, worldPos, org.bukkit.entity.EntityType.CAVE_SPIDER, 2);
++                            // CraftBukkit end
+                         }
+                     }
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
new file mode 100644
index 0000000000..bfd5ae0532
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
+@@ -1265,10 +_,13 @@
+                 BlockPos worldPos = this.getWorldPos(3, 5, 5);
+                 if (box.isInside(worldPos)) {
+                     this.hasPlacedSpawner = true;
+-                    level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
+-                    if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
+-                        spawnerBlockEntity.setEntityId(EntityType.BLAZE, random);
+-                    }
++                    // CraftBukkit start
++                    // level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
++                    // if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
++                    //     spawnerBlockEntity.setEntityId(EntityType.BLAZE, random);
++                    // }
++                    this.placeCraftSpawner(level, worldPos, org.bukkit.entity.EntityType.BLAZE, 2);
++                    // CraftBukkit end
+                 }
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
new file mode 100644
index 0000000000..7153875623
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
+@@ -25,8 +_,6 @@
+ import net.minecraft.world.level.block.ChestBlock;
+ import net.minecraft.world.level.block.Mirror;
+ import net.minecraft.world.level.block.Rotation;
+-import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.block.entity.ChestBlockEntity;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.chunk.ChunkGenerator;
+ import net.minecraft.world.level.levelgen.Heightmap;
+@@ -314,14 +_,20 @@
+         @Override
+         protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+             if ("chest".equals(name)) {
+-                level.setBlock(
+-                    pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, Boolean.valueOf(level.getFluidState(pos).is(FluidTags.WATER))), 2
+-                );
+-                BlockEntity blockEntity = level.getBlockEntity(pos);
+-                if (blockEntity instanceof ChestBlockEntity) {
+-                    ((ChestBlockEntity)blockEntity)
+-                        .setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, random.nextLong());
+-                }
++                // CraftBukkit start - transform block to ensure loot table is accessible
++                // level.setBlock(
++                //     pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, Boolean.valueOf(level.getFluidState(pos).is(FluidTags.WATER))), 2
++                // );
++                // BlockEntity blockEntity = level.getBlockEntity(pos);
++                // if (blockEntity instanceof ChestBlockEntity) {
++                //     ((ChestBlockEntity)blockEntity)
++                //         .setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, random.nextLong());
++                // }
++                org.bukkit.craftbukkit.block.CraftChest craftChest = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(level, pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, level.getFluidState(pos).is(FluidTags.WATER)), null);
++                craftChest.setSeed(random.nextLong());
++                craftChest.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL));
++                this.placeCraftBlockEntity(level, pos, craftChest, 2);
++                // CraftBukkit end
+             } else if ("drowned".equals(name)) {
+                 Drowned drowned = EntityType.DROWNED.create(level.getLevel(), EntitySpawnReason.STRUCTURE);
+                 if (drowned != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
new file mode 100644
index 0000000000..08c3ca9aad
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java
+@@ -121,7 +_,10 @@
+         protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+             ResourceKey<LootTable> resourceKey = ShipwreckPieces.MARKERS_TO_LOOT.get(name);
+             if (resourceKey != null) {
+-                RandomizableContainer.setBlockEntityLootTable(level, random, pos.below(), resourceKey);
++                // CraftBukkit start
++                // RandomizableContainer.setBlockEntityLootTable(level, random, pos.below(), resourceKey);
++                this.setCraftLootTable(level, pos.below(), random, resourceKey);
++                // CraftBukkit end
+             }
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
new file mode 100644
index 0000000000..1821e06e5d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
+@@ -7,7 +_,6 @@
+ import net.minecraft.core.Direction;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.util.RandomSource;
+-import net.minecraft.world.entity.EntityType;
+ import net.minecraft.world.level.ChunkPos;
+ import net.minecraft.world.level.StructureManager;
+ import net.minecraft.world.level.WorldGenLevel;
+@@ -870,10 +_,13 @@
+                 BlockPos worldPos = this.getWorldPos(5, 3, 6);
+                 if (box.isInside(worldPos)) {
+                     this.hasPlacedSpawner = true;
+-                    level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
+-                    if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
+-                        spawnerBlockEntity.setEntityId(EntityType.SILVERFISH, random);
+-                    }
++                    // CraftBukkit start
++                    // level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
++                    // if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
++                    //     spawnerBlockEntity.setEntityId(EntityType.SILVERFISH, random);
++                    // }
++                    this.placeCraftSpawner(level, worldPos, org.bukkit.entity.EntityType.SILVERFISH, 2);
++                    // CraftBukkit end
+                 }
+             }
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
new file mode 100644
index 0000000000..fe1c847fa4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
+@@ -97,7 +_,7 @@
+                         witch.setPersistenceRequired();
+                         witch.moveTo(worldPos.getX() + 0.5, worldPos.getY(), worldPos.getZ() + 0.5, 0.0F, 0.0F);
+                         witch.finalizeSpawn(level, level.getCurrentDifficultyAt(worldPos), EntitySpawnReason.STRUCTURE, null);
+-                        level.addFreshEntityWithPassengers(witch);
++                        level.addFreshEntityWithPassengers(witch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+                     }
+                 }
+             }
+@@ -116,7 +_,7 @@
+                     cat.setPersistenceRequired();
+                     cat.moveTo(worldPos.getX() + 0.5, worldPos.getY(), worldPos.getZ() + 0.5, 0.0F, 0.0F);
+                     cat.finalizeSpawn(level, level.getCurrentDifficultyAt(worldPos), EntitySpawnReason.STRUCTURE, null);
+-                    level.addFreshEntityWithPassengers(cat);
++                    level.addFreshEntityWithPassengers(cat, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+                 }
+             }
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
deleted file mode 100644
index 0ec21beb4f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
-@@ -285,7 +285,12 @@
-                 BlockPos blockposition1 = pos.below();
- 
-                 if (boundingBox.isInside(blockposition1)) {
--                    RandomizableContainer.setBlockEntityLootTable(world, random, blockposition1, BuiltInLootTables.END_CITY_TREASURE);
-+                    // CraftBukkit start - ensure block transformation
-+                    /*
-+                    RandomizableContainer.setBlockEntityLootTable(worldaccess, randomsource, blockposition1, LootTables.END_CITY_TREASURE);
-+                    */
-+                    this.setCraftLootTable(world, blockposition1, random, BuiltInLootTables.END_CITY_TREASURE);
-+                    // CraftBukkit end
-                 }
-             } else if (boundingBox.isInside(pos) && Level.isInSpawnableBounds(pos)) {
-                 if (metadata.startsWith("Sentry")) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
deleted file mode 100644
index 5dc0652fc4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
-@@ -14,8 +14,6 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
-@@ -86,11 +84,16 @@
-         protected void handleDataMarker(String metadata, BlockPos pos, ServerLevelAccessor world, RandomSource random, BoundingBox boundingBox) {
-             if ("chest".equals(metadata)) {
-                 world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
--                BlockEntity tileentity = world.getBlockEntity(pos.below());
-+                // CraftBukkit start - ensure block transformation
-+                /*
-+                TileEntity tileentity = worldaccess.getBlockEntity(blockposition.below());
- 
--                if (tileentity instanceof ChestBlockEntity) {
--                    ((ChestBlockEntity) tileentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, random.nextLong());
-+                if (tileentity instanceof TileEntityChest) {
-+                    ((TileEntityChest) tileentity).setLootTable(LootTables.IGLOO_CHEST, randomsource.nextLong());
-                 }
-+                */
-+                this.setCraftLootTable(world, pos.below(), random, BuiltInLootTables.IGLOO_CHEST);
-+                // CraftBukkit end
- 
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
deleted file mode 100644
index 32925fee48..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
+++ /dev/null
@@ -1,68 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
-@@ -12,6 +12,7 @@
- import net.minecraft.core.Direction;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.tags.BiomeTags;
- import net.minecraft.util.RandomSource;
-@@ -30,8 +31,6 @@
- import net.minecraft.world.level.block.FenceBlock;
- import net.minecraft.world.level.block.RailBlock;
- import net.minecraft.world.level.block.WallTorchBlock;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.level.chunk.ChunkGenerator;
-@@ -520,14 +519,19 @@
- 
-                         if (chunkBox.isInside(blockposition_mutableblockposition) && this.isInterior(world, 1, 0, l, chunkBox)) {
-                             this.hasPlacedSpider = true;
--                            world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
--                            BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition);
--
--                            if (tileentity instanceof SpawnerBlockEntity) {
--                                SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity;
-+                            // CraftBukkit start
-+                            /*
-+                            generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
-+                            TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
- 
--                                tileentitymobspawner.setEntityId(EntityType.CAVE_SPIDER, random);
-+                            if (tileentity instanceof TileEntityMobSpawner) {
-+                                TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
-+
-+                                tileentitymobspawner.setEntityId(EntityTypes.CAVE_SPIDER, randomsource);
-                             }
-+                            */
-+                            this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.CAVE_SPIDER, 2);
-+                            // CraftBukkit end
-                         }
-                     }
-                 }
-@@ -819,11 +823,11 @@
- 
-         public MineShaftRoom(CompoundTag nbt) {
-             super(StructurePieceType.MINE_SHAFT_ROOM, nbt);
--            DataResult dataresult = BoundingBox.CODEC.listOf().parse(NbtOps.INSTANCE, nbt.getList("Entrances", 11));
-+            DataResult<List<BoundingBox>> dataresult = BoundingBox.CODEC.listOf().parse(NbtOps.INSTANCE, nbt.getList("Entrances", 11)); // CraftBukkit - decompile error
-             Logger logger = MineshaftPieces.LOGGER;
- 
-             Objects.requireNonNull(logger);
--            Optional optional = dataresult.resultOrPartial(logger::error);
-+            Optional<List<BoundingBox>> optional = dataresult.resultOrPartial(logger::error); // CraftBukkit - decompile error
-             List list = this.childEntranceBoxes;
- 
-             Objects.requireNonNull(this.childEntranceBoxes);
-@@ -929,7 +933,7 @@
-         @Override
-         protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag nbt) {
-             super.addAdditionalSaveData(context, nbt);
--            DataResult dataresult = BoundingBox.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.childEntranceBoxes);
-+            DataResult<Tag> dataresult = BoundingBox.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.childEntranceBoxes); // CraftBukkit - decompile error
-             Logger logger = MineshaftPieces.LOGGER;
- 
-             Objects.requireNonNull(logger);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
deleted file mode 100644
index 23051dcaf5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
+++ /dev/null
@@ -1,45 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
-@@ -8,15 +8,12 @@
- import net.minecraft.core.Direction;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.util.RandomSource;
--import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.StructureManager;
- import net.minecraft.world.level.WorldGenLevel;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.FenceBlock;
- import net.minecraft.world.level.block.StairBlock;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.structure.BoundingBox;
-@@ -428,14 +425,19 @@
- 
-                 if (chunkBox.isInside(blockposition_mutableblockposition)) {
-                     this.hasPlacedSpawner = true;
--                    world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
--                    BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition);
--
--                    if (tileentity instanceof SpawnerBlockEntity) {
--                        SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity;
--
--                        tileentitymobspawner.setEntityId(EntityType.BLAZE, random);
-+                    // CraftBukkit start
-+                    /*
-+                    generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
-+                    TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
-+
-+                    if (tileentity instanceof TileEntityMobSpawner) {
-+                        TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
-+
-+                        tileentitymobspawner.setEntityId(EntityTypes.BLAZE, randomsource);
-                     }
-+                    */
-+                    this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.BLAZE, 2);
-+                    // CraftBukkit end
-                 }
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
deleted file mode 100644
index 7e1e399a8b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
-@@ -27,8 +27,6 @@
- import net.minecraft.world.level.block.ChestBlock;
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
-@@ -200,12 +198,20 @@
-         @Override
-         protected void handleDataMarker(String metadata, BlockPos pos, ServerLevelAccessor world, RandomSource random, BoundingBox boundingBox) {
-             if ("chest".equals(metadata)) {
--                world.setBlock(pos, (BlockState) Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, world.getFluidState(pos).is(FluidTags.WATER)), 2);
--                BlockEntity tileentity = world.getBlockEntity(pos);
--
--                if (tileentity instanceof ChestBlockEntity) {
--                    ((ChestBlockEntity) tileentity).setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, random.nextLong());
-+                // CraftBukkit start - transform block to ensure loot table is accessible
-+                /*
-+                worldaccess.setBlock(blockposition, (IBlockData) Blocks.CHEST.defaultBlockState().setValue(BlockChest.WATERLOGGED, worldaccess.getFluidState(blockposition).is(TagsFluid.WATER)), 2);
-+                TileEntity tileentity = worldaccess.getBlockEntity(blockposition);
-+
-+                if (tileentity instanceof TileEntityChest) {
-+                    ((TileEntityChest) tileentity).setLootTable(this.isLarge ? LootTables.UNDERWATER_RUIN_BIG : LootTables.UNDERWATER_RUIN_SMALL, randomsource.nextLong());
-                 }
-+                */
-+                org.bukkit.craftbukkit.block.CraftChest craftChest = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, world.getFluidState(pos).is(FluidTags.WATER)), null);
-+                craftChest.setSeed(random.nextLong());
-+                craftChest.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL));
-+                this.placeCraftBlockEntity(world, pos, craftChest, 2);
-+                // CraftBukkit end
-             } else if ("drowned".equals(metadata)) {
-                 Drowned entitydrowned = (Drowned) EntityType.DROWNED.create(world.getLevel(), EntitySpawnReason.STRUCTURE);
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
deleted file mode 100644
index d667e572c3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/ShipwreckPieces.java
-@@ -79,7 +79,12 @@
-             ResourceKey<LootTable> resourcekey = (ResourceKey) ShipwreckPieces.MARKERS_TO_LOOT.get(metadata);
- 
-             if (resourcekey != null) {
--                RandomizableContainer.setBlockEntityLootTable(world, random, pos.below(), resourcekey);
-+                // CraftBukkit start - ensure block transformation
-+                /*
-+                RandomizableContainer.setBlockEntityLootTable(worldaccess, randomsource, blockposition.below(), resourcekey);
-+                */
-+                this.setCraftLootTable(world, pos.below(), random, resourcekey);
-+                // CraftBukkit end
-             }
- 
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
deleted file mode 100644
index 90efca8745..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
-@@ -8,7 +8,6 @@
- import net.minecraft.core.Direction;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.util.RandomSource;
--import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.StructureManager;
- import net.minecraft.world.level.WorldGenLevel;
-@@ -22,8 +21,6 @@
- import net.minecraft.world.level.block.SlabBlock;
- import net.minecraft.world.level.block.StairBlock;
- import net.minecraft.world.level.block.WallTorchBlock;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
- import net.minecraft.world.level.block.state.properties.SlabType;
-@@ -53,7 +50,7 @@
-                 public boolean doPlace(int chainLength) {
-                     return super.doPlace(chainLength) && chainLength > 5;
-                 }
--            }};
-+            } }; // CraftBukkit - fix decompile styling
-     private static List<StrongholdPieces.PieceWeight> currentPieces;
-     static Class<? extends StrongholdPieces.StrongholdPiece> imposedPiece;
-     private static int totalWeight;
-@@ -1136,14 +1133,19 @@
- 
-                 if (chunkBox.isInside(blockposition_mutableblockposition)) {
-                     this.hasPlacedSpawner = true;
--                    world.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
--                    BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition);
--
--                    if (tileentity instanceof SpawnerBlockEntity) {
--                        SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity;
--
--                        tileentitymobspawner.setEntityId(EntityType.SILVERFISH, random);
-+                    // CraftBukkit start
-+                    /*
-+                    generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
-+                    TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
-+
-+                    if (tileentity instanceof TileEntityMobSpawner) {
-+                        TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
-+
-+                        tileentitymobspawner.setEntityId(EntityTypes.SILVERFISH, randomsource);
-                     }
-+                    */
-+                    this.placeCraftSpawner(world, blockposition_mutableblockposition, org.bukkit.entity.EntityType.SILVERFISH, 2);
-+                    // CraftBukkit end
-                 }
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
deleted file mode 100644
index ad3c0e1641..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
-+++ b/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
-@@ -100,7 +100,7 @@
-                         entitywitch.setPersistenceRequired();
-                         entitywitch.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F);
-                         entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(blockposition_mutableblockposition), EntitySpawnReason.STRUCTURE, (SpawnGroupData) null);
--                        world.addFreshEntityWithPassengers(entitywitch);
-+                        world.addFreshEntityWithPassengers(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
-                     }
-                 }
-             }
-@@ -121,7 +121,7 @@
-                     entitycat.setPersistenceRequired();
-                     entitycat.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F);
-                     entitycat.finalizeSpawn(world, world.getCurrentDifficultyAt(blockposition_mutableblockposition), EntitySpawnReason.STRUCTURE, (SpawnGroupData) null);
--                    world.addFreshEntityWithPassengers(entitycat);
-+                    world.addFreshEntityWithPassengers(entitycat, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
-                 }
-             }
-         }

From 7b75c1b42e55c2a32ec354ed7622344b0d08b56e Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 10:48:01 -0800
Subject: [PATCH 104/285] net.minecraft.world.entity.boss.wither

---
 .../entity/boss/wither/WitherBoss.java.patch  | 123 +++++++++++++
 .../entity/boss/wither/WitherBoss.java.patch  | 165 ------------------
 2 files changed, 123 insertions(+), 165 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
new file mode 100644
index 0000000000..b4eaea2841
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -0,0 +1,123 @@
+--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -77,6 +_,12 @@
+         && entity.attackable();
+     private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR);
+ 
++    // Paper start
++    private boolean canPortal = false;
++
++    public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
++    // Paper end
++
+     public WitherBoss(EntityType<? extends WitherBoss> entityType, Level level) {
+         super(entityType, level);
+         this.moveControl = new FlyingMoveControl(this, 10, false);
+@@ -260,15 +_,40 @@
+             int i = this.getInvulnerableTicks() - 1;
+             this.bossEvent.setProgress(1.0F - i / 220.0F);
+             if (i <= 0) {
+-                level.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, Level.ExplosionInteraction.MOB);
++                // CraftBukkit start
++                org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false);
++                level.getCraftServer().getPluginManager().callEvent(event);
++
++                if (!event.isCancelled()) {
++                    level.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
++                }
++                // CraftBukkit end
+                 if (!this.isSilent()) {
+-                    level.globalLevelEvent(1023, this.blockPosition(), 0);
++                    // CraftBukkit start - Use relative location for far away sounds
++                    // level.globalLevelEvent(1023, this.blockPosition(), 0);
++                    int viewDistance = level.getCraftServer().getViewDistance() * 16;
++                    for (ServerPlayer player : level.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
++                        double deltaX = this.getX() - player.getX();
++                        double deltaZ = this.getZ() - player.getZ();
++                        double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++                        final double soundRadiusSquared = level.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule
++                        if (!level.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule
++                        if (distanceSquared > viewDistance * viewDistance) {
++                            double deltaLength = Math.sqrt(distanceSquared);
++                            double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
++                            double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
++                            player.connection.send(new ClientboundLevelEventPacket(1023, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
++                        } else {
++                            player.connection.send(new ClientboundLevelEventPacket(1023, this.blockPosition(), 0, true));
++                        }
++                    }
++                    // CraftBukkit end
+                 }
+             }
+ 
+             this.setInvulnerableTicks(i);
+             if (this.tickCount % 10 == 0) {
+-                this.heal(10.0F);
++                this.heal(10.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
+             }
+         } else {
+             super.customServerAiStep(level);
+@@ -305,6 +_,7 @@
+                         );
+                         if (!nearbyEntities.isEmpty()) {
+                             LivingEntity livingEntity1 = nearbyEntities.get(this.random.nextInt(nearbyEntities.size()));
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this, livingEntity1, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit
+                             this.setAlternativeTarget(ix, livingEntity1.getId());
+                         }
+                     }
+@@ -334,6 +_,11 @@
+                     )) {
+                         BlockState blockState = level.getBlockState(blockPos);
+                         if (canDestroy(blockState)) {
++                            // CraftBukkit start
++                            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                                continue;
++                            }
++                            // CraftBukkit end
+                             flag = level.destroyBlock(blockPos, true, this) || flag;
+                         }
+                     }
+@@ -345,7 +_,7 @@
+             }
+ 
+             if (this.tickCount % 20 == 0) {
+-                this.heal(1.0F);
++                this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+             }
+ 
+             this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
+@@ -483,16 +_,16 @@
+     @Override
+     protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
+         super.dropCustomDeathLoot(level, damageSource, recentlyHit);
+-        ItemEntity itemEntity = this.spawnAtLocation(level, Items.NETHER_STAR);
++        ItemEntity itemEntity = this.spawnAtLocation(level, new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer
+         if (itemEntity != null) {
+-            itemEntity.setExtendedLifetime();
++            itemEntity.setExtendedLifetime(); // Paper - diff on change
+         }
+     }
+ 
+     @Override
+     public void checkDespawn() {
+         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
+         } else {
+             this.noActionTime = 0;
+         }
+@@ -547,12 +_,12 @@
+ 
+     @Override
+     public boolean canUsePortal(boolean allowPassengers) {
+-        return false;
++        return this.canPortal; // Paper
+     }
+ 
+     @Override
+     public boolean canBeAffected(MobEffectInstance potioneffect) {
+-        return !potioneffect.is(MobEffects.WITHER) && super.canBeAffected(potioneffect);
++        return (!potioneffect.is(MobEffects.WITHER) || !this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither) && super.canBeAffected(potioneffect);
+     }
+ 
+     class WitherDoNothingGoal extends Goal {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
deleted file mode 100644
index 67144bc046..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
+++ /dev/null
@@ -1,165 +0,0 @@
---- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
-+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
-@@ -10,14 +10,10 @@
- import net.minecraft.core.particles.ParticleTypes;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
-+import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
- import net.minecraft.network.syncher.EntityDataAccessor;
- import net.minecraft.network.syncher.EntityDataSerializers;
- import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.server.level.ServerBossEvent;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.server.level.ServerPlayer;
--import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundEvents;
- import net.minecraft.tags.BlockTags;
- import net.minecraft.tags.DamageTypeTags;
- import net.minecraft.tags.EntityTypeTags;
-@@ -54,8 +50,21 @@
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.ItemLike;
- import net.minecraft.world.level.Level;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerBossEvent;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundEvents;
-+import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRegainHealthEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.event.entity.ExplosionPrimeEvent;
-+// CraftBukkit end
- 
- public class WitherBoss extends Monster implements RangedAttackMob {
- 
-@@ -77,7 +86,12 @@
-         return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable();
-     };
-     private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR);
-+    // Paper start
-+    private boolean canPortal = false;
- 
-+    public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
-+    // Paper end
-+
-     public WitherBoss(EntityType<? extends WitherBoss> type, Level world) {
-         super(type, world);
-         this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true);
-@@ -252,15 +266,42 @@
-             i = this.getInvulnerableTicks() - 1;
-             this.bossEvent.setProgress(1.0F - (float) i / 220.0F);
-             if (i <= 0) {
--                world.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, Level.ExplosionInteraction.MOB);
-+                // CraftBukkit start
-+                // worldserver.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, World.a.MOB);
-+                ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false);
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (!event.isCancelled()) {
-+                    world.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
-+                }
-+                // CraftBukkit end
-+
-                 if (!this.isSilent()) {
--                    world.globalLevelEvent(1023, this.blockPosition(), 0);
-+                    // CraftBukkit start - Use relative location for far away sounds
-+                    // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0);
-+                    int viewDistance = world.getCraftServer().getViewDistance() * 16;
-+                    for (ServerPlayer player : world.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
-+                        double deltaX = this.getX() - player.getX();
-+                        double deltaZ = this.getZ() - player.getZ();
-+                        double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-+                        final double soundRadiusSquared = world.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule
-+                        if ( !world.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule
-+                        if (distanceSquared > viewDistance * viewDistance) {
-+                            double deltaLength = Math.sqrt(distanceSquared);
-+                            double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
-+                            double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                            player.connection.send(new ClientboundLevelEventPacket(1023, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
-+                        } else {
-+                            player.connection.send(new ClientboundLevelEventPacket(1023, this.blockPosition(), 0, true));
-+                        }
-+                    }
-+                    // CraftBukkit end
-                 }
-             }
- 
-             this.setInvulnerableTicks(i);
-             if (this.tickCount % 10 == 0) {
--                this.heal(10.0F);
-+                this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
-             }
- 
-         } else {
-@@ -305,6 +346,7 @@
-                         if (!list.isEmpty()) {
-                             LivingEntity entityliving1 = (LivingEntity) list.get(this.random.nextInt(list.size()));
- 
-+                            if (CraftEventFactory.callEntityTargetLivingEvent(this, entityliving1, EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit
-                             this.setAlternativeTarget(i, entityliving1.getId());
-                         }
-                     }
-@@ -331,6 +373,11 @@
-                         BlockState iblockdata = world.getBlockState(blockposition);
- 
-                         if (WitherBoss.canDestroy(iblockdata)) {
-+                            // CraftBukkit start
-+                            if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                                continue;
-+                            }
-+                            // CraftBukkit end
-                             flag = world.destroyBlock(blockposition, true, this) || flag;
-                         }
-                     }
-@@ -342,7 +389,7 @@
-             }
- 
-             if (this.tickCount % 20 == 0) {
--                this.heal(1.0F);
-+                this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
-             }
- 
-             this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
-@@ -488,10 +535,10 @@
-     @Override
-     protected void dropCustomDeathLoot(ServerLevel world, DamageSource source, boolean causedByPlayer) {
-         super.dropCustomDeathLoot(world, source, causedByPlayer);
--        ItemEntity entityitem = this.spawnAtLocation(world, (ItemLike) Items.NETHER_STAR);
-+        ItemEntity entityitem = this.spawnAtLocation(world, new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer
- 
-         if (entityitem != null) {
--            entityitem.setExtendedLifetime();
-+            entityitem.setExtendedLifetime(); // Paper - diff on change
-         }
- 
-     }
-@@ -499,7 +546,7 @@
-     @Override
-     public void checkDespawn() {
-         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
--            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-         } else {
-             this.noActionTime = 0;
-         }
-@@ -549,12 +596,12 @@
- 
-     @Override
-     public boolean canUsePortal(boolean allowVehicles) {
--        return false;
-+        return this.canPortal; // Paper
-     }
- 
-     @Override
-     public boolean canBeAffected(MobEffectInstance effect) {
--        return effect.is(MobEffects.WITHER) ? false : super.canBeAffected(effect);
-+        return effect.is(MobEffects.WITHER) && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects
-     }
- 
-     private class WitherDoNothingGoal extends Goal {

From 3cce21ddce70048d768748209e32913bdc932968 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 11:20:46 -0800
Subject: [PATCH 105/285] net.minecraft.world.level.portal

---
 .../level/portal/PortalForcer.java.patch      | 115 +++++++++
 .../world/level/portal/PortalShape.java.patch | 154 ++++++++++++
 .../portal/TeleportTransition.java.patch      |  80 +++++++
 .../level/portal/PortalForcer.java.patch      | 149 ------------
 .../world/level/portal/PortalShape.java.patch | 226 ------------------
 .../portal/TeleportTransition.java.patch      |  71 ------
 6 files changed, 349 insertions(+), 446 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalForcer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalShape.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/portal/TeleportTransition.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch
new file mode 100644
index 0000000000..76fbe68029
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalForcer.java.patch
@@ -0,0 +1,115 @@
+--- a/net/minecraft/world/level/portal/PortalForcer.java
++++ b/net/minecraft/world/level/portal/PortalForcer.java
+@@ -38,18 +_,32 @@
+         this.level = level;
+     }
+ 
++    @io.papermc.paper.annotation.DoNotUse // Paper
+     public Optional<BlockPos> findClosestPortalPosition(BlockPos exitPos, boolean isNether, WorldBorder worldBorder) {
++        // CraftBukkit start
++        return this.findClosestPortalPosition(exitPos, worldBorder, isNether ? 16 : 128); // Search Radius
++    }
++
++    public Optional<BlockPos> findClosestPortalPosition(BlockPos exitPos, WorldBorder worldBorder, int i) {
+         PoiManager poiManager = this.level.getPoiManager();
+-        int i = isNether ? 16 : 128;
++        // int i = isNether ? 16 : 128;
++        // CraftBukkit end
+         poiManager.ensureLoadedAndValid(this.level, exitPos, i);
+         return poiManager.getInSquare(holder -> holder.is(PoiTypes.NETHER_PORTAL), exitPos, i, PoiManager.Occupancy.ANY)
+             .map(PoiRecord::getPos)
+             .filter(worldBorder::isWithinBounds)
++            .filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) // Paper - Configurable nether ceiling damage
+             .filter(blockPos -> this.level.getBlockState(blockPos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS))
+             .min(Comparator.<BlockPos>comparingDouble(blockPos -> blockPos.distSqr(exitPos)).thenComparingInt(Vec3i::getY));
+     }
+ 
+     public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos pos, Direction.Axis axis) {
++        // CraftBukkit start
++        return this.createPortal(pos, axis, null, 16);
++    }
++
++    public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos pos, Direction.Axis axis, net.minecraft.world.entity.Entity entity, int createRadius) {
++        // CraftBukkit end
+         Direction direction = Direction.get(Direction.AxisDirection.POSITIVE, axis);
+         double d = -1.0;
+         BlockPos blockPos = null;
+@@ -57,10 +_,15 @@
+         BlockPos blockPos1 = null;
+         WorldBorder worldBorder = this.level.getWorldBorder();
+         int min = Math.min(this.level.getMaxY(), this.level.getMinY() + this.level.getLogicalHeight() - 1);
++        // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height
++        if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) {
++            min = Math.min(min, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1);
++        }
++        // Paper end - Configurable nether ceiling damage
+         int i = 1;
+         BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
+ 
+-        for (BlockPos.MutableBlockPos mutableBlockPos1 : BlockPos.spiralAround(pos, 16, Direction.EAST, Direction.SOUTH)) {
++        for (BlockPos.MutableBlockPos mutableBlockPos1 : BlockPos.spiralAround(pos, createRadius, Direction.EAST, Direction.SOUTH)) { // CraftBukkit
+             int min1 = Math.min(min, this.level.getHeight(Heightmap.Types.MOTION_BLOCKING, mutableBlockPos1.getX(), mutableBlockPos1.getZ()));
+             if (worldBorder.isWithinBounds(mutableBlockPos1) && worldBorder.isWithinBounds(mutableBlockPos1.move(direction, 1))) {
+                 mutableBlockPos1.move(direction.getOpposite(), 1);
+@@ -104,6 +_,7 @@
+             d = d1;
+         }
+ 
++        org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(this.level); // CraftBukkit - Use BlockStateListPopulator
+         if (d == -1.0) {
+             int max = Math.max(this.level.getMinY() - -1, 70);
+             int i4 = min - 9;
+@@ -122,7 +_,7 @@
+                         mutableBlockPos.setWithOffset(
+                             blockPos, i2 * direction.getStepX() + i1x * clockWise.getStepX(), i3, i2 * direction.getStepZ() + i1x * clockWise.getStepZ()
+                         );
+-                        this.level.setBlockAndUpdate(mutableBlockPos, blockState);
++                        blockList.setBlock(mutableBlockPos, blockState, 3); // CraftBukkit
+                     }
+                 }
+             }
+@@ -132,7 +_,7 @@
+             for (int i4 = -1; i4 < 4; i4++) {
+                 if (max == -1 || max == 2 || i4 == -1 || i4 == 3) {
+                     mutableBlockPos.setWithOffset(blockPos, max * direction.getStepX(), i4, max * direction.getStepZ());
+-                    this.level.setBlock(mutableBlockPos, Blocks.OBSIDIAN.defaultBlockState(), 3);
++                    blockList.setBlock(mutableBlockPos, Blocks.OBSIDIAN.defaultBlockState(), 3); // CraftBukkit
+                 }
+             }
+         }
+@@ -142,10 +_,20 @@
+         for (int i4x = 0; i4x < 2; i4x++) {
+             for (int min1 = 0; min1 < 3; min1++) {
+                 mutableBlockPos.setWithOffset(blockPos, i4x * direction.getStepX(), min1, i4x * direction.getStepZ());
+-                this.level.setBlock(mutableBlockPos, blockState1, 18);
++                blockList.setBlock(mutableBlockPos, blockState1, 18); // CraftBukkit
+             }
+         }
+ 
++        // CraftBukkit start
++        org.bukkit.World bworld = this.level.getWorld();
++        org.bukkit.event.world.PortalCreateEvent event = new org.bukkit.event.world.PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.NETHER_PAIR);
++
++        this.level.getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return Optional.empty();
++        }
++        blockList.updateList();
++        // CraftBukkit end
+         return Optional.of(new BlockUtil.FoundRectangle(blockPos.immutable(), 2, 3));
+     }
+ 
+@@ -165,6 +_,13 @@
+                     i1,
+                     direction.getStepZ() * i + clockWise.getStepZ() * offsetScale
+                 );
++                // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
++                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) {
++                    if (!this.level.getBlockState(offsetPos).isDestroyable()) {
++                        return false;
++                    }
++                }
++                // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+                 if (i1 < 0 && !this.level.getBlockState(offsetPos).isSolid()) {
+                     return false;
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch
new file mode 100644
index 0000000000..d92f339f46
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/portal/PortalShape.java.patch
@@ -0,0 +1,154 @@
+--- a/net/minecraft/world/level/portal/PortalShape.java
++++ b/net/minecraft/world/level/portal/PortalShape.java
+@@ -37,8 +_,12 @@
+     private final BlockPos bottomLeft;
+     private final int height;
+     private final int width;
++    // CraftBukkit start - add field
++    private final org.bukkit.craftbukkit.util.BlockStateListPopulator blocks;
+ 
+-    private PortalShape(Direction.Axis axis, int numPortalBlocks, Direction rightDir, BlockPos bottomLeft, int width, int height) {
++    private PortalShape(Direction.Axis axis, int numPortalBlocks, Direction rightDir, BlockPos bottomLeft, int width, int height, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) {
++        this.blocks = blocks;
++        // CraftBukkit end
+         this.axis = axis;
+         this.numPortalBlocks = numPortalBlocks;
+         this.rightDir = rightDir;
+@@ -62,24 +_,25 @@
+     }
+ 
+     public static PortalShape findAnyShape(BlockGetter level, BlockPos bottomLeft, Direction.Axis axis) {
++        org.bukkit.craftbukkit.util.BlockStateListPopulator blocks = new org.bukkit.craftbukkit.util.BlockStateListPopulator(((LevelAccessor) level).getMinecraftWorld()); // CraftBukkit
+         Direction direction = axis == Direction.Axis.X ? Direction.WEST : Direction.SOUTH;
+-        BlockPos blockPos = calculateBottomLeft(level, direction, bottomLeft);
++        BlockPos blockPos = calculateBottomLeft(level, direction, bottomLeft, blocks); // CraftBukkit
+         if (blockPos == null) {
+-            return new PortalShape(axis, 0, direction, bottomLeft, 0, 0);
++            return new PortalShape(axis, 0, direction, bottomLeft, 0, 0, blocks); // CraftBukkit
+         } else {
+-            int i = calculateWidth(level, blockPos, direction);
++            int i = calculateWidth(level, blockPos, direction, blocks); // CraftBukkit
+             if (i == 0) {
+-                return new PortalShape(axis, 0, direction, blockPos, 0, 0);
++                return new PortalShape(axis, 0, direction, blockPos, 0, 0, blocks); // CraftBukkit
+             } else {
+                 MutableInt mutableInt = new MutableInt();
+-                int i1 = calculateHeight(level, blockPos, direction, i, mutableInt);
+-                return new PortalShape(axis, mutableInt.getValue(), direction, blockPos, i, i1);
++                int i1 = calculateHeight(level, blockPos, direction, i, mutableInt, blocks); // CraftBukkit
++                return new PortalShape(axis, mutableInt.getValue(), direction, blockPos, i, i1, blocks); // CraftBukkit
+             }
+         }
+     }
+ 
+     @Nullable
+-    private static BlockPos calculateBottomLeft(BlockGetter level, Direction direction, BlockPos pos) {
++    private static BlockPos calculateBottomLeft(BlockGetter level, Direction direction, BlockPos pos, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) { // CraftBukkit
+         int max = Math.max(level.getMinY(), pos.getY() - 21);
+ 
+         while (pos.getY() > max && isEmpty(level.getBlockState(pos.below()))) {
+@@ -87,16 +_,16 @@
+         }
+ 
+         Direction opposite = direction.getOpposite();
+-        int i = getDistanceUntilEdgeAboveFrame(level, pos, opposite) - 1;
++        int i = getDistanceUntilEdgeAboveFrame(level, pos, opposite, blocks) - 1; // CraftBukkit
+         return i < 0 ? null : pos.relative(opposite, i);
+     }
+ 
+-    private static int calculateWidth(BlockGetter level, BlockPos bottomLeft, Direction direction) {
+-        int distanceUntilEdgeAboveFrame = getDistanceUntilEdgeAboveFrame(level, bottomLeft, direction);
++    private static int calculateWidth(BlockGetter level, BlockPos bottomLeft, Direction direction, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) { // CraftBukkit
++        int distanceUntilEdgeAboveFrame = getDistanceUntilEdgeAboveFrame(level, bottomLeft, direction, blocks); // CraftBukkit
+         return distanceUntilEdgeAboveFrame >= 2 && distanceUntilEdgeAboveFrame <= 21 ? distanceUntilEdgeAboveFrame : 0;
+     }
+ 
+-    private static int getDistanceUntilEdgeAboveFrame(BlockGetter level, BlockPos pos, Direction direction) {
++    private static int getDistanceUntilEdgeAboveFrame(BlockGetter level, BlockPos pos, Direction direction, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) { // CraftBukkit
+         BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ 
+         for (int i = 0; i <= 21; i++) {
+@@ -104,6 +_,7 @@
+             BlockState blockState = level.getBlockState(mutableBlockPos);
+             if (!isEmpty(blockState)) {
+                 if (FRAME.test(blockState, level, mutableBlockPos)) {
++                    blocks.setBlock(mutableBlockPos, blockState, 18); // CraftBukkit - lower left / right
+                     return i;
+                 }
+                 break;
+@@ -113,32 +_,34 @@
+             if (!FRAME.test(blockState1, level, mutableBlockPos)) {
+                 break;
+             }
++            blocks.setBlock(mutableBlockPos, blockState1, 18); // CraftBukkit - bottom row
+         }
+ 
+         return 0;
+     }
+ 
+-    private static int calculateHeight(BlockGetter level, BlockPos pos, Direction direction, int width, MutableInt portalBlocks) {
++    private static int calculateHeight(BlockGetter level, BlockPos pos, Direction direction, int width, MutableInt portalBlocks, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) { // CraftBukkit
+         BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+-        int distanceUntilTop = getDistanceUntilTop(level, pos, direction, mutableBlockPos, width, portalBlocks);
+-        return distanceUntilTop >= 3 && distanceUntilTop <= 21 && hasTopFrame(level, pos, direction, mutableBlockPos, width, distanceUntilTop)
++        int distanceUntilTop = getDistanceUntilTop(level, pos, direction, mutableBlockPos, width, portalBlocks, blocks); // CraftBukkit
++        return distanceUntilTop >= 3 && distanceUntilTop <= 21 && hasTopFrame(level, pos, direction, mutableBlockPos, width, distanceUntilTop, blocks) // CraftBukkit
+             ? distanceUntilTop
+             : 0;
+     }
+ 
+-    private static boolean hasTopFrame(BlockGetter level, BlockPos pos, Direction direction, BlockPos.MutableBlockPos checkPos, int width, int distanceUntilTop) {
++    private static boolean hasTopFrame(BlockGetter level, BlockPos pos, Direction direction, BlockPos.MutableBlockPos checkPos, int width, int distanceUntilTop, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks) { // CraftBukkit
+         for (int i = 0; i < width; i++) {
+             BlockPos.MutableBlockPos mutableBlockPos = checkPos.set(pos).move(Direction.UP, distanceUntilTop).move(direction, i);
+             if (!FRAME.test(level.getBlockState(mutableBlockPos), level, mutableBlockPos)) {
+                 return false;
+             }
++            blocks.setBlock(mutableBlockPos, level.getBlockState(mutableBlockPos), 18); // CraftBukkit - upper row
+         }
+ 
+         return true;
+     }
+ 
+     private static int getDistanceUntilTop(
+-        BlockGetter level, BlockPos pos, Direction direction, BlockPos.MutableBlockPos checkPos, int width, MutableInt portalBlocks
++        BlockGetter level, BlockPos pos, Direction direction, BlockPos.MutableBlockPos checkPos, int width, MutableInt portalBlocks, org.bukkit.craftbukkit.util.BlockStateListPopulator blocks // CraftBukkit
+     ) {
+         for (int i = 0; i < 21; i++) {
+             checkPos.set(pos).move(Direction.UP, i).move(direction, -1);
+@@ -162,6 +_,10 @@
+                     portalBlocks.increment();
+                 }
+             }
++            // CraftBukkit start - left and right
++            blocks.setBlock(checkPos.set(pos).move(Direction.UP, i).move(direction, -1), level.getBlockState(checkPos), 18);
++            blocks.setBlock(checkPos.set(pos).move(Direction.UP, i).move(direction, i), level.getBlockState(checkPos), 18);
++            // CraftBukkit end
+         }
+ 
+         return 21;
+@@ -175,10 +_,23 @@
+         return this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21;
+     }
+ 
+-    public void createPortalBlocks(LevelAccessor level) {
++    // CraftBukkit start - return boolean, add entity
++    public boolean createPortalBlocks(LevelAccessor level, Entity entity) {
++        org.bukkit.World bworld = level.getMinecraftWorld().getWorld();
++        // Copy below for loop
+         BlockState blockState = Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis);
+         BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1))
++            .forEach(pos -> this.blocks.setBlock(pos, blockState, 18));
++        org.bukkit.event.world.PortalCreateEvent event = new org.bukkit.event.world.PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) this.blocks.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.FIRE);
++        level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event);
++
++        if (event.isCancelled()) {
++            return false;
++        }
++        // CraftBukkit end
++        BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1))
+             .forEach(pos -> level.setBlock(pos, blockState, 18));
++        return true; // CraftBukkit
+     }
+ 
+     public boolean isComplete() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch b/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch
new file mode 100644
index 0000000000..b65e02e918
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/portal/TeleportTransition.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/level/portal/TeleportTransition.java
++++ b/net/minecraft/world/level/portal/TeleportTransition.java
+@@ -19,15 +_,34 @@
+     boolean asPassenger,
+     Set<Relative> relatives,
+     TeleportTransition.PostTeleportTransition postTeleportTransition
++    , org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause // CraftBukkit
+ ) {
+     public static final TeleportTransition.PostTeleportTransition DO_NOTHING = entity -> {};
+     public static final TeleportTransition.PostTeleportTransition PLAY_PORTAL_SOUND = TeleportTransition::playPortalSound;
+     public static final TeleportTransition.PostTeleportTransition PLACE_PORTAL_TICKET = TeleportTransition::placePortalTicket;
+ 
++    // CraftBukkit start
++    public TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set<Relative> relatives, TeleportTransition.PostTeleportTransition postTeleportTransition) {
++        this(newLevel, position, deltaMovement, yRot, xRot, missingRespawnBlock, asPassenger, relatives, postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++    }
++
++    public TeleportTransition(org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++        this(null, Vec3.ZERO, Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), DO_NOTHING, cause);
++    }
++    // CraftBukkit end
++
+     public TeleportTransition(
+         ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, TeleportTransition.PostTeleportTransition postTeleportTransition
+     ) {
+-        this(newLevel, position, deltaMovement, yRot, xRot, Set.of(), postTeleportTransition);
++        // CraftBukkit start
++        this(newLevel, position, deltaMovement, yRot, xRot, postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++    }
++
++    public TeleportTransition(
++        ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, TeleportTransition.PostTeleportTransition postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause
++    ) {
++        this(newLevel, position, deltaMovement, yRot, xRot, Set.of(), postTeleportTransition, cause);
++        // CraftBukkit end
+     }
+ 
+     public TeleportTransition(
+@@ -39,11 +_,30 @@
+         Set<Relative> relatives,
+         TeleportTransition.PostTeleportTransition postTeleportTransition
+     ) {
+-        this(newLevel, position, deltaMovement, yRot, xRot, false, false, relatives, postTeleportTransition);
++        // CraftBukkit start
++        this(newLevel, position, deltaMovement, yRot, xRot, relatives, postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++    }
++    public TeleportTransition(
++        ServerLevel newLevel,
++        Vec3 position,
++        Vec3 deltaMovement,
++        float yRot,
++        float xRot,
++        Set<Relative> relatives,
++        TeleportTransition.PostTeleportTransition postTeleportTransition,
++        org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause
++    ) {
++        this(newLevel, position, deltaMovement, yRot, xRot, false, false, relatives, postTeleportTransition, cause);
++        // CraftBukkit end
+     }
+ 
+     public TeleportTransition(ServerLevel level, Entity entity, TeleportTransition.PostTeleportTransition postTeleportTransition) {
+-        this(level, findAdjustedSharedSpawnPos(level, entity), Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), postTeleportTransition);
++        // CraftBukkit start
++        this(level, entity, postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++    }
++    public TeleportTransition(ServerLevel level, Entity entity, TeleportTransition.PostTeleportTransition postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++        this(level, findAdjustedSharedSpawnPos(level, entity), Vec3.ZERO, level.getSharedSpawnAngle(), 0.0F, false, false, Set.of(), postTeleportTransition, cause); // Paper - MC-200092 - fix first spawn pos yaw being ignored
++        // CraftBukkit end
+     }
+ 
+     private static void playPortalSound(Entity entity) {
+@@ -57,7 +_,7 @@
+     }
+ 
+     public static TeleportTransition missingRespawnBlock(ServerLevel level, Entity entity, TeleportTransition.PostTeleportTransition postTeleportTransition) {
+-        return new TeleportTransition(level, findAdjustedSharedSpawnPos(level, entity), Vec3.ZERO, 0.0F, 0.0F, true, false, Set.of(), postTeleportTransition);
++        return new TeleportTransition(level, findAdjustedSharedSpawnPos(level, entity), Vec3.ZERO, level.getSharedSpawnAngle(), 0.0F, true, false, Set.of(), postTeleportTransition); // Paper - MC-200092 - fix spawn pos yaw being ignored
+     }
+ 
+     private static Vec3 findAdjustedSharedSpawnPos(ServerLevel level, Entity entity) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalForcer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalForcer.java.patch
deleted file mode 100644
index 2bcc6db1ba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalForcer.java.patch
+++ /dev/null
@@ -1,149 +0,0 @@
---- a/net/minecraft/world/level/portal/PortalForcer.java
-+++ b/net/minecraft/world/level/portal/PortalForcer.java
-@@ -42,34 +42,52 @@
-         this.level = world;
-     }
- 
-+    @io.papermc.paper.annotation.DoNotUse // Paper
-     public Optional<BlockPos> findClosestPortalPosition(BlockPos pos, boolean destIsNether, WorldBorder worldBorder) {
-+        // CraftBukkit start
-+        return this.findClosestPortalPosition(pos, worldBorder, destIsNether ? 16 : 128); // Search Radius
-+    }
-+
-+    public Optional<BlockPos> findClosestPortalPosition(BlockPos blockposition, WorldBorder worldborder, int i) {
-         PoiManager villageplace = this.level.getPoiManager();
--        int i = destIsNether ? 16 : 128;
-+        // int i = flag ? 16 : 128;
-+        // CraftBukkit end
- 
--        villageplace.ensureLoadedAndValid(this.level, pos, i);
--        Stream stream = villageplace.getInSquare((holder) -> {
-+        villageplace.ensureLoadedAndValid(this.level, blockposition, i);
-+        Stream<BlockPos> stream = villageplace.getInSquare((holder) -> { // CraftBukkit - decompile error
-             return holder.is(PoiTypes.NETHER_PORTAL);
--        }, pos, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos);
-+        }, blockposition, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos);
- 
--        Objects.requireNonNull(worldBorder);
--        return stream.filter(worldBorder::isWithinBounds).filter((blockposition1) -> {
-+        Objects.requireNonNull(worldborder);
-+        return stream.filter(worldborder::isWithinBounds).filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))).filter((blockposition1) -> { // Paper - Configurable nether ceiling damage
-             return this.level.getBlockState(blockposition1).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
--        }).min(Comparator.comparingDouble((blockposition1) -> {
--            return blockposition1.distSqr(pos);
-+        }).min(Comparator.comparingDouble((BlockPos blockposition1) -> { // CraftBukkit - decompile error
-+            return blockposition1.distSqr(blockposition);
-         }).thenComparingInt(Vec3i::getY));
-     }
- 
-     public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos pos, Direction.Axis axis) {
--        Direction enumdirection = Direction.get(Direction.AxisDirection.POSITIVE, axis);
-+        // CraftBukkit start
-+        return this.createPortal(pos, axis, null, 16);
-+    }
-+
-+    public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos blockposition, Direction.Axis enumdirection_enumaxis, net.minecraft.world.entity.Entity entity, int createRadius) {
-+        // CraftBukkit end
-+        Direction enumdirection = Direction.get(Direction.AxisDirection.POSITIVE, enumdirection_enumaxis);
-         double d0 = -1.0D;
-         BlockPos blockposition1 = null;
-         double d1 = -1.0D;
-         BlockPos blockposition2 = null;
-         WorldBorder worldborder = this.level.getWorldBorder();
-         int i = Math.min(this.level.getMaxY(), this.level.getMinY() + this.level.getLogicalHeight() - 1);
-+        // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height
-+        if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) {
-+            i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1);
-+        }
-+        // Paper end - Configurable nether ceiling damage
-         boolean flag = true;
--        BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
--        Iterator iterator = BlockPos.spiralAround(pos, 16, Direction.EAST, Direction.SOUTH).iterator();
-+        BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
-+        Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit
- 
-         int j;
-         int k;
-@@ -95,7 +113,7 @@
-                             if (i1 <= 0 || i1 >= 3) {
-                                 blockposition_mutableblockposition1.setY(k);
-                                 if (this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, 0)) {
--                                    double d2 = pos.distSqr(blockposition_mutableblockposition1);
-+                                    double d2 = blockposition.distSqr(blockposition_mutableblockposition1);
- 
-                                     if (this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, -1) && this.canHostFrame(blockposition_mutableblockposition1, blockposition_mutableblockposition, enumdirection, 1) && (d0 == -1.0D || d0 > d2)) {
-                                         d0 = d2;
-@@ -122,6 +140,7 @@
-         int j1;
-         int k1;
- 
-+        org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(this.level); // CraftBukkit - Use BlockStateListPopulator
-         if (d0 == -1.0D) {
-             j1 = Math.max(this.level.getMinY() - -1, 70);
-             k1 = i - 9;
-@@ -129,7 +148,7 @@
-                 return Optional.empty();
-             }
- 
--            blockposition1 = (new BlockPos(pos.getX() - enumdirection.getStepX() * 1, Mth.clamp(pos.getY(), j1, k1), pos.getZ() - enumdirection.getStepZ() * 1)).immutable();
-+            blockposition1 = (new BlockPos(blockposition.getX() - enumdirection.getStepX() * 1, Mth.clamp(blockposition.getY(), j1, k1), blockposition.getZ() - enumdirection.getStepZ() * 1)).immutable();
-             blockposition1 = worldborder.clampToBounds(blockposition1);
-             Direction enumdirection1 = enumdirection.getClockWise();
- 
-@@ -139,7 +158,7 @@
-                         BlockState iblockdata = i1 < 0 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
- 
-                         blockposition_mutableblockposition.setWithOffset(blockposition1, l * enumdirection.getStepX() + k * enumdirection1.getStepX(), i1, l * enumdirection.getStepZ() + k * enumdirection1.getStepZ());
--                        this.level.setBlockAndUpdate(blockposition_mutableblockposition, iblockdata);
-+                        blockList.setBlock(blockposition_mutableblockposition, iblockdata, 3); // CraftBukkit
-                     }
-                 }
-             }
-@@ -149,20 +168,30 @@
-             for (k1 = -1; k1 < 4; ++k1) {
-                 if (j1 == -1 || j1 == 2 || k1 == -1 || k1 == 3) {
-                     blockposition_mutableblockposition.setWithOffset(blockposition1, j1 * enumdirection.getStepX(), k1, j1 * enumdirection.getStepZ());
--                    this.level.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3);
-+                    blockList.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3); // CraftBukkit
-                 }
-             }
-         }
- 
--        BlockState iblockdata1 = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, axis);
-+        BlockState iblockdata1 = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, enumdirection_enumaxis);
- 
-         for (k1 = 0; k1 < 2; ++k1) {
-             for (j = 0; j < 3; ++j) {
-                 blockposition_mutableblockposition.setWithOffset(blockposition1, k1 * enumdirection.getStepX(), j, k1 * enumdirection.getStepZ());
--                this.level.setBlock(blockposition_mutableblockposition, iblockdata1, 18);
-+                blockList.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit
-             }
-         }
- 
-+        // CraftBukkit start
-+        org.bukkit.World bworld = this.level.getWorld();
-+        org.bukkit.event.world.PortalCreateEvent event = new org.bukkit.event.world.PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.NETHER_PAIR);
-+
-+        this.level.getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return Optional.empty();
-+        }
-+        blockList.updateList();
-+        // CraftBukkit end
-         return Optional.of(new BlockUtil.FoundRectangle(blockposition1.immutable(), 2, 3));
-     }
- 
-@@ -178,6 +207,13 @@
-         for (int j = -1; j < 3; ++j) {
-             for (int k = -1; k < 4; ++k) {
-                 temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal);
-+                // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
-+                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) {
-+                    if (!this.level.getBlockState(temp).isDestroyable()) {
-+                        return false;
-+                    }
-+                }
-+                // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
-                 if (k < 0 && !this.level.getBlockState(temp).isSolid()) {
-                     return false;
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalShape.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalShape.java.patch
deleted file mode 100644
index f0cf745dd6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/portal/PortalShape.java.patch
+++ /dev/null
@@ -1,226 +0,0 @@
---- a/net/minecraft/world/level/portal/PortalShape.java
-+++ b/net/minecraft/world/level/portal/PortalShape.java
-@@ -23,6 +23,11 @@
- import net.minecraft.world.phys.shapes.VoxelShape;
- import org.apache.commons.lang3.mutable.MutableInt;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.util.BlockStateListPopulator;
-+import org.bukkit.event.world.PortalCreateEvent;
-+// CraftBukkit end
-+
- public class PortalShape {
- 
-     private static final int MIN_WIDTH = 2;
-@@ -40,14 +45,18 @@
-     private final BlockPos bottomLeft;
-     private final int height;
-     private final int width;
-+    // CraftBukkit start - add field
-+    private final BlockStateListPopulator blocks;
- 
--    private PortalShape(Direction.Axis axis, int foundPortalBlocks, Direction negativeDir, BlockPos lowerCorner, int width, int height) {
--        this.axis = axis;
--        this.numPortalBlocks = foundPortalBlocks;
--        this.rightDir = negativeDir;
--        this.bottomLeft = lowerCorner;
--        this.width = width;
--        this.height = height;
-+    private PortalShape(Direction.Axis enumdirection_enumaxis, int i, Direction enumdirection, BlockPos blockposition, int j, int k, BlockStateListPopulator blocks) {
-+        this.blocks = blocks;
-+        // CraftBukkit end
-+        this.axis = enumdirection_enumaxis;
-+        this.numPortalBlocks = i;
-+        this.rightDir = enumdirection;
-+        this.bottomLeft = blockposition;
-+        this.width = j;
-+        this.height = k;
-     }
- 
-     public static Optional<PortalShape> findEmptyPortalShape(LevelAccessor world, BlockPos pos, Direction.Axis firstCheckedAxis) {
-@@ -69,110 +78,118 @@
-     }
- 
-     public static PortalShape findAnyShape(BlockGetter world, BlockPos pos, Direction.Axis axis) {
-+        BlockStateListPopulator blocks = new BlockStateListPopulator(((LevelAccessor) world).getMinecraftWorld()); // CraftBukkit
-         Direction enumdirection = axis == Direction.Axis.X ? Direction.WEST : Direction.SOUTH;
--        BlockPos blockposition1 = PortalShape.calculateBottomLeft(world, enumdirection, pos);
-+        BlockPos blockposition1 = PortalShape.calculateBottomLeft(world, enumdirection, pos, blocks); // CraftBukkit
- 
-         if (blockposition1 == null) {
--            return new PortalShape(axis, 0, enumdirection, pos, 0, 0);
-+            return new PortalShape(axis, 0, enumdirection, pos, 0, 0, blocks); // CraftBukkit
-         } else {
--            int i = PortalShape.calculateWidth(world, blockposition1, enumdirection);
-+            int i = PortalShape.calculateWidth(world, blockposition1, enumdirection, blocks); // CraftBukkit
- 
-             if (i == 0) {
--                return new PortalShape(axis, 0, enumdirection, blockposition1, 0, 0);
-+                return new PortalShape(axis, 0, enumdirection, blockposition1, 0, 0, blocks); // CraftBukkit
-             } else {
-                 MutableInt mutableint = new MutableInt();
--                int j = PortalShape.calculateHeight(world, blockposition1, enumdirection, i, mutableint);
-+                int j = PortalShape.calculateHeight(world, blockposition1, enumdirection, i, mutableint, blocks); // CraftBukkit
- 
--                return new PortalShape(axis, mutableint.getValue(), enumdirection, blockposition1, i, j);
-+                return new PortalShape(axis, mutableint.getValue(), enumdirection, blockposition1, i, j, blocks); // CraftBukkit
-             }
-         }
-     }
- 
-     @Nullable
--    private static BlockPos calculateBottomLeft(BlockGetter world, Direction direction, BlockPos pow) {
--        for (int i = Math.max(world.getMinY(), pow.getY() - 21); pow.getY() > i && PortalShape.isEmpty(world.getBlockState(pow.below())); pow = pow.below()) {
-+    private static BlockPos calculateBottomLeft(BlockGetter iblockaccess, Direction enumdirection, BlockPos blockposition, BlockStateListPopulator blocks) { // CraftBukkit
-+        for (int i = Math.max(iblockaccess.getMinY(), blockposition.getY() - 21); blockposition.getY() > i && PortalShape.isEmpty(iblockaccess.getBlockState(blockposition.below())); blockposition = blockposition.below()) {
-             ;
-         }
- 
--        Direction enumdirection1 = direction.getOpposite();
--        int j = PortalShape.getDistanceUntilEdgeAboveFrame(world, pow, enumdirection1) - 1;
-+        Direction enumdirection1 = enumdirection.getOpposite();
-+        int j = PortalShape.getDistanceUntilEdgeAboveFrame(iblockaccess, blockposition, enumdirection1, blocks) - 1; // CraftBukkit
- 
--        return j < 0 ? null : pow.relative(enumdirection1, j);
-+        return j < 0 ? null : blockposition.relative(enumdirection1, j);
-     }
- 
--    private static int calculateWidth(BlockGetter world, BlockPos lowerCorner, Direction negativeDir) {
--        int i = PortalShape.getDistanceUntilEdgeAboveFrame(world, lowerCorner, negativeDir);
-+    private static int calculateWidth(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockStateListPopulator blocks) { // CraftBukkit
-+        int i = PortalShape.getDistanceUntilEdgeAboveFrame(iblockaccess, blockposition, enumdirection, blocks); // CraftBukkit
- 
-         return i >= 2 && i <= 21 ? i : 0;
-     }
- 
--    private static int getDistanceUntilEdgeAboveFrame(BlockGetter world, BlockPos lowerCorner, Direction negativeDir) {
-+    private static int getDistanceUntilEdgeAboveFrame(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockStateListPopulator blocks) { // CraftBukkit
-         BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- 
-         for (int i = 0; i <= 21; ++i) {
--            blockposition_mutableblockposition.set(lowerCorner).move(negativeDir, i);
--            BlockState iblockdata = world.getBlockState(blockposition_mutableblockposition);
-+            blockposition_mutableblockposition.set(blockposition).move(enumdirection, i);
-+            BlockState iblockdata = iblockaccess.getBlockState(blockposition_mutableblockposition);
- 
-             if (!PortalShape.isEmpty(iblockdata)) {
--                if (PortalShape.FRAME.test(iblockdata, world, blockposition_mutableblockposition)) {
-+                if (PortalShape.FRAME.test(iblockdata, iblockaccess, blockposition_mutableblockposition)) {
-+                    blocks.setBlock(blockposition_mutableblockposition, iblockdata, 18); // CraftBukkit - lower left / right
-                     return i;
-                 }
-                 break;
-             }
- 
--            BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition.move(Direction.DOWN));
-+            BlockState iblockdata1 = iblockaccess.getBlockState(blockposition_mutableblockposition.move(Direction.DOWN));
- 
--            if (!PortalShape.FRAME.test(iblockdata1, world, blockposition_mutableblockposition)) {
-+            if (!PortalShape.FRAME.test(iblockdata1, iblockaccess, blockposition_mutableblockposition)) {
-                 break;
-             }
-+            blocks.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit - bottom row
-         }
- 
-         return 0;
-     }
- 
--    private static int calculateHeight(BlockGetter world, BlockPos lowerCorner, Direction negativeDir, int width, MutableInt foundPortalBlocks) {
--        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
--        int j = PortalShape.getDistanceUntilTop(world, lowerCorner, negativeDir, blockposition_mutableblockposition, width, foundPortalBlocks);
-+    private static int calculateHeight(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, int i, MutableInt mutableint, BlockStateListPopulator blocks) { // CraftBukkit
-+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
-+        int j = PortalShape.getDistanceUntilTop(iblockaccess, blockposition, enumdirection, blockposition_mutableblockposition, i, mutableint, blocks); // CraftBukkit
- 
--        return j >= 3 && j <= 21 && PortalShape.hasTopFrame(world, lowerCorner, negativeDir, blockposition_mutableblockposition, width, j) ? j : 0;
-+        return j >= 3 && j <= 21 && PortalShape.hasTopFrame(iblockaccess, blockposition, enumdirection, blockposition_mutableblockposition, i, j, blocks) ? j : 0; // CraftBukkit
-     }
- 
--    private static boolean hasTopFrame(BlockGetter world, BlockPos lowerCorner, Direction direction, BlockPos.MutableBlockPos pos, int width, int height) {
--        for (int k = 0; k < width; ++k) {
--            BlockPos.MutableBlockPos blockposition_mutableblockposition1 = pos.set(lowerCorner).move(Direction.UP, height).move(direction, k);
-+    private static boolean hasTopFrame(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockPos.MutableBlockPos blockposition_mutableblockposition, int i, int j, BlockStateListPopulator blocks) { // CraftBukkit
-+        for (int k = 0; k < i; ++k) {
-+            BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, k);
- 
--            if (!PortalShape.FRAME.test(world.getBlockState(blockposition_mutableblockposition1), world, blockposition_mutableblockposition1)) {
-+            if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition1), iblockaccess, blockposition_mutableblockposition1)) {
-                 return false;
-             }
-+            blocks.setBlock(blockposition_mutableblockposition1, iblockaccess.getBlockState(blockposition_mutableblockposition1), 18); // CraftBukkit - upper row
-         }
- 
-         return true;
-     }
- 
--    private static int getDistanceUntilTop(BlockGetter world, BlockPos lowerCorner, Direction negativeDir, BlockPos.MutableBlockPos pos, int width, MutableInt foundPortalBlocks) {
-+    private static int getDistanceUntilTop(BlockGetter iblockaccess, BlockPos blockposition, Direction enumdirection, BlockPos.MutableBlockPos blockposition_mutableblockposition, int i, MutableInt mutableint, BlockStateListPopulator blocks) { // CraftBukkit
-         for (int j = 0; j < 21; ++j) {
--            pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, -1);
--            if (!PortalShape.FRAME.test(world.getBlockState(pos), world, pos)) {
-+            blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, -1);
-+            if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition), iblockaccess, blockposition_mutableblockposition)) {
-                 return j;
-             }
- 
--            pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, width);
--            if (!PortalShape.FRAME.test(world.getBlockState(pos), world, pos)) {
-+            blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, i);
-+            if (!PortalShape.FRAME.test(iblockaccess.getBlockState(blockposition_mutableblockposition), iblockaccess, blockposition_mutableblockposition)) {
-                 return j;
-             }
- 
--            for (int k = 0; k < width; ++k) {
--                pos.set(lowerCorner).move(Direction.UP, j).move(negativeDir, k);
--                BlockState iblockdata = world.getBlockState(pos);
-+            for (int k = 0; k < i; ++k) {
-+                blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, k);
-+                BlockState iblockdata = iblockaccess.getBlockState(blockposition_mutableblockposition);
- 
-                 if (!PortalShape.isEmpty(iblockdata)) {
-                     return j;
-                 }
- 
-                 if (iblockdata.is(Blocks.NETHER_PORTAL)) {
--                    foundPortalBlocks.increment();
-+                    mutableint.increment();
-                 }
-             }
-+            // CraftBukkit start - left and right
-+            blocks.setBlock(blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, -1), iblockaccess.getBlockState(blockposition_mutableblockposition), 18);
-+            blocks.setBlock(blockposition_mutableblockposition.set(blockposition).move(Direction.UP, j).move(enumdirection, i), iblockaccess.getBlockState(blockposition_mutableblockposition), 18);
-+            // CraftBukkit end
-         }
- 
-         return 21;
-@@ -186,12 +203,28 @@
-         return this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21;
-     }
- 
--    public void createPortalBlocks(LevelAccessor world) {
-+    // CraftBukkit start - return boolean, add entity
-+    public boolean createPortalBlocks(LevelAccessor generatoraccess, Entity entity) {
-+        org.bukkit.World bworld = generatoraccess.getMinecraftWorld().getWorld();
-+
-+        // Copy below for loop
-         BlockState iblockdata = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis);
- 
-         BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> {
--            world.setBlock(blockposition, iblockdata, 18);
-+            this.blocks.setBlock(blockposition, iblockdata, 18);
-         });
-+
-+        PortalCreateEvent event = new PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) this.blocks.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE);
-+        generatoraccess.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event);
-+
-+        if (event.isCancelled()) {
-+            return false;
-+        }
-+        // CraftBukkit end
-+        BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> {
-+            generatoraccess.setBlock(blockposition, iblockdata, 18);
-+        });
-+        return true; // CraftBukkit
-     }
- 
-     public boolean isComplete() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/portal/TeleportTransition.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/portal/TeleportTransition.java.patch
deleted file mode 100644
index 1d72cf288c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/portal/TeleportTransition.java.patch
+++ /dev/null
@@ -1,71 +0,0 @@
---- a/net/minecraft/world/level/portal/TeleportTransition.java
-+++ b/net/minecraft/world/level/portal/TeleportTransition.java
-@@ -8,26 +8,55 @@
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.Relative;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.player.PlayerTeleportEvent;
- 
--public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set<Relative> relatives, TeleportTransition.PostTeleportTransition postTeleportTransition) {
-+public record TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set<Relative> relatives, TeleportTransition.PostTeleportTransition postTeleportTransition, PlayerTeleportEvent.TeleportCause cause) {
- 
-+    public TeleportTransition(ServerLevel newLevel, Vec3 position, Vec3 deltaMovement, float yRot, float xRot, boolean missingRespawnBlock, boolean asPassenger, Set<Relative> relatives, TeleportTransition.PostTeleportTransition postTeleportTransition) {
-+        this(newLevel, position, deltaMovement, yRot, xRot, missingRespawnBlock, asPassenger, relatives, postTeleportTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-+    }
-+
-+    public TeleportTransition(PlayerTeleportEvent.TeleportCause cause) {
-+        this(null, Vec3.ZERO, Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), DO_NOTHING, cause);
-+    }
-+    // CraftBukkit end
-+
-     public static final TeleportTransition.PostTeleportTransition DO_NOTHING = (entity) -> {
-     };
-     public static final TeleportTransition.PostTeleportTransition PLAY_PORTAL_SOUND = TeleportTransition::playPortalSound;
-     public static final TeleportTransition.PostTeleportTransition PLACE_PORTAL_TICKET = TeleportTransition::placePortalTicket;
- 
-     public TeleportTransition(ServerLevel world, Vec3 pos, Vec3 velocity, float yaw, float pitch, TeleportTransition.PostTeleportTransition postDimensionTransition) {
--        this(world, pos, velocity, yaw, pitch, Set.of(), postDimensionTransition);
-+        // CraftBukkit start
-+        this(world, pos, velocity, yaw, pitch, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-     }
- 
-+    public TeleportTransition(ServerLevel worldserver, Vec3 vec3d, Vec3 vec3d1, float f, float f1, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) {
-+        this(worldserver, vec3d, vec3d1, f, f1, Set.of(), teleporttransition_a, cause);
-+        // CraftBukkit end
-+    }
-+
-     public TeleportTransition(ServerLevel world, Vec3 pos, Vec3 velocity, float yaw, float pitch, Set<Relative> flags, TeleportTransition.PostTeleportTransition postDimensionTransition) {
--        this(world, pos, velocity, yaw, pitch, false, false, flags, postDimensionTransition);
-+        // CraftBukkit start
-+        this(world, pos, velocity, yaw, pitch, flags, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-     }
- 
-+    public TeleportTransition(ServerLevel worldserver, Vec3 vec3d, Vec3 vec3d1, float f, float f1, Set<Relative> set, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) {
-+        this(worldserver, vec3d, vec3d1, f, f1, false, false, set, teleporttransition_a, cause);
-+        // CraftBukkit end
-+    }
-+
-     public TeleportTransition(ServerLevel world, Entity entity, TeleportTransition.PostTeleportTransition postDimensionTransition) {
--        this(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, false, false, Set.of(), postDimensionTransition);
-+        // CraftBukkit start
-+        this(world, entity, postDimensionTransition, PlayerTeleportEvent.TeleportCause.UNKNOWN);
-     }
- 
-+    public TeleportTransition(ServerLevel worldserver, Entity entity, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerTeleportEvent.TeleportCause cause) {
-+        this(worldserver, findAdjustedSharedSpawnPos(worldserver, entity), Vec3.ZERO, worldserver.getSharedSpawnAngle(), 0.0F, false, false, Set.of(), teleporttransition_a, cause); // Paper - MC-200092 - fix first spawn pos yaw being ignored
-+        // CraftBukkit end
-+    }
-+
-     private static void playPortalSound(Entity entity) {
-         if (entity instanceof ServerPlayer entityplayer) {
-             entityplayer.connection.send(new ClientboundLevelEventPacket(1032, BlockPos.ZERO, 0, false));
-@@ -40,7 +69,7 @@
-     }
- 
-     public static TeleportTransition missingRespawnBlock(ServerLevel world, Entity entity, TeleportTransition.PostTeleportTransition postDimensionTransition) {
--        return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, 0.0F, 0.0F, true, false, Set.of(), postDimensionTransition);
-+        return new TeleportTransition(world, findAdjustedSharedSpawnPos(world, entity), Vec3.ZERO, world.getSharedSpawnAngle(), 0.0F, true, false, Set.of(), postDimensionTransition); // Paper - MC-200092 - fix spawn pos yaw being ignored
-     }
- 
-     private static Vec3 findAdjustedSharedSpawnPos(ServerLevel world, Entity entity) {

From e28654cfb224933135d2027c1464ac7270dbe51b Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 11:29:09 -0800
Subject: [PATCH 106/285] net.minecraft.world.food

---
 .../minecraft/world/food/FoodData.java.patch  | 77 +++++++------------
 .../world/food/FoodProperties.java.patch      | 11 +++
 .../world/food/FoodProperties.java.patch      | 19 -----
 3 files changed, 39 insertions(+), 68 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/food/FoodData.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/food/FoodProperties.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/food/FoodData.java.patch b/paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/food/FoodData.java.patch
rename to paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch
index 68a35b8aa4..b18d67421a 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/food/FoodData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/food/FoodData.java.patch
@@ -1,22 +1,6 @@
 --- a/net/minecraft/world/food/FoodData.java
 +++ b/net/minecraft/world/food/FoodData.java
-@@ -1,11 +1,14 @@
- package net.minecraft.world.food;
- 
-+import net.minecraft.world.level.GameRules;
- import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.network.protocol.game.ClientboundSetHealthPacket;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.Mth;
- import net.minecraft.world.Difficulty;
--import net.minecraft.world.level.GameRules;
-+import net.minecraft.world.item.ItemStack;
-+// CraftBukkit end
- 
- public class FoodData {
- 
-@@ -13,6 +16,11 @@
+@@ -12,6 +_,11 @@
      public float saturationLevel = 5.0F;
      public float exhaustionLevel;
      private int tickTimer;
@@ -26,33 +10,30 @@
 +    public int starvationRate = 80;
 +    // CraftBukkit end
  
-     public FoodData() {}
- 
-@@ -29,6 +37,20 @@
-         this.add(foodComponent.nutrition(), foodComponent.saturation());
+     private void add(int foodLevel, float saturationLevel) {
+         this.foodLevel = Mth.clamp(foodLevel + this.foodLevel, 0, 20);
+@@ -26,6 +_,17 @@
+         this.add(foodProperties.nutrition(), foodProperties.saturation());
      }
  
 +    // CraftBukkit start
-+    public void eat(FoodProperties foodinfo, ItemStack itemstack, ServerPlayer entityplayer) {
++    public void eat(FoodProperties foodProperties, net.minecraft.world.item.ItemStack stack, ServerPlayer serverPlayer) {
 +        int oldFoodLevel = this.foodLevel;
-+
-+        org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityplayer, foodinfo.nutrition() + oldFoodLevel, itemstack);
-+
++        org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(serverPlayer, foodProperties.nutrition() + oldFoodLevel, stack);
 +        if (!event.isCancelled()) {
-+            this.add(event.getFoodLevel() - oldFoodLevel, foodinfo.saturation());
++            this.add(event.getFoodLevel() - oldFoodLevel, foodProperties.saturation());
 +        }
-+
-+        entityplayer.getBukkitEntity().sendHealthUpdate();
++        serverPlayer.getBukkitEntity().sendHealthUpdate();
 +    }
 +    // CraftBukkit end
 +
      public void tick(ServerPlayer player) {
-         ServerLevel worldserver = player.serverLevel();
-         Difficulty enumdifficulty = worldserver.getDifficulty();
-@@ -38,7 +60,15 @@
+         ServerLevel serverLevel = player.serverLevel();
+         Difficulty difficulty = serverLevel.getDifficulty();
+@@ -34,29 +_,39 @@
              if (this.saturationLevel > 0.0F) {
                  this.saturationLevel = Math.max(this.saturationLevel - 1.0F, 0.0F);
-             } else if (enumdifficulty != Difficulty.PEACEFUL) {
+             } else if (difficulty != Difficulty.PEACEFUL) {
 -                this.foodLevel = Math.max(this.foodLevel - 1, 0);
 +                // CraftBukkit start
 +                org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, Math.max(this.foodLevel - 1, 0));
@@ -61,28 +42,26 @@
 +                    this.foodLevel = event.getFoodLevel();
 +                }
 +
-+                player.connection.send(new ClientboundSetHealthPacket(player.getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel));
++                player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetHealthPacket(player.getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel));
 +                // CraftBukkit end
              }
          }
  
-@@ -46,23 +76,25 @@
- 
-         if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
-             ++this.tickTimer;
+         boolean _boolean = serverLevel.getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
+         if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
+             this.tickTimer++;
 -            if (this.tickTimer >= 10) {
 +            if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit
-                 float f = Math.min(this.saturationLevel, 6.0F);
- 
--                player.heal(f / 6.0F);
--                this.addExhaustion(f);
-+                player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen
-+                // this.addExhaustion(f); CraftBukkit - EntityExhaustionEvent
-+                player.causeFoodExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent
+                 float min = Math.min(this.saturationLevel, 6.0F);
+-                player.heal(min / 6.0F);
+-                this.addExhaustion(min);
++                player.heal(min / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen
++                // this.addExhaustion(min); CraftBukkit - EntityExhaustionEvent
++                player.causeFoodExhaustion(min, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent
                  this.tickTimer = 0;
              }
-         } else if (flag && this.foodLevel >= 18 && player.isHurt()) {
-             ++this.tickTimer;
+         } else if (_boolean && this.foodLevel >= 18 && player.isHurt()) {
+             this.tickTimer++;
 -            if (this.tickTimer >= 80) {
 -                player.heal(1.0F);
 -                this.addExhaustion(6.0F);
@@ -93,9 +72,9 @@
                  this.tickTimer = 0;
              }
          } else if (this.foodLevel <= 0) {
-             ++this.tickTimer;
+             this.tickTimer++;
 -            if (this.tickTimer >= 80) {
 +            if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation
-                 if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) {
-                     player.hurtServer(worldserver, player.damageSources().starve(), 1.0F);
+                 if (player.getHealth() > 10.0F || difficulty == Difficulty.HARD || player.getHealth() > 1.0F && difficulty == Difficulty.NORMAL) {
+                     player.hurtServer(serverLevel, player.damageSources().starve(), 1.0F);
                  }
diff --git a/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch b/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch
new file mode 100644
index 0000000000..d6ade19bb4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/food/FoodProperties.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/food/FoodProperties.java
++++ b/net/minecraft/world/food/FoodProperties.java
+@@ -41,7 +_,7 @@
+         RandomSource random = entity.getRandom();
+         level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, random.triangle(1.0F, 0.4F));
+         if (entity instanceof Player player) {
+-            player.getFoodData().eat(this);
++            player.getFoodData().eat(this, stack, (net.minecraft.server.level.ServerPlayer) player); // CraftBukkit
+             level.playSound(
+                 null, player.getX(), player.getY(), player.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(random, 0.9F, 1.0F)
+             );
diff --git a/paper-server/patches/unapplied/net/minecraft/world/food/FoodProperties.java.patch b/paper-server/patches/unapplied/net/minecraft/world/food/FoodProperties.java.patch
deleted file mode 100644
index a8c85757de..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/food/FoodProperties.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/world/food/FoodProperties.java
-+++ b/net/minecraft/world/food/FoodProperties.java
-@@ -5,6 +5,7 @@
- import net.minecraft.network.RegistryFriendlyByteBuf;
- import net.minecraft.network.codec.ByteBufCodecs;
- import net.minecraft.network.codec.StreamCodec;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
-@@ -31,7 +32,7 @@
- 
-         world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), (SoundEvent) consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, randomsource.triangle(1.0F, 0.4F));
-         if (user instanceof Player entityhuman) {
--            entityhuman.getFoodData().eat(this);
-+            entityhuman.getFoodData().eat(this, stack, (ServerPlayer) entityhuman); // CraftBukkit
-             world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F));
-         }
- 

From 718d970f97478edd5373b0133c06ddcd27805ca8 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 11:38:01 -0800
Subject: [PATCH 107/285] net.minecraft.world.level.levelgen.structure

---
 .../LegacyStructureDataHandler.java.patch     |  33 ++++
 .../structure/StructureCheck.java.patch       |  36 ++--
 .../structure/StructurePiece.java.patch       | 128 ++++++++++++++
 .../structure/StructureStart.java.patch       |  54 ++++++
 .../LegacyStructureDataHandler.java.patch     |  32 ----
 .../structure/StructurePiece.java.patch       | 164 ------------------
 .../structure/StructureStart.java.patch       |  59 -------
 7 files changed, 233 insertions(+), 273 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch (67%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
new file mode 100644
index 0000000000..f363fd7dbb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
++++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
+@@ -18,7 +_,7 @@
+ import net.minecraft.resources.ResourceKey;
+ import net.minecraft.util.datafix.DataFixTypes;
+ import net.minecraft.world.level.ChunkPos;
+-import net.minecraft.world.level.Level;
++import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+ 
+ public class LegacyStructureDataHandler {
+@@ -217,17 +_,17 @@
+         }
+     }
+ 
+-    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, @Nullable DimensionDataStorage storage) {
+-        if (level == Level.OVERWORLD) {
++    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, @Nullable DimensionDataStorage storage) { // CraftBukkit
++        if (level == LevelStem.OVERWORLD) { // CraftBukkit
+             return new LegacyStructureDataHandler(
+                 storage,
+                 ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"),
+                 ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument")
+             );
+-        } else if (level == Level.NETHER) {
++        } else if (level == LevelStem.NETHER) { // CraftBukkit
+             List<String> list = ImmutableList.of("Fortress");
+             return new LegacyStructureDataHandler(storage, list, list);
+-        } else if (level == Level.END) {
++        } else if (level == LevelStem.END) { // CraftBukkit
+             List<String> list = ImmutableList.of("EndCity");
+             return new LegacyStructureDataHandler(storage, list, list);
+         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch
similarity index 67%
rename from paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch
index 9e6cb9ad46..d79e90f861 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 +++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-@@ -40,7 +40,7 @@
+@@ -40,7 +_,7 @@
      private final ChunkScanAccess storageAccess;
      private final RegistryAccess registryAccess;
      private final StructureTemplateManager structureTemplateManager;
@@ -9,17 +9,17 @@
      private final ChunkGenerator chunkGenerator;
      private final RandomState randomState;
      private final LevelHeightAccessor heightAccessor;
-@@ -54,7 +54,7 @@
-         ChunkScanAccess chunkIoWorker,
-         RegistryAccess registryManager,
+@@ -54,7 +_,7 @@
+         ChunkScanAccess storageAccess,
+         RegistryAccess registryAccess,
          StructureTemplateManager structureTemplateManager,
--        ResourceKey<Level> worldKey,
-+        ResourceKey<net.minecraft.world.level.dimension.LevelStem> worldKey, // Paper - fix missing CB diff
+-        ResourceKey<Level> dimension,
++        ResourceKey<net.minecraft.world.level.dimension.LevelStem> dimension, // Paper - fix missing CB diff
          ChunkGenerator chunkGenerator,
-         RandomState noiseConfig,
-         LevelHeightAccessor world,
-@@ -74,6 +74,20 @@
-         this.fixerUpper = dataFixer;
+         RandomState randomState,
+         LevelHeightAccessor heightAccessor,
+@@ -74,6 +_,20 @@
+         this.fixerUpper = fixerUpper;
      }
  
 +    // Paper start - add missing structure salt configs
@@ -36,15 +36,15 @@
 +    }
 +    // Paper end - add missing structure seed configs
 +
-     public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) {
-         long l = pos.toLong();
-         Object2IntMap<Structure> object2IntMap = this.loadedChunks.get(l);
-@@ -83,7 +97,7 @@
-             StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l);
+     public StructureCheckResult checkStart(ChunkPos chunkPos, Structure structure, StructurePlacement placement, boolean skipKnownStructures) {
+         long packedChunkPos = chunkPos.toLong();
+         Object2IntMap<Structure> map = this.loadedChunks.get(packedChunkPos);
+@@ -83,7 +_,7 @@
+             StructureCheckResult structureCheckResult = this.tryLoadFromStorage(chunkPos, structure, skipKnownStructures, packedChunkPos);
              if (structureCheckResult != null) {
                  return structureCheckResult;
--            } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) {
-+            } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs
+-            } else if (!placement.applyAdditionalChunkRestrictions(chunkPos.x, chunkPos.z, this.seed)) {
++            } else if (!placement.applyAdditionalChunkRestrictions(chunkPos.x, chunkPos.z, this.seed, this.getSaltOverride(structure))) { // Paper - add missing structure seed configs
                  return StructureCheckResult.START_NOT_PRESENT;
              } else {
-                 boolean bl = this.featureChecks
+                 boolean flag = this.featureChecks
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
new file mode 100644
index 0000000000..e53e4b3cd9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java
++++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java
+@@ -26,8 +_,6 @@
+ import net.minecraft.world.level.block.Mirror;
+ import net.minecraft.world.level.block.Rotation;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.block.entity.ChestBlockEntity;
+-import net.minecraft.world.level.block.entity.DispenserBlockEntity;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.chunk.ChunkGenerator;
+ import net.minecraft.world.level.levelgen.Heightmap;
+@@ -47,7 +_,7 @@
+     private Rotation rotation;
+     protected int genDepth;
+     private final StructurePieceType type;
+-    private static final Set<Block> SHAPE_CHECK_BLOCKS = ImmutableSet.<Block>builder()
++    public static final Set<Block> SHAPE_CHECK_BLOCKS = ImmutableSet.<Block>builder() // PAIL private -> public
+         .add(Blocks.NETHER_BRICK_FENCE)
+         .add(Blocks.TORCH)
+         .add(Blocks.WALL_TORCH)
+@@ -189,6 +_,11 @@
+                 }
+ 
+                 level.setBlock(worldPos, blockstate, 2);
++                // CraftBukkit start - fluid handling is already done if we have a transformer generator access
++                if (level instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess) {
++                    return;
++                }
++                // CraftBukkit end
+                 FluidState fluidState = level.getFluidState(worldPos);
+                 if (!fluidState.isEmpty()) {
+                     level.scheduleTick(worldPos, fluidState.getType(), 0);
+@@ -201,6 +_,38 @@
+         }
+     }
+ 
++    // CraftBukkit start
++    protected boolean placeCraftBlockEntity(ServerLevelAccessor serverLevelAccessor, BlockPos pos, org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState, int flags) {
++        if (serverLevelAccessor instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++            return transformerAccess.setCraftBlock(pos, craftBlockEntityState, flags);
++        }
++        boolean result = serverLevelAccessor.setBlock(pos, craftBlockEntityState.getHandle(), flags);
++        BlockEntity tileEntity = serverLevelAccessor.getBlockEntity(pos);
++        if (tileEntity != null) {
++            tileEntity.loadWithComponents(craftBlockEntityState.getSnapshotNBT(), serverLevelAccessor.registryAccess());
++        }
++        return result;
++    }
++
++    protected void placeCraftSpawner(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.entity.EntityType entityType, int i) {
++        // This method is used in structures that are generated by code and place spawners as they set the entity after the block was placed making it impossible for plugins to access that information
++        org.bukkit.craftbukkit.block.CraftCreatureSpawner spawner = (org.bukkit.craftbukkit.block.CraftCreatureSpawner) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, Blocks.SPAWNER.defaultBlockState(), null);
++        spawner.setSpawnedType(entityType);
++        this.placeCraftBlockEntity(worldAccess, position, spawner, i);
++    }
++
++    protected void setCraftLootTable(ServerLevelAccessor worldAccess, BlockPos position, RandomSource randomSource, ResourceKey<LootTable> loottableKey) {
++        // This method is used in structures that use data markers to a loot table to loot containers as otherwise plugins won't have access to that information.
++        net.minecraft.world.level.block.entity.BlockEntity tileEntity = worldAccess.getBlockEntity(position);
++        if (tileEntity instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity tileEntityLootable) {
++            tileEntityLootable.setLootTable(loottableKey, randomSource.nextLong());
++            if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++                transformerAccess.setCraftBlock(position, (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, tileEntity.getBlockState(), tileEntityLootable.saveWithFullMetadata(worldAccess.registryAccess())), 3);
++            }
++        }
++    }
++    // CraftBukkit end
++
+     protected boolean canBeReplaced(LevelReader level, int x, int y, int z, BoundingBox box) {
+         return true;
+     }
+@@ -429,11 +_,17 @@
+                 state = reorient(level, pos, Blocks.CHEST.defaultBlockState());
+             }
+ 
+-            level.setBlock(pos, state, 2);
+-            BlockEntity blockEntity = level.getBlockEntity(pos);
+-            if (blockEntity instanceof ChestBlockEntity) {
+-                ((ChestBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
+-            }
++            // CraftBukkit start
++            // level.setBlock(pos, state, 2);
++            // BlockEntity blockEntity = level.getBlockEntity(pos);
++            // if (blockEntity instanceof ChestBlockEntity) {
++            //     ((ChestBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
++            // }
++            org.bukkit.craftbukkit.block.CraftChest chestState = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(level, pos, state, null);
++            chestState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable));
++            chestState.setSeed(random.nextLong());
++            this.placeCraftBlockEntity(level, pos, chestState, 2);
++            // CraftBukkit end
+ 
+             return true;
+         } else {
+@@ -446,11 +_,28 @@
+     ) {
+         BlockPos worldPos = this.getWorldPos(x, y, z);
+         if (box.isInside(worldPos) && !level.getBlockState(worldPos).is(Blocks.DISPENSER)) {
+-            this.placeBlock(level, Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, box);
+-            BlockEntity blockEntity = level.getBlockEntity(worldPos);
+-            if (blockEntity instanceof DispenserBlockEntity) {
+-                ((DispenserBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
+-            }
++            // CraftBukkit start
++            // this.placeBlock(level, Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, box);
++            // BlockEntity blockEntity = level.getBlockEntity(worldPos);
++            // if (blockEntity instanceof DispenserBlockEntity) {
++            //     ((DispenserBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
++            // }
++            if (!this.canBeReplaced(level, x, y, z, this.boundingBox)) {
++                return true;
++            }
++            BlockState dispenserBlockState = Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing);
++            if (this.mirror != Mirror.NONE) {
++                dispenserBlockState = dispenserBlockState.mirror(this.mirror);
++            }
++            if (this.rotation != Rotation.NONE) {
++                dispenserBlockState = dispenserBlockState.rotate(this.rotation);
++            }
++
++            org.bukkit.craftbukkit.block.CraftDispenser dispenserState = (org.bukkit.craftbukkit.block.CraftDispenser) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(level, worldPos, dispenserBlockState, null);
++            dispenserState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable));
++            dispenserState.setSeed(random.nextLong());
++            this.placeCraftBlockEntity(level, worldPos, dispenserState, 2);
++            // CraftBukkit end
+ 
+             return true;
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
new file mode 100644
index 0000000000..9efa80e8e3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/level/levelgen/structure/StructureStart.java
++++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java
+@@ -30,6 +_,12 @@
+     @Nullable
+     private volatile BoundingBox cachedBoundingBox;
+ 
++    // CraftBukkit start
++    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
++    public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(StructureStart.DATA_TYPE_REGISTRY);
++    public org.bukkit.event.world.AsyncStructureGenerateEvent.Cause generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.WORLD_GENERATION;
++    // CraftBukkit end
++
+     public StructureStart(Structure structure, ChunkPos chunkPos, int references, PiecesContainer pieceContainer) {
+         this.structure = structure;
+         this.chunkPos = chunkPos;
+@@ -87,11 +_,23 @@
+             BlockPos center = boundingBox.getCenter();
+             BlockPos blockPos = new BlockPos(center.getX(), boundingBox.minY(), center.getZ());
+ 
+-            for (StructurePiece structurePiece : list) {
+-                if (structurePiece.getBoundingBox().intersects(box)) {
+-                    structurePiece.postProcess(level, structureManager, generator, random, box, chunkPos, blockPos);
++            // CraftBukkit start
++            // for (StructurePiece structurePiece : list) {
++            //     if (structurePiece.getBoundingBox().intersects(box)) {
++            //         structurePiece.postProcess(level, structureManager, generator, random, box, chunkPos, blockPos);
++            //     }
++            // }
++            List<StructurePiece> pieces = list.stream().filter(piece -> piece.getBoundingBox().intersects(box)).toList();
++            if (!pieces.isEmpty()) {
++                org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess = new org.bukkit.craftbukkit.util.TransformerGeneratorAccess();
++                transformerAccess.setHandle(level);
++                transformerAccess.setStructureTransformer(new org.bukkit.craftbukkit.util.CraftStructureTransformer(this.generationEventCause, level, structureManager, this.structure, box, chunkPos));
++                for (StructurePiece piece : pieces) {
++                    piece.postProcess(transformerAccess, structureManager, generator, random, box, chunkPos, blockPos);
+                 }
++                transformerAccess.getStructureTransformer().discard();
+             }
++            // CraftBukkit end
+ 
+             this.structure.afterPlace(level, structureManager, generator, random, box, chunkPos, this.pieceContainer);
+         }
+@@ -99,6 +_,11 @@
+ 
+     public CompoundTag createTag(StructurePieceSerializationContext context, ChunkPos chunkPos) {
+         CompoundTag compoundTag = new CompoundTag();
++        // CraftBukkit start - store persistent data in nbt
++        if (!this.persistentDataContainer.isEmpty()) {
++            compoundTag.put("StructureBukkitValues", this.persistentDataContainer.toTagCompound());
++        }
++        // CraftBukkit end
+         if (this.isValid()) {
+             compoundTag.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString());
+             compoundTag.putInt("ChunkX", chunkPos.x);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
deleted file mode 100644
index 04607e1797..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
-+++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
-@@ -18,7 +18,7 @@
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.util.datafix.DataFixTypes;
- import net.minecraft.world.level.ChunkPos;
--import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- 
- public class LegacyStructureDataHandler {
-@@ -233,16 +233,16 @@
-         }
-     }
- 
--    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> world, @Nullable DimensionDataStorage persistentStateManager) {
--        if (world == Level.OVERWORLD) {
-+    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> world, @Nullable DimensionDataStorage persistentStateManager) { // CraftBukkit
-+        if (world == LevelStem.OVERWORLD) { // CraftBukkit
-             return new LegacyStructureDataHandler(persistentStateManager, ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"), ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument"));
-         } else {
-             ImmutableList immutablelist;
- 
--            if (world == Level.NETHER) {
-+            if (world == LevelStem.NETHER) { // CraftBukkit
-                 immutablelist = ImmutableList.of("Fortress");
-                 return new LegacyStructureDataHandler(persistentStateManager, immutablelist, immutablelist);
--            } else if (world == Level.END) {
-+            } else if (world == LevelStem.END) { // CraftBukkit
-                 immutablelist = ImmutableList.of("EndCity");
-                 return new LegacyStructureDataHandler(persistentStateManager, immutablelist, immutablelist);
-             } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
deleted file mode 100644
index 918d4c34ed..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
+++ /dev/null
@@ -1,164 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java
-+++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java
-@@ -29,8 +29,6 @@
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
- import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
--import net.minecraft.world.level.block.entity.DispenserBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
-@@ -51,7 +49,7 @@
-     private Rotation rotation;
-     protected int genDepth;
-     private final StructurePieceType type;
--    private static final Set<Block> SHAPE_CHECK_BLOCKS = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.PALE_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build();
-+    public static final Set<Block> SHAPE_CHECK_BLOCKS = ImmutableSet.<Block>builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.PALE_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); // CraftBukkit - decompile error / PAIL private -> public
- 
-     protected StructurePiece(StructurePieceType type, int length, BoundingBox boundingBox) {
-         this.type = type;
-@@ -80,13 +78,11 @@
-         CompoundTag nbttagcompound = new CompoundTag();
- 
-         nbttagcompound.putString("id", BuiltInRegistries.STRUCTURE_PIECE.getKey(this.getType()).toString());
--        DataResult dataresult = BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox);
--        Logger logger = StructurePiece.LOGGER;
--
--        Objects.requireNonNull(logger);
--        dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
--            nbttagcompound.put("BB", nbtbase);
-+        // CraftBukkit start - decompile error
-+        BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox).resultOrPartial(Objects.requireNonNull(StructurePiece.LOGGER)::error).ifPresent((nbtbase) -> {
-+             nbttagcompound.put("BB", nbtbase);
-         });
-+        // CraftBukkit end
-         Direction enumdirection = this.getOrientation();
- 
-         nbttagcompound.putInt("O", enumdirection == null ? -1 : enumdirection.get2DDataValue());
-@@ -186,6 +182,11 @@
-                 }
- 
-                 world.setBlock(blockposition_mutableblockposition, block, 2);
-+                // CraftBukkit start - fluid handling is already done if we have a transformer generator access
-+                if (world instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 FluidState fluid = world.getFluidState(blockposition_mutableblockposition);
- 
-                 if (!fluid.isEmpty()) {
-@@ -195,10 +196,42 @@
-                 if (StructurePiece.SHAPE_CHECK_BLOCKS.contains(block.getBlock())) {
-                     world.getChunk(blockposition_mutableblockposition).markPosForPostprocessing(blockposition_mutableblockposition);
-                 }
-+
-+            }
-+        }
-+    }
-+
-+    // CraftBukkit start
-+    protected boolean placeCraftBlockEntity(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState, int i) {
-+        if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
-+            return transformerAccess.setCraftBlock(position, craftBlockEntityState, i);
-+        }
-+        boolean result = worldAccess.setBlock(position, craftBlockEntityState.getHandle(), i);
-+        BlockEntity tileEntity = worldAccess.getBlockEntity(position);
-+        if (tileEntity != null) {
-+            tileEntity.loadWithComponents(craftBlockEntityState.getSnapshotNBT(), worldAccess.registryAccess());
-+        }
-+        return result;
-+    }
- 
-+    protected void placeCraftSpawner(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.entity.EntityType entityType, int i) {
-+        // This method is used in structures that are generated by code and place spawners as they set the entity after the block was placed making it impossible for plugins to access that information
-+        org.bukkit.craftbukkit.block.CraftCreatureSpawner spawner = (org.bukkit.craftbukkit.block.CraftCreatureSpawner) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, Blocks.SPAWNER.defaultBlockState(), null);
-+        spawner.setSpawnedType(entityType);
-+        this.placeCraftBlockEntity(worldAccess, position, spawner, i);
-+    }
-+
-+    protected void setCraftLootTable(ServerLevelAccessor worldAccess, BlockPos position, RandomSource randomSource, ResourceKey<LootTable> loottableKey) {
-+        // This method is used in structures that use data markers to a loot table to loot containers as otherwise plugins won't have access to that information.
-+        net.minecraft.world.level.block.entity.BlockEntity tileEntity = worldAccess.getBlockEntity(position);
-+        if (tileEntity instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity tileEntityLootable) {
-+            tileEntityLootable.setLootTable(loottableKey, randomSource.nextLong());
-+            if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
-+                transformerAccess.setCraftBlock(position, (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(worldAccess, position, tileEntity.getBlockState(), tileEntityLootable.saveWithFullMetadata(worldAccess.registryAccess())), 3);
-             }
-         }
-     }
-+    // CraftBukkit end
- 
-     protected boolean canBeReplaced(LevelReader world, int x, int y, int z, BoundingBox box) {
-         return true;
-@@ -393,12 +426,20 @@
-                 block = StructurePiece.reorient(world, pos, Blocks.CHEST.defaultBlockState());
-             }
- 
--            world.setBlock(pos, block, 2);
--            BlockEntity tileentity = world.getBlockEntity(pos);
-+            // CraftBukkit start
-+            /*
-+            worldaccess.setBlock(blockposition, iblockdata, 2);
-+            TileEntity tileentity = worldaccess.getBlockEntity(blockposition);
- 
--            if (tileentity instanceof ChestBlockEntity) {
--                ((ChestBlockEntity) tileentity).setLootTable(lootTable, random.nextLong());
-+            if (tileentity instanceof TileEntityChest) {
-+                ((TileEntityChest) tileentity).setLootTable(resourcekey, randomsource.nextLong());
-             }
-+            */
-+            org.bukkit.craftbukkit.block.CraftChest chestState = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos, block, null);
-+            chestState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable));
-+            chestState.setSeed(random.nextLong());
-+            this.placeCraftBlockEntity(world, pos, chestState, 2);
-+            // CraftBukkit end
- 
-             return true;
-         } else {
-@@ -410,13 +451,32 @@
-         BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getWorldPos(x, y, z);
- 
-         if (boundingBox.isInside(blockposition_mutableblockposition) && !world.getBlockState(blockposition_mutableblockposition).is(Blocks.DISPENSER)) {
--            this.placeBlock(world, (BlockState) Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, boundingBox);
--            BlockEntity tileentity = world.getBlockEntity(blockposition_mutableblockposition);
-+            // CraftBukkit start
-+            /*
-+            this.placeBlock(generatoraccessseed, (IBlockData) Blocks.DISPENSER.defaultBlockState().setValue(BlockDispenser.FACING, enumdirection), i, j, k, structureboundingbox);
-+            TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
- 
--            if (tileentity instanceof DispenserBlockEntity) {
--                ((DispenserBlockEntity) tileentity).setLootTable(lootTable, random.nextLong());
-+            if (tileentity instanceof TileEntityDispenser) {
-+                ((TileEntityDispenser) tileentity).setLootTable(resourcekey, randomsource.nextLong());
-             }
-+            */
-+            if (!this.canBeReplaced(world, x, y, z, boundingBox)) {
-+                return true;
-+            }
-+            BlockState iblockdata = Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing);
-+            if (this.mirror != Mirror.NONE) {
-+                iblockdata = iblockdata.mirror(this.mirror);
-+            }
-+            if (this.rotation != Rotation.NONE) {
-+                iblockdata = iblockdata.rotate(this.rotation);
-+            }
- 
-+            org.bukkit.craftbukkit.block.CraftDispenser dispenserState = (org.bukkit.craftbukkit.block.CraftDispenser) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition_mutableblockposition, iblockdata, null);
-+            dispenserState.setLootTable(org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(lootTable));
-+            dispenserState.setSeed(random.nextLong());
-+            this.placeCraftBlockEntity(world, blockposition_mutableblockposition, dispenserState, 2);
-+            // CraftBukkit end
-+
-             return true;
-         } else {
-             return false;
-@@ -428,7 +488,7 @@
-     }
- 
-     public static BoundingBox createBoundingBox(Stream<StructurePiece> pieces) {
--        Stream stream1 = pieces.map(StructurePiece::getBoundingBox);
-+        Stream<BoundingBox> stream1 = pieces.map(StructurePiece::getBoundingBox); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(stream1);
-         return (BoundingBox) BoundingBox.encapsulatingBoxes(stream1::iterator).orElseThrow(() -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
deleted file mode 100644
index 83539110a1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- a/net/minecraft/world/level/levelgen/structure/StructureStart.java
-+++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java
-@@ -32,6 +32,12 @@
-     @Nullable
-     private volatile BoundingBox cachedBoundingBox;
- 
-+    // CraftBukkit start
-+    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
-+    public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(StructureStart.DATA_TYPE_REGISTRY);
-+    public org.bukkit.event.world.AsyncStructureGenerateEvent.Cause generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.WORLD_GENERATION;
-+    // CraftBukkit end
-+
-     public StructureStart(Structure structure, ChunkPos pos, int references, PiecesContainer children) {
-         this.structure = structure;
-         this.chunkPos = pos;
-@@ -91,15 +97,29 @@
-             BoundingBox structureboundingbox1 = ((StructurePiece) list.get(0)).boundingBox;
-             BlockPos blockposition = structureboundingbox1.getCenter();
-             BlockPos blockposition1 = new BlockPos(blockposition.getX(), structureboundingbox1.minY(), blockposition.getZ());
-+            // CraftBukkit start
-+            /*
-             Iterator iterator = list.iterator();
- 
-             while (iterator.hasNext()) {
-                 StructurePiece structurepiece = (StructurePiece) iterator.next();
- 
--                if (structurepiece.getBoundingBox().intersects(chunkBox)) {
--                    structurepiece.postProcess(world, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, blockposition1);
-+                if (structurepiece.getBoundingBox().intersects(structureboundingbox)) {
-+                    structurepiece.postProcess(generatoraccessseed, structuremanager, chunkgenerator, randomsource, structureboundingbox, chunkcoordintpair, blockposition1);
-                 }
-             }
-+            */
-+            List<StructurePiece> pieces = list.stream().filter(piece -> piece.getBoundingBox().intersects(chunkBox)).toList();
-+            if (!pieces.isEmpty()) {
-+                org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess = new org.bukkit.craftbukkit.util.TransformerGeneratorAccess();
-+                transformerAccess.setHandle(world);
-+                transformerAccess.setStructureTransformer(new org.bukkit.craftbukkit.util.CraftStructureTransformer(this.generationEventCause, world, structureAccessor, this.structure, chunkBox, chunkPos));
-+                for (StructurePiece piece : pieces) {
-+                    piece.postProcess(transformerAccess, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, blockposition1);
-+                }
-+                transformerAccess.getStructureTransformer().discard();
-+            }
-+            // CraftBukkit end
- 
-             this.structure.afterPlace(world, structureAccessor, chunkGenerator, random, chunkBox, chunkPos, this.pieceContainer);
-         }
-@@ -107,6 +127,11 @@
- 
-     public CompoundTag createTag(StructurePieceSerializationContext context, ChunkPos chunkPos) {
-         CompoundTag nbttagcompound = new CompoundTag();
-+        // CraftBukkit start - store persistent data in nbt
-+        if (!this.persistentDataContainer.isEmpty()) {
-+            nbttagcompound.put("StructureBukkitValues", this.persistentDataContainer.toTagCompound());
-+        }
-+        // CraftBukkit end
- 
-         if (this.isValid()) {
-             nbttagcompound.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString());

From 31913c0b0a2fd1de1206eb83e9058467a1383c55 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 12:50:20 -0800
Subject: [PATCH 108/285] net.minecraft.world.item

---
 build-data/paper.at                           |   1 -
 .../minecraft/world/item/BlockItem.java.patch |  13 +
 .../world/item/DebugStickItem.java.patch      |   7 +
 .../item/ExperienceBottleItem.java.patch      |  48 +-
 .../world/item/FireChargeItem.java.patch      |  31 +
 .../world/item/FireworkRocketItem.java.patch  |  50 +-
 .../world/item/FishingRodItem.java.patch      |  54 ++
 .../world/item/FlintAndSteelItem.java.patch   |  28 +
 .../world/item/HangingEntityItem.java.patch   |  22 +
 .../world/item/HoneycombItem.java.patch       |  10 +-
 .../world/item/ItemCooldowns.java.patch       |  16 +
 .../minecraft/world/item/ItemStack.java.patch | 404 +++++++++++
 .../minecraft/world/item/ItemUtils.java.patch |  12 +-
 .../minecraft/world/item/LeadItem.java.patch  |  70 ++
 .../world/item/LingeringPotionItem.java.patch |  16 +-
 .../minecraft/world/item/MapItem.java.patch   |  22 +
 .../world/item/MinecartItem.java.patch        |  17 +
 .../world/item/NameTagItem.java.patch         |  12 +-
 .../world/item/PotionItem.java.patch          |  15 +
 .../item/ProjectileWeaponItem.java.patch      |  55 ++
 .../world/item/ServerItemCooldowns.java.patch |  11 +-
 .../world/item/ShovelItem.java.patch          |  33 +
 .../minecraft/world/item/SignItem.java.patch  |  22 +
 .../world/item/SnowballItem.java.patch        |  54 ++
 .../world/item/SpawnEggItem.java.patch        |  19 +
 .../world/item/SplashPotionItem.java.patch    |  16 +-
 .../item/StandingAndWallBlockItem.java.patch  |  24 +
 .../world/item/ThrowablePotionItem.java.patch |  35 +
 .../world/item/TridentItem.java.patch         |  51 ++
 .../world/item/WindChargeItem.java.patch      |  44 ++
 .../world/item/WrittenBookItem.java.patch     |  11 +
 .../world/item/ArmorStandItem.java.patch      |  15 -
 .../minecraft/world/item/AxeItem.java.patch   |  14 -
 .../minecraft/world/item/BlockItem.java.patch | 109 ---
 .../minecraft/world/item/BoatItem.java.patch  |  33 -
 .../world/item/BoneMealItem.java.patch        |  41 --
 .../world/item/BucketItem.java.patch          | 168 -----
 .../world/item/CrossbowItem.java.patch        |  48 --
 .../world/item/DebugStickItem.java.patch      |  25 -
 .../minecraft/world/item/DyeItem.java.patch   |  29 -
 .../minecraft/world/item/EggItem.java.patch   |  40 --
 .../world/item/EndCrystalItem.java.patch      |  31 -
 .../world/item/EnderEyeItem.java.patch        |  56 --
 .../world/item/EnderpearlItem.java.patch      |  39 --
 .../world/item/FireChargeItem.java.patch      |  31 -
 .../world/item/FishingRodItem.java.patch      |  50 --
 .../world/item/FlintAndSteelItem.java.patch   |  28 -
 .../world/item/HangingEntityItem.java.patch   |  67 --
 .../world/item/ItemCooldowns.java.patch       |  16 -
 .../minecraft/world/item/ItemStack.java.patch | 630 ------------------
 .../minecraft/world/item/LeadItem.java.patch  |  92 ---
 .../minecraft/world/item/MapItem.java.patch   |  22 -
 .../world/item/MinecartItem.java.patch        |  17 -
 .../world/item/PotionItem.java.patch          |  15 -
 .../item/ProjectileWeaponItem.java.patch      |  52 --
 .../world/item/ShovelItem.java.patch          |  33 -
 .../minecraft/world/item/SignItem.java.patch  |  23 -
 .../world/item/SnowballItem.java.patch        |  37 -
 .../world/item/SpawnEggItem.java.patch        |  24 -
 .../item/StandingAndWallBlockItem.java.patch  |  41 --
 .../world/item/ThrowablePotionItem.java.patch |  34 -
 .../world/item/TridentItem.java.patch         |  51 --
 .../world/item/WindChargeItem.java.patch      |  42 --
 .../world/item/WrittenBookItem.java.patch     |  11 -
 64 files changed, 1134 insertions(+), 2053 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/ExperienceBottleItem.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/FireworkRocketItem.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/HoneycombItem.java.patch (71%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/ItemUtils.java.patch (52%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/LingeringPotionItem.java.patch (54%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/NameTagItem.java.patch (65%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/ServerItemCooldowns.java.patch (86%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/SplashPotionItem.java.patch (54%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ArmorStandItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/AxeItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/BlockItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/BoatItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/BoneMealItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/BucketItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/CrossbowItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/DebugStickItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/DyeItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/EggItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/EndCrystalItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/EnderEyeItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/EnderpearlItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/FireChargeItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/FishingRodItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/FlintAndSteelItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/HangingEntityItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ItemCooldowns.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ItemStack.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/LeadItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/MapItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/MinecartItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/PotionItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ProjectileWeaponItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ShovelItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/SignItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/SnowballItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/SpawnEggItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/ThrowablePotionItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/TridentItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/WindChargeItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/WrittenBookItem.java.patch

diff --git a/build-data/paper.at b/build-data/paper.at
index 4b3b886b7f..c646267776 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -479,7 +479,6 @@ public net.minecraft.world.item.DebugStickItem handleInteraction(Lnet/minecraft/
 public net.minecraft.world.item.ItemCooldowns cooldowns
 public net.minecraft.world.item.ItemCooldowns tickCount
 public net.minecraft.world.item.ItemCooldowns$CooldownInstance
-public net.minecraft.world.item.ItemCooldowns$CooldownInstance endTime
 public net.minecraft.world.item.ItemStackLinkedSet TYPE_AND_TAG
 public net.minecraft.world.item.JukeboxSongPlayer song
 public net.minecraft.world.item.MapItem createNewSavedData(Lnet/minecraft/world/level/Level;IIIZZLnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/level/saveddata/maps/MapId;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
new file mode 100644
index 0000000000..1814a4c1bb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/item/BlockItem.java
++++ b/net/minecraft/world/item/BlockItem.java
+@@ -9,9 +_,9 @@
+ import net.minecraft.core.registries.Registries;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.network.chat.Component;
++import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.sounds.SoundEvent;
+-import net.minecraft.sounds.SoundSource;
+ import net.minecraft.world.InteractionResult;
+ import net.minecraft.world.entity.item.ItemEntity;
+ import net.minecraft.world.entity.player.Player;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
new file mode 100644
index 0000000000..be32bd3b62
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
@@ -0,0 +1,7 @@
+--- a/net/minecraft/world/item/DebugStickItem.java
++++ b/net/minecraft/world/item/DebugStickItem.java
+@@ -1,3 +_,4 @@
++// mc-dev import
+ package net.minecraft.world.item;
+ 
+ import java.util.Collection;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ExperienceBottleItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/item/ExperienceBottleItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
index e16430df8d..51cfc0c1ed 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ExperienceBottleItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
@@ -1,53 +1,53 @@
 --- a/net/minecraft/world/item/ExperienceBottleItem.java
 +++ b/net/minecraft/world/item/ExperienceBottleItem.java
-@@ -21,22 +21,38 @@
+@@ -21,22 +_,38 @@
      @Override
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemStack = user.getItemInHand(hand);
--        world.playSound(
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+-        level.playSound(
 -            null,
--            user.getX(),
--            user.getY(),
--            user.getZ(),
+-            player.getX(),
+-            player.getY(),
+-            player.getZ(),
 -            SoundEvents.EXPERIENCE_BOTTLE_THROW,
 -            SoundSource.NEUTRAL,
 -            0.5F,
--            0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)
+-            0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
 -        );
 +        // Paper - PlayerLaunchProjectileEvent - moved down
-         if (world instanceof ServerLevel serverLevel) {
--            Projectile.spawnProjectileFromRotation(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F);
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(ThrownExperienceBottle::new, serverLevel, itemInHand, player, -20.0F, 0.7F, 1.0F);
 +            // Paper start - PlayerLaunchProjectileEvent
-+            final Projectile.Delayed<ThrownExperienceBottle> thrownExperienceBottle = Projectile.spawnProjectileFromRotationDelayed(ThrownExperienceBottle::new, serverLevel, itemStack, user, -20.0F, 0.7F, 1.0F);
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownExperienceBottle.projectile().getBukkitEntity());
++            final Projectile.Delayed<ThrownExperienceBottle> thrownExperienceBottle = Projectile.spawnProjectileFromRotationDelayed(ThrownExperienceBottle::new, serverLevel, itemInHand, player, -20.0F, 0.7F, 1.0F);
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownExperienceBottle.projectile().getBukkitEntity());
 +            if (event.callEvent() && thrownExperienceBottle.attemptSpawn()) {
 +                if (event.shouldConsume()) {
-+                    itemStack.consume(1, user);
-+                } else if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
++                    itemInHand.consume(1, player);
++                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
 +                }
 +
-+                world.playSound(
++                level.playSound(
 +                    null,
-+                    user.getX(),
-+                    user.getY(),
-+                    user.getZ(),
++                    player.getX(),
++                    player.getY(),
++                    player.getZ(),
 +                    SoundEvents.EXPERIENCE_BOTTLE_THROW,
 +                    SoundSource.NEUTRAL,
 +                    0.5F,
-+                    0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)
++                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
 +                );
 +            } else {
-+                if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
++                if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
 +                }
 +                return InteractionResult.FAIL;
 +            }
 +            // Paper end - PlayerLaunchProjectileEvent
          }
  
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemStack.consume(1, user);
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
 +        // Paper - PlayerLaunchProjectileEvent - moved up
          return InteractionResult.SUCCESS;
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch
new file mode 100644
index 0000000000..a182da8d73
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/FireChargeItem.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/item/FireChargeItem.java
++++ b/net/minecraft/world/item/FireChargeItem.java
+@@ -35,12 +_,28 @@
+         if (!CampfireBlock.canLight(blockState) && !CandleBlock.canLight(blockState) && !CandleCakeBlock.canLight(blockState)) {
+             clickedPos = clickedPos.relative(context.getClickedFace());
+             if (BaseFireBlock.canBePlacedAt(level, clickedPos, context.getHorizontalDirection())) {
++                // CraftBukkit start - fire BlockIgniteEvent
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, clickedPos, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
++                    if (!context.getPlayer().getAbilities().instabuild) {
++                        context.getItemInHand().shrink(1);
++                    }
++                    return InteractionResult.PASS;
++                }
++                // CraftBukkit end
+                 this.playSound(level, clickedPos);
+                 level.setBlockAndUpdate(clickedPos, BaseFireBlock.getState(level, clickedPos));
+                 level.gameEvent(context.getPlayer(), GameEvent.BLOCK_PLACE, clickedPos);
+                 flag = true;
+             }
+         } else {
++            // CraftBukkit start - fire BlockIgniteEvent
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, clickedPos, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
++                if (!context.getPlayer().getAbilities().instabuild) {
++                    context.getItemInHand().shrink(1);
++                }
++                return InteractionResult.PASS;
++            }
++            // CraftBukkit end
+             this.playSound(level, clickedPos);
+             level.setBlockAndUpdate(clickedPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)));
+             level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, clickedPos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/FireworkRocketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/item/FireworkRocketItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
index 9d8f816a26..a707470352 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/FireworkRocketItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
@@ -1,48 +1,48 @@
 --- a/net/minecraft/world/item/FireworkRocketItem.java
 +++ b/net/minecraft/world/item/FireworkRocketItem.java
-@@ -33,7 +33,7 @@
-             ItemStack itemStack = context.getItemInHand();
-             Vec3 vec3 = context.getClickLocation();
-             Direction direction = context.getClickedFace();
+@@ -33,7 +_,7 @@
+             ItemStack itemInHand = context.getItemInHand();
+             Vec3 clickLocation = context.getClickLocation();
+             Direction clickedFace = context.getClickedFace();
 -            Projectile.spawnProjectile(
 +            final Projectile.Delayed<FireworkRocketEntity> fireworkRocketEntity = Projectile.spawnProjectileDelayed( // Paper - PlayerLaunchProjectileEvent
                  new FireworkRocketEntity(
                      level,
                      context.getPlayer(),
-@@ -43,9 +43,14 @@
-                     itemStack
+@@ -43,9 +_,14 @@
+                     itemInHand
                  ),
                  serverLevel,
--                itemStack
-+                itemStack, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID()  // Paper - firework api - assign spawning entity uuid
+-                itemInHand
++                itemInHand, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID()  // Paper - firework api - assign spawning entity uuid
              );
--            itemStack.shrink(1);
+-            itemInHand.shrink(1);
 +            // Paper start - PlayerLaunchProjectileEvent
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.projectile().getBukkitEntity());
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) fireworkRocketEntity.projectile().getBukkitEntity());
 +            if (!event.callEvent() || !fireworkRocketEntity.attemptSpawn()) return InteractionResult.PASS;
-+            if (event.shouldConsume() && !context.getPlayer().hasInfiniteMaterials()) itemStack.shrink(1);
++            if (event.shouldConsume() && !context.getPlayer().hasInfiniteMaterials()) itemInHand.shrink(1);
 +            else if (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory();
 +            // Paper end - PlayerLaunchProjectileEvent
          }
  
          return InteractionResult.SUCCESS;
-@@ -56,9 +61,19 @@
-         if (user.isFallFlying()) {
-             ItemStack itemStack = user.getItemInHand(hand);
-             if (world instanceof ServerLevel serverLevel) {
--                Projectile.spawnProjectile(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack);
--                itemStack.consume(1, user);
--                user.awardStat(Stats.ITEM_USED.get(this));
+@@ -56,9 +_,19 @@
+         if (player.isFallFlying()) {
+             ItemStack itemInHand = player.getItemInHand(hand);
+             if (level instanceof ServerLevel serverLevel) {
+-                Projectile.spawnProjectile(new FireworkRocketEntity(level, itemInHand, player), serverLevel, itemInHand);
+-                itemInHand.consume(1, player);
+-                player.awardStat(Stats.ITEM_USED.get(this));
 +                // Paper start - PlayerElytraBoostEvent
-+                final Projectile.Delayed<FireworkRocketEntity> delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(world, itemStack, user), serverLevel, itemStack, f -> f.spawningEntity = user.getUUID());  // Paper - firework api - assign spawning entity uuid
-+                com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand));
++                final Projectile.Delayed<FireworkRocketEntity> delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(level, itemInHand, player), serverLevel, itemInHand, f -> f.spawningEntity = player.getUUID());  // Paper - firework api - assign spawning entity uuid
++                com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand));
 +                if (event.callEvent() && delayed.attemptSpawn()) {
-+                    user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below
-+                    if (event.shouldConsume() && !user.hasInfiniteMaterials()) {
-+                        itemStack.shrink(1); // Moved up from below
-+                    } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
++                    player.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below
++                    if (event.shouldConsume() && !player.hasInfiniteMaterials()) {
++                        itemInHand.shrink(1); // Moved up from below
++                    } else ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
 +                } else {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
 +                }
 +                // Moved up consume/stat
 +                // Paper end - PlayerElytraBoostEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
new file mode 100644
index 0000000000..e9d2feeb53
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/item/FishingRodItem.java
++++ b/net/minecraft/world/item/FishingRodItem.java
+@@ -24,7 +_,7 @@
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (player.fishing != null) {
+             if (!level.isClientSide) {
+-                int i = player.fishing.retrieve(itemInHand);
++                int i = player.fishing.retrieve(hand, itemInHand); // Paper - Add hand parameter to PlayerFishEvent
+                 itemInHand.hurtAndBreak(i, player, LivingEntity.getSlotForHand(hand));
+             }
+ 
+@@ -40,20 +_,31 @@
+             );
+             player.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+         } else {
+-            level.playSound(
+-                null,
+-                player.getX(),
+-                player.getY(),
+-                player.getZ(),
+-                SoundEvents.FISHING_BOBBER_THROW,
+-                SoundSource.NEUTRAL,
+-                0.5F,
+-                0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+-            );
++            // CraftBukkit - moved down
+             if (level instanceof ServerLevel serverLevel) {
+                 int i1 = (int)(EnchantmentHelper.getFishingTimeReduction(serverLevel, itemInHand, player) * 20.0F);
+                 int fishingLuckBonus = EnchantmentHelper.getFishingLuckBonus(serverLevel, itemInHand, player);
+-                Projectile.spawnProjectile(new FishingHook(player, level, fishingLuckBonus, i1), serverLevel, itemInHand);
++                // CraftBukkit start
++                FishingHook entityfishinghook = new FishingHook(player, level, fishingLuckBonus, i1);
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) player.getBukkitEntity(), null, (org.bukkit.entity.FishHook) entityfishinghook.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.FISHING);
++                level.getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++                if (playerFishEvent.isCancelled()) {
++                    player.fishing = null;
++                    return InteractionResult.PASS;
++                }
++                level.playSound(
++                    null,
++                    player.getX(),
++                    player.getY(),
++                    player.getZ(),
++                    SoundEvents.FISHING_BOBBER_THROW,
++                    SoundSource.NEUTRAL,
++                    0.5F,
++                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
++                );
++                Projectile.spawnProjectile(entityfishinghook, serverLevel, itemInHand);
++                // CraftBukkit end
+             }
+ 
+             player.awardStat(Stats.ITEM_USED.get(this));
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch
new file mode 100644
index 0000000000..19ba3b4a20
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/FlintAndSteelItem.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/item/FlintAndSteelItem.java
++++ b/net/minecraft/world/item/FlintAndSteelItem.java
+@@ -32,6 +_,12 @@
+         if (!CampfireBlock.canLight(blockState) && !CandleBlock.canLight(blockState) && !CandleCakeBlock.canLight(blockState)) {
+             BlockPos blockPos = clickedPos.relative(context.getClickedFace());
+             if (BaseFireBlock.canBePlacedAt(level, blockPos, context.getHorizontalDirection())) {
++                // CraftBukkit start - Store the clicked block
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, blockPos, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, player).isCancelled()) {
++                    context.getItemInHand().hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand()));
++                    return InteractionResult.PASS;
++                }
++                // CraftBukkit end
+                 level.playSound(player, blockPos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+                 BlockState state = BaseFireBlock.getState(level, blockPos);
+                 level.setBlock(blockPos, state, 11);
+@@ -47,6 +_,12 @@
+                 return InteractionResult.FAIL;
+             }
+         } else {
++            // CraftBukkit start - Store the clicked block
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, clickedPos, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, player).isCancelled()) {
++                context.getItemInHand().hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand()));
++                return InteractionResult.PASS;
++            }
++            // CraftBukkit end
+             level.playSound(player, clickedPos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+             level.setBlock(clickedPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)), 11);
+             level.gameEvent(player, GameEvent.BLOCK_CHANGE, clickedPos);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
new file mode 100644
index 0000000000..f5c2f3dbb7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/HangingEntityItem.java
++++ b/net/minecraft/world/item/HangingEntityItem.java
+@@ -66,6 +_,19 @@
+ 
+             if (hangingEntity.survives()) {
+                 if (!level.isClientSide) {
++                    // CraftBukkit start - fire HangingPlaceEvent
++                    org.bukkit.entity.Player who = (context.getPlayer() == null) ? null : (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity();
++                    org.bukkit.block.Block blockClicked = level.getWorld().getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ());
++                    org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(clickedFace);
++                    org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand());
++
++                    org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) hangingEntity.getBukkitEntity(), who, blockClicked, blockFace, hand, org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemInHand));
++                    level.getCraftServer().getPluginManager().callEvent(event);
++
++                    if (event.isCancelled()) {
++                        return InteractionResult.FAIL;
++                    }
++                    // CraftBukkit end
+                     hangingEntity.playPlacementSound();
+                     level.gameEvent(player, GameEvent.ENTITY_PLACE, hangingEntity.position());
+                     level.addFreshEntity(hangingEntity);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/HoneycombItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch
similarity index 71%
rename from paper-server/patches/unapplied/net/minecraft/world/item/HoneycombItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch
index 02d0f745ad..9a4c3ee1c3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/HoneycombItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/HoneycombItem.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/item/HoneycombItem.java
 +++ b/net/minecraft/world/item/HoneycombItem.java
-@@ -74,6 +74,14 @@
-         return getWaxed(blockState).map(state -> {
+@@ -74,6 +_,14 @@
+         return getWaxed(blockState).<InteractionResult>map(blockState1 -> {
              Player player = context.getPlayer();
-             ItemStack itemStack = context.getItemInHand();
+             ItemStack itemInHand = context.getItemInHand();
 +            // Paper start - EntityChangeBlockEvent
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) {
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, blockState)) {
 +                if (!player.isCreative()) {
 +                    player.containerMenu.sendAllDataToRemote();
 +                }
@@ -13,5 +13,5 @@
 +            }
 +            // Paper end
              if (player instanceof ServerPlayer serverPlayer) {
-                 CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverPlayer, blockPos, itemStack);
+                 CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverPlayer, clickedPos, itemInHand);
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch
new file mode 100644
index 0000000000..4b0f5b2897
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemCooldowns.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/item/ItemCooldowns.java
++++ b/net/minecraft/world/item/ItemCooldowns.java
+@@ -56,6 +_,13 @@
+     }
+ 
+     public void addCooldown(ResourceLocation group, int cooldown) {
++        // Paper start - Item cooldown events
++        this.addCooldown(group, cooldown, true);
++    }
++
++    public void addCooldown(ResourceLocation group, int cooldown, boolean callEvent) {
++        // Event called in server override
++        // Paper end - Item cooldown events
+         this.cooldowns.put(group, new ItemCooldowns.CooldownInstance(this.tickCount, this.tickCount + cooldown));
+         this.onCooldownStarted(group, cooldown);
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
new file mode 100644
index 0000000000..24e6257081
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
@@ -0,0 +1,404 @@
+--- a/net/minecraft/world/item/ItemStack.java
++++ b/net/minecraft/world/item/ItemStack.java
+@@ -22,6 +_,7 @@
+ import net.minecraft.ChatFormatting;
+ import net.minecraft.advancements.CriteriaTriggers;
+ import net.minecraft.core.BlockPos;
++import net.minecraft.core.Direction;
+ import net.minecraft.core.Holder;
+ import net.minecraft.core.HolderLookup;
+ import net.minecraft.core.HolderSet;
+@@ -45,10 +_,12 @@
+ import net.minecraft.network.chat.MutableComponent;
+ import net.minecraft.network.codec.ByteBufCodecs;
+ import net.minecraft.network.codec.StreamCodec;
++import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
+ import net.minecraft.resources.RegistryOps;
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundSource;
+ import net.minecraft.stats.Stats;
+ import net.minecraft.tags.TagKey;
+ import net.minecraft.util.ExtraCodecs;
+@@ -136,18 +_,35 @@
+             } else {
+                 Holder<Item> holder = ITEM_STREAM_CODEC.decode(buffer);
+                 DataComponentPatch dataComponentPatch = DataComponentPatch.STREAM_CODEC.decode(buffer);
+-                return new ItemStack(holder, varInt, dataComponentPatch);
++                // CraftBukkit start
++                ItemStack stack = new ItemStack(holder, varInt, dataComponentPatch);
++                if (false && !dataComponentPatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata
++                    org.bukkit.craftbukkit.inventory.CraftItemStack.setItemMeta(stack, org.bukkit.craftbukkit.inventory.CraftItemStack.getItemMeta(stack));
++                }
++                return stack;
++                // CraftBukkit end
+             }
+         }
+ 
+         @Override
+         public void encode(RegistryFriendlyByteBuf buffer, ItemStack value) {
+-            if (value.isEmpty()) {
++            if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
+                 buffer.writeVarInt(0);
+             } else {
+                 buffer.writeVarInt(value.getCount());
+                 ITEM_STREAM_CODEC.encode(buffer, value.getItemHolder());
++                // Spigot start - filter
++                // value = value.copy();
++                // CraftItemStack.setItemMeta(value, CraftItemStack.getItemMeta(value)); // Paper - This is no longer with raw NBT being handled in metadata
++                // Paper start - adventure; conditionally render translatable components
++                boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
++                try {
++                    net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
+                 DataComponentPatch.STREAM_CODEC.encode(buffer, value.components.asPatch());
++                } finally {
++                    net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
++                }
++                // Paper end - adventure; conditionally render translatable components
+             }
+         }
+     };
+@@ -365,10 +_,180 @@
+             return InteractionResult.PASS;
+         } else {
+             Item item = this.getItem();
+-            InteractionResult interactionResult = item.useOn(context);
++            // InteractionResult interactionResult = item.useOn(context);
++            // CraftBukkit start - handle all block place event logic here
++            DataComponentPatch oldData = this.components.asPatch();
++            int oldCount = this.getCount();
++            ServerLevel serverLevel = (ServerLevel) context.getLevel();
++
++            if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement
++                serverLevel.captureBlockStates = true;
++                // special case bonemeal
++                if (item == Items.BONE_MEAL) {
++                    serverLevel.captureTreeGeneration = true;
++                }
++            }
++            InteractionResult interactionResult;
++            try {
++                interactionResult = item.useOn(context);
++            } finally {
++                serverLevel.captureBlockStates = false;
++            }
++            DataComponentPatch newData = this.components.asPatch();
++            int newCount = this.getCount();
++            this.setCount(oldCount);
++            this.restorePatch(oldData);
++            if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && serverLevel.capturedBlockStates.size() > 0) {
++                serverLevel.captureTreeGeneration = false;
++                org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld());
++                org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
++                net.minecraft.world.level.block.SaplingBlock.treeType = null;
++                List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
++                serverLevel.capturedBlockStates.clear();
++                org.bukkit.event.world.StructureGrowEvent structureEvent = null;
++                if (treeType != null) {
++                    boolean isBonemeal = this.getItem() == Items.BONE_MEAL;
++                    structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, isBonemeal, (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) blocks);
++                    org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
++                }
++
++                org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, clickedPos), (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) blocks);
++                fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
++                org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
++
++                if (!fertilizeEvent.isCancelled()) {
++                    // Change the stack to its new contents if it hasn't been tampered with.
++                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
++                        this.restorePatch(newData);
++                        this.setCount(newCount);
++                    }
++                    for (org.bukkit.craftbukkit.block.CraftBlockState blockstate : blocks) {
++                        // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest
++                        org.bukkit.craftbukkit.block.CapturedBlockState.setBlockState(blockstate);
++                        serverLevel.checkCapturedTreeStateForObserverNotify(clickedPos, blockstate); // Paper - notify observers even if grow failed
++                    }
++                    player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
++                }
++
++                SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
++                return interactionResult;
++            }
++            serverLevel.captureTreeGeneration = false;
+             if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) {
+-                player.awardStat(Stats.ITEM_USED.get(item));
++                // player.awardStat(Stats.ITEM_USED.get(item));
++                InteractionHand enumhand = context.getHand();
++                org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
++                List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
++                serverLevel.capturedBlockStates.clear();
++                if (blocks.size() > 1) {
++                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, enumhand, blocks, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
++                } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
++                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, enumhand, blocks.get(0), clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
++                }
++
++                if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++                    interactionResult = InteractionResult.FAIL; // cancel placement
++                    // PAIL: Remove this when MC-99075 fixed
++                    placeEvent.getPlayer().updateInventory();
++                    serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot
++                    // revert back all captured blocks
++                    serverLevel.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
++                serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
++                    for (org.bukkit.block.BlockState blockstate : blocks) {
++                        blockstate.update(true, false);
++                    }
++                serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
++                    serverLevel.preventPoiUpdated = false;
++
++                    // Brute force all possible updates
++                    // Paper start - Don't resync blocks
++                    // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
++                    // for (Direction dir : Direction.values()) {
++                    //     ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir)));
++                    // }
++                    // Paper end - Don't resync blocks
++                    SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
++                } else {
++                    // Change the stack to its new contents if it hasn't been tampered with.
++                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
++                        this.restorePatch(newData);
++                        this.setCount(newCount);
++                    }
++
++                    for (java.util.Map.Entry<BlockPos, net.minecraft.world.level.block.entity.BlockEntity> e : serverLevel.capturedTileEntities.entrySet()) {
++                        serverLevel.setBlockEntity(e.getValue());
++                    }
++
++                    for (org.bukkit.block.BlockState blockstate : blocks) {
++                        int updateFlag = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getFlag();
++                        net.minecraft.world.level.block.state.BlockState oldBlock = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getHandle();
++                        BlockPos newblockposition = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition();
++                        net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newblockposition);
++
++                        if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically
++                            block.onPlace(serverLevel, newblockposition, oldBlock, true, context);
++                        }
++
++                        serverLevel.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, serverLevel.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
++                    }
++
++                    if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
++                        BlockPos bp = clickedPos;
++                        if (!serverLevel.getBlockState(clickedPos).canBeReplaced()) {
++                            if (!serverLevel.getBlockState(clickedPos).isSolid()) {
++                                bp = null;
++                            } else {
++                                bp = bp.relative(context.getClickedFace());
++                            }
++                        }
++                        if (bp != null) {
++                            net.minecraft.world.level.block.entity.BlockEntity te = serverLevel.getBlockEntity(bp);
++                            if (te instanceof net.minecraft.world.level.block.entity.SkullBlockEntity) {
++                                net.minecraft.world.level.block.WitherSkullBlock.checkSpawn(serverLevel, bp, (net.minecraft.world.level.block.entity.SkullBlockEntity) te);
++                            }
++                        }
++                    }
++
++                    // SPIGOT-4678
++                    if (this.item instanceof SignItem && SignItem.openSign != null) {
++                        try {
++                            if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity tileentitysign) {
++                                if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock blocksign) {
++                                    blocksign.openTextEdit(player, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Craftbukkit // Paper - Add PlayerOpenSignEvent
++                                }
++                            }
++                        } finally {
++                            SignItem.openSign = null;
++                        }
++                    }
++
++                    // SPIGOT-7315: Moved from BlockBed#setPlacedBy
++                    if (placeEvent != null && this.item instanceof BedItem) {
++                        BlockPos position = ((org.bukkit.craftbukkit.block.CraftBlock) placeEvent.getBlock()).getPosition();
++                        net.minecraft.world.level.block.state.BlockState blockData = serverLevel.getBlockState(position);
++
++                        if (blockData.getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
++                            serverLevel.blockUpdated(position, net.minecraft.world.level.block.Blocks.AIR);
++                            blockData.updateNeighbourShapes(serverLevel, position, 3);
++                        }
++                    }
++
++                    // SPIGOT-1288 - play sound stripped from ItemBlock
++                    if (this.item instanceof BlockItem) {
++                    // Paper start - Fix spigot sound playing for BlockItem ItemStacks
++                    BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos();
++                    net.minecraft.world.level.block.state.BlockState blockData = serverLevel.getBlockState(position);
++                    net.minecraft.world.level.block.SoundType soundeffecttype = blockData.getSoundType();
++                    // Paper end - Fix spigot sound playing for BlockItem ItemStacks
++                        serverLevel.playSound(player, clickedPos, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
++                    }
++
++                    player.awardStat(Stats.ITEM_USED.get(item));
++                }
+             }
++            serverLevel.capturedTileEntities.clear();
++            serverLevel.capturedBlockStates.clear();
++            // CraftBukkit end
+ 
+             return interactionResult;
+         }
+@@ -470,30 +_,69 @@
+     }
+ 
+     public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
+-        int i = this.processDurabilityChange(damage, level, player);
++        // Paper start - add force boolean overload
++        this.hurtAndBreak(damage, level, player, onBreak, false);
++    }
++    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak, boolean force) {  // Paper - Add EntityDamageItemEvent
++        // Paper end
++        final int originalDamage = damage; // Paper - Expand PlayerItemDamageEvent
++        int i = this.processDurabilityChange(damage, level, player, force); // Paper
++        // CraftBukkit start
++        if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++            org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i, originalDamage); // Paper - Add EntityDamageItemEvent
++            event.getPlayer().getServer().getPluginManager().callEvent(event);
++
++            if (i != event.getDamage() || event.isCancelled()) {
++                event.getPlayer().updateInventory();
++            }
++            if (event.isCancelled()) {
++                return;
++            }
++
++            i = event.getDamage();
++            // Paper start - Add EntityDamageItemEvent
++        } else if (player != null) {
++            io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i);
++            if (!event.callEvent()) {
++                return;
++            }
++            i = event.getDamage();
++            // Paper end - Add EntityDamageItemEvent
++        }
++        // CraftBukkit end
+         if (i != 0) {
+             this.applyDamage(this.getDamageValue() + i, player, onBreak);
+         }
+     }
+ 
+-    private int processDurabilityChange(int damage, ServerLevel level, @Nullable ServerPlayer player) {
++    private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent
++        // Paper start - itemstack damage api
++        return processDurabilityChange(damage, level, player, false);
++    }
++    private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player, boolean force) {
++        // Paper end - itemstack damage api
+         if (!this.isDamageableItem()) {
+             return 0;
+-        } else if (player != null && player.hasInfiniteMaterials()) {
++        } else if (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force) { // Paper - Add EntityDamageItemEvent
+             return 0;
+         } else {
+             return damage > 0 ? EnchantmentHelper.processDurabilityChange(level, this, damage) : damage;
+         }
+     }
+ 
+-    private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
+-        if (player != null) {
+-            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
++    private void applyDamage(int damage, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
++        if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent
+         }
+ 
+         this.setDamageValue(damage);
+         if (this.isBroken()) {
+             Item item = this.getItem();
++            // CraftBukkit start - Check for item breaking
++            if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++                org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
++            }
++            // CraftBukkit end
+             this.shrink(1);
+             onBreak.accept(item);
+         }
+@@ -512,9 +_,14 @@
+     }
+ 
+     public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) {
++        // Paper start - add param to skip infinite mats check
++        this.hurtAndBreak(amount, entity, slot, false);
++    }
++    public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) {
++        // Paper end - add param to skip infinite mats check
+         if (entity.level() instanceof ServerLevel serverLevel) {
+             this.hurtAndBreak(
+-                amount, serverLevel, entity instanceof ServerPlayer serverPlayer ? serverPlayer : null, item -> entity.onEquippedItemBroken(item, slot)
++                amount, serverLevel, entity, item -> {if (slot != null) entity.onEquippedItemBroken(item, slot); }, force // Paper - Add EntityDamageItemEvent & itemstack damage API - do not process entity related callbacks when damaging from API
+             );
+         }
+     }
+@@ -715,6 +_,12 @@
+         return this.getItem().useOnRelease(this);
+     }
+ 
++    // CraftBukkit start
++    public void restorePatch(DataComponentPatch datacomponentpatch) {
++        this.components.restorePatch(datacomponentpatch);
++    }
++    // CraftBukkit end
++
+     @Nullable
+     public <T> T set(DataComponentType<? super T> component, @Nullable T value) {
+         return this.components.set(component, value);
+@@ -748,6 +_,25 @@
+         }
+     }
+ 
++    // Paper start - (this is just a good no conflict location)
++    public org.bukkit.inventory.ItemStack asBukkitMirror() {
++        return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
++    }
++    public org.bukkit.inventory.ItemStack asBukkitCopy() {
++        return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.copy());
++    }
++    public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
++        return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemstack);
++    }
++    private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
++    public org.bukkit.inventory.ItemStack getBukkitStack() {
++        if (bukkitStack == null || bukkitStack.handle != this) {
++            bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
++        }
++        return bukkitStack;
++    }
++    // Paper end
++
+     public void applyComponents(DataComponentPatch components) {
+         this.components.applyPatch(components);
+         this.getItem().verifyComponentsAfterLoad(this);
+@@ -1016,6 +_,19 @@
+         EnchantmentHelper.forEachModifier(this, equipmentSLot, action);
+     }
+ 
++    // CraftBukkit start
++    @Deprecated
++    public void setItem(Item item) {
++        this.bukkitStack = null; // Paper
++        this.item = item;
++        // Paper start - change base component prototype
++        final DataComponentPatch patch = this.getComponentsPatch();
++        this.components = new PatchedDataComponentMap(this.item.components());
++        this.applyComponents(patch);
++        // Paper end - change base component prototype
++    }
++    // CraftBukkit end
++
+     public Component getDisplayName() {
+         MutableComponent mutableComponent = Component.empty().append(this.getHoverName());
+         if (this.has(DataComponents.CUSTOM_NAME)) {
+@@ -1072,7 +_,7 @@
+     }
+ 
+     public void consume(int amount, @Nullable LivingEntity entity) {
+-        if (entity == null || !entity.hasInfiniteMaterials()) {
++        if ((entity == null || !entity.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit
+             this.shrink(amount);
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ItemUtils.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/item/ItemUtils.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
index 163c53a0d4..0956357249 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ItemUtils.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/item/ItemUtils.java
 +++ b/net/minecraft/world/item/ItemUtils.java
-@@ -41,7 +41,15 @@
-     public static void onContainerDestroyed(ItemEntity itemEntity, Iterable<ItemStack> contents) {
-         Level level = itemEntity.level();
+@@ -41,7 +_,15 @@
+     public static void onContainerDestroyed(ItemEntity container, Iterable<ItemStack> contents) {
+         Level level = container.level();
          if (!level.isClientSide) {
--            contents.forEach(stack -> level.addFreshEntity(new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack)));
+-            contents.forEach(itemStack -> level.addFreshEntity(new ItemEntity(level, container.getX(), container.getY(), container.getZ(), itemStack)));
 +            // Paper start - call EntityDropItemEvent
 +            contents.forEach(stack -> {
-+                ItemEntity droppedItem = new ItemEntity(level, itemEntity.getX(), itemEntity.getY(), itemEntity.getZ(), stack);
-+                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(itemEntity.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity());
++                ItemEntity droppedItem = new ItemEntity(level, container.getX(), container.getY(), container.getZ(), stack);
++                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(container.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity());
 +                if (event.callEvent()) {
 +                    level.addFreshEntity(droppedItem);
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch
new file mode 100644
index 0000000000..284a57db81
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/item/LeadItem.java
++++ b/net/minecraft/world/item/LeadItem.java
+@@ -28,23 +_,43 @@
+         if (blockState.is(BlockTags.FENCES)) {
+             Player player = context.getPlayer();
+             if (!level.isClientSide && player != null) {
+-                return bindPlayerMobs(player, level, clickedPos);
++                return bindPlayerMobs(player, level, clickedPos, context.getHand()); // CraftBukkit - Pass hand
+             }
+         }
+ 
+         return InteractionResult.PASS;
+     }
+ 
+-    public static InteractionResult bindPlayerMobs(Player player, Level level, BlockPos pos) {
++    public static InteractionResult bindPlayerMobs(Player player, Level level, BlockPos pos, net.minecraft.world.InteractionHand interactionHand) { // CraftBukkit - Add InteractionHand
+         LeashFenceKnotEntity leashFenceKnotEntity = null;
+         List<Leashable> list = leashableInArea(level, pos, leashable1 -> leashable1.getLeashHolder() == player);
+ 
+-        for (Leashable leashable : list) {
++        for (java.util.Iterator<Leashable> iterator = list.iterator(); iterator.hasNext();) { // Paper - use iterator to remove
++            Leashable leashable = iterator.next(); // Paper - use iterator to remove
+             if (leashFenceKnotEntity == null) {
+                 leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos);
++                // CraftBukkit start - fire HangingPlaceEvent
++                org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(interactionHand);
++                org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) leashFenceKnotEntity.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.block.BlockFace.SELF, hand);
++                level.getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    leashFenceKnotEntity.discard(null); // CraftBukkit - add Bukkit remove cause
++                    return InteractionResult.PASS;
++                }
++                // CraftBukkit end
+                 leashFenceKnotEntity.playPlacementSound();
+             }
+ 
++            // CraftBukkit start
++            if (leashable instanceof Entity leashed) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(leashed, leashFenceKnotEntity, player, interactionHand).isCancelled()) {
++                    iterator.remove();
++                    continue;
++                }
++            }
++            // CraftBukkit end
++
+             leashable.setLeashedTo(leashFenceKnotEntity, true);
+         }
+ 
+@@ -52,9 +_,20 @@
+             level.gameEvent(GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of(player));
+             return InteractionResult.SUCCESS_SERVER;
+         } else {
++            // CraftBukkit start - remove leash if we do not leash any entity because of the cancelled event
++            if (leashFenceKnotEntity != null) {
++                leashFenceKnotEntity.discard(null);
++            }
++            // CraftBukkit end
+             return InteractionResult.PASS;
+         }
+     }
++
++    // CraftBukkit start
++    public static InteractionResult bindPlayerMobs(Player player, Level world, BlockPos pos) {
++        return LeadItem.bindPlayerMobs(player, world, pos, net.minecraft.world.InteractionHand.MAIN_HAND);
++    }
++    // CraftBukkit end
+ 
+     public static List<Leashable> leashableInArea(Level level, BlockPos pos, Predicate<Leashable> predicate) {
+         double d = 7.0;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/LingeringPotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/item/LingeringPotionItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch
index 04fd231da8..3c0c4271a4 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/LingeringPotionItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/LingeringPotionItem.java.patch
@@ -1,21 +1,21 @@
 --- a/net/minecraft/world/item/LingeringPotionItem.java
 +++ b/net/minecraft/world/item/LingeringPotionItem.java
-@@ -24,6 +24,10 @@
+@@ -24,6 +_,10 @@
  
      @Override
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
 +        // Paper start - PlayerLaunchProjectileEvent
-+        final InteractionResult wrapper = super.use(world, user, hand);
++        final InteractionResult wrapper = super.use(level, player, hand);
 +        if (wrapper instanceof InteractionResult.Fail) return wrapper;
 +        // Paper end - PlayerLaunchProjectileEvent
-         world.playSound(
+         level.playSound(
              null,
-             user.getX(),
-@@ -34,6 +38,6 @@
+             player.getX(),
+@@ -34,6 +_,6 @@
              0.5F,
-             0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)
+             0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
          );
--        return super.use(world, user, hand);
+-        return super.use(level, player, hand);
 +        return wrapper; // Paper - PlayerLaunchProjectileEvent
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch
new file mode 100644
index 0000000000..c31ec01d7b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/MapItem.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/MapItem.java
++++ b/net/minecraft/world/item/MapItem.java
+@@ -99,8 +_,8 @@
+                             int i9 = (i1 / i + i6 - 64) * i;
+                             int i10 = (i2 / i + i7 - 64) * i;
+                             Multiset<MapColor> multiset = LinkedHashMultiset.create();
+-                            LevelChunk chunk = level.getChunk(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10));
+-                            if (!chunk.isEmpty()) {
++                            LevelChunk chunk = level.getChunkIfLoaded(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10)); // Paper - Maps shouldn't load chunks
++                            if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks
+                                 int i11 = 0;
+                                 double d1 = 0.0;
+                                 if (level.dimensionType().hasCeiling()) {
+@@ -207,7 +_,7 @@
+ 
+                 for (int i5 = 0; i5 < 128; i5++) {
+                     for (int i6 = 0; i6 < 128; i6++) {
+-                        Holder<Biome> biome = serverLevel.getBiome(mutableBlockPos.set((i3 + i6) * i, 0, (i4 + i5) * i));
++                        Holder<Biome> biome = serverLevel.getUncachedNoiseBiome((i3 + i6) * i, 0, (i4 + i5) * i); // Paper - Perf: Use seed based lookup for treasure maps
+                         flags[i5 * 128 + i6] = biome.is(BiomeTags.WATER_ON_MAP_OUTLINES);
+                     }
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
new file mode 100644
index 0000000000..3af3872029
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/item/MinecartItem.java
++++ b/net/minecraft/world/item/MinecartItem.java
+@@ -57,7 +_,13 @@
+                 }
+ 
+                 if (level instanceof ServerLevel serverLevel) {
+-                    serverLevel.addFreshEntity(abstractMinecart);
++                    // CraftBukkit start
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, abstractMinecart).isCancelled()) {
++                    if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                        return InteractionResult.FAIL;
++                    }
++                    // CraftBukkit end
++                    if (!serverLevel.addFreshEntity(abstractMinecart)) return InteractionResult.PASS; // CraftBukkit
+                     serverLevel.gameEvent(
+                         GameEvent.ENTITY_PLACE, clickedPos, GameEvent.Context.of(context.getPlayer(), serverLevel.getBlockState(clickedPos.below()))
+                     );
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/NameTagItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/item/NameTagItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
index 735436752e..37e96e3a20 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/NameTagItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
@@ -1,13 +1,13 @@
 --- a/net/minecraft/world/item/NameTagItem.java
 +++ b/net/minecraft/world/item/NameTagItem.java
-@@ -18,8 +18,13 @@
+@@ -18,8 +_,13 @@
          Component component = stack.get(DataComponents.CUSTOM_NAME);
-         if (component != null && entity.getType().canSerialize() && entity.canBeNameTagged()) {
-             if (!user.level().isClientSide && entity.isAlive()) {
--                entity.setCustomName(component);
--                if (entity instanceof Mob mob) {
+         if (component != null && target.getType().canSerialize() && target.canBeNameTagged()) {
+             if (!player.level().isClientSide && target.isAlive()) {
+-                target.setCustomName(component);
+-                if (target instanceof Mob mob) {
 +                // Paper start - Add PlayerNameEntityEvent
-+                io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true);
++                io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity(), target.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true);
 +                if (!event.callEvent()) return InteractionResult.PASS;
 +                LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle();
 +                newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch
new file mode 100644
index 0000000000..a253469109
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/PotionItem.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/item/PotionItem.java
++++ b/net/minecraft/world/item/PotionItem.java
+@@ -42,6 +_,12 @@
+         PotionContents potionContents = itemInHand.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
+         BlockState blockState = level.getBlockState(clickedPos);
+         if (context.getClickedFace() != Direction.DOWN && blockState.is(BlockTags.CONVERTABLE_TO_MUD) && potionContents.is(Potions.WATER)) {
++            // Paper start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, Blocks.MUD.defaultBlockState())) {
++                player.containerMenu.sendAllDataToRemote();
++                return InteractionResult.PASS;
++            }
++            // Paper end
+             level.playSound(null, clickedPos, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F);
+             player.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemInHand, player, new ItemStack(Items.GLASS_BOTTLE)));
+             player.awardStat(Stats.ITEM_USED.get(itemInHand.getItem()));
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
new file mode 100644
index 0000000000..4e04b8e5b1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/item/ProjectileWeaponItem.java
++++ b/net/minecraft/world/item/ProjectileWeaponItem.java
+@@ -62,12 +_,25 @@
+                 float f4 = f2 + f3 * ((i + 1) / 2) * f1;
+                 f3 = -f3;
+                 int i1 = i;
+-                Projectile.spawnProjectile(
+-                    this.createProjectile(level, shooter, weapon, itemStack, isCrit),
+-                    level,
+-                    itemStack,
+-                    projectile -> this.shootProjectile(shooter, projectile, i1, velocity, inaccuracy, f4, target)
+-                );
++                // CraftBukkit start
++                Projectile projectile = this.createProjectile(level, shooter, weapon, itemStack, isCrit);
++                this.shootProjectile(shooter, projectile, i1, velocity, inaccuracy, f4, target);
++
++                org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, weapon, itemStack, projectile, hand, velocity, true);
++                if (event.isCancelled()) {
++                    event.getProjectile().remove();
++                    return;
++                }
++
++                if (event.getProjectile() == projectile.getBukkitEntity()) {
++                    if (Projectile.spawnProjectile(projectile, level, itemStack).isRemoved()) {
++                        if (shooter instanceof net.minecraft.server.level.ServerPlayer) {
++                            ((net.minecraft.server.level.ServerPlayer) shooter).getBukkitEntity().updateInventory();
++                        }
++                        return;
++                    }
++                }
++                // CraftBukkit end
+                 weapon.hurtAndBreak(this.getDurabilityUse(itemStack), shooter, LivingEntity.getSlotForHand(hand));
+                 if (weapon.isEmpty()) {
+                     break;
+@@ -95,6 +_,11 @@
+     }
+ 
+     protected static List<ItemStack> draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter) {
++        // Paper start
++        return draw(weapon, ammo, shooter, true);
++    }
++    protected static List<ItemStack> draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter, boolean consume) {
++        // Paper end
+         if (ammo.isEmpty()) {
+             return List.of();
+         } else {
+@@ -103,7 +_,7 @@
+             ItemStack itemStack = ammo.copy();
+ 
+             for (int i1 = 0; i1 < i; i1++) {
+-                ItemStack itemStack1 = useAmmo(weapon, i1 == 0 ? ammo : itemStack, shooter, i1 > 0);
++                ItemStack itemStack1 = useAmmo(weapon, i1 == 0 ? ammo : itemStack, shooter, i1 > 0 || !consume); // Paper
+                 if (!itemStack1.isEmpty()) {
+                     list.add(itemStack1);
+                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ServerItemCooldowns.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch
similarity index 86%
rename from paper-server/patches/unapplied/net/minecraft/world/item/ServerItemCooldowns.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch
index b9e17d595b..6e1f3fcec1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ServerItemCooldowns.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ServerItemCooldowns.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/item/ServerItemCooldowns.java
 +++ b/net/minecraft/world/item/ServerItemCooldowns.java
-@@ -11,7 +11,40 @@
+@@ -11,6 +_,39 @@
          this.player = player;
      }
  
 +    // Paper start - Add PlayerItemCooldownEvent
-     @Override
++    @Override
 +    public void addCooldown(ItemStack item, int duration) {
 +        final ResourceLocation cooldownGroup = this.getCooldownGroup(item);
 +        final io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent(
@@ -37,7 +37,6 @@
 +    }
 +    // Paper end - Add PlayerItemCooldownEvent
 +
-+    @Override
-     protected void onCooldownStarted(ResourceLocation groupId, int duration) {
-         super.onCooldownStarted(groupId, duration);
-         this.player.connection.send(new ClientboundCooldownPacket(groupId, duration));
+     @Override
+     protected void onCooldownStarted(ResourceLocation group, int cooldown) {
+         super.onCooldownStarted(group, cooldown);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch
new file mode 100644
index 0000000000..142d691825
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ShovelItem.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/item/ShovelItem.java
++++ b/net/minecraft/world/item/ShovelItem.java
+@@ -46,20 +_,29 @@
+             Player player = context.getPlayer();
+             BlockState blockState1 = FLATTENABLES.get(blockState.getBlock());
+             BlockState blockState2 = null;
++            Runnable afterAction = null; // Paper
+             if (blockState1 != null && level.getBlockState(clickedPos.above()).isAir()) {
+-                level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);
++                afterAction = () -> level.playSound(player, clickedPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper
+                 blockState2 = blockState1;
+             } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) {
++                afterAction = () -> { // Paper
+                 if (!level.isClientSide()) {
+                     level.levelEvent(null, 1009, clickedPos, 0);
+                 }
+ 
+                 CampfireBlock.dowse(context.getPlayer(), level, clickedPos, blockState);
++                }; // Paper
+                 blockState2 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false));
+             }
+ 
+             if (blockState2 != null) {
+                 if (!level.isClientSide) {
++                    // Paper start
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), clickedPos, blockState2)) {
++                        return InteractionResult.PASS;
++                    }
++                    afterAction.run();
++                    // Paper end
+                     level.setBlock(clickedPos, blockState2, 11);
+                     level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, blockState2));
+                     if (player != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch
new file mode 100644
index 0000000000..fe3d4a6926
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/SignItem.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/SignItem.java
++++ b/net/minecraft/world/item/SignItem.java
+@@ -11,6 +_,7 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ 
+ public class SignItem extends StandingAndWallBlockItem {
++    public static BlockPos openSign; // CraftBukkit
+     public SignItem(Block standingBlock, Block wallBlock, Item.Properties properties) {
+         super(standingBlock, wallBlock, Direction.DOWN, properties);
+     }
+@@ -27,7 +_,10 @@
+             && player != null
+             && level.getBlockEntity(pos) instanceof SignBlockEntity signBlockEntity
+             && level.getBlockState(pos).getBlock() instanceof SignBlock signBlock) {
+-            signBlock.openTextEdit(player, signBlockEntity, true);
++            // CraftBukkit start - SPIGOT-4678
++            // signBlock.openTextEdit(player, signBlockEntity, true);
++            SignItem.openSign = pos;
++            // CraftBukkit end
+         }
+ 
+         return flag;
diff --git a/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
new file mode 100644
index 0000000000..77ff25b46f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/item/SnowballItem.java
++++ b/net/minecraft/world/item/SnowballItem.java
+@@ -23,22 +_,38 @@
+     @Override
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+-        level.playSound(
+-            null,
+-            player.getX(),
+-            player.getY(),
+-            player.getZ(),
+-            SoundEvents.SNOWBALL_THROW,
+-            SoundSource.NEUTRAL,
+-            0.5F,
+-            0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+-        );
++        // CraftBukkit start - moved down
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(Snowball::new, serverLevel, itemInHand, player, 0.0F, PROJECTILE_SHOOT_POWER, 1.0F);
++            // Paper start - PlayerLaunchProjectileEvent
++            final Projectile.Delayed<Snowball> snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, serverLevel, itemInHand, player, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F);
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity());
++            if (event.callEvent() && snowball.attemptSpawn()) {
++                player.awardStat(Stats.ITEM_USED.get(this));
++                if (event.shouldConsume()) {
++                    itemInHand.consume(1, player);
++                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++            // Paper end - PlayerLaunchProjectileEvent
++
++                level.playSound(
++                    null,
++                    player.getX(),
++                    player.getY(),
++                    player.getZ(),
++                    SoundEvents.SNOWBALL_THROW,
++                    SoundSource.NEUTRAL,
++                    0.5F,
++                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
++                );
++            } else { if (player instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent - return fail
++                ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++            } return InteractionResult.FAIL; } // Paper - PlayerLaunchProjectileEvent - return fail
++            // CraftBukkit end
+         }
+ 
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
++        // Paper - PlayerLaunchProjectileEvent - moved up
++        // itemInHand.consume(1, player); // CraftBukkit - moved up
+         return InteractionResult.SUCCESS;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch
new file mode 100644
index 0000000000..03672c4a17
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/SpawnEggItem.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/item/SpawnEggItem.java
++++ b/net/minecraft/world/item/SpawnEggItem.java
+@@ -55,6 +_,7 @@
+             Direction clickedFace = context.getClickedFace();
+             BlockState blockState = level.getBlockState(clickedPos);
+             if (level.getBlockEntity(clickedPos) instanceof Spawner spawner) {
++                if (level.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation
+                 EntityType<?> type = this.getType(level.registryAccess(), itemInHand);
+                 spawner.setEntityId(type, level.getRandom());
+                 level.sendBlockUpdated(clickedPos, blockState, blockState, 3);
+@@ -169,7 +_,7 @@
+                     return Optional.empty();
+                 } else {
+                     breedOffspring.moveTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F);
+-                    serverLevel.addFreshEntityWithPassengers(breedOffspring);
++                    serverLevel.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit
+                     breedOffspring.setCustomName(stack.get(DataComponents.CUSTOM_NAME));
+                     stack.consume(1, player);
+                     return Optional.of(breedOffspring);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/SplashPotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/item/SplashPotionItem.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch
index bdf8aca4d5..1744dbddc9 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/SplashPotionItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/SplashPotionItem.java.patch
@@ -1,21 +1,21 @@
 --- a/net/minecraft/world/item/SplashPotionItem.java
 +++ b/net/minecraft/world/item/SplashPotionItem.java
-@@ -14,6 +14,10 @@
+@@ -14,6 +_,10 @@
  
      @Override
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
 +        // Paper start - PlayerLaunchProjectileEvent
-+        final InteractionResult wrapper = super.use(world, user, hand);
++        final InteractionResult wrapper = super.use(level, player, hand);
 +        if (wrapper instanceof InteractionResult.Fail) return wrapper;
 +        // Paper end - PlayerLaunchProjectileEvent
-         world.playSound(
+         level.playSound(
              null,
-             user.getX(),
-@@ -24,6 +28,6 @@
+             player.getX(),
+@@ -24,6 +_,6 @@
              0.5F,
-             0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)
+             0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
          );
--        return super.use(world, user, hand);
+-        return super.use(level, player, hand);
 +        return wrapper; // Paper - PlayerLaunchProjectileEvent
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
new file mode 100644
index 0000000000..65b7227e59
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/item/StandingAndWallBlockItem.java
++++ b/net/minecraft/world/item/StandingAndWallBlockItem.java
+@@ -42,7 +_,20 @@
+             }
+         }
+ 
+-        return blockState != null && level.isUnobstructed(blockState, clickedPos, CollisionContext.empty()) ? blockState : null;
++        // return blockState != null && level.isUnobstructed(blockState, clickedPos, CollisionContext.empty()) ? blockState : null;
++        // CraftBukkit start
++        if (blockState != null) {
++            boolean defaultReturn = level.isUnobstructed(blockState, clickedPos, CollisionContext.empty());
++            org.bukkit.entity.Player player = (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
++
++            org.bukkit.event.block.BlockCanBuildEvent event = new org.bukkit.event.block.BlockCanBuildEvent(org.bukkit.craftbukkit.block.CraftBlock.at(context.getLevel(), clickedPos), player, org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
++            context.getLevel().getCraftServer().getPluginManager().callEvent(event);
++
++            return (event.isBuildable()) ? blockState : null;
++        } else {
++            return null;
++        }
++        // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
new file mode 100644
index 0000000000..b5afefd1a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/ThrowablePotionItem.java
++++ b/net/minecraft/world/item/ThrowablePotionItem.java
+@@ -22,11 +_,29 @@
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
++            // Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
++            // Paper start - PlayerLaunchProjectileEvent
++            final Projectile.Delayed<ThrownPotion> thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
++            // Paper start - PlayerLaunchProjectileEvent
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity());
++            if (event.callEvent() && thrownPotion.attemptSpawn()) {
++                if (event.shouldConsume()) {
++                    itemInHand.consume(1, player);
++                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++
++                player.awardStat(Stats.ITEM_USED.get(this));
++            } else {
++                if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++                return InteractionResult.FAIL;
++            }
++            // Paper end - PlayerLaunchProjectileEvent
+         }
+ 
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
++        // Paper - PlayerLaunchProjectileEvent - move up
+         return InteractionResult.SUCCESS;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
new file mode 100644
index 0000000000..b07462cc0f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/item/TridentItem.java
++++ b/net/minecraft/world/item/TridentItem.java
+@@ -87,19 +_,37 @@
+                         .orElse(SoundEvents.TRIDENT_THROW);
+                     player.awardStat(Stats.ITEM_USED.get(this));
+                     if (level instanceof ServerLevel serverLevel) {
+-                        stack.hurtWithoutBreaking(1, player);
++                        // stack.hurtWithoutBreaking(1, player); // CraftBukkit - moved down
+                         if (tridentSpinAttackStrength == 0.0F) {
+-                            ThrownTrident thrownTrident = Projectile.spawnProjectileFromRotation(
++                            Projectile.Delayed<ThrownTrident> tridentDelayed = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent
+                                 ThrownTrident::new, serverLevel, stack, player, 0.0F, 2.5F, 1.0F
+                             );
++                            // Paper start - PlayerLaunchProjectileEvent
++                            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity());
++                            if (!event.callEvent() || !tridentDelayed.attemptSpawn()) {
++                                // CraftBukkit start
++                            // Paper end - PlayerLaunchProjectileEvent
++                                if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
++                                    serverPlayer.getBukkitEntity().updateInventory();
++                                }
++                                return false;
++                            }
++                            ThrownTrident thrownTrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent
++                            if (event.shouldConsume()) stack.hurtWithoutBreaking(1, player); // Paper - PlayerLaunchProjectileEvent
++                            thrownTrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved
++                            // CraftBukkit end
+                             if (player.hasInfiniteMaterials()) {
+                                 thrownTrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
+-                            } else {
++                            } else if (event.shouldConsume()) { // Paper - PlayerLaunchProjectileEvent
+                                 player.getInventory().removeItem(stack);
+                             }
+ 
+                             level.playSound(null, thrownTrident, holder.value(), SoundSource.PLAYERS, 1.0F, 1.0F);
+                             return true;
++                            // CraftBukkit start - SPIGOT-5458 also need in this branch :(
++                        } else {
++                            stack.hurtWithoutBreaking(1, player);
++                            // CraftBukkit end
+                         }
+                     }
+ 
+@@ -113,6 +_,7 @@
+                         f *= tridentSpinAttackStrength / squareRoot;
+                         f1 *= tridentSpinAttackStrength / squareRoot;
+                         f2 *= tridentSpinAttackStrength / squareRoot;
++                        org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(player, stack, f, f1, f2); // CraftBukkit
+                         player.push(f, f1, f2);
+                         player.startAutoSpinAttack(20, 8.0F, stack);
+                         if (player.onGround()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch
new file mode 100644
index 0000000000..26a26944da
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/WindChargeItem.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/item/WindChargeItem.java
++++ b/net/minecraft/world/item/WindChargeItem.java
+@@ -27,7 +_,7 @@
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(
++            final Projectile.Delayed<WindCharge> windCharge = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent
+                 (level1, owner, spawnedFrom) -> new WindCharge(player, level, player.position().x(), player.getEyePosition().y(), player.position().z()),
+                 serverLevel,
+                 itemInHand,
+@@ -36,6 +_,22 @@
+                 PROJECTILE_SHOOT_POWER,
+                 1.0F
+             );
++            // Paper start - PlayerLaunchProjectileEvent
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) windCharge.projectile().getBukkitEntity());
++            if (!event.callEvent() || !windCharge.attemptSpawn()) {
++                player.containerMenu.sendAllDataToRemote();
++                if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
++                    serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundCooldownPacket(player.getCooldowns().getCooldownGroup(itemInHand), 0)); // prevent visual desync of cooldown on the slot
++                }
++                return InteractionResult.FAIL;
++            }
++
++            player.awardStat(Stats.ITEM_USED.get(this));
++            if (event.shouldConsume()) itemInHand.consume(1, player);
++            else if (!player.hasInfiniteMaterials()) {
++                player.containerMenu.sendAllDataToRemote();
++            }
++            // Paper end - PlayerLaunchProjectileEvent
+         }
+ 
+         level.playSound(
+@@ -48,8 +_,7 @@
+             0.5F,
+             0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+         );
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
++        // Paper - PlayerLaunchProjectileEvent; moved up
+         return InteractionResult.SUCCESS;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch
new file mode 100644
index 0000000000..a8a03bfeee
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/WrittenBookItem.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/WrittenBookItem.java
++++ b/net/minecraft/world/item/WrittenBookItem.java
+@@ -41,7 +_,7 @@
+ 
+     public static boolean resolveBookComponents(ItemStack bookStack, CommandSourceStack resolvingSource, @Nullable Player resolvingPlayer) {
+         WrittenBookContent writtenBookContent = bookStack.get(DataComponents.WRITTEN_BOOK_CONTENT);
+-        if (writtenBookContent != null && !writtenBookContent.resolved()) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && writtenBookContent != null && !writtenBookContent.resolved()) { // Paper - Disable component selector resolving in books by default
+             WrittenBookContent writtenBookContent1 = writtenBookContent.resolve(resolvingSource, resolvingPlayer);
+             if (writtenBookContent1 != null) {
+                 bookStack.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent1);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ArmorStandItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ArmorStandItem.java.patch
deleted file mode 100644
index 5be546c2b6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ArmorStandItem.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/item/ArmorStandItem.java
-+++ b/net/minecraft/world/item/ArmorStandItem.java
-@@ -53,6 +53,12 @@
-                     float f = (float) Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
- 
-                     entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F);
-+                    // CraftBukkit start
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityarmorstand).isCancelled()) {
-+                        if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-+                        return InteractionResult.FAIL;
-+                    }
-+                    // CraftBukkit end
-                     worldserver.addFreshEntityWithPassengers(entityarmorstand);
-                     world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F);
-                     entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer());
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/AxeItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/AxeItem.java.patch
deleted file mode 100644
index 858ea3302d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/AxeItem.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/item/AxeItem.java
-+++ b/net/minecraft/world/item/AxeItem.java
-@@ -67,6 +67,11 @@
-                 return InteractionResult.PASS;
-             } else {
-                 ItemStack itemStack = context.getItemInHand();
-+                // Paper start - EntityChangeBlockEvent
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) {
-+                    return InteractionResult.PASS;
-+                }
-+                // Paper end
-                 if (player instanceof ServerPlayer) {
-                     CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack);
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/BlockItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/BlockItem.java.patch
deleted file mode 100644
index 52e43aa9e4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/BlockItem.java.patch
+++ /dev/null
@@ -1,109 +0,0 @@
---- a/net/minecraft/world/item/BlockItem.java
-+++ b/net/minecraft/world/item/BlockItem.java
-@@ -10,9 +10,9 @@
- import net.minecraft.core.registries.Registries;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundSource;
- import net.minecraft.world.InteractionResult;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
-@@ -31,6 +31,10 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.shapes.CollisionContext;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.block.data.CraftBlockData;
-+import org.bukkit.event.block.BlockCanBuildEvent;
-+// CraftBukkit end
- 
- public class BlockItem extends Item {
- 
-@@ -62,6 +66,13 @@
-                 return InteractionResult.FAIL;
-             } else {
-                 BlockState iblockdata = this.getPlacementState(blockactioncontext1);
-+                // CraftBukkit start - special case for handling block placement with water lilies and snow buckets
-+                org.bukkit.block.BlockState blockstate = null;
-+                if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) {
-+                    blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos());
-+                }
-+                final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - Reset placed block on exception
-+                // CraftBukkit end
- 
-                 if (iblockdata == null) {
-                     return InteractionResult.FAIL;
-@@ -76,9 +87,34 @@
- 
-                     if (iblockdata1.is(iblockdata.getBlock())) {
-                         iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1);
-+                        // Paper start - Reset placed block on exception
-+                        try {
-                         this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1);
-                         BlockItem.updateBlockEntityComponents(world, blockposition, itemstack);
-+                        } catch (Exception e) {
-+                            oldBlockstate.update(true, false);
-+                            if (entityhuman instanceof ServerPlayer player) {
-+                                org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e);
-+                                player.getBukkitEntity().kickPlayer("Packet processing error");
-+                                return InteractionResult.FAIL;
-+                            }
-+                            throw e; // Rethrow exception if not placed by a player
-+                        }
-+                        // Paper end - Reset placed block on exception
-                         iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack);
-+                        // CraftBukkit start
-+                        if (blockstate != null) {
-+                            org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent((ServerLevel) world, entityhuman, blockactioncontext1.getHand(), blockstate, blockposition.getX(), blockposition.getY(), blockposition.getZ());
-+                            if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
-+                                blockstate.update(true, false);
-+
-+                                if (true) { // Paper - if the event is called here, the inventory should be updated
-+                                    ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541
-+                                }
-+                                return InteractionResult.FAIL;
-+                            }
-+                        }
-+                        // CraftBukkit end
-                         if (entityhuman instanceof ServerPlayer) {
-                             CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) entityhuman, blockposition, itemstack);
-                         }
-@@ -86,7 +122,7 @@
- 
-                     SoundType soundeffecttype = iblockdata1.getSoundType();
- 
--                    world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
-+                    if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker)
-                     world.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1));
-                     itemstack.consume(1, entityhuman);
-                     return InteractionResult.SUCCESS;
-@@ -144,8 +180,16 @@
-     protected boolean canPlace(BlockPlaceContext context, BlockState state) {
-         Player entityhuman = context.getPlayer();
-         CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman);
-+        // CraftBukkit start - store default return
-+        Level world = context.getLevel(); // Paper - Cancel hit for vanished players
-+        boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players
-+        org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
- 
--        return (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision);
-+        BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
-+        context.getLevel().getCraftServer().getPluginManager().callEvent(event);
-+
-+        return event.isBuildable();
-+        // CraftBukkit end
-     }
- 
-     protected boolean mustSurvive() {
-@@ -178,7 +222,7 @@
-                         return false;
-                     }
- 
--                    if (tileentitytypes1.onlyOpCanSetNbt() && (player == null || !player.canUseGameMasterBlocks())) {
-+                    if (tileentitytypes1.onlyOpCanSetNbt() && (player == null || !(player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place"))))) { // Spigot - add permission
-                         return false;
-                     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/BoatItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/BoatItem.java.patch
deleted file mode 100644
index c8502fcea3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/BoatItem.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/world/item/BoatItem.java
-+++ b/net/minecraft/world/item/BoatItem.java
-@@ -58,6 +58,13 @@
-             }
- 
-             if (movingobjectpositionblock.getType() == HitResult.Type.BLOCK) {
-+                // CraftBukkit start - Boat placement
-+                org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(user, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, false, hand, movingobjectpositionblock.getLocation());
-+
-+                if (event.isCancelled()) {
-+                    return InteractionResult.PASS;
-+                }
-+                // CraftBukkit end
-                 AbstractBoat abstractboat = this.getBoat(world, movingobjectpositionblock, itemstack, user);
- 
-                 if (abstractboat == null) {
-@@ -68,7 +75,15 @@
-                         return InteractionResult.FAIL;
-                     } else {
-                         if (!world.isClientSide) {
--                            world.addFreshEntity(abstractboat);
-+                            // CraftBukkit start
-+                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(world, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), user, abstractboat, hand).isCancelled()) {
-+                                return InteractionResult.FAIL;
-+                            }
-+
-+                            if (!world.addFreshEntity(abstractboat)) {
-+                                return InteractionResult.PASS;
-+                            }
-+                            // CraftBukkit end
-                             world.gameEvent((Entity) user, (Holder) GameEvent.ENTITY_PLACE, movingobjectpositionblock.getLocation());
-                             itemstack.consume(1, user);
-                         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/BoneMealItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/BoneMealItem.java.patch
deleted file mode 100644
index 4cf7f7eebf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/BoneMealItem.java.patch
+++ /dev/null
@@ -1,41 +0,0 @@
---- a/net/minecraft/world/item/BoneMealItem.java
-+++ b/net/minecraft/world/item/BoneMealItem.java
-@@ -35,24 +35,30 @@
- 
-     @Override
-     public InteractionResult useOn(UseOnContext context) {
--        Level world = context.getLevel();
--        BlockPos blockposition = context.getClickedPos();
--        BlockPos blockposition1 = blockposition.relative(context.getClickedFace());
-+        // CraftBukkit start - extract bonemeal application logic to separate, static method
-+        return BoneMealItem.applyBonemeal(context);
-+    }
- 
--        if (BoneMealItem.growCrop(context.getItemInHand(), world, blockposition)) {
-+    public static InteractionResult applyBonemeal(UseOnContext itemactioncontext) {
-+        // CraftBukkit end
-+        Level world = itemactioncontext.getLevel();
-+        BlockPos blockposition = itemactioncontext.getClickedPos();
-+        BlockPos blockposition1 = blockposition.relative(itemactioncontext.getClickedFace());
-+
-+        if (BoneMealItem.growCrop(itemactioncontext.getItemInHand(), world, blockposition)) {
-             if (!world.isClientSide) {
--                context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
-+                if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
-                 world.levelEvent(1505, blockposition, 15);
-             }
- 
-             return InteractionResult.SUCCESS;
-         } else {
-             BlockState iblockdata = world.getBlockState(blockposition);
--            boolean flag = iblockdata.isFaceSturdy(world, blockposition, context.getClickedFace());
-+            boolean flag = iblockdata.isFaceSturdy(world, blockposition, itemactioncontext.getClickedFace());
- 
--            if (flag && BoneMealItem.growWaterPlant(context.getItemInHand(), world, blockposition1, context.getClickedFace())) {
-+            if (flag && BoneMealItem.growWaterPlant(itemactioncontext.getItemInHand(), world, blockposition1, itemactioncontext.getClickedFace())) {
-                 if (!world.isClientSide) {
--                    context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
-+                    if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
-                     world.levelEvent(1505, blockposition1, 15);
-                 }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/BucketItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/BucketItem.java.patch
deleted file mode 100644
index 6e51878d93..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/BucketItem.java.patch
+++ /dev/null
@@ -1,168 +0,0 @@
---- a/net/minecraft/world/item/BucketItem.java
-+++ b/net/minecraft/world/item/BucketItem.java
-@@ -6,6 +6,8 @@
- import net.minecraft.core.Direction;
- import net.minecraft.core.Holder;
- import net.minecraft.core.particles.ParticleTypes;
-+import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
-@@ -29,9 +31,17 @@
- import net.minecraft.world.level.material.Fluids;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.HitResult;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
-+import org.bukkit.event.player.PlayerBucketEmptyEvent;
-+import org.bukkit.event.player.PlayerBucketFillEvent;
-+// CraftBukkit end
- 
- public class BucketItem extends Item implements DispensibleContainerItem {
- 
-+    private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
-+
-     public final Fluid content;
- 
-     public BucketItem(Fluid fluid, Item.Properties settings) {
-@@ -63,7 +73,18 @@
- 
-                     if (block instanceof BucketPickup) {
-                         BucketPickup ifluidsource = (BucketPickup) block;
-+                        // CraftBukkit start
-+                        ItemStack dummyFluid = ifluidsource.pickupBlock(user, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata);
-+                        if (dummyFluid.isEmpty()) return InteractionResult.FAIL; // Don't fire event if the bucket won't be filled.
-+                        PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand);
- 
-+                        if (event.isCancelled()) {
-+                            // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks
-+                            ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541
-+                            return InteractionResult.FAIL;
-+                        }
-+                        // CraftBukkit end
-+
-                         itemstack1 = ifluidsource.pickupBlock(user, world, blockposition, iblockdata);
-                         if (!itemstack1.isEmpty()) {
-                             user.awardStat(Stats.ITEM_USED.get(this));
-@@ -71,7 +92,7 @@
-                                 user.playSound(soundeffect, 1.0F, 1.0F);
-                             });
-                             world.gameEvent((Entity) user, (Holder) GameEvent.FLUID_PICKUP, blockposition);
--                            ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, user, itemstack1);
-+                            ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, user, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
- 
-                             if (!world.isClientSide) {
-                                 CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer) user, itemstack1);
-@@ -86,7 +107,7 @@
-                     iblockdata = world.getBlockState(blockposition);
-                     BlockPos blockposition2 = iblockdata.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockposition : blockposition1;
- 
--                    if (this.emptyContents(user, world, blockposition2, movingobjectpositionblock)) {
-+                    if (this.emptyContents(user, world, blockposition2, movingobjectpositionblock, movingobjectpositionblock.getDirection(), blockposition, itemstack, hand)) { // CraftBukkit
-                         this.checkExtraContent(user, world, itemstack, blockposition2);
-                         if (user instanceof ServerPlayer) {
-                             CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) user, blockposition2, itemstack);
-@@ -106,6 +127,13 @@
-     }
- 
-     public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) {
-+        // Paper start - Fix PlayerBucketEmptyEvent result itemstack
-+        if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) {
-+            ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent;
-+            itemLeftInHandAfterPlayerBucketEmptyEvent = null;
-+            return itemInHand;
-+        }
-+        // Paper end - Fix PlayerBucketEmptyEvent result itemstack
-         return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack;
-     }
- 
-@@ -114,6 +142,12 @@
- 
-     @Override
-     public boolean emptyContents(@Nullable Player player, Level world, BlockPos pos, @Nullable BlockHitResult hitResult) {
-+        // CraftBukkit start
-+        return this.emptyContents(player, world, pos, hitResult, null, null, null, InteractionHand.MAIN_HAND);
-+    }
-+
-+    public boolean emptyContents(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack, InteractionHand enumhand) {
-+        // CraftBukkit end
-         Fluid fluidtype = this.content;
- 
-         if (!(fluidtype instanceof FlowingFluid fluidtypeflowing)) {
-@@ -126,7 +160,7 @@
-             boolean flag1;
-             label70:
-             {
--                iblockdata = world.getBlockState(pos);
-+                iblockdata = world.getBlockState(blockposition);
-                 block = iblockdata.getBlock();
-                 flag = iblockdata.canBeReplaced(this.content);
-                 if (!iblockdata.isAir() && !flag) {
-@@ -134,7 +168,7 @@
-                     {
-                         if (block instanceof LiquidBlockContainer) {
-                             ifluidcontainer = (LiquidBlockContainer) block;
--                            if (ifluidcontainer.canPlaceLiquid(player, world, pos, iblockdata, this.content)) {
-+                            if (ifluidcontainer.canPlaceLiquid(entityhuman, world, blockposition, iblockdata, this.content)) {
-                                 break label67;
-                             }
-                         }
-@@ -149,14 +183,25 @@
- 
-             boolean flag2 = flag1;
- 
-+            // CraftBukkit start
-+            if (flag2 && entityhuman != null) {
-+                PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand);
-+                if (event.isCancelled()) {
-+                    // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks
-+                    ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541
-+                    return false;
-+                }
-+                itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack
-+            }
-+            // CraftBukkit end
-             if (!flag2) {
--                return hitResult != null && this.emptyContents(player, world, hitResult.getBlockPos().relative(hitResult.getDirection()), (BlockHitResult) null);
-+                return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
-             } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
--                int i = pos.getX();
--                int j = pos.getY();
--                int k = pos.getZ();
-+                int i = blockposition.getX();
-+                int j = blockposition.getY();
-+                int k = blockposition.getZ();
- 
--                world.playSound(player, pos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
-+                world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
- 
-                 for (int l = 0; l < 8; ++l) {
-                     world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D);
-@@ -167,20 +212,20 @@
-                 if (block instanceof LiquidBlockContainer) {
-                     ifluidcontainer = (LiquidBlockContainer) block;
-                     if (this.content == Fluids.WATER) {
--                        ifluidcontainer.placeLiquid(world, pos, iblockdata, fluidtypeflowing.getSource(false));
--                        this.playEmptySound(player, world, pos);
-+                        ifluidcontainer.placeLiquid(world, blockposition, iblockdata, fluidtypeflowing.getSource(false));
-+                        this.playEmptySound(entityhuman, world, blockposition);
-                         return true;
-                     }
-                 }
- 
-                 if (!world.isClientSide && flag && !iblockdata.liquid()) {
--                    world.destroyBlock(pos, true);
-+                    world.destroyBlock(blockposition, true);
-                 }
- 
--                if (!world.setBlock(pos, this.content.defaultFluidState().createLegacyBlock(), 11) && !iblockdata.getFluidState().isSource()) {
-+                if (!world.setBlock(blockposition, this.content.defaultFluidState().createLegacyBlock(), 11) && !iblockdata.getFluidState().isSource()) {
-                     return false;
-                 } else {
--                    this.playEmptySound(player, world, pos);
-+                    this.playEmptySound(entityhuman, world, blockposition);
-                     return true;
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/CrossbowItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/CrossbowItem.java.patch
deleted file mode 100644
index d6f5a8f499..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/CrossbowItem.java.patch
+++ /dev/null
@@ -1,48 +0,0 @@
---- a/net/minecraft/world/item/CrossbowItem.java
-+++ b/net/minecraft/world/item/CrossbowItem.java
-@@ -90,7 +90,14 @@
-     public boolean releaseUsing(ItemStack stack, Level world, LivingEntity user, int remainingUseTicks) {
-         int i = this.getUseDuration(stack, user) - remainingUseTicks;
-         float f = getPowerForTime(i, stack, user);
--        if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) {
-+        // Paper start - Add EntityLoadCrossbowEvent
-+        if (f >= 1.0F && !isCharged(stack)) {
-+            final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(user.getUsedItemHand()));
-+            if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) {
-+                if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote();
-+                return false;
-+            }
-+            // Paper end - Add EntityLoadCrossbowEvent
-             CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack);
-             chargingSounds.end()
-                 .ifPresent(
-@@ -111,8 +118,14 @@
-         }
-     }
- 
--    private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) {
--        List<ItemStack> list = draw(crossbow, shooter.getProjectile(crossbow), shooter);
-+    @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent
-+    private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow)  {
-+        // Paper start - Add EntityLoadCrossbowEvent
-+        return CrossbowItem.tryLoadProjectiles(shooter, crossbow, true);
-+    }
-+    private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow, boolean consume) {
-+        List<ItemStack> list = draw(crossbow, shooter.getProjectile(crossbow), shooter, consume);
-+        // Paper end - Add EntityLoadCrossbowEvent
-         if (!list.isEmpty()) {
-             crossbow.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list));
-             return true;
-@@ -164,7 +177,11 @@
-     @Override
-     protected Projectile createProjectile(Level world, LivingEntity shooter, ItemStack weaponStack, ItemStack projectileStack, boolean critical) {
-         if (projectileStack.is(Items.FIREWORK_ROCKET)) {
--            return new FireworkRocketEntity(world, projectileStack, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true);
-+            // Paper start
-+            FireworkRocketEntity entity =  new FireworkRocketEntity(world, projectileStack, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true);
-+            entity.spawningEntity = shooter.getUUID(); // Paper
-+            return entity;
-+            // Paper end
-         } else {
-             Projectile projectile = super.createProjectile(world, shooter, weaponStack, projectileStack, critical);
-             if (projectile instanceof AbstractArrow abstractArrow) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/DebugStickItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/DebugStickItem.java.patch
deleted file mode 100644
index b81a2063de..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/DebugStickItem.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/item/DebugStickItem.java
-+++ b/net/minecraft/world/item/DebugStickItem.java
-@@ -1,3 +1,4 @@
-+// mc-dev import
- package net.minecraft.world.item;
- 
- import java.util.Collection;
-@@ -52,7 +53,7 @@
-     }
- 
-     public boolean handleInteraction(Player player, BlockState state, LevelAccessor world, BlockPos pos, boolean update, ItemStack stack) {
--        if (!player.canUseGameMasterBlocks()) {
-+        if (!player.canUseGameMasterBlocks() && !(player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.debugstick")) && !player.getBukkitEntity().hasPermission("minecraft.debugstick.always")) { // Spigot
-             return false;
-         } else {
-             Holder<Block> holder = state.getBlockHolder();
-@@ -92,7 +93,7 @@
-     }
- 
-     private static <T extends Comparable<T>> BlockState cycleState(BlockState state, Property<T> property, boolean inverse) {
--        return (BlockState) state.setValue(property, (Comparable) DebugStickItem.getRelative(property.getPossibleValues(), state.getValue(property), inverse));
-+        return (BlockState) state.setValue(property, DebugStickItem.getRelative(property.getPossibleValues(), state.getValue(property), inverse)); // CraftBukkit - decompile error
-     }
- 
-     private static <T> T getRelative(Iterable<T> elements, @Nullable T current, boolean inverse) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/DyeItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/DyeItem.java.patch
deleted file mode 100644
index ca96f3388a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/DyeItem.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/item/DyeItem.java
-+++ b/net/minecraft/world/item/DyeItem.java
-@@ -12,6 +12,7 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.entity.SignBlockEntity;
-+import org.bukkit.event.entity.SheepDyeWoolEvent; // CraftBukkit
- 
- public class DyeItem extends Item implements SignApplicator {
- 
-@@ -30,7 +31,17 @@
-             if (entitysheep.isAlive() && !entitysheep.isSheared() && entitysheep.getColor() != this.dyeColor) {
-                 entitysheep.level().playSound(user, (Entity) entitysheep, SoundEvents.DYE_USE, SoundSource.PLAYERS, 1.0F, 1.0F);
-                 if (!user.level().isClientSide) {
--                    entitysheep.setColor(this.dyeColor);
-+                    // CraftBukkit start
-+                    byte bColor = (byte) this.dyeColor.getId();
-+                    SheepDyeWoolEvent event = new SheepDyeWoolEvent((org.bukkit.entity.Sheep) entitysheep.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData(bColor), (org.bukkit.entity.Player) user.getBukkitEntity());
-+                    entitysheep.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (event.isCancelled()) {
-+                        return InteractionResult.PASS;
-+                    }
-+
-+                    entitysheep.setColor(DyeColor.byId((byte) event.getColor().getWoolData()));
-+                    // CraftBukkit end
-                     stack.shrink(1);
-                 }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/EggItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/EggItem.java.patch
deleted file mode 100644
index 6281e950e3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/EggItem.java.patch
+++ /dev/null
@@ -1,40 +0,0 @@
---- a/net/minecraft/world/item/EggItem.java
-+++ b/net/minecraft/world/item/EggItem.java
-@@ -25,13 +25,32 @@
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemstack = user.getItemInHand(hand);
- 
--        world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+        // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); // CraftBukkit - moved down
-         if (world instanceof ServerLevel worldserver) {
--            Projectile.spawnProjectileFromRotation(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F);
--        }
-+            // CraftBukkit start
-+            // Paper start - PlayerLaunchProjectileEvent
-+            final Projectile.Delayed<ThrownEgg> thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity());
-+            if (event.callEvent() && thrownEgg.attemptSpawn()) {
-+                if (event.shouldConsume()) {
-+                    itemstack.consume(1, user);
-+                } else if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
- 
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemstack.consume(1, user);
-+                world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+                user.awardStat(Stats.ITEM_USED.get(this));
-+            } else {
-+                // Paper end - PlayerLaunchProjectileEvent
-+                if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+                return InteractionResult.FAIL;
-+            }
-+            // CraftBukkit end
-+        }
-+        world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+        // Paper - PlayerLaunchProjectileEvent - moved up
-         return InteractionResult.SUCCESS;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/EndCrystalItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/EndCrystalItem.java.patch
deleted file mode 100644
index 89844fdef2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/EndCrystalItem.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/item/EndCrystalItem.java
-+++ b/net/minecraft/world/item/EndCrystalItem.java
-@@ -30,7 +30,7 @@
-         if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) {
-             return InteractionResult.FAIL;
-         } else {
--            BlockPos blockposition1 = blockposition.above();
-+            BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER
- 
-             if (!world.isEmptyBlock(blockposition1)) {
-                 return InteractionResult.FAIL;
-@@ -47,12 +47,18 @@
-                         EndCrystal entityendercrystal = new EndCrystal(world, d0 + 0.5D, d1, d2 + 0.5D);
- 
-                         entityendercrystal.setShowBottom(false);
-+                        // CraftBukkit start
-+                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityendercrystal).isCancelled()) {
-+                            if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-+                            return InteractionResult.FAIL;
-+                        }
-+                        // CraftBukkit end
-                         world.addFreshEntity(entityendercrystal);
-                         world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.ENTITY_PLACE, blockposition1);
-                         EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight();
- 
-                         if (enderdragonbattle != null) {
--                            enderdragonbattle.tryRespawn();
-+                            enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup
-                         }
-                     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/EnderEyeItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/EnderEyeItem.java.patch
deleted file mode 100644
index c343069562..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/EnderEyeItem.java.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- a/net/minecraft/world/item/EnderEyeItem.java
-+++ b/net/minecraft/world/item/EnderEyeItem.java
-@@ -45,6 +45,11 @@
-                 return InteractionResult.SUCCESS;
-             } else {
-                 BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true);
-+                // Paper start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) {
-+                    return InteractionResult.PASS;
-+                }
-+                // Paper end
- 
-                 Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition);
-                 world.setBlock(blockposition, iblockdata1, 2);
-@@ -62,7 +67,27 @@
-                         }
-                     }
- 
--                    world.globalLevelEvent(1038, blockposition1.offset(1, 0, 1), 0);
-+                    // CraftBukkit start - Use relative location for far away sounds
-+                    // world.globalLevelEvent(1038, blockposition1.offset(1, 0, 1), 0);
-+                    int viewDistance = world.getCraftServer().getViewDistance() * 16;
-+                    BlockPos soundPos = blockposition1.offset(1, 0, 1);
-+                    final net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) world; // Paper - respect global sound events gamerule - ensured by isClientSide check above
-+                    for (ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
-+                        double deltaX = soundPos.getX() - player.getX();
-+                        double deltaZ = soundPos.getZ() - player.getZ();
-+                        double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-+                        final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.endPortalSoundRadius); // Paper - respect global sound events gamerule
-+                        if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule
-+                        if (distanceSquared > viewDistance * viewDistance) {
-+                            double deltaLength = Math.sqrt(distanceSquared);
-+                            double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
-+                            double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, new BlockPos((int) relativeX, (int) soundPos.getY(), (int) relativeZ), 0, true));
-+                        } else {
-+                            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, soundPos, 0, true));
-+                        }
-+                    }
-+                    // CraftBukkit end
-                 }
- 
-                 return InteractionResult.SUCCESS;
-@@ -99,7 +124,11 @@
-                 entityendersignal.setItem(itemstack);
-                 entityendersignal.signalTo(blockposition);
-                 world.gameEvent((Holder) GameEvent.PROJECTILE_SHOOT, entityendersignal.position(), GameEvent.Context.of((Entity) user));
--                world.addFreshEntity(entityendersignal);
-+                // CraftBukkit start
-+                if (!world.addFreshEntity(entityendersignal)) {
-+                    return InteractionResult.FAIL;
-+                }
-+                // CraftBukkit end
-                 if (user instanceof ServerPlayer) {
-                     ServerPlayer entityplayer = (ServerPlayer) user;
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/EnderpearlItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/EnderpearlItem.java.patch
deleted file mode 100644
index e025880d4f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/EnderpearlItem.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/item/EnderpearlItem.java
-+++ b/net/minecraft/world/item/EnderpearlItem.java
-@@ -23,13 +23,32 @@
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemstack = user.getItemInHand(hand);
- 
--        world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-         if (world instanceof ServerLevel worldserver) {
--            Projectile.spawnProjectileFromRotation(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            // CraftBukkit start
-+            // Paper start - PlayerLaunchProjectileEvent
-+            final Projectile.Delayed<ThrownEnderpearl> thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity());
-+            if (event.callEvent() && thrownEnderpearl.attemptSpawn()) {
-+                if (event.shouldConsume()) {
-+                    itemstack.consume(1, user);
-+                } else if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+
-+                world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+                user.awardStat(Stats.ITEM_USED.get(this));
-+            } else {
-+            // Paper end - PlayerLaunchProjectileEvent
-+                if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+                return InteractionResult.FAIL;
-+            }
-         }
-+        world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+        // CraftBukkit end
- 
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemstack.consume(1, user);
-+        // Paper - PlayerLaunchProjectileEvent - moved up
-         return InteractionResult.SUCCESS;
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/FireChargeItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/FireChargeItem.java.patch
deleted file mode 100644
index ee72808622..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/FireChargeItem.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/item/FireChargeItem.java
-+++ b/net/minecraft/world/item/FireChargeItem.java
-@@ -40,12 +40,28 @@
-         if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) {
-             blockposition = blockposition.relative(context.getClickedFace());
-             if (BaseFireBlock.canBePlacedAt(world, blockposition, context.getHorizontalDirection())) {
-+                // CraftBukkit start - fire BlockIgniteEvent
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
-+                    if (!context.getPlayer().getAbilities().instabuild) {
-+                        context.getItemInHand().shrink(1);
-+                    }
-+                    return InteractionResult.PASS;
-+                }
-+                // CraftBukkit end
-                 this.playSound(world, blockposition);
-                 world.setBlockAndUpdate(blockposition, BaseFireBlock.getState(world, blockposition));
-                 world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_PLACE, blockposition);
-                 flag = true;
-             }
-         } else {
-+            // CraftBukkit start - fire BlockIgniteEvent
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
-+                if (!context.getPlayer().getAbilities().instabuild) {
-+                    context.getItemInHand().shrink(1);
-+                }
-+                return InteractionResult.PASS;
-+            }
-+            // CraftBukkit end
-             this.playSound(world, blockposition);
-             world.setBlockAndUpdate(blockposition, (BlockState) iblockdata.setValue(BlockStateProperties.LIT, true));
-             world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_CHANGE, blockposition);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/FishingRodItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/FishingRodItem.java.patch
deleted file mode 100644
index 6c64321f95..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/FishingRodItem.java.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- a/net/minecraft/world/item/FishingRodItem.java
-+++ b/net/minecraft/world/item/FishingRodItem.java
-@@ -14,6 +14,11 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start
-+import org.bukkit.event.player.PlayerFishEvent;
-+import org.bukkit.craftbukkit.CraftEquipmentSlot;
-+// CraftBukkit end
-+
- public class FishingRodItem extends Item {
- 
-     public FishingRodItem(Item.Properties settings) {
-@@ -26,7 +31,7 @@
- 
-         if (user.fishing != null) {
-             if (!world.isClientSide) {
--                int i = user.fishing.retrieve(itemstack);
-+                int i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent
- 
-                 itemstack.hurtAndBreak(i, user, LivingEntity.getSlotForHand(hand));
-             }
-@@ -34,13 +39,24 @@
-             world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_RETRIEVE, SoundSource.NEUTRAL, 1.0F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-             user.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
-         } else {
--            world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+            // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.FISHING_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
-                 int j = (int) (EnchantmentHelper.getFishingTimeReduction(worldserver, itemstack, user) * 20.0F);
-                 int k = EnchantmentHelper.getFishingLuckBonus(worldserver, itemstack, user);
- 
--                Projectile.spawnProjectile(new FishingHook(user, world, k, j), worldserver, itemstack);
-+                // CraftBukkit start
-+                FishingHook entityfishinghook = new FishingHook(user, world, k, j);
-+                PlayerFishEvent playerFishEvent = new PlayerFishEvent((org.bukkit.entity.Player) user.getBukkitEntity(), null, (org.bukkit.entity.FishHook) entityfishinghook.getBukkitEntity(), CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.FISHING);
-+                world.getCraftServer().getPluginManager().callEvent(playerFishEvent);
-+
-+                if (playerFishEvent.isCancelled()) {
-+                    user.fishing = null;
-+                    return InteractionResult.PASS;
-+                }
-+                world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+                Projectile.spawnProjectile(entityfishinghook, worldserver, itemstack);
-+                // CraftBukkit end
-             }
- 
-             user.awardStat(Stats.ITEM_USED.get(this));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/FlintAndSteelItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/FlintAndSteelItem.java.patch
deleted file mode 100644
index 0d633ff507..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/FlintAndSteelItem.java.patch
+++ /dev/null
@@ -1,28 +0,0 @@
---- a/net/minecraft/world/item/FlintAndSteelItem.java
-+++ b/net/minecraft/world/item/FlintAndSteelItem.java
-@@ -37,6 +37,12 @@
-             BlockPos blockposition1 = blockposition.relative(context.getClickedFace());
- 
-             if (BaseFireBlock.canBePlacedAt(world, blockposition1, context.getHorizontalDirection())) {
-+                // CraftBukkit start - Store the clicked block
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition1, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) {
-+                    context.getItemInHand().hurtAndBreak(1, entityhuman, LivingEntity.getSlotForHand(context.getHand()));
-+                    return InteractionResult.PASS;
-+                }
-+                // CraftBukkit end
-                 world.playSound(entityhuman, blockposition1, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F);
-                 BlockState iblockdata1 = BaseFireBlock.getState(world, blockposition1);
- 
-@@ -54,6 +60,12 @@
-                 return InteractionResult.FAIL;
-             }
-         } else {
-+            // CraftBukkit start - Store the clicked block
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) {
-+                context.getItemInHand().hurtAndBreak(1, entityhuman, LivingEntity.getSlotForHand(context.getHand()));
-+                return InteractionResult.PASS;
-+            }
-+            // CraftBukkit end
-             world.playSound(entityhuman, blockposition, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F);
-             world.setBlock(blockposition, (BlockState) iblockdata.setValue(BlockStateProperties.LIT, true), 11);
-             world.gameEvent((Entity) entityhuman, (Holder) GameEvent.BLOCK_CHANGE, blockposition);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/HangingEntityItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/HangingEntityItem.java.patch
deleted file mode 100644
index 79e498f9a5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/HangingEntityItem.java.patch
+++ /dev/null
@@ -1,67 +0,0 @@
---- a/net/minecraft/world/item/HangingEntityItem.java
-+++ b/net/minecraft/world/item/HangingEntityItem.java
-@@ -19,12 +19,16 @@
- import net.minecraft.world.entity.decoration.ItemFrame;
- import net.minecraft.world.entity.decoration.Painting;
- import net.minecraft.world.entity.decoration.PaintingVariant;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.component.CustomData;
- import net.minecraft.world.item.context.UseOnContext;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.gameevent.GameEvent;
- 
-+// CraftBukkit start
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.hanging.HangingPlaceEvent;
-+// CraftBukkit end
-+
- public class HangingEntityItem extends Item {
- 
-     private static final Component TOOLTIP_RANDOM_VARIANT = Component.translatable("painting.random").withStyle(ChatFormatting.GRAY);
-@@ -40,7 +44,7 @@
-         BlockPos blockposition = context.getClickedPos();
-         Direction enumdirection = context.getClickedFace();
-         BlockPos blockposition1 = blockposition.relative(enumdirection);
--        Player entityhuman = context.getPlayer();
-+        net.minecraft.world.entity.player.Player entityhuman = context.getPlayer();
-         ItemStack itemstack = context.getItemInHand();
- 
-         if (entityhuman != null && !this.mayPlace(entityhuman, enumdirection, itemstack, blockposition1)) {
-@@ -75,6 +79,19 @@
- 
-             if (((HangingEntity) object).survives()) {
-                 if (!world.isClientSide) {
-+                    // CraftBukkit start - fire HangingPlaceEvent
-+                    Player who = (context.getPlayer() == null) ? null : (Player) context.getPlayer().getBukkitEntity();
-+                    org.bukkit.block.Block blockClicked = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
-+                    org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection);
-+                    org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand());
-+
-+                    HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) ((HangingEntity) object).getBukkitEntity(), who, blockClicked, blockFace, hand, org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack));
-+                    world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (event.isCancelled()) {
-+                        return InteractionResult.FAIL;
-+                    }
-+                    // CraftBukkit end
-                     ((HangingEntity) object).playPlacementSound();
-                     world.gameEvent((Entity) entityhuman, (Holder) GameEvent.ENTITY_PLACE, ((HangingEntity) object).position());
-                     world.addFreshEntity((Entity) object);
-@@ -88,7 +105,7 @@
-         }
-     }
- 
--    protected boolean mayPlace(Player player, Direction side, ItemStack stack, BlockPos pos) {
-+    protected boolean mayPlace(net.minecraft.world.entity.player.Player player, Direction side, ItemStack stack, BlockPos pos) {
-         return !side.getAxis().isVertical() && player.mayUseItemAt(pos, side, stack);
-     }
- 
-@@ -102,7 +119,7 @@
- 
-             if (!customdata.isEmpty()) {
-                 customdata.read(holderlookup_a.createSerializationContext(NbtOps.INSTANCE), Painting.VARIANT_MAP_CODEC).result().ifPresentOrElse((holder) -> {
--                    Optional optional = ((PaintingVariant) holder.value()).title();
-+                    Optional<Component> optional = ((PaintingVariant) holder.value()).title(); // CraftBukkit - decompile error
- 
-                     Objects.requireNonNull(tooltip);
-                     optional.ifPresent(tooltip::add);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ItemCooldowns.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ItemCooldowns.java.patch
deleted file mode 100644
index f8d61e11c7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ItemCooldowns.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/item/ItemCooldowns.java
-+++ b/net/minecraft/world/item/ItemCooldowns.java
-@@ -56,6 +56,13 @@
-     }
- 
-     public void addCooldown(ResourceLocation groupId, int duration) {
-+        // Paper start - Item cooldown events
-+        this.addCooldown(groupId, duration, true);
-+    }
-+
-+    public void addCooldown(ResourceLocation groupId, int duration, boolean callEvent) {
-+        // Event called in server override
-+        // Paper end - Item cooldown events
-         this.cooldowns.put(groupId, new ItemCooldowns.CooldownInstance(this.tickCount, this.tickCount + duration));
-         this.onCooldownStarted(groupId, duration);
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ItemStack.java.patch
deleted file mode 100644
index 4409d94819..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ItemStack.java.patch
+++ /dev/null
@@ -1,630 +0,0 @@
---- a/net/minecraft/world/item/ItemStack.java
-+++ b/net/minecraft/world/item/ItemStack.java
-@@ -23,6 +23,7 @@
- import net.minecraft.ChatFormatting;
- import net.minecraft.advancements.CriteriaTriggers;
- import net.minecraft.core.BlockPos;
-+import net.minecraft.core.Direction;
- import net.minecraft.core.Holder;
- import net.minecraft.core.HolderLookup;
- import net.minecraft.core.HolderSet;
-@@ -46,10 +47,12 @@
- import net.minecraft.network.chat.MutableComponent;
- import net.minecraft.network.codec.ByteBufCodecs;
- import net.minecraft.network.codec.StreamCodec;
-+import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
- import net.minecraft.resources.RegistryOps;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundSource;
- import net.minecraft.stats.Stats;
- import net.minecraft.tags.TagKey;
- import net.minecraft.util.ExtraCodecs;
-@@ -70,7 +73,6 @@
- import net.minecraft.world.entity.ai.attributes.Attributes;
- import net.minecraft.world.entity.decoration.ItemFrame;
- import net.minecraft.world.entity.item.ItemEntity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.flag.FeatureFlagSet;
- import net.minecraft.world.inventory.ClickAction;
- import net.minecraft.world.inventory.Slot;
-@@ -88,26 +90,53 @@
- import net.minecraft.world.item.enchantment.EnchantmentHelper;
- import net.minecraft.world.item.enchantment.ItemEnchantments;
- import net.minecraft.world.item.enchantment.Repairable;
--import net.minecraft.world.level.ItemLike;
--import net.minecraft.world.level.Level;
--import net.minecraft.world.level.block.state.BlockState;
--import net.minecraft.world.level.block.state.pattern.BlockInWorld;
- import net.minecraft.world.level.saveddata.maps.MapId;
- import org.apache.commons.lang3.mutable.MutableBoolean;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.Map;
-+import java.util.Objects;
-+import net.minecraft.world.level.ItemLike;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.BaseEntityBlock;
-+import net.minecraft.world.level.block.BedBlock;
-+import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.SaplingBlock;
-+import net.minecraft.world.level.block.SignBlock;
-+import net.minecraft.world.level.block.SoundType;
-+import net.minecraft.world.level.block.WitherSkullBlock;
-+import net.minecraft.world.level.block.entity.BlockEntity;
-+import net.minecraft.world.level.block.entity.SignBlockEntity;
-+import net.minecraft.world.level.block.entity.SkullBlockEntity;
-+import net.minecraft.world.level.block.state.pattern.BlockInWorld;
-+import net.minecraft.world.level.gameevent.GameEvent;
-+import org.bukkit.Location;
-+import org.bukkit.TreeType;
-+import org.bukkit.block.BlockState;
-+import org.bukkit.craftbukkit.block.CapturedBlockState;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.block.CraftBlockState;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.block.BlockFertilizeEvent;
-+import org.bukkit.event.player.PlayerItemDamageEvent;
-+import org.bukkit.event.world.StructureGrowEvent;
-+// CraftBukkit end
-+
- public final class ItemStack implements DataComponentHolder {
- 
-     private static final List<Component> OP_NBT_WARNING = List.of(Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED), Component.translatable("item.op_warning.line3").withStyle(ChatFormatting.RED));
-     public static final Codec<ItemStack> CODEC = Codec.lazyInitialized(() -> {
--        return RecordCodecBuilder.create((instance) -> {
-+        return RecordCodecBuilder.<ItemStack>create((instance) -> { // CraftBukkit - decompile error
-             return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), ExtraCodecs.intRange(1, 99).fieldOf("count").orElse(1).forGetter(ItemStack::getCount), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> {
-                 return itemstack.components.asPatch();
-             })).apply(instance, ItemStack::new);
-         });
-     });
-     public static final Codec<ItemStack> SINGLE_ITEM_CODEC = Codec.lazyInitialized(() -> {
--        return RecordCodecBuilder.create((instance) -> {
-+        return RecordCodecBuilder.<ItemStack>create((instance) -> { // CraftBukkit - decompile error
-             return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> {
-                 return itemstack.components.asPatch();
-             })).apply(instance, (holder, datacomponentpatch) -> {
-@@ -132,20 +161,38 @@
-             if (i <= 0) {
-                 return ItemStack.EMPTY;
-             } else {
--                Holder<Item> holder = (Holder) null.ITEM_STREAM_CODEC.decode(registryfriendlybytebuf);
-+                Holder<Item> holder = (Holder) ITEM_STREAM_CODEC.decode(registryfriendlybytebuf); // CraftBukkit - decompile error
-                 DataComponentPatch datacomponentpatch = (DataComponentPatch) DataComponentPatch.STREAM_CODEC.decode(registryfriendlybytebuf);
- 
--                return new ItemStack(holder, i, datacomponentpatch);
-+                // CraftBukkit start
-+                ItemStack itemstack = new ItemStack(holder, i, datacomponentpatch);
-+                if (false && !datacomponentpatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata
-+                    CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack));
-+                }
-+                return itemstack;
-+                // CraftBukkit end
-             }
-         }
- 
-         public void encode(RegistryFriendlyByteBuf registryfriendlybytebuf, ItemStack itemstack) {
--            if (itemstack.isEmpty()) {
-+            if (itemstack.isEmpty() || itemstack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
-                 registryfriendlybytebuf.writeVarInt(0);
-             } else {
-                 registryfriendlybytebuf.writeVarInt(itemstack.getCount());
--                null.ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder());
-+                // Spigot start - filter
-+                // itemstack = itemstack.copy();
-+                // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer with raw NBT being handled in metadata
-+                // Spigot end
-+                ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error
-+                // Paper start - adventure; conditionally render translatable components
-+                boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
-+                try {
-+                    net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
-                 DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.components.asPatch());
-+                } finally {
-+                    net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
-+                }
-+                // Paper end - adventure; conditionally render translatable components
-             }
-         }
-     };
-@@ -187,7 +234,7 @@
- 
-         return dataresult.isError() ? dataresult.map((unit) -> {
-             return stack;
--        }) : (stack.getCount() > stack.getMaxStackSize() ? DataResult.error(() -> {
-+        }) : (stack.getCount() > stack.getMaxStackSize() ? DataResult.<ItemStack>error(() -> { // CraftBukkit - decompile error
-             int i = stack.getCount();
- 
-             return "Item stack with stack size of " + i + " was larger than maximum: " + stack.getMaxStackSize();
-@@ -294,8 +341,9 @@
-                 j = itemstack.getMaxStackSize();
-             } while (i <= j);
- 
-+            int finalI = i, finalJ = j; // CraftBukkit - decompile error
-             return DataResult.error(() -> {
--                return "Item stack with count of " + i + " was larger than maximum: " + j;
-+                return "Item stack with count of " + finalI + " was larger than maximum: " + finalJ; // CraftBukkit - decompile error
-             });
-         }
-     }
-@@ -370,32 +418,200 @@
-     }
- 
-     public InteractionResult useOn(UseOnContext context) {
--        Player entityhuman = context.getPlayer();
-+        net.minecraft.world.entity.player.Player entityhuman = context.getPlayer();
-         BlockPos blockposition = context.getClickedPos();
- 
-         if (entityhuman != null && !entityhuman.getAbilities().mayBuild && !this.canPlaceOnBlockInAdventureMode(new BlockInWorld(context.getLevel(), blockposition, false))) {
-             return InteractionResult.PASS;
-         } else {
-             Item item = this.getItem();
--            InteractionResult enuminteractionresult = item.useOn(context);
-+            // CraftBukkit start - handle all block place event logic here
-+            DataComponentPatch oldData = this.components.asPatch();
-+            int oldCount = this.getCount();
-+            ServerLevel world = (ServerLevel) context.getLevel();
- 
-+            if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement
-+                world.captureBlockStates = true;
-+                // special case bonemeal
-+                if (item == Items.BONE_MEAL) {
-+                    world.captureTreeGeneration = true;
-+                }
-+            }
-+            InteractionResult enuminteractionresult;
-+            try {
-+                enuminteractionresult = item.useOn(context);
-+            } finally {
-+                world.captureBlockStates = false;
-+            }
-+            DataComponentPatch newData = this.components.asPatch();
-+            int newCount = this.getCount();
-+            this.setCount(oldCount);
-+            this.restorePatch(oldData);
-+            if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
-+                world.captureTreeGeneration = false;
-+                Location location = CraftLocation.toBukkit(blockposition, world.getWorld());
-+                TreeType treeType = SaplingBlock.treeType;
-+                SaplingBlock.treeType = null;
-+                List<CraftBlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
-+                world.capturedBlockStates.clear();
-+                StructureGrowEvent structureEvent = null;
-+                if (treeType != null) {
-+                    boolean isBonemeal = this.getItem() == Items.BONE_MEAL;
-+                    structureEvent = new StructureGrowEvent(location, treeType, isBonemeal, (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List<? extends BlockState>) blocks);
-+                    org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
-+                }
-+
-+                BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List<? extends BlockState>) blocks);
-+                fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
-+                org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
-+
-+                if (!fertilizeEvent.isCancelled()) {
-+                    // Change the stack to its new contents if it hasn't been tampered with.
-+                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
-+                        this.restorePatch(newData);
-+                        this.setCount(newCount);
-+                    }
-+                    for (CraftBlockState blockstate : blocks) {
-+                        // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest
-+                        CapturedBlockState.setBlockState(blockstate);
-+                        world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed
-+                    }
-+                    entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
-+                }
-+
-+                SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
-+                return enuminteractionresult;
-+            }
-+            world.captureTreeGeneration = false;
-+
-             if (entityhuman != null && enuminteractionresult instanceof InteractionResult.Success) {
-                 InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult;
- 
-                 if (enuminteractionresult_d.wasItemInteraction()) {
--                    entityhuman.awardStat(Stats.ITEM_USED.get(item));
-+                    InteractionHand enumhand = context.getHand();
-+                    org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
-+                    List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
-+                    world.capturedBlockStates.clear();
-+                    if (blocks.size() > 1) {
-+                        placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
-+                    } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
-+                        placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
-+                    }
-+
-+                    if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
-+                        enuminteractionresult = InteractionResult.FAIL; // cancel placement
-+                        // PAIL: Remove this when MC-99075 fixed
-+                        placeEvent.getPlayer().updateInventory();
-+                        world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot
-+                        // revert back all captured blocks
-+                        world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
-+                    world.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
-+                        for (BlockState blockstate : blocks) {
-+                            blockstate.update(true, false);
-+                        }
-+                    world.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
-+                        world.preventPoiUpdated = false;
-+
-+                        // Brute force all possible updates
-+                        // Paper start - Don't resync blocks
-+                        // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
-+                        // for (Direction dir : Direction.values()) {
-+                        //     ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir)));
-+                        // }
-+                        // Paper end - Don't resync blocks
-+                        SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
-+                    } else {
-+                        // Change the stack to its new contents if it hasn't been tampered with.
-+                        if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
-+                            this.restorePatch(newData);
-+                            this.setCount(newCount);
-+                        }
-+
-+                        for (Map.Entry<BlockPos, BlockEntity> e : world.capturedTileEntities.entrySet()) {
-+                            world.setBlockEntity(e.getValue());
-+                        }
-+
-+                        for (BlockState blockstate : blocks) {
-+                            int updateFlag = ((CraftBlockState) blockstate).getFlag();
-+                            net.minecraft.world.level.block.state.BlockState oldBlock = ((CraftBlockState) blockstate).getHandle();
-+                            BlockPos newblockposition = ((CraftBlockState) blockstate).getPosition();
-+                            net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition);
-+
-+                            if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically
-+                                block.onPlace(world, newblockposition, oldBlock, true, context);
-+                            }
-+
-+                            world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
-+                        }
-+
-+                        if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
-+                            BlockPos bp = blockposition;
-+                            if (!world.getBlockState(blockposition).canBeReplaced()) {
-+                                if (!world.getBlockState(blockposition).isSolid()) {
-+                                    bp = null;
-+                                } else {
-+                                    bp = bp.relative(context.getClickedFace());
-+                                }
-+                            }
-+                            if (bp != null) {
-+                                BlockEntity te = world.getBlockEntity(bp);
-+                                if (te instanceof SkullBlockEntity) {
-+                                    WitherSkullBlock.checkSpawn(world, bp, (SkullBlockEntity) te);
-+                                }
-+                            }
-+                        }
-+
-+                        // SPIGOT-4678
-+                        if (this.item instanceof SignItem && SignItem.openSign != null) {
-+                            try {
-+                                if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) {
-+                                    if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) {
-+                                        blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Craftbukkit // Paper - Add PlayerOpenSignEvent
-+                                    }
-+                                }
-+                            } finally {
-+                                SignItem.openSign = null;
-+                            }
-+                        }
-+
-+                        // SPIGOT-7315: Moved from BlockBed#setPlacedBy
-+                        if (placeEvent != null && this.item instanceof BedItem) {
-+                            BlockPos position = ((CraftBlock) placeEvent.getBlock()).getPosition();
-+                            net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position);
-+
-+                            if (blockData.getBlock() instanceof BedBlock) {
-+                                world.blockUpdated(position, Blocks.AIR);
-+                                blockData.updateNeighbourShapes(world, position, 3);
-+                            }
-+                        }
-+
-+                        // SPIGOT-1288 - play sound stripped from ItemBlock
-+                        if (this.item instanceof BlockItem) {
-+                        // Paper start - Fix spigot sound playing for BlockItem ItemStacks
-+                        BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos();
-+                        net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position);
-+                        SoundType soundeffecttype = blockData.getSoundType();
-+                        // Paper end - Fix spigot sound playing for BlockItem ItemStacks
-+                            world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
-+                        }
-+
-+                        entityhuman.awardStat(Stats.ITEM_USED.get(item));
-+                    }
-                 }
-             }
-+            world.capturedTileEntities.clear();
-+            world.capturedBlockStates.clear();
-+            // CraftBukkit end
- 
-             return enuminteractionresult;
-         }
-     }
- 
--    public float getDestroySpeed(BlockState state) {
-+    public float getDestroySpeed(net.minecraft.world.level.block.state.BlockState state) {
-         return this.getItem().getDestroySpeed(this, state);
-     }
- 
--    public InteractionResult use(Level world, Player user, InteractionHand hand) {
-+    public InteractionResult use(Level world, net.minecraft.world.entity.player.Player user, InteractionHand hand) {
-         ItemStack itemstack = this.copy();
-         boolean flag = this.getUseDuration(user) <= 0;
-         InteractionResult enuminteractionresult = this.getItem().use(world, user, hand);
-@@ -490,27 +706,66 @@
-         return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
-     }
- 
--    public void hurtAndBreak(int amount, ServerLevel world, @Nullable ServerPlayer player, Consumer<Item> breakCallback) {
--        int j = this.processDurabilityChange(amount, world, player);
-+    public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer<Item> breakCallback) {  // Paper - Add EntityDamageItemEvent
-+        // Paper start - add force boolean overload
-+        this.hurtAndBreak(amount, world, player, breakCallback, false);
-+    }
-+    public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer<Item> breakCallback, boolean force) {  // Paper - Add EntityDamageItemEvent
-+        // Paper end
-+        int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent
-+        int j = this.processDurabilityChange(amount, world, player, force); // Paper
-+        // CraftBukkit start
-+        if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
-+            PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j, originalDamage); // Paper - Add EntityDamageItemEvent
-+            event.getPlayer().getServer().getPluginManager().callEvent(event);
- 
-+            if (j != event.getDamage() || event.isCancelled()) {
-+                event.getPlayer().updateInventory();
-+            }
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+
-+            j = event.getDamage();
-+            // Paper start - Add EntityDamageItemEvent
-+        } else if (player != null) {
-+            io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount);
-+            if (!event.callEvent()) {
-+                return;
-+            }
-+            j = event.getDamage();
-+            // Paper end - Add EntityDamageItemEvent
-+        }
-+        // CraftBukkit end
-+
-         if (j != 0) {
-             this.applyDamage(this.getDamageValue() + j, player, breakCallback);
-         }
- 
-     }
- 
--    private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable ServerPlayer player) {
--        return !this.isDamageableItem() ? 0 : (player != null && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage));
-+    private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player) {  // Paper - Add EntityDamageItemEvent
-+        // Paper start - itemstack damage api
-+        return processDurabilityChange(baseDamage, world, player, false);
-     }
-+    private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player, boolean force) {
-+        return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage));  // Paper - Add EntityDamageItemEvent
-+        // Paper end - itemstack damage api
-+    }
- 
--    private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> breakCallback) {
--        if (player != null) {
--            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
-+    private void applyDamage(int damage, @Nullable LivingEntity player, Consumer<Item> breakCallback) { // Paper - Add EntityDamageItemEvent
-+        if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
-+            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent
-         }
- 
-         this.setDamageValue(damage);
-         if (this.isBroken()) {
-             Item item = this.getItem();
-+            // CraftBukkit start - Check for item breaking
-+            if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
-+                org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
-+            }
-+            // CraftBukkit end
- 
-             this.shrink(1);
-             breakCallback.accept(item);
-@@ -518,7 +773,7 @@
- 
-     }
- 
--    public void hurtWithoutBreaking(int amount, Player player) {
-+    public void hurtWithoutBreaking(int amount, net.minecraft.world.entity.player.Player player) {
-         if (player instanceof ServerPlayer entityplayer) {
-             int j = this.processDurabilityChange(amount, entityplayer.serverLevel(), entityplayer);
- 
-@@ -535,6 +790,11 @@
-     }
- 
-     public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) {
-+        // Paper start - add param to skip infinite mats check
-+        this.hurtAndBreak(amount, entity, slot, false);
-+    }
-+    public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) {
-+        // Paper end - add param to skip infinite mats check
-         Level world = entity.level();
- 
-         if (world instanceof ServerLevel worldserver) {
-@@ -546,9 +806,9 @@
-                 entityplayer = null;
-             }
- 
--            this.hurtAndBreak(amount, worldserver, entityplayer, (item) -> {
--                entity.onEquippedItemBroken(item, slot);
--            });
-+            this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent
-+                if (slot != null) entity.onEquippedItemBroken(item, slot); // Paper - itemstack damage API - do not process entity related callbacks when damaging from API
-+            }, force); // Paper - itemstack damage API
-         }
- 
-     }
-@@ -580,11 +840,11 @@
-         return this.getItem().getBarColor(this);
-     }
- 
--    public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, Player player) {
-+    public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player) {
-         return this.getItem().overrideStackedOnOther(this, slot, clickType, player);
-     }
- 
--    public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, Player player, SlotAccess cursorStackReference) {
-+    public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player, SlotAccess cursorStackReference) {
-         return this.getItem().overrideOtherStackedOnMe(this, stack, slot, clickType, player, cursorStackReference);
-     }
- 
-@@ -592,8 +852,8 @@
-         Item item = this.getItem();
- 
-         if (item.hurtEnemy(this, target, user)) {
--            if (user instanceof Player) {
--                Player entityhuman = (Player) user;
-+            if (user instanceof net.minecraft.world.entity.player.Player) {
-+                net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) user;
- 
-                 entityhuman.awardStat(Stats.ITEM_USED.get(item));
-             }
-@@ -608,7 +868,7 @@
-         this.getItem().postHurtEnemy(this, target, user);
-     }
- 
--    public void mineBlock(Level world, BlockState state, BlockPos pos, Player miner) {
-+    public void mineBlock(Level world, net.minecraft.world.level.block.state.BlockState state, BlockPos pos, net.minecraft.world.entity.player.Player miner) {
-         Item item = this.getItem();
- 
-         if (item.mineBlock(this, world, state, pos, miner)) {
-@@ -617,11 +877,11 @@
- 
-     }
- 
--    public boolean isCorrectToolForDrops(BlockState state) {
-+    public boolean isCorrectToolForDrops(net.minecraft.world.level.block.state.BlockState state) {
-         return this.getItem().isCorrectToolForDrops(this, state);
-     }
- 
--    public InteractionResult interactLivingEntity(Player user, LivingEntity entity, InteractionHand hand) {
-+    public InteractionResult interactLivingEntity(net.minecraft.world.entity.player.Player user, LivingEntity entity, InteractionHand hand) {
-         return this.getItem().interactLivingEntity(this, user, entity, hand);
-     }
- 
-@@ -736,7 +996,7 @@
- 
-     }
- 
--    public void onCraftedBy(Level world, Player player, int amount) {
-+    public void onCraftedBy(Level world, net.minecraft.world.entity.player.Player player, int amount) {
-         player.awardStat(Stats.ITEM_CRAFTED.get(this.getItem()), amount);
-         this.getItem().onCraftedBy(this, world, player);
-     }
-@@ -768,7 +1028,13 @@
- 
-     public boolean useOnRelease() {
-         return this.getItem().useOnRelease(this);
-+    }
-+
-+    // CraftBukkit start
-+    public void restorePatch(DataComponentPatch datacomponentpatch) {
-+        this.components.restorePatch(datacomponentpatch);
-     }
-+    // CraftBukkit end
- 
-     @Nullable
-     public <T> T set(DataComponentType<? super T> type, @Nullable T value) {
-@@ -805,6 +1071,25 @@
-             this.getItem().verifyComponentsAfterLoad(this);
-         }
-     }
-+
-+    // Paper start - (this is just a good no conflict location)
-+    public org.bukkit.inventory.ItemStack asBukkitMirror() {
-+        return CraftItemStack.asCraftMirror(this);
-+    }
-+    public org.bukkit.inventory.ItemStack asBukkitCopy() {
-+        return CraftItemStack.asCraftMirror(this.copy());
-+    }
-+    public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
-+        return CraftItemStack.asNMSCopy(itemstack);
-+    }
-+    private org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
-+    public org.bukkit.inventory.ItemStack getBukkitStack() {
-+        if (bukkitStack == null || bukkitStack.handle != this) {
-+            bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
-+        }
-+        return bukkitStack;
-+    }
-+    // Paper end
- 
-     public void applyComponents(DataComponentPatch changes) {
-         this.components.applyPatch(changes);
-@@ -858,7 +1143,7 @@
-     }
- 
-     private <T extends TooltipProvider> void addToTooltip(DataComponentType<T> componentType, Item.TooltipContext context, Consumer<Component> textConsumer, TooltipFlag type) {
--        T t0 = (TooltipProvider) this.get(componentType);
-+        T t0 = (T) this.get(componentType); // CraftBukkit - decompile error
- 
-         if (t0 != null) {
-             t0.addToTooltip(context, textConsumer, type);
-@@ -866,7 +1151,7 @@
- 
-     }
- 
--    public List<Component> getTooltipLines(Item.TooltipContext context, @Nullable Player player, TooltipFlag type) {
-+    public List<Component> getTooltipLines(Item.TooltipContext context, @Nullable net.minecraft.world.entity.player.Player player, TooltipFlag type) {
-         boolean flag = this.getItem().shouldPrintOpWarning(this, player);
- 
-         if (!type.isCreative() && this.has(DataComponents.HIDE_TOOLTIP)) {
-@@ -941,7 +1226,7 @@
-         }
-     }
- 
--    private void addAttributeTooltips(Consumer<Component> textConsumer, @Nullable Player player) {
-+    private void addAttributeTooltips(Consumer<Component> textConsumer, @Nullable net.minecraft.world.entity.player.Player player) {
-         ItemAttributeModifiers itemattributemodifiers = (ItemAttributeModifiers) this.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
- 
-         if (itemattributemodifiers.showInTooltip()) {
-@@ -966,7 +1251,7 @@
-         }
-     }
- 
--    private void addModifierTooltip(Consumer<Component> textConsumer, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
-+    private void addModifierTooltip(Consumer<Component> textConsumer, @Nullable net.minecraft.world.entity.player.Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
-         double d0 = modifier.amount();
-         boolean flag = false;
- 
-@@ -1091,6 +1376,19 @@
-         EnchantmentHelper.forEachModifier(this, slot, attributeModifierConsumer);
-     }
- 
-+    // CraftBukkit start
-+    @Deprecated
-+    public void setItem(Item item) {
-+        this.bukkitStack = null; // Paper
-+        this.item = item;
-+        // Paper start - change base component prototype
-+        final DataComponentPatch patch = this.getComponentsPatch();
-+        this.components = new PatchedDataComponentMap(this.item.components());
-+        this.applyComponents(patch);
-+        // Paper end - change base component prototype
-+    }
-+    // CraftBukkit end
-+
-     public Component getDisplayName() {
-         MutableComponent ichatmutablecomponent = Component.empty().append(this.getHoverName());
- 
-@@ -1153,7 +1451,7 @@
-     }
- 
-     public void consume(int amount, @Nullable LivingEntity entity) {
--        if (entity == null || !entity.hasInfiniteMaterials()) {
-+        if ((entity == null || !entity.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit
-             this.shrink(amount);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/LeadItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/LeadItem.java.patch
deleted file mode 100644
index e45ad43774..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/LeadItem.java.patch
+++ /dev/null
@@ -1,92 +0,0 @@
---- a/net/minecraft/world/item/LeadItem.java
-+++ b/net/minecraft/world/item/LeadItem.java
-@@ -18,6 +18,11 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.AABB;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.CraftEquipmentSlot;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.hanging.HangingPlaceEvent;
-+// CraftBukkit end
- 
- public class LeadItem extends Item {
- 
-@@ -35,37 +40,70 @@
-             Player entityhuman = context.getPlayer();
- 
-             if (!world.isClientSide && entityhuman != null) {
--                return LeadItem.bindPlayerMobs(entityhuman, world, blockposition);
-+                return LeadItem.bindPlayerMobs(entityhuman, world, blockposition, context.getHand()); // CraftBukkit - Pass hand
-             }
-         }
- 
-         return InteractionResult.PASS;
-     }
- 
--    public static InteractionResult bindPlayerMobs(Player player, Level world, BlockPos pos) {
-+    public static InteractionResult bindPlayerMobs(Player entityhuman, Level world, BlockPos blockposition, net.minecraft.world.InteractionHand enumhand) { // CraftBukkit - Add EnumHand
-         LeashFenceKnotEntity entityleash = null;
--        List<Leashable> list = LeadItem.leashableInArea(world, pos, (leashable) -> {
--            return leashable.getLeashHolder() == player;
-+        List<Leashable> list = LeadItem.leashableInArea(world, blockposition, (leashable) -> {
-+            return leashable.getLeashHolder() == entityhuman;
-         });
- 
-         Leashable leashable;
- 
--        for (Iterator iterator = list.iterator(); iterator.hasNext(); leashable.setLeashedTo(entityleash, true)) {
-+        for (Iterator iterator = list.iterator(); iterator.hasNext();) { // CraftBukkit - handle setLeashedTo at end of loop
-             leashable = (Leashable) iterator.next();
-             if (entityleash == null) {
--                entityleash = LeashFenceKnotEntity.getOrCreateKnot(world, pos);
-+                entityleash = LeashFenceKnotEntity.getOrCreateKnot(world, blockposition);
-+
-+                // CraftBukkit start - fire HangingPlaceEvent
-+                org.bukkit.inventory.EquipmentSlot hand = CraftEquipmentSlot.getHand(enumhand);
-+                HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityleash.getBukkitEntity(), entityhuman != null ? (org.bukkit.entity.Player) entityhuman.getBukkitEntity() : null, CraftBlock.at(world, blockposition), org.bukkit.block.BlockFace.SELF, hand);
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    entityleash.discard(null); // CraftBukkit - add Bukkit remove cause
-+                    return InteractionResult.PASS;
-+                }
-+                // CraftBukkit end
-                 entityleash.playPlacementSound();
-             }
-+
-+            // CraftBukkit start
-+            if (leashable instanceof Entity leashed) {
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(leashed, entityleash, entityhuman, enumhand).isCancelled()) {
-+                    iterator.remove();
-+                    continue;
-+                }
-+            }
-+
-+            leashable.setLeashedTo(entityleash, true);
-+            // CraftBukkit end
-         }
- 
-         if (!list.isEmpty()) {
--            world.gameEvent((Holder) GameEvent.BLOCK_ATTACH, pos, GameEvent.Context.of((Entity) player));
-+            world.gameEvent((Holder) GameEvent.BLOCK_ATTACH, blockposition, GameEvent.Context.of((Entity) entityhuman));
-             return InteractionResult.SUCCESS_SERVER;
-         } else {
-+            // CraftBukkit start- remove leash if we do not leash any entity because of the cancelled event
-+            if (entityleash != null) {
-+                entityleash.discard(null);
-+            }
-+            // CraftBukkit end
-             return InteractionResult.PASS;
-         }
-     }
- 
-+    // CraftBukkit start
-+    public static InteractionResult bindPlayerMobs(Player player, Level world, BlockPos pos) {
-+        return LeadItem.bindPlayerMobs(player, world, pos, net.minecraft.world.InteractionHand.MAIN_HAND);
-+    }
-+    // CraftBukkit end
-+
-     public static List<Leashable> leashableInArea(Level world, BlockPos pos, Predicate<Leashable> predicate) {
-         double d0 = 7.0D;
-         int i = pos.getX();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/MapItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/MapItem.java.patch
deleted file mode 100644
index 5374da2aa0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/MapItem.java.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- a/net/minecraft/world/item/MapItem.java
-+++ b/net/minecraft/world/item/MapItem.java
-@@ -97,8 +97,8 @@
-                             int r = (j / i + o - 64) * i;
-                             int s = (k / i + p - 64) * i;
-                             Multiset<MapColor> multiset = LinkedHashMultiset.create();
--                            LevelChunk levelChunk = world.getChunk(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s));
--                            if (!levelChunk.isEmpty()) {
-+                            LevelChunk levelChunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); // Paper - Maps shouldn't load chunks
-+                            if (levelChunk != null && !levelChunk.isEmpty()) { // Paper - Maps shouldn't load chunks
-                                 int t = 0;
-                                 double e = 0.0;
-                                 if (world.dimensionType().hasCeiling()) {
-@@ -205,7 +205,7 @@
- 
-                 for (int n = 0; n < 128; n++) {
-                     for (int o = 0; o < 128; o++) {
--                        Holder<Biome> holder = world.getBiome(mutableBlockPos.set((l + o) * i, 0, (m + n) * i));
-+                        Holder<Biome> holder = world.getUncachedNoiseBiome((l + o) * i, 0, (m + n) * i); // Paper - Perf: Use seed based lookup for treasure maps
-                         bls[n * 128 + o] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES);
-                     }
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/MinecartItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/MinecartItem.java.patch
deleted file mode 100644
index f97004f5f9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/MinecartItem.java.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- a/net/minecraft/world/item/MinecartItem.java
-+++ b/net/minecraft/world/item/MinecartItem.java
-@@ -67,7 +67,13 @@
-                 if (world instanceof ServerLevel) {
-                     ServerLevel worldserver = (ServerLevel) world;
- 
--                    worldserver.addFreshEntity(entityminecartabstract);
-+                    // CraftBukkit start
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityminecartabstract).isCancelled()) {
-+                    if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-+                        return InteractionResult.FAIL;
-+                    }
-+                    // CraftBukkit end
-+                    if (!worldserver.addFreshEntity(entityminecartabstract)) return InteractionResult.PASS; // CraftBukkit
-                     worldserver.gameEvent((Holder) GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of(context.getPlayer(), worldserver.getBlockState(blockposition.below())));
-                 }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/PotionItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/PotionItem.java.patch
deleted file mode 100644
index 185bcf8992..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/PotionItem.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/item/PotionItem.java
-+++ b/net/minecraft/world/item/PotionItem.java
-@@ -42,6 +42,12 @@
-         PotionContents potionContents = itemStack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
-         BlockState blockState = level.getBlockState(blockPos);
-         if (context.getClickedFace() != Direction.DOWN && blockState.is(BlockTags.CONVERTABLE_TO_MUD) && potionContents.is(Potions.WATER)) {
-+            // Paper start
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, Blocks.MUD.defaultBlockState())) {
-+                player.containerMenu.sendAllDataToRemote();
-+                return InteractionResult.PASS;
-+            }
-+            // Paper end
-             level.playSound(null, blockPos, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F);
-             player.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemStack, player, new ItemStack(Items.GLASS_BOTTLE)));
-             player.awardStat(Stats.ITEM_USED.get(itemStack.getItem()));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ProjectileWeaponItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ProjectileWeaponItem.java.patch
deleted file mode 100644
index dfdb8d27c6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ProjectileWeaponItem.java.patch
+++ /dev/null
@@ -1,52 +0,0 @@
---- a/net/minecraft/world/item/ProjectileWeaponItem.java
-+++ b/net/minecraft/world/item/ProjectileWeaponItem.java
-@@ -54,9 +54,25 @@
-                 float f6 = f4 + f5 * (float) ((i + 1) / 2) * f3;
- 
-                 f5 = -f5;
--                Projectile.spawnProjectile(this.createProjectile(world, shooter, stack, itemstack1, critical), world, itemstack1, (iprojectile) -> {
--                    this.shootProjectile(shooter, iprojectile, i, speed, divergence, f6, target);
--                });
-+                // CraftBukkit start
-+                Projectile iprojectile = this.createProjectile(world, shooter, stack, itemstack1, critical);
-+                this.shootProjectile(shooter, iprojectile, i, speed, divergence, f6, target);
-+
-+                org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, stack, itemstack1, iprojectile, hand, speed, true);
-+                if (event.isCancelled()) {
-+                    event.getProjectile().remove();
-+                    return;
-+                }
-+
-+                if (event.getProjectile() == iprojectile.getBukkitEntity()) {
-+                    if (Projectile.spawnProjectile(iprojectile, world, itemstack1).isRemoved()) {
-+                        if (shooter instanceof net.minecraft.server.level.ServerPlayer) {
-+                            ((net.minecraft.server.level.ServerPlayer) shooter).getBukkitEntity().updateInventory();
-+                        }
-+                        return;
-+                    }
-+                }
-+                // CraftBukkit end
-                 stack.hurtAndBreak(this.getDurabilityUse(itemstack1), shooter, LivingEntity.getSlotForHand(hand));
-                 if (stack.isEmpty()) {
-                     break;
-@@ -93,6 +109,11 @@
-     }
- 
-     protected static List<ItemStack> draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter) {
-+        // Paper start
-+        return draw(stack, projectileStack, shooter, true);
-+    }
-+    protected static List<ItemStack> draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter, boolean consume) {
-+        // Paper end
-         if (projectileStack.isEmpty()) {
-             return List.of();
-         } else {
-@@ -112,7 +133,7 @@
-             ItemStack itemstack2 = projectileStack.copy();
- 
-             for (int k = 0; k < j; ++k) {
--                ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0);
-+                ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0 || !consume); // Paper
- 
-                 if (!itemstack3.isEmpty()) {
-                     list.add(itemstack3);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ShovelItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ShovelItem.java.patch
deleted file mode 100644
index 0840475938..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ShovelItem.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/world/item/ShovelItem.java
-+++ b/net/minecraft/world/item/ShovelItem.java
-@@ -46,20 +46,29 @@
-             Player player = context.getPlayer();
-             BlockState blockState2 = FLATTENABLES.get(blockState.getBlock());
-             BlockState blockState3 = null;
-+            Runnable afterAction = null; // Paper
-             if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) {
--                level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper
-                 blockState3 = blockState2;
-             } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) {
-+                afterAction = () -> { // Paper
-                 if (!level.isClientSide()) {
-                     level.levelEvent(null, 1009, blockPos, 0);
-                 }
- 
-                 CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState);
-+                }; // Paper
-                 blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false));
-             }
- 
-             if (blockState3 != null) {
-                 if (!level.isClientSide) {
-+                    // Paper start
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) {
-+                        return InteractionResult.PASS;
-+                    }
-+                    afterAction.run();
-+                    // Paper end
-                     level.setBlock(blockPos, blockState3, 11);
-                     level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3));
-                     if (player != null) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/SignItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/SignItem.java.patch
deleted file mode 100644
index 50c0cdf7d1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/SignItem.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/world/item/SignItem.java
-+++ b/net/minecraft/world/item/SignItem.java
-@@ -13,6 +13,8 @@
- 
- public class SignItem extends StandingAndWallBlockItem {
- 
-+    public static BlockPos openSign; // CraftBukkit
-+
-     public SignItem(Block standingBlock, Block wallBlock, Item.Properties settings) {
-         super(standingBlock, wallBlock, Direction.DOWN, settings);
-     }
-@@ -35,7 +37,10 @@
-                 if (block instanceof SignBlock) {
-                     SignBlock blocksign = (SignBlock) block;
- 
--                    blocksign.openTextEdit(player, tileentitysign, true);
-+                    // CraftBukkit start - SPIGOT-4678
-+                    // blocksign.openTextEdit(entityhuman, tileentitysign, true);
-+                    SignItem.openSign = pos;
-+                    // CraftBukkit end
-                 }
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/SnowballItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/SnowballItem.java.patch
deleted file mode 100644
index bb5a1788d6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/SnowballItem.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/world/item/SnowballItem.java
-+++ b/net/minecraft/world/item/SnowballItem.java
-@@ -25,13 +25,30 @@
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemstack = user.getItemInHand(hand);
- 
--        world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+        // CraftBukkit start - moved down
-+        // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-         if (world instanceof ServerLevel worldserver) {
--            Projectile.spawnProjectileFromRotation(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            // Paper start - PlayerLaunchProjectileEvent
-+            final Projectile.Delayed<Snowball> snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity());
-+            if (event.callEvent() && snowball.attemptSpawn()) {
-+                user.awardStat(Stats.ITEM_USED.get(this));
-+                if (event.shouldConsume()) {
-+                    itemstack.consume(1, user);
-+                } else if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+            // Paper end - PlayerLaunchProjectileEvent
-+
-+                world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
-+            } else { if (user instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent - return fail
-+                ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+            } return InteractionResult.FAIL; } // Paper - PlayerLaunchProjectileEvent - return fail
-+            // CraftBukkit end
-         }
- 
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemstack.consume(1, user);
-+        // Paper - PlayerLaunchProjectileEvent - moved up
-+        // itemstack.consume(1, entityhuman); // CraftBukkit - moved up
-         return InteractionResult.SUCCESS;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/SpawnEggItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/SpawnEggItem.java.patch
deleted file mode 100644
index a33a635c7a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/SpawnEggItem.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/item/SpawnEggItem.java
-+++ b/net/minecraft/world/item/SpawnEggItem.java
-@@ -63,6 +63,8 @@
-             EntityType entitytypes;
- 
-             if (tileentity instanceof Spawner) {
-+                if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation
-+
-                 Spawner spawner = (Spawner) tileentity;
- 
-                 entitytypes = this.getType(world.registryAccess(), itemstack);
-@@ -176,10 +178,10 @@
-                     return Optional.empty();
-                 } else {
-                     ((Mob) object).moveTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F);
--                    world.addFreshEntityWithPassengers((Entity) object);
-+                    world.addFreshEntityWithPassengers((Entity) object, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit
-                     ((Mob) object).setCustomName((Component) stack.get(DataComponents.CUSTOM_NAME));
-                     stack.consume(1, user);
--                    return Optional.of(object);
-+                    return Optional.of((Mob) object); // CraftBukkit - decompile error
-                 }
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
deleted file mode 100644
index 9e1a576a36..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
+++ /dev/null
@@ -1,41 +0,0 @@
---- a/net/minecraft/world/item/StandingAndWallBlockItem.java
-+++ b/net/minecraft/world/item/StandingAndWallBlockItem.java
-@@ -4,12 +4,17 @@
- import javax.annotation.Nullable;
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.world.item.context.BlockPlaceContext;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelReader;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.shapes.CollisionContext;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.block.data.CraftBlockData;
-+import org.bukkit.event.block.BlockCanBuildEvent;
-+// CraftBukkit end
- 
- public class StandingAndWallBlockItem extends BlockItem {
- 
-@@ -49,7 +54,19 @@
-             }
-         }
- 
--        return iblockdata1 != null && world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty()) ? iblockdata1 : null;
-+        // CraftBukkit start
-+        if (iblockdata1 != null) {
-+            boolean defaultReturn = world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty());
-+            org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
-+
-+            BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
-+            context.getLevel().getCraftServer().getPluginManager().callEvent(event);
-+
-+            return (event.isBuildable()) ? iblockdata1 : null;
-+        } else {
-+            return null;
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/ThrowablePotionItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/ThrowablePotionItem.java.patch
deleted file mode 100644
index 13c480eabd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/ThrowablePotionItem.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/world/item/ThrowablePotionItem.java
-+++ b/net/minecraft/world/item/ThrowablePotionItem.java
-@@ -22,11 +22,28 @@
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemStack = user.getItemInHand(hand);
-         if (world instanceof ServerLevel serverLevel) {
--            Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
-+            // Paper start - PlayerLaunchProjectileEvent
-+            final Projectile.Delayed<ThrownPotion> thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
-+            // Paper start - PlayerLaunchProjectileEvent
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity());
-+            if (event.callEvent() && thrownPotion.attemptSpawn()) {
-+                if (event.shouldConsume()) {
-+                    itemStack.consume(1, user);
-+                } else if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+
-+                user.awardStat(Stats.ITEM_USED.get(this));
-+            } else {
-+                if (user instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
-+                }
-+                return InteractionResult.FAIL;
-+            }
-+            // Paper end - PlayerLaunchProjectileEvent
-         }
- 
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemStack.consume(1, user);
-+        // Paper - PlayerLaunchProjectileEvent - move up
-         return InteractionResult.SUCCESS;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/TridentItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/TridentItem.java.patch
deleted file mode 100644
index a23995cfe3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/TridentItem.java.patch
+++ /dev/null
@@ -1,51 +0,0 @@
---- a/net/minecraft/world/item/TridentItem.java
-+++ b/net/minecraft/world/item/TridentItem.java
-@@ -86,18 +86,37 @@
-                     if (world instanceof ServerLevel) {
-                         ServerLevel worldserver = (ServerLevel) world;
- 
--                        stack.hurtWithoutBreaking(1, entityhuman);
-+                        // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down
-                         if (f == 0.0F) {
--                            ThrownTrident entitythrowntrident = (ThrownTrident) Projectile.spawnProjectileFromRotation(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F);
-+                            // Paper start - PlayerLaunchProjectileEvent
-+                            Projectile.Delayed<ThrownTrident> tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F);
-+                            // Paper start - PlayerLaunchProjectileEvent
-+                            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity());
-+                            if (!event.callEvent() || !tridentDelayed.attemptSpawn()) {
-+                                // CraftBukkit start
-+                            // Paper end - PlayerLaunchProjectileEvent
-+                                if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) {
-+                                    ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory();
-+                                }
-+                                return false;
-+                            }
-+                            ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent
-+                            if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent
-+                            entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved
-+                            // CraftBukkit end
- 
-                             if (entityhuman.hasInfiniteMaterials()) {
-                                 entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
--                            } else {
-+                            } else if (event.shouldConsume()) { // Paper - PlayerLaunchProjectileEvent
-                                 entityhuman.getInventory().removeItem(stack);
-                             }
- 
-                             world.playSound((Player) null, (Entity) entitythrowntrident, (SoundEvent) holder.value(), SoundSource.PLAYERS, 1.0F, 1.0F);
-                             return true;
-+                            // CraftBukkit start - SPIGOT-5458 also need in this branch :(
-+                        } else {
-+                            stack.hurtWithoutBreaking(1, entityhuman);
-+                            // CraftBukkkit end
-                         }
-                     }
- 
-@@ -112,6 +131,7 @@
-                         f3 *= f / f6;
-                         f4 *= f / f6;
-                         f5 *= f / f6;
-+                        org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(entityhuman, stack, f3, f4, f5); // CraftBukkit
-                         entityhuman.push((double) f3, (double) f4, (double) f5);
-                         entityhuman.startAutoSpinAttack(20, 8.0F, stack);
-                         if (entityhuman.onGround()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/WindChargeItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/WindChargeItem.java.patch
deleted file mode 100644
index 8fd7be1352..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/WindChargeItem.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/item/WindChargeItem.java
-+++ b/net/minecraft/world/item/WindChargeItem.java
-@@ -27,7 +27,7 @@
-     public InteractionResult use(Level world, Player user, InteractionHand hand) {
-         ItemStack itemStack = user.getItemInHand(hand);
-         if (world instanceof ServerLevel serverLevel) {
--            Projectile.spawnProjectileFromRotation(
-+            final Projectile.Delayed<WindCharge> windCharge = Projectile.spawnProjectileFromRotationDelayed( // Paper - PlayerLaunchProjectileEvent
-                 (world2, shooter, stack) -> new WindCharge(user, world, user.position().x(), user.getEyePosition().y(), user.position().z()),
-                 serverLevel,
-                 itemStack,
-@@ -36,6 +36,21 @@
-                 PROJECTILE_SHOOT_POWER,
-                 1.0F
-             );
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) windCharge.projectile().getBukkitEntity());
-+            if (!event.callEvent() || !windCharge.attemptSpawn()) {
-+                user.containerMenu.sendAllDataToRemote();
-+                if (user instanceof net.minecraft.server.level.ServerPlayer player) {
-+                    player.connection.send(new net.minecraft.network.protocol.game.ClientboundCooldownPacket(user.getCooldowns().getCooldownGroup(itemStack), 0)); // prevent visual desync of cooldown on the slot
-+                }
-+                return InteractionResult.FAIL;
-+            }
-+
-+            user.awardStat(Stats.ITEM_USED.get(this));
-+            if (event.shouldConsume()) itemStack.consume(1, user);
-+            else if (!user.hasInfiniteMaterials()) {
-+                user.containerMenu.sendAllDataToRemote();
-+            }
-+            // Paper end - PlayerLaunchProjectileEvent
-         }
- 
-         world.playSound(
-@@ -48,8 +63,6 @@
-             0.5F,
-             0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)
-         );
--        user.awardStat(Stats.ITEM_USED.get(this));
--        itemStack.consume(1, user);
-         return InteractionResult.SUCCESS;
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/WrittenBookItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/WrittenBookItem.java.patch
deleted file mode 100644
index f29d9ca860..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/WrittenBookItem.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/item/WrittenBookItem.java
-+++ b/net/minecraft/world/item/WrittenBookItem.java
-@@ -41,7 +41,7 @@
- 
-     public static boolean resolveBookComponents(ItemStack book, CommandSourceStack commandSource, @Nullable Player player) {
-         WrittenBookContent writtenBookContent = book.get(DataComponents.WRITTEN_BOOK_CONTENT);
--        if (writtenBookContent != null && !writtenBookContent.resolved()) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && writtenBookContent != null && !writtenBookContent.resolved()) { // Paper - Disable component selector resolving in books by default
-             WrittenBookContent writtenBookContent2 = writtenBookContent.resolve(commandSource, player);
-             if (writtenBookContent2 != null) {
-                 book.set(DataComponents.WRITTEN_BOOK_CONTENT, writtenBookContent2);

From aa4dd1b840aace36baaeace96c6f3003e55d205b Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:55:18 -0500
Subject: [PATCH 109/285] net/minecraft/network/protocol/handshake/

---
 build-data/paper.at                             |  2 --
 .../handshake/ClientIntentionPacket.java.patch  | 16 ++++++++++++++++
 .../handshake/ClientIntentionPacket.java.patch  | 17 -----------------
 3 files changed, 16 insertions(+), 19 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch

diff --git a/build-data/paper.at b/build-data/paper.at
index c646267776..91c5e8792f 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -39,7 +39,6 @@ public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket xRot
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket y
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket yRot
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket z
-public net.minecraft.network.protocol.handshake.ClientIntentionPacket port
 public net.minecraft.resources.RegistryOps lookupProvider
 public net.minecraft.resources.RegistryOps$HolderLookupAdapter
 public net.minecraft.server.Main forceUpgrade(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lcom/mojang/datafixers/DataFixer;ZLjava/util/function/BooleanSupplier;Lnet/minecraft/core/RegistryAccess;Z)V
@@ -676,7 +675,6 @@ public net.minecraft.world.level.storage.LevelStorageSource$LevelStorageAccess l
 public net.minecraft.world.level.storage.PrimaryLevelData settings
 public net.minecraft.world.scores.Objective displayName
 public net.minecraft.world.scores.criteria.ObjectiveCriteria CRITERIA_CACHE
-public-f net.minecraft.network.protocol.handshake.ClientIntentionPacket hostName
 public-f net.minecraft.server.MinecraftServer potionBrewing
 public-f net.minecraft.server.MinecraftServer storageSource
 public-f net.minecraft.server.ReloadableServerResources commands
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
new file mode 100644
index 0000000000..65ea146b9f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
++++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
+@@ -1,3 +_,4 @@
++// mc-dev import
+ package net.minecraft.network.protocol.handshake;
+ 
+ import net.minecraft.network.FriendlyByteBuf;
+@@ -20,7 +_,7 @@
+     }
+ 
+     private ClientIntentionPacket(FriendlyByteBuf buffer) {
+-        this(buffer.readVarInt(), buffer.readUtf(255), buffer.readUnsignedShort(), ClientIntent.byId(buffer.readVarInt()));
++        this(buffer.readVarInt(), buffer.readUtf(Short.MAX_VALUE), buffer.readUnsignedShort(), ClientIntent.byId(buffer.readVarInt())); // Spigot - increase max hostName length
+     }
+ 
+     private void write(FriendlyByteBuf buffer) {
diff --git a/paper-server/patches/unapplied/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/paper-server/patches/unapplied/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
deleted file mode 100644
index 071ea04212..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
-+++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
-@@ -1,3 +1,4 @@
-+// mc-dev import
- package net.minecraft.network.protocol.handshake;
- 
- import net.minecraft.network.FriendlyByteBuf;
-@@ -11,7 +12,8 @@
-     private static final int MAX_HOST_LENGTH = 255;
- 
-     private ClientIntentionPacket(FriendlyByteBuf buf) {
--        this(buf.readVarInt(), buf.readUtf(255), buf.readUnsignedShort(), ClientIntent.byId(buf.readVarInt()));
-+        // Spigot - increase max hostName length
-+        this(buf.readVarInt(), buf.readUtf(Short.MAX_VALUE), buf.readUnsignedShort(), ClientIntent.byId(buf.readVarInt()));
-     }
- 
-     private void write(FriendlyByteBuf buf) {

From 5c36e5e6b5b668083e9c176a7d00db8a39d991c6 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 15:59:29 -0500
Subject: [PATCH 110/285] net/minecraft/core/registries

---
 .../registries/BuiltInRegistries.java.patch   | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/core/registries/BuiltInRegistries.java.patch (83%)

diff --git a/paper-server/patches/unapplied/net/minecraft/core/registries/BuiltInRegistries.java.patch b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/core/registries/BuiltInRegistries.java.patch
rename to paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
index d363add98b..bc224c349b 100644
--- a/paper-server/patches/unapplied/net/minecraft/core/registries/BuiltInRegistries.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/registries/BuiltInRegistries.java
 +++ b/net/minecraft/core/registries/BuiltInRegistries.java
-@@ -296,6 +296,17 @@
+@@ -296,6 +_,17 @@
      public static final Registry<SlotDisplay.Type<?>> SLOT_DISPLAY = registerSimple(Registries.SLOT_DISPLAY, SlotDisplays::bootstrap);
      public static final Registry<RecipeBookCategory> RECIPE_BOOK_CATEGORY = registerSimple(Registries.RECIPE_BOOK_CATEGORY, RecipeBookCategories::bootstrap);
      public static final Registry<? extends Registry<?>> REGISTRY = WRITABLE_REGISTRY;
@@ -16,18 +16,17 @@
 +    });
 +    // Paper end - add built-in registry conversions
  
-     private static <T> Registry<T> registerSimple(ResourceKey<? extends Registry<T>> key, BuiltInRegistries.RegistryBootstrap<T> initializer) {
-         return internalRegister(key, new MappedRegistry<>(key, Lifecycle.stable(), false), initializer);
-@@ -323,14 +334,22 @@
-         ResourceKey<? extends Registry<T>> key, R registry, BuiltInRegistries.RegistryBootstrap<T> initializer
+     private static <T> Registry<T> registerSimple(ResourceKey<? extends Registry<T>> key, BuiltInRegistries.RegistryBootstrap<T> bootstrap) {
+         return internalRegister(key, new MappedRegistry<>(key, Lifecycle.stable(), false), bootstrap);
+@@ -321,6 +_,7 @@
+         ResourceKey<? extends Registry<T>> key, R registry, BuiltInRegistries.RegistryBootstrap<T> bootstrap
      ) {
          Bootstrap.checkBootstrapCalled(() -> "registry " + key.location());
 +        io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry
          ResourceLocation resourceLocation = key.location();
-         LOADERS.put(resourceLocation, () -> initializer.run(registry));
--        WRITABLE_REGISTRY.register((ResourceKey<WritableRegistry<?>>)key, registry, RegistrationInfo.BUILT_IN);
-+        WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix
-         return registry;
+         LOADERS.put(resourceLocation, () -> bootstrap.run(registry));
+         WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN);
+@@ -328,7 +_,14 @@
      }
  
      public static void bootStrap() {
@@ -42,7 +41,7 @@
          freeze();
          validate(REGISTRY);
      }
-@@ -348,6 +367,7 @@
+@@ -346,6 +_,7 @@
  
          for (Registry<?> registry : REGISTRY) {
              bindBootstrappedTagsToEmpty(registry);

From 636e4ccaf2c35f2c42f6cdd1b21936922487d744 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 12:59:41 -0800
Subject: [PATCH 111/285] net.minecraft.world.entity.monster.hoglin

---
 .../entity/monster/hoglin/Hoglin.java.patch   | 16 +++++++
 .../monster/hoglin/HoglinBase.java.patch      | 11 +++++
 .../entity/monster/hoglin/Hoglin.java.patch   | 48 -------------------
 .../monster/hoglin/HoglinBase.java.patch      | 11 -----
 4 files changed, 27 insertions(+), 59 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
new file mode 100644
index 0000000000..a7f4ebaaef
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java
++++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+@@ -262,7 +_,12 @@
+     }
+ 
+     private void finishConversion() {
+-        this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), mob -> mob.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)));
++        final Entity converted = this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false),  mob -> {mob.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));}, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
++        // Paper start - Fix issues with mob conversion; reset to prevent event spam
++        if (converted == null) {
++            this.timeInOverworld = 0;
++        }
++        // Paper end - Fix issues with mob conversion
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch
new file mode 100644
index 0000000000..273ab55a4d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/hoglin/HoglinBase.java
++++ b/net/minecraft/world/entity/monster/hoglin/HoglinBase.java
+@@ -45,7 +_,7 @@
+             double d3 = d * (hoglin.level().random.nextFloat() * 0.5F + 0.2F);
+             Vec3 vec3 = new Vec3(d1, 0.0, d2).normalize().scale(d3).yRot(f);
+             double d4 = d * hoglin.level().random.nextFloat() * 0.5;
+-            target.push(vec3.x, d4, vec3.z);
++            target.push(vec3.x, d4, vec3.z, hoglin); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+             target.hurtMarked = true;
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
deleted file mode 100644
index 345420cb17..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/Hoglin.java.patch
+++ /dev/null
@@ -1,48 +0,0 @@
---- a/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-+++ b/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-@@ -63,7 +63,8 @@
-     public int timeInOverworld;
-     public boolean cannotBeHunted;
-     protected static final ImmutableList<? extends SensorType<? extends Sensor<? super Hoglin>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ADULT, SensorType.HOGLIN_SPECIFIC_SENSOR);
--    protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED, MemoryModuleType.IS_PANICKING});
-+    // CraftBukkit - decompile error
-+    protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.<MemoryModuleType<?>>of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLIN, new MemoryModuleType[]{MemoryModuleType.AVOID_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_ADULT_HOGLINS, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.NEAREST_REPELLENT, MemoryModuleType.PACIFIED, MemoryModuleType.IS_PANICKING});
- 
-     public Hoglin(EntityType<? extends Hoglin> type, Level world) {
-         super(type, world);
-@@ -134,7 +135,7 @@
- 
-     @Override
-     public Brain<Hoglin> getBrain() {
--        return super.getBrain();
-+        return (Brain<Hoglin>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -240,9 +241,15 @@
-     }
- 
-     private void finishConversion() {
--        this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> {
-+        net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> {
-             entityzoglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
--        });
-+        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
-+
-+        // Paper start - Fix issues with mob conversion; reset to prevent event spam
-+        if (converted == null) {
-+            this.timeInOverworld = 0;
-+        }
-+        // Paper end - Fix issues with mob conversion
-     }
- 
-     @Override
-@@ -326,7 +333,7 @@
- 
-     @Override
-     protected SoundEvent getAmbientSound() {
--        return this.level().isClientSide ? null : (SoundEvent) HoglinAi.getSoundForCurrentActivity(this).orElse((Object) null);
-+        return this.level().isClientSide ? null : (SoundEvent) HoglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch
deleted file mode 100644
index 634a383e72..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/hoglin/HoglinBase.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/monster/hoglin/HoglinBase.java
-+++ b/net/minecraft/world/entity/monster/hoglin/HoglinBase.java
-@@ -45,7 +45,7 @@
-             double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F);
-             Vec3 vec3 = new Vec3(g, 0.0, h).normalize().scale(j).yRot(i);
-             double k = f * (double)attacker.level().random.nextFloat() * 0.5;
--            target.push(vec3.x, k, vec3.z);
-+            target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-             target.hurtMarked = true;
-         }
-     }

From 8ec3dedfbdae41723e578e54143cc3394e47190a Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 13:03:13 -0800
Subject: [PATCH 112/285] net.minecraft.world.level.block.grower

---
 .../level/block/grower/TreeGrower.java.patch  |  70 ++++++++++
 .../level/block/grower/TreeGrower.java.patch  | 126 ------------------
 2 files changed, 70 insertions(+), 126 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/grower/TreeGrower.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
new file mode 100644
index 0000000000..432b04d350
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/level/block/grower/TreeGrower.java
++++ b/net/minecraft/world/level/block/grower/TreeGrower.java
+@@ -164,6 +_,7 @@
+             if (holder1 == null) {
+                 return false;
+             } else {
++                this.setTreeType(holder1); // CraftBukkit
+                 ConfiguredFeature<?, ?> configuredFeature2 = holder1.value();
+                 BlockState blockState1 = level.getFluidState(pos).createLegacyBlock();
+                 level.setBlock(pos, blockState1, 4);
+@@ -198,4 +_,59 @@
+ 
+         return false;
+     }
++
++    // CraftBukkit start
++    private void setTreeType(Holder<ConfiguredFeature<?, ?>> holder) {
++        ResourceKey<ConfiguredFeature<?, ?>> treeFeature = holder.unwrapKey().get();
++        if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE;
++        } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM;
++        } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM;
++        } else if (treeFeature == TreeFeatures.JUNGLE_TREE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE;
++        } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE;
++        } else if (treeFeature == TreeFeatures.PINE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD;
++        } else if (treeFeature == TreeFeatures.SPRUCE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD;
++        } else if (treeFeature == TreeFeatures.ACACIA) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA;
++        } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH;
++        } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH;
++        } else if (treeFeature == TreeFeatures.SWAMP_OAK) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP;
++        } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE;
++        } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH;
++        } else if (treeFeature == TreeFeatures.DARK_OAK) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK;
++        } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD;
++        } else if (treeFeature == TreeFeatures.MEGA_PINE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE;
++        } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE;
++        } else if (treeFeature == TreeFeatures.AZALEA_TREE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA;
++        } else if (treeFeature == TreeFeatures.MANGROVE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE;
++        } else if (treeFeature == TreeFeatures.TALL_MANGROVE) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE;
++        } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY;
++        } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK;
++        } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) {
++            net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING;
++        } else {
++            throw new IllegalArgumentException("Unknown tree generator " + treeFeature);
++        }
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/grower/TreeGrower.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/grower/TreeGrower.java.patch
deleted file mode 100644
index 859e74b185..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/grower/TreeGrower.java.patch
+++ /dev/null
@@ -1,126 +0,0 @@
---- a/net/minecraft/world/level/block/grower/TreeGrower.java
-+++ b/net/minecraft/world/level/block/grower/TreeGrower.java
-@@ -20,9 +20,14 @@
- import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.SaplingBlock;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
-+// CraftBukkit start
-+import net.minecraft.data.worldgen.features.TreeFeatures;
-+import org.bukkit.TreeType;
-+// CraftBukkit end
- 
- public final class TreeGrower {
- 
-@@ -75,21 +80,22 @@
-             }
-         }
- 
--        return flowersNearby && this.flowers.isPresent() ? (ResourceKey) this.flowers.get() : (ResourceKey) this.tree.orElse((Object) null);
-+        return flowersNearby && this.flowers.isPresent() ? (ResourceKey) this.flowers.get() : (ResourceKey) this.tree.orElse(null); // CraftBukkit - decompile error
-     }
- 
-     @Nullable
-     private ResourceKey<ConfiguredFeature<?, ?>> getConfiguredMegaFeature(RandomSource random) {
--        return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? (ResourceKey) this.secondaryMegaTree.get() : (ResourceKey) this.megaTree.orElse((Object) null);
-+        return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? (ResourceKey) this.secondaryMegaTree.get() : (ResourceKey) this.megaTree.orElse(null); // CraftBukkit - decompile error
-     }
- 
-     public boolean growTree(ServerLevel world, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) {
-         ResourceKey<ConfiguredFeature<?, ?>> resourcekey = this.getConfiguredMegaFeature(random);
- 
-         if (resourcekey != null) {
--            Holder<ConfiguredFeature<?, ?>> holder = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey).orElse((Object) null);
-+            Holder<ConfiguredFeature<?, ?>> holder = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey).orElse(null); // CraftBukkit - decompile error
- 
-             if (holder != null) {
-+                this.setTreeType(holder); // CraftBukkit
-                 for (int i = 0; i >= -1; --i) {
-                     for (int j = 0; j >= -1; --j) {
-                         if (TreeGrower.isTwoByTwoSapling(state, world, pos, i, j)) {
-@@ -120,11 +126,12 @@
-         if (resourcekey1 == null) {
-             return false;
-         } else {
--            Holder<ConfiguredFeature<?, ?>> holder1 = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey1).orElse((Object) null);
-+            Holder<ConfiguredFeature<?, ?>> holder1 = (Holder) world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(resourcekey1).orElse(null); // CraftBukkit - decompile error
- 
-             if (holder1 == null) {
-                 return false;
-             } else {
-+                this.setTreeType(holder1); // CraftBukkit
-                 ConfiguredFeature<?, ?> worldgenfeatureconfigured1 = (ConfiguredFeature) holder1.value();
-                 BlockState iblockdata2 = world.getFluidState(pos).createLegacyBlock();
- 
-@@ -165,11 +172,66 @@
-         return true;
-     }
- 
-+    // CraftBukkit start
-+    private void setTreeType(Holder<ConfiguredFeature<?, ?>> holder) {
-+        ResourceKey<ConfiguredFeature<?, ?>> worldgentreeabstract = holder.unwrapKey().get();
-+        if (worldgentreeabstract == TreeFeatures.OAK || worldgentreeabstract == TreeFeatures.OAK_BEES_005) {
-+            SaplingBlock.treeType = TreeType.TREE;
-+        } else if (worldgentreeabstract == TreeFeatures.HUGE_RED_MUSHROOM) {
-+            SaplingBlock.treeType = TreeType.RED_MUSHROOM;
-+        } else if (worldgentreeabstract == TreeFeatures.HUGE_BROWN_MUSHROOM) {
-+            SaplingBlock.treeType = TreeType.BROWN_MUSHROOM;
-+        } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE) {
-+            SaplingBlock.treeType = TreeType.COCOA_TREE;
-+        } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE_NO_VINE) {
-+            SaplingBlock.treeType = TreeType.SMALL_JUNGLE;
-+        } else if (worldgentreeabstract == TreeFeatures.PINE) {
-+            SaplingBlock.treeType = TreeType.TALL_REDWOOD;
-+        } else if (worldgentreeabstract == TreeFeatures.SPRUCE) {
-+            SaplingBlock.treeType = TreeType.REDWOOD;
-+        } else if (worldgentreeabstract == TreeFeatures.ACACIA) {
-+            SaplingBlock.treeType = TreeType.ACACIA;
-+        } else if (worldgentreeabstract == TreeFeatures.BIRCH || worldgentreeabstract == TreeFeatures.BIRCH_BEES_005) {
-+            SaplingBlock.treeType = TreeType.BIRCH;
-+        } else if (worldgentreeabstract == TreeFeatures.SUPER_BIRCH_BEES_0002) {
-+            SaplingBlock.treeType = TreeType.TALL_BIRCH;
-+        } else if (worldgentreeabstract == TreeFeatures.SWAMP_OAK) {
-+            SaplingBlock.treeType = TreeType.SWAMP;
-+        } else if (worldgentreeabstract == TreeFeatures.FANCY_OAK || worldgentreeabstract == TreeFeatures.FANCY_OAK_BEES_005) {
-+            SaplingBlock.treeType = TreeType.BIG_TREE;
-+        } else if (worldgentreeabstract == TreeFeatures.JUNGLE_BUSH) {
-+            SaplingBlock.treeType = TreeType.JUNGLE_BUSH;
-+        } else if (worldgentreeabstract == TreeFeatures.DARK_OAK) {
-+            SaplingBlock.treeType = TreeType.DARK_OAK;
-+        } else if (worldgentreeabstract == TreeFeatures.MEGA_SPRUCE) {
-+            SaplingBlock.treeType = TreeType.MEGA_REDWOOD;
-+        } else if (worldgentreeabstract == TreeFeatures.MEGA_PINE) {
-+            SaplingBlock.treeType = TreeType.MEGA_PINE;
-+        } else if (worldgentreeabstract == TreeFeatures.MEGA_JUNGLE_TREE) {
-+            SaplingBlock.treeType = TreeType.JUNGLE;
-+        } else if (worldgentreeabstract == TreeFeatures.AZALEA_TREE) {
-+            SaplingBlock.treeType = TreeType.AZALEA;
-+        } else if (worldgentreeabstract == TreeFeatures.MANGROVE) {
-+            SaplingBlock.treeType = TreeType.MANGROVE;
-+        } else if (worldgentreeabstract == TreeFeatures.TALL_MANGROVE) {
-+            SaplingBlock.treeType = TreeType.TALL_MANGROVE;
-+        } else if (worldgentreeabstract == TreeFeatures.CHERRY || worldgentreeabstract == TreeFeatures.CHERRY_BEES_005) {
-+            SaplingBlock.treeType = TreeType.CHERRY;
-+        } else if (worldgentreeabstract == TreeFeatures.PALE_OAK || worldgentreeabstract == TreeFeatures.PALE_OAK_BONEMEAL) {
-+            SaplingBlock.treeType = TreeType.PALE_OAK;
-+        } else if (worldgentreeabstract == TreeFeatures.PALE_OAK_CREAKING) {
-+            SaplingBlock.treeType = TreeType.PALE_OAK_CREAKING;
-+        } else {
-+            throw new IllegalArgumentException("Unknown tree generator " + worldgentreeabstract);
-+        }
-+    }
-+    // CraftBukkit end
-+
-     static {
--        Function function = (worldgentreeprovider) -> {
-+        Function<TreeGrower, String> function = (worldgentreeprovider) -> { // CraftBukkit - decompile error
-             return worldgentreeprovider.name;
-         };
--        Map map = TreeGrower.GROWERS;
-+        Map<String, TreeGrower> map = TreeGrower.GROWERS; // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(map);
-         CODEC = Codec.stringResolver(function, map::get);

From 004ee8651c8c4717759ce78143afc8e340ecc878 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 13:12:42 -0800
Subject: [PATCH 113/285] net.minecraft.world.effect

---
 .../effect/HealOrHarmMobEffect.java.patch     | 20 +++++++++
 .../world/effect/HungerMobEffect.java.patch   | 11 +++++
 .../world/effect/InfestedMobEffect.java.patch | 15 +++++++
 .../world/effect/MobEffectUtil.java.patch     | 45 +++++++++++++++++++
 .../world/effect/OozingMobEffect.java.patch   | 11 +++++
 .../world/effect/PoisonMobEffect.java.patch   | 11 +++++
 .../effect/RegenerationMobEffect.java.patch   |  4 +-
 .../effect/SaturationMobEffect.java.patch     | 19 ++++++++
 .../world/effect/WeavingMobEffect.java.patch  | 24 +++++-----
 .../level/block/grower/TreeGrower.java.patch  |  8 ++++
 .../effect/HealOrHarmMobEffect.java.patch     | 20 ---------
 .../world/effect/HungerMobEffect.java.patch   | 11 -----
 .../world/effect/InfestedMobEffect.java.patch | 15 -------
 .../world/effect/MobEffectUtil.java.patch     | 39 ----------------
 .../world/effect/OozingMobEffect.java.patch   | 11 -----
 .../world/effect/PoisonMobEffect.java.patch   | 11 -----
 .../effect/SaturationMobEffect.java.patch     | 30 -------------
 17 files changed, 154 insertions(+), 151 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/effect/RegenerationMobEffect.java.patch (83%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/effect/WeavingMobEffect.java.patch (56%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/HungerMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/InfestedMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/MobEffectUtil.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/OozingMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/PoisonMobEffect.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/effect/SaturationMobEffect.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
new file mode 100644
index 0000000000..040430b443
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/effect/HealOrHarmMobEffect.java
++++ b/net/minecraft/world/effect/HealOrHarmMobEffect.java
+@@ -16,7 +_,7 @@
+     @Override
+     public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
+         if (this.isHarm == entity.isInvertedHealAndHarm()) {
+-            entity.heal(Math.max(4 << amplifier, 0));
++            entity.heal(Math.max(4 << amplifier, 0), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+         } else {
+             entity.hurtServer(level, entity.damageSources().magic(), 6 << amplifier);
+         }
+@@ -30,7 +_,7 @@
+     ) {
+         if (this.isHarm == entity.isInvertedHealAndHarm()) {
+             int i = (int)(health * (4 << amplifier) + 0.5);
+-            entity.heal(i);
++            entity.heal(i, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+         } else {
+             int i = (int)(health * (6 << amplifier) + 0.5);
+             if (source == null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch
new file mode 100644
index 0000000000..0328a5b344
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/HungerMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/HungerMobEffect.java
++++ b/net/minecraft/world/effect/HungerMobEffect.java
+@@ -12,7 +_,7 @@
+     @Override
+     public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
+         if (entity instanceof Player player) {
+-            player.causeFoodExhaustion(0.005F * (amplifier + 1));
++            player.causeFoodExhaustion(0.005F * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+         }
+ 
+         return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch
new file mode 100644
index 0000000000..05442e7403
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/InfestedMobEffect.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/effect/InfestedMobEffect.java
++++ b/net/minecraft/world/effect/InfestedMobEffect.java
+@@ -44,7 +_,11 @@
+             Vector3f vector3f = entity.getLookAngle().toVector3f().mul(0.3F).mul(1.0F, 1.5F, 1.0F).rotateY(f1);
+             silverfish.moveTo(x, y, z, level.getRandom().nextFloat() * 360.0F, 0.0F);
+             silverfish.setDeltaMovement(new Vec3(vector3f));
+-            level.addFreshEntity(silverfish);
++            // CraftBukkit start
++            if (!level.addFreshEntity(silverfish, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) {
++                return;
++            }
++            // CraftBukkit end
+             silverfish.playSound(SoundEvents.SILVERFISH_HURT);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
new file mode 100644
index 0000000000..eeeb7fd665
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/effect/MobEffectUtil.java
++++ b/net/minecraft/world/effect/MobEffectUtil.java
+@@ -47,18 +_,31 @@
+     public static List<ServerPlayer> addEffectToPlayersAround(
+         ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance effect, int duration
+     ) {
++        // CraftBukkit start
++        return MobEffectUtil.addEffectToPlayersAround(level, source, pos, radius, effect, duration, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance effect, int duration, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
++        // Paper start - Add ElderGuardianAppearanceEvent
++        return addEffectToPlayersAround(level, source, pos, radius, effect, duration, cause, null);
++    }
++
++    public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance effect, int duration, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate<ServerPlayer> playerPredicate) {
++        // Paper end - Add ElderGuardianAppearanceEvent
++        // CraftBukkit end
+         Holder<MobEffect> effect1 = effect.getEffect();
+-        List<ServerPlayer> players = level.getPlayers(
+-            serverPlayer -> serverPlayer.gameMode.isSurvival()
+-                && (source == null || !source.isAlliedTo(serverPlayer))
+-                && pos.closerThan(serverPlayer.position(), radius)
+-                && (
+-                    !serverPlayer.hasEffect(effect1)
+-                        || serverPlayer.getEffect(effect1).getAmplifier() < effect.getAmplifier()
+-                        || serverPlayer.getEffect(effect1).endsWithin(duration - 1)
+-                )
+-        );
+-        players.forEach(serverPlayer -> serverPlayer.addEffect(new MobEffectInstance(effect), source));
++        List<ServerPlayer> players = level.getPlayers((entityplayer) -> {
++            // Paper start - Add ElderGuardianAppearanceEvent
++            boolean condition = entityplayer.gameMode.isSurvival() && (source == null || !source.isAlliedTo((Entity) entityplayer)) && pos.closerThan(entityplayer.position(), radius) && (!entityplayer.hasEffect(effect1) || entityplayer.getEffect(effect1).getAmplifier() < effect.getAmplifier() || entityplayer.getEffect(effect1).endsWithin(duration - 1));
++            if (condition) {
++                return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true
++            } else {
++                return false;
++            }
++            // Paper end - Add ElderGuardianAppearanceEvent
++        });
++
++        players.forEach(serverPlayer -> serverPlayer.addEffect(new MobEffectInstance(effect), source, cause)); // CraftBukkit
+         return players;
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch
new file mode 100644
index 0000000000..33ed7473ee
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/OozingMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/OozingMobEffect.java
++++ b/net/minecraft/world/effect/OozingMobEffect.java
+@@ -49,7 +_,7 @@
+         if (slime != null) {
+             slime.setSize(2, true);
+             slime.moveTo(x, y, z, level.getRandom().nextFloat() * 360.0F, 0.0F);
+-            level.addFreshEntity(slime);
++            level.addFreshEntity(slime, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch
new file mode 100644
index 0000000000..66061ffa2d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/PoisonMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/PoisonMobEffect.java
++++ b/net/minecraft/world/effect/PoisonMobEffect.java
+@@ -13,7 +_,7 @@
+     @Override
+     public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
+         if (entity.getHealth() > 1.0F) {
+-            entity.hurtServer(level, entity.damageSources().magic(), 1.0F);
++            entity.hurtServer(level, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
+         }
+ 
+         return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/RegenerationMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/world/effect/RegenerationMobEffect.java.patch
rename to paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch
index 9a24fc0330..c7b4805bbb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/RegenerationMobEffect.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/effect/RegenerationMobEffect.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/world/effect/RegenerationMobEffect.java
 +++ b/net/minecraft/world/effect/RegenerationMobEffect.java
-@@ -12,7 +12,7 @@
+@@ -11,7 +_,7 @@
      @Override
-     public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) {
+     public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
          if (entity.getHealth() < entity.getMaxHealth()) {
 -            entity.heal(1.0F);
 +            entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch
new file mode 100644
index 0000000000..d9dff00176
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/effect/SaturationMobEffect.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/effect/SaturationMobEffect.java
++++ b/net/minecraft/world/effect/SaturationMobEffect.java
+@@ -12,7 +_,15 @@
+     @Override
+     public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) {
+         if (entity instanceof Player player) {
+-            player.getFoodData().eat(amplifier + 1, 1.0F);
++            // CraftBukkit start
++            int oldFoodLevel = player.getFoodData().foodLevel;
++            org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, amplifier + 1 + oldFoodLevel);
++            if (!event.isCancelled()) {
++                player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
++            }
++
++            ((org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity()).sendHealthUpdate();
++            // CraftBukkit end
+         }
+ 
+         return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/WeavingMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/effect/WeavingMobEffect.java.patch
rename to paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch
index 8beb585d3f..66fd20e36b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/WeavingMobEffect.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/effect/WeavingMobEffect.java.patch
@@ -1,24 +1,24 @@
 --- a/net/minecraft/world/effect/WeavingMobEffect.java
 +++ b/net/minecraft/world/effect/WeavingMobEffect.java
-@@ -25,11 +25,11 @@
+@@ -25,11 +_,11 @@
      @Override
-     public void onMobRemoved(ServerLevel world, LivingEntity entity, int amplifier, Entity.RemovalReason reason) {
-         if (reason == Entity.RemovalReason.KILLED && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) {
--            this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition());
-+            this.spawnCobwebsRandomlyAround(world, entity.getRandom(), entity.blockPosition(), entity); // Paper - Fire EntityChangeBlockEvent in more places
+     public void onMobRemoved(ServerLevel level, LivingEntity entity, int amplifier, Entity.RemovalReason reason) {
+         if (reason == Entity.RemovalReason.KILLED && (entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) {
+-            this.spawnCobwebsRandomlyAround(level, entity.getRandom(), entity.blockPosition());
++            this.spawnCobwebsRandomlyAround(level, entity.getRandom(), entity.blockPosition(), entity); // Paper - Fire EntityChangeBlockEvent in more places
          }
      }
  
--    private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos) {
-+    private void spawnCobwebsRandomlyAround(ServerLevel world, RandomSource random, BlockPos pos, LivingEntity entity) { // Paper - Fire EntityChangeBlockEvent in more places
+-    private void spawnCobwebsRandomlyAround(ServerLevel level, RandomSource random, BlockPos pos) {
++    private void spawnCobwebsRandomlyAround(ServerLevel level, RandomSource random, BlockPos pos, LivingEntity entity) { // Paper - Fire EntityChangeBlockEvent in more places
          Set<BlockPos> set = Sets.newHashSet();
          int i = this.maxCobwebs.applyAsInt(random);
  
-@@ -46,6 +46,7 @@
+@@ -46,6 +_,7 @@
          }
  
-         for (BlockPos blockPos3 : set) {
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, Blocks.COBWEB.defaultBlockState())) continue; // Paper - Fire EntityChangeBlockEvent in more places
-             world.setBlock(blockPos3, Blocks.COBWEB.defaultBlockState(), 3);
-             world.levelEvent(3018, blockPos3, 0);
+         for (BlockPos blockPosx : set) {
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPosx, Blocks.COBWEB.defaultBlockState())) continue; // Paper - Fire EntityChangeBlockEvent in more places
+             level.setBlock(blockPosx, Blocks.COBWEB.defaultBlockState(), 3);
+             level.levelEvent(3018, blockPosx, 0);
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
index 432b04d350..c4316b4d6e 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch
@@ -1,5 +1,13 @@
 --- a/net/minecraft/world/level/block/grower/TreeGrower.java
 +++ b/net/minecraft/world/level/block/grower/TreeGrower.java
+@@ -132,6 +_,7 @@
+                 .get(configuredMegaFeature)
+                 .orElse(null);
+             if (holder != null) {
++                this.setTreeType(holder); // CraftBukkit
+                 for (int i = 0; i >= -1; i--) {
+                     for (int i1 = 0; i1 >= -1; i1--) {
+                         if (isTwoByTwoSapling(state, level, pos, i, i1)) {
 @@ -164,6 +_,7 @@
              if (holder1 == null) {
                  return false;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
deleted file mode 100644
index d8a62a429d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/effect/HealOrHarmMobEffect.java
-+++ b/net/minecraft/world/effect/HealOrHarmMobEffect.java
-@@ -17,7 +17,7 @@
-     @Override
-     public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) {
-         if (this.isHarm == entity.isInvertedHealAndHarm()) {
--            entity.heal((float) Math.max(4 << amplifier, 0));
-+            entity.heal((float) Math.max(4 << amplifier, 0), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
-         } else {
-             entity.hurtServer(world, entity.damageSources().magic(), (float) (6 << amplifier));
-         }
-@@ -31,7 +31,7 @@
- 
-         if (this.isHarm == target.isInvertedHealAndHarm()) {
-             j = (int) (proximity * (double) (4 << amplifier) + 0.5D);
--            target.heal((float) j);
-+            target.heal((float) j, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
-         } else {
-             j = (int) (proximity * (double) (6 << amplifier) + 0.5D);
-             if (effectEntity == null) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/HungerMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/HungerMobEffect.java.patch
deleted file mode 100644
index 9757b68e1a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/HungerMobEffect.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/effect/HungerMobEffect.java
-+++ b/net/minecraft/world/effect/HungerMobEffect.java
-@@ -13,7 +13,7 @@
-     @Override
-     public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) {
-         if (entity instanceof Player entityhuman) {
--            entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1));
-+            entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
-         }
- 
-         return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/InfestedMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/InfestedMobEffect.java.patch
deleted file mode 100644
index 98497613dc..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/InfestedMobEffect.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/effect/InfestedMobEffect.java
-+++ b/net/minecraft/world/effect/InfestedMobEffect.java
-@@ -48,7 +48,11 @@
- 
-             entitysilverfish.moveTo(x, y, z, world.getRandom().nextFloat() * 360.0F, 0.0F);
-             entitysilverfish.setDeltaMovement(new Vec3(vector3f));
--            world.addFreshEntity(entitysilverfish);
-+            // CraftBukkit start
-+            if (!world.addFreshEntity(entitysilverfish, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT)) {
-+                return;
-+            }
-+            // CraftBukkit end
-             entitysilverfish.playSound(SoundEvents.SILVERFISH_HURT);
-         }
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/MobEffectUtil.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/MobEffectUtil.java.patch
deleted file mode 100644
index 6c259deb9b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/MobEffectUtil.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/effect/MobEffectUtil.java
-+++ b/net/minecraft/world/effect/MobEffectUtil.java
-@@ -50,13 +50,32 @@
-     }
- 
-     public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel world, @Nullable Entity entity, Vec3 origin, double range, MobEffectInstance statusEffectInstance, int duration) {
--        Holder<MobEffect> holder = statusEffectInstance.getEffect();
--        List<ServerPlayer> list = world.getPlayers((entityplayer) -> {
--            return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && origin.closerThan(entityplayer.position(), range) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < statusEffectInstance.getAmplifier() || entityplayer.getEffect(holder).endsWithin(duration - 1));
-+        // CraftBukkit start
-+        return MobEffectUtil.addEffectToPlayersAround(world, entity, origin, range, statusEffectInstance, duration, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
-+    }
-+
-+    public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
-+        // Paper start - Add ElderGuardianAppearanceEvent
-+        return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null);
-+    }
-+
-+    public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate<ServerPlayer> playerPredicate) {
-+        // Paper end - Add ElderGuardianAppearanceEvent
-+        // CraftBukkit end
-+        Holder<MobEffect> holder = mobeffect.getEffect();
-+        List<ServerPlayer> list = worldserver.getPlayers((entityplayer) -> {
-+            // Paper start - Add ElderGuardianAppearanceEvent
-+            boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1));
-+            if (condition) {
-+                return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true
-+            } else {
-+                return false;
-+            }
-+            // Paper ned - Add ElderGuardianAppearanceEvent
-         });
- 
-         list.forEach((entityplayer) -> {
--            entityplayer.addEffect(new MobEffectInstance(statusEffectInstance), entity);
-+            entityplayer.addEffect(new MobEffectInstance(mobeffect), entity, cause); // CraftBukkit
-         });
-         return list;
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/OozingMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/OozingMobEffect.java.patch
deleted file mode 100644
index 4a2bef9952..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/OozingMobEffect.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/effect/OozingMobEffect.java
-+++ b/net/minecraft/world/effect/OozingMobEffect.java
-@@ -52,7 +52,7 @@
-         if (entityslime != null) {
-             entityslime.setSize(2, true);
-             entityslime.moveTo(x, y, z, world.getRandom().nextFloat() * 360.0F, 0.0F);
--            world.addFreshEntity(entityslime);
-+            world.addFreshEntity(entityslime, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.POTION_EFFECT); // CraftBukkit
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/PoisonMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/PoisonMobEffect.java.patch
deleted file mode 100644
index 367358c2d8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/PoisonMobEffect.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/effect/PoisonMobEffect.java
-+++ b/net/minecraft/world/effect/PoisonMobEffect.java
-@@ -14,7 +14,7 @@
-     @Override
-     public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) {
-         if (entity.getHealth() > 1.0F) {
--            entity.hurtServer(world, entity.damageSources().magic(), 1.0F);
-+            entity.hurtServer(world, entity.damageSources().poison(), 1.0F);  // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
-         }
- 
-         return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/effect/SaturationMobEffect.java.patch b/paper-server/patches/unapplied/net/minecraft/world/effect/SaturationMobEffect.java.patch
deleted file mode 100644
index 5d38f090c8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/effect/SaturationMobEffect.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/effect/SaturationMobEffect.java
-+++ b/net/minecraft/world/effect/SaturationMobEffect.java
-@@ -3,6 +3,10 @@
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.player.Player;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- class SaturationMobEffect extends InstantenousMobEffect {
- 
-@@ -13,7 +17,15 @@
-     @Override
-     public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) {
-         if (entity instanceof Player entityhuman) {
--            entityhuman.getFoodData().eat(amplifier + 1, 1.0F);
-+            // CraftBukkit start
-+            int oldFoodLevel = entityhuman.getFoodData().foodLevel;
-+            org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel);
-+            if (!event.isCancelled()) {
-+                entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
-+            }
-+
-+            ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate();
-+            // CraftBukkit end
-         }
- 
-         return true;

From 97043e3e5b6f3ca434feff027890c895ce070c67 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:24:00 -0500
Subject: [PATCH 114/285] net/minecraft/world/level/block/piston/

---
 .../block/piston/PistonBaseBlock.java.patch   | 168 ++++++++++++++++
 .../piston/PistonMovingBlockEntity.java.patch |  12 +-
 .../block/piston/PistonBaseBlock.java.patch   | 180 ------------------
 3 files changed, 174 insertions(+), 186 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch (73%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
new file mode 100644
index 0000000000..bd32b0d966
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -0,0 +1,168 @@
+--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java
++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+@@ -145,6 +_,18 @@
+                 i = 2;
+             }
+ 
++            // CraftBukkit start
++            // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down
++            //     org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            //     BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.<org.bukkit.block.Block>of(), CraftBlock.notchToBlockFace(enumdirection));
++            //     world.getCraftServer().getPluginManager().callEvent(event);
++            //
++            //     if (event.isCancelled()) {
++            //         return;
++            //     }
++            // }
++            // PAIL: checkME - what happened to setTypeAndData?
++            // CraftBukkit end
+             level.blockEvent(pos, this, i, direction.get3DDataValue());
+         }
+     }
+@@ -174,6 +_,12 @@
+     @Override
+     protected boolean triggerEvent(BlockState state, Level level, BlockPos pos, int id, int param) {
+         Direction direction = state.getValue(FACING);
++        // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
++        Direction directionQueuedAs = Direction.from3DDataValue(param & 7); // Paper - copied from below
++        if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && directionQueuedAs != directionQueuedAs) {
++            return false;
++        }
++        // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+         BlockState blockState = state.setValue(EXTENDED, Boolean.valueOf(true));
+         if (!level.isClientSide) {
+             boolean neighborSignal = this.getNeighborSignal(level, pos, direction);
+@@ -205,10 +_,17 @@
+                 .defaultBlockState()
+                 .setValue(MovingPistonBlock.FACING, direction)
+                 .setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT);
++            // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires
++            if (!this.isSticky) {
++                if (!new org.bukkit.event.block.BlockPistonRetractEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), java.util.Collections.emptyList(), org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction)).callEvent()) {
++                    return false;
++                }
++            }
++            // Paper end - Fix sticky pistons and BlockPistonRetractEvent
+             level.setBlock(pos, blockState1, 20);
+             level.setBlockEntity(
+                 MovingPistonBlock.newMovingBlockEntity(
+-                    pos, blockState1, this.defaultBlockState().setValue(FACING, Direction.from3DDataValue(param & 7)), direction, false, true
++                    pos, blockState1, this.defaultBlockState().setValue(FACING, Direction.from3DDataValue(param & 7)), direction, false, true // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change
+                 )
+             );
+             level.blockUpdated(pos, blockState1.getBlock());
+@@ -232,13 +_,27 @@
+                         || blockState2.getPistonPushReaction() != PushReaction.NORMAL
+                             && !blockState2.is(Blocks.PISTON)
+                             && !blockState2.is(Blocks.STICKY_PISTON)) {
++                        // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air)
++                        if (id == TRIGGER_CONTRACT && blockState1.isAir()) {
++                            if (!new org.bukkit.event.block.BlockPistonRetractEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), java.util.Collections.emptyList(), org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction)).callEvent()) {
++                                return false;
++                            }
++                        }
++                        // Paper end - Fix sticky pistons and BlockPistonRetractEvent
+                         level.removeBlock(pos.relative(direction), false);
+                     } else {
+                         this.moveBlocks(level, pos, direction, false);
+                     }
+                 }
+             } else {
+-                level.removeBlock(pos.relative(direction), false);
++                // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks
++                BlockPos headPos = pos.relative(direction);
++                if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || level.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, direction)) { // double check to make sure we're not a headless piston.
++                    level.removeBlock(headPos, false);
++                } else {
++                    ((ServerLevel) level).getChunkSource().blockChanged(headPos); // ... fix client desync
++                }
++                // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
+             }
+ 
+             level.playSound(null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, level.random.nextFloat() * 0.15F + 0.6F);
+@@ -305,12 +_,54 @@
+             BlockState[] blockStates = new BlockState[toPush.size() + toDestroy.size()];
+             Direction direction = extending ? facing : facing.getOpposite();
+             int i = 0;
++            // CraftBukkit start
++            final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++            final List<BlockPos> moved = pistonStructureResolver.getToPush();
++            final List<BlockPos> broken = pistonStructureResolver.getToDestroy();
++
++            List<org.bukkit.block.Block> blocks = new java.util.AbstractList<org.bukkit.block.Block>() {
++
++                @Override
++                public int size() {
++                    return moved.size() + broken.size();
++                }
++
++                @Override
++                public org.bukkit.block.Block get(int index) {
++                    if (index >= this.size() || index < 0) {
++                        throw new ArrayIndexOutOfBoundsException(index);
++                    }
++                    BlockPos pos = (BlockPos) (index < moved.size() ? moved.get(index) : broken.get(index - moved.size()));
++                    return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++                }
++            };
++            org.bukkit.event.block.BlockPistonEvent event;
++            if (extending) {
++                event = new org.bukkit.event.block.BlockPistonExtendEvent(bblock, blocks, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction));
++            } else {
++                event = new org.bukkit.event.block.BlockPistonRetractEvent(bblock, blocks, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction));
++            }
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            if (event.isCancelled()) {
++                for (BlockPos b : broken) {
++                    level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++                }
++                for (BlockPos b : moved) {
++                    level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++                    b = b.relative(direction);
++                    level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++                }
++                return false;
++            }
++            // CraftBukkit end
+ 
+             for (int i1 = toDestroy.size() - 1; i1 >= 0; i1--) {
+                 BlockPos blockPos2 = toDestroy.get(i1);
+                 BlockState blockState1 = level.getBlockState(blockPos2);
+                 BlockEntity blockEntity = blockState1.hasBlockEntity() ? level.getBlockEntity(blockPos2) : null;
+-                dropResources(blockState1, level, blockPos2, blockEntity);
++                dropResources(blockState1, level, blockPos2, blockEntity, pos); // Paper - Add BlockBreakBlockEvent
+                 level.setBlock(blockPos2, Blocks.AIR.defaultBlockState(), 18);
+                 level.gameEvent(GameEvent.BLOCK_DESTROY, blockPos2, GameEvent.Context.of(blockState1));
+                 if (!blockState1.is(BlockTags.FIRE)) {
+@@ -321,13 +_,27 @@
+             }
+ 
+             for (int i1 = toPush.size() - 1; i1 >= 0; i1--) {
+-                BlockPos blockPos2 = toPush.get(i1);
+-                BlockState blockState1 = level.getBlockState(blockPos2);
++                // Paper start - fix a variety of piston desync dupes
++                boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication;
++                BlockPos blockPos2;
++                BlockPos oldPos = blockPos2 = toPush.get(i1);
++                BlockState blockState1 = allowDesync ? level.getBlockState(oldPos) : null;
++                // Paper end - fix a variety of piston desync dupes
+                 blockPos2 = blockPos2.relative(direction);
+                 map.remove(blockPos2);
+                 BlockState blockState2 = Blocks.MOVING_PISTON.defaultBlockState().setValue(FACING, facing);
+                 level.setBlock(blockPos2, blockState2, 68);
+                 level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, list.get(i1), facing, extending, false));
++                // Paper start - fix a variety of piston desync dupes
++                if (!allowDesync) {
++                    blockState1 = level.getBlockState(oldPos);
++                    map.replace(oldPos, blockState1);
++                }
++                level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, allowDesync ? (BlockState) list.get(i1) : blockState1, facing, extending, false));
++                if (!allowDesync) {
++                    level.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block
++                }
++                // Paper end - fix a variety of piston desync dupes
+                 blockStates[i++] = blockState1;
+             }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch
index 03d9e64403..2774d551a4 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
 +++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-@@ -306,7 +306,7 @@
-                 if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) {
-                     BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos);
+@@ -299,7 +_,7 @@
+                 if (level.getBlockState(pos).is(Blocks.MOVING_PISTON)) {
+                     BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, level, pos);
                      if (blockState.isAir()) {
--                        world.setBlock(pos, blockEntity.movedState, 84);
-+                        world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication ? 84 : (84 | Block.UPDATE_CLIENTS)); // Paper - fix a variety of piston desync dupes; force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air
-                         Block.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3);
+-                        level.setBlock(pos, blockEntity.movedState, 84);
++                        level.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication ? 84 : (84 | Block.UPDATE_CLIENTS)); // Paper - fix a variety of piston desync dupes; force notify (flag 2), it's possible the set type by the piston block (which doesn't notify) set this block to air
+                         Block.updateOrDestroy(blockEntity.movedState, blockState, level, pos, 3);
                      } else {
                          if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
deleted file mode 100644
index d269336ba6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
+++ /dev/null
@@ -1,180 +0,0 @@
---- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java
-+++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
-@@ -44,6 +44,13 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import com.google.common.collect.ImmutableList;
-+import java.util.AbstractList;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockPistonRetractEvent;
-+import org.bukkit.event.block.BlockPistonExtendEvent;
-+// CraftBukkit end
- 
- public class PistonBaseBlock extends DirectionalBlock {
- 
-@@ -155,6 +162,18 @@
-                 }
-             }
- 
-+            // CraftBukkit start
-+            // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down
-+            //     org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            //     BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.<org.bukkit.block.Block>of(), CraftBlock.notchToBlockFace(enumdirection));
-+            //     world.getCraftServer().getPluginManager().callEvent(event);
-+            //
-+            //     if (event.isCancelled()) {
-+            //         return;
-+            //     }
-+            // }
-+            // PAIL: checkME - what happened to setTypeAndData?
-+            // CraftBukkit end
-             world.blockEvent(pos, this, b0, enumdirection.get3DDataValue());
-         }
- 
-@@ -197,6 +216,12 @@
-     @Override
-     protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
-         Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING);
-+        // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
-+        Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below
-+        if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) {
-+            return false;
-+        }
-+        // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
-         BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true);
- 
-         if (!world.isClientSide) {
-@@ -229,8 +254,15 @@
- 
-             BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT);
- 
-+            // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires
-+            if (!this.isSticky) {
-+                if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) {
-+                    return false;
-+                }
-+            }
-+            // Paper end - Fix sticky pistons and BlockPistonRetractEvent
-             world.setBlock(pos, iblockdata2, 20);
--            world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true));
-+            world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change
-             world.blockUpdated(pos, iblockdata2.getBlock());
-             iblockdata2.updateNeighbourShapes(world, pos, 2);
-             if (this.isSticky) {
-@@ -255,11 +287,25 @@
-                     if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) {
-                         this.moveBlocks(world, pos, enumdirection, false);
-                     } else {
-+                        // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air)
-+                        if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) {
-+                            if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) {
-+                                return false;
-+                            }
-+                        }
-+                        // Paper end - Fix sticky pistons and BlockPistonRetractEvent
-                         world.removeBlock(pos.relative(enumdirection), false);
-                     }
-                 }
-             } else {
--                world.removeBlock(pos.relative(enumdirection), false);
-+                // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks
-+                BlockPos headPos = pos.relative(enumdirection);
-+                if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston.
-+                    world.removeBlock(headPos, false);
-+                } else {
-+                    ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync
-+                }
-+                // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
-             }
- 
-             world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F);
-@@ -335,7 +381,49 @@
-             BlockState[] aiblockdata = new BlockState[list.size() + list2.size()];
-             Direction enumdirection1 = extend ? dir : dir.getOpposite();
-             int i = 0;
-+            // CraftBukkit start
-+            final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+
-+            final List<BlockPos> moved = pistonextendschecker.getToPush();
-+            final List<BlockPos> broken = pistonextendschecker.getToDestroy();
-+
-+            List<org.bukkit.block.Block> blocks = new AbstractList<org.bukkit.block.Block>() {
- 
-+                @Override
-+                public int size() {
-+                    return moved.size() + broken.size();
-+                }
-+
-+                @Override
-+                public org.bukkit.block.Block get(int index) {
-+                    if (index >= this.size() || index < 0) {
-+                        throw new ArrayIndexOutOfBoundsException(index);
-+                    }
-+                    BlockPos pos = (BlockPos) (index < moved.size() ? moved.get(index) : broken.get(index - moved.size()));
-+                    return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+                }
-+            };
-+            org.bukkit.event.block.BlockPistonEvent event;
-+            if (extend) {
-+                event = new BlockPistonExtendEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1));
-+            } else {
-+                event = new BlockPistonRetractEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1));
-+            }
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                for (BlockPos b : broken) {
-+                    world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3);
-+                }
-+                for (BlockPos b : moved) {
-+                    world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3);
-+                    b = b.relative(enumdirection1);
-+                    world.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), world.getBlockState(b), 3);
-+                }
-+                return false;
-+            }
-+            // CraftBukkit end
-+
-             BlockPos blockposition3;
-             int j;
-             BlockState iblockdata1;
-@@ -345,7 +433,7 @@
-                 iblockdata1 = world.getBlockState(blockposition3);
-                 BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null;
- 
--                dropResources(iblockdata1, world, blockposition3, tileentity);
-+                dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - Add BlockBreakBlockEvent
-                 world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18);
-                 world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1));
-                 if (!iblockdata1.is(BlockTags.FIRE)) {
-@@ -358,13 +446,25 @@
-             BlockState iblockdata2;
- 
-             for (j = list.size() - 1; j >= 0; --j) {
--                blockposition3 = (BlockPos) list.get(j);
--                iblockdata1 = world.getBlockState(blockposition3);
-+                // Paper start - fix a variety of piston desync dupes
-+                boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication;
-+                BlockPos oldPos = blockposition3 = (BlockPos) list.get(j);
-+                iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null;
-+                // Paper end - fix a variety of piston desync dupes
-                 blockposition3 = blockposition3.relative(enumdirection1);
-                 map.remove(blockposition3);
-                 iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir);
-                 world.setBlock(blockposition3, iblockdata2, 68);
--                world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(j), dir, extend, false));
-+                // Paper start - fix a variety of piston desync dupes
-+                if (!allowDesync) {
-+                    iblockdata1 = world.getBlockState(oldPos);
-+                    map.replace(oldPos, iblockdata1);
-+                }
-+                world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? (BlockState) list1.get(j) : iblockdata1, dir, extend, false));
-+                if (!allowDesync) {
-+                    world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block
-+                }
-+                // Paper end - fix a variety of piston desync dupes
-                 aiblockdata[i++] = iblockdata1;
-             }
- 

From a0a4359af14445651e30617ed61465fa0e47cd78 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:29:32 -0500
Subject: [PATCH 115/285] net/minecraft/stats/

---
 .../stats/ServerRecipeBook.java.patch         | 29 ++++++++++++++
 .../stats/ServerStatsCounter.java.patch       | 33 ++++------------
 .../minecraft/stats/StatsCounter.java.patch   | 10 ++---
 .../stats/ServerRecipeBook.java.patch         | 38 -------------------
 4 files changed, 41 insertions(+), 69 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/stats/ServerStatsCounter.java.patch (61%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/stats/StatsCounter.java.patch (61%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/stats/ServerRecipeBook.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
new file mode 100644
index 0000000000..c4644730ac
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/stats/ServerRecipeBook.java
++++ b/net/minecraft/stats/ServerRecipeBook.java
+@@ -67,7 +_,7 @@
+ 
+         for (RecipeHolder<?> recipeHolder : recipes) {
+             ResourceKey<Recipe<?>> resourceKey = recipeHolder.id();
+-            if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial()) {
++            if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial()  && org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, resourceKey.location())) { // CraftBukkit{
+                 this.add(resourceKey);
+                 this.addHighlight(resourceKey);
+                 this.displayResolver
+@@ -78,7 +_,7 @@
+             }
+         }
+ 
+-        if (!list.isEmpty()) {
++        if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent
+             player.connection.send(new ClientboundRecipeBookAddPacket(list, false));
+         }
+ 
+@@ -96,7 +_,7 @@
+             }
+         }
+ 
+-        if (!list.isEmpty()) {
++        if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent
+             player.connection.send(new ClientboundRecipeBookRemovePacket(list));
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/stats/ServerStatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/stats/ServerStatsCounter.java.patch
rename to paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
index f82072300e..978eabf546 100644
--- a/paper-server/patches/unapplied/net/minecraft/stats/ServerStatsCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/stats/ServerStatsCounter.java
 +++ b/net/minecraft/stats/ServerStatsCounter.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.stats;
  
  import com.google.common.collect.Maps;
-@@ -57,9 +58,22 @@
+@@ -51,9 +_,22 @@
+                 LOGGER.error("Couldn't parse statistics file {}", file, var5);
              }
          }
- 
 +        // Paper start - Moved after stat fetching for player state file
 +        // Moves the loading after vanilla loading, so it overrides the values.
 +        // Disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving)
@@ -27,32 +27,13 @@
 +        if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot
          try {
              FileUtils.writeStringToFile(this.file, this.toJson());
-         } catch (IOException ioexception) {
-@@ -70,6 +84,8 @@
+         } catch (IOException var2) {
+@@ -63,6 +_,8 @@
  
      @Override
-     public void setValue(Player player, Stat<?> stat, int value) {
+     public void setValue(Player player, Stat<?> stat, int i) {
 +        if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot
 +        if (stat.getType() == Stats.CUSTOM && stat.getValue() instanceof final ResourceLocation resourceLocation && org.spigotmc.SpigotConfig.forcedStats.get(resourceLocation) != null) return; // Paper - disable saving forced stats
-         super.setValue(player, stat, value);
+         super.setValue(player, stat, i);
          this.dirty.add(stat);
      }
-@@ -158,13 +174,12 @@
-     }
- 
-     private <T> Optional<Stat<T>> getStat(StatType<T> type, String id) {
--        Optional optional = Optional.ofNullable(ResourceLocation.tryParse(id));
--        Registry iregistry = type.getRegistry();
-+        // CraftBukkit - decompile error start
-+        Optional<ResourceLocation> optional = Optional.ofNullable(ResourceLocation.tryParse(id));
-+        Registry<T> iregistry = type.getRegistry();
- 
--        Objects.requireNonNull(iregistry);
--        optional = optional.flatMap(iregistry::getOptional);
--        Objects.requireNonNull(type);
--        return optional.map(type::get);
-+        return optional.flatMap(iregistry::getOptional).map(type::get);
-+        // CraftBukkit - decompile error end
-     }
- 
-     private static CompoundTag fromJson(JsonObject json) {
diff --git a/paper-server/patches/unapplied/net/minecraft/stats/StatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/stats/StatsCounter.java.patch
rename to paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
index 51c6680579..5c300c2297 100644
--- a/paper-server/patches/unapplied/net/minecraft/stats/StatsCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/stats/StatsCounter.java
 +++ b/net/minecraft/stats/StatsCounter.java
-@@ -16,6 +16,12 @@
-     public void increment(Player player, Stat<?> stat, int value) {
-         int j = (int) Math.min((long) this.getValue(stat) + (long) value, 2147483647L);
+@@ -18,6 +_,12 @@
+     }
  
+     public void setValue(Player player, Stat<?> stat, int value) {
 +        // CraftBukkit start - fire Statistic events
-+        org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), j);
++        org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), value);
 +        if (cancellable != null && cancellable.isCancelled()) {
 +            return;
 +        }
 +        // CraftBukkit end
-         this.setValue(player, stat, j);
+         this.stats.put(stat, value);
      }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/stats/ServerRecipeBook.java.patch b/paper-server/patches/unapplied/net/minecraft/stats/ServerRecipeBook.java.patch
deleted file mode 100644
index f95e98f66a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/stats/ServerRecipeBook.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/stats/ServerRecipeBook.java
-+++ b/net/minecraft/stats/ServerRecipeBook.java
-@@ -29,6 +29,8 @@
- import net.minecraft.world.item.crafting.display.RecipeDisplayId;
- import org.slf4j.Logger;
- 
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
-+
- public class ServerRecipeBook extends RecipeBook {
- 
-     public static final String RECIPE_BOOK_TAG = "recipeBook";
-@@ -72,7 +74,7 @@
-             RecipeHolder<?> recipeholder = (RecipeHolder) iterator.next();
-             ResourceKey<Recipe<?>> resourcekey = recipeholder.id();
- 
--            if (!this.known.contains(resourcekey) && !recipeholder.value().isSpecial()) {
-+            if (!this.known.contains(resourcekey) && !recipeholder.value().isSpecial() && CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, resourcekey.location())) { // CraftBukkit
-                 this.add(resourcekey);
-                 this.addHighlight(resourcekey);
-                 this.displayResolver.displaysForRecipe(resourcekey, (recipedisplayentry) -> {
-@@ -82,7 +84,7 @@
-             }
-         }
- 
--        if (!list.isEmpty()) {
-+        if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent
-             player.connection.send(new ClientboundRecipeBookAddPacket(list, false));
-         }
- 
-@@ -105,7 +107,7 @@
-             }
-         }
- 
--        if (!list.isEmpty()) {
-+        if (!list.isEmpty() && player.connection != null) { // SPIGOT-4478 during PlayerLoginEvent
-             player.connection.send(new ClientboundRecipeBookRemovePacket(list));
-         }
- 

From 973fe2a945acbf0c77d4c6a6a61518a33b120de4 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:38:13 -0500
Subject: [PATCH 116/285] net/minecraft/world/entity/monster/creaking

---
 .../monster/creaking/Creaking.java.patch      | 35 +++++++++++
 .../monster/creaking/Creaking.java.patch      | 60 -------------------
 2 files changed, 35 insertions(+), 60 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/monster/creaking/Creaking.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch
new file mode 100644
index 0000000000..3c8cc3d167
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/creaking/Creaking.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/monster/creaking/Creaking.java
++++ b/net/minecraft/world/entity/monster/creaking/Creaking.java
+@@ -190,9 +_,9 @@
+     }
+ 
+     @Override
+-    public void push(double x, double y, double z) {
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - add push source entity param
+         if (this.canMove()) {
+-            super.push(x, y, z);
++            super.push(x, y, z, pushingEntity); // Paper - add push source entity param
+         }
+     }
+ 
+@@ -317,7 +_,7 @@
+         }
+ 
+         this.makeSound(this.getDeathSound());
+-        this.remove(Entity.RemovalReason.DISCARDED);
++        this.remove(Entity.RemovalReason.DISCARDED, null); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     public void creakingDeathEffects(DamageSource damageSource) {
+@@ -480,9 +_,9 @@
+     }
+ 
+     @Override
+-    public void knockback(double strength, double x, double z) {
++    public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events
+         if (this.canMove()) {
+-            super.knockback(strength, x, z);
++            super.knockback(strength, x, z, attacker, cause); // Paper - knockback events
+         }
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/creaking/Creaking.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/monster/creaking/Creaking.java.patch
deleted file mode 100644
index d3d16a6b28..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/monster/creaking/Creaking.java.patch
+++ /dev/null
@@ -1,60 +0,0 @@
---- a/net/minecraft/world/entity/monster/creaking/Creaking.java
-+++ b/net/minecraft/world/entity/monster/creaking/Creaking.java
-@@ -198,15 +198,15 @@
-     }
- 
-     @Override
--    public void push(double deltaX, double deltaY, double deltaZ) {
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param
-         if (this.canMove()) {
--            super.push(deltaX, deltaY, deltaZ);
-+            super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param
-         }
-     }
- 
-     @Override
-     public Brain<Creaking> getBrain() {
--        return super.getBrain();
-+        return (Brain<Creaking>) super.getBrain(); // CraftBukkit - decompile error
-     }
- 
-     @Override
-@@ -329,7 +329,7 @@
-         }
- 
-         this.makeSound(this.getDeathSound());
--        this.remove(Entity.RemovalReason.DISCARDED);
-+        this.remove(Entity.RemovalReason.DISCARDED, null); // CraftBukkit - add Bukkit remove cause
-     }
- 
-     public void creakingDeathEffects(DamageSource damageSource) {
-@@ -476,7 +476,7 @@
- 
-     @Override
-     protected SoundEvent getHurtSound(DamageSource source) {
--        return this.isHeartBound() ? SoundEvents.CREAKING_SWAY : super.getHurtSound(source);
-+        return SoundEvents.CREAKING_SWAY;
-     }
- 
-     @Override
-@@ -502,9 +502,9 @@
-     }
- 
-     @Override
--    public void knockback(double strength, double x, double z) {
-+    public void knockback(double strength, double x, double z, @Nullable Entity attacker, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause cause) { // Paper - knockback events
-         if (this.canMove()) {
--            super.knockback(strength, x, z);
-+            super.knockback(strength, x, z, attacker, cause); // Paper - knockback events
-         }
-     }
- 
-@@ -549,7 +549,7 @@
-     }
- 
-     public void activate(Player player) {
--        this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, (Object) player);
-+        this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, player); // CraftBukkit - decompile error
-         this.gameEvent(GameEvent.ENTITY_ACTION);
-         this.makeSound(SoundEvents.CREAKING_ACTIVATE);
-         this.setIsActive(true);

From 93114d09f225e72c62647976fe883070dcf82323 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 13:42:43 -0800
Subject: [PATCH 117/285] net.minecraft.server.commands

---
 .../server/commands/BanIpCommands.java.patch  |  6 +-
 .../commands/BanPlayerCommands.java.patch     | 11 ++++
 .../server/commands/DeOpCommands.java.patch   |  4 +-
 .../DefaultGameModeCommands.java.patch        | 14 ++--
 .../commands/DifficultyCommand.java.patch     | 16 +++++
 .../server/commands/EffectCommands.java.patch | 29 +++++++++
 .../commands/GameModeCommand.java.patch       | 12 ++--
 .../commands/GameRuleCommand.java.patch       | 20 ++++++
 .../server/commands/GiveCommand.java.patch    | 33 ++++++++++
 .../server/commands/KickCommand.java.patch    |  4 +-
 .../commands/ListPlayersCommand.java.patch    | 18 +++++
 .../server/commands/LootCommand.java.patch    | 10 +++
 .../server/commands/OpCommand.java.patch      |  4 +-
 .../server/commands/PlaceCommand.java.patch   | 10 +++
 .../server/commands/ReloadCommand.java.patch  | 24 +++----
 .../commands/ScheduleCommand.java.patch       | 29 +++++++++
 .../commands/SetSpawnCommand.java.patch       | 43 ++++++++++++
 .../commands/SetWorldSpawnCommand.java.patch  | 10 +--
 .../commands/SpreadPlayersCommand.java.patch  | 10 +++
 .../server/commands/SummonCommand.java.patch  | 19 ++++++
 .../commands/TeleportCommand.java.patch       | 43 ++++++++++++
 .../server/commands/TimeCommand.java.patch    | 37 +++++++++++
 .../server/commands/WeatherCommand.java.patch | 30 +++++++++
 .../commands/WorldBorderCommand.java.patch    | 63 ++++++++++++++++++
 .../commands/BanPlayerCommands.java.patch     | 11 ----
 .../commands/DifficultyCommand.java.patch     | 17 -----
 .../server/commands/EffectCommands.java.patch | 29 ---------
 .../commands/GameRuleCommand.java.patch       | 23 -------
 .../server/commands/GiveCommand.java.patch    | 33 ----------
 .../commands/ListPlayersCommand.java.patch    | 18 -----
 .../server/commands/LootCommand.java.patch    | 19 ------
 .../server/commands/PlaceCommand.java.patch   | 10 ---
 .../commands/ScheduleCommand.java.patch       | 29 ---------
 .../commands/SetSpawnCommand.java.patch       | 42 ------------
 .../commands/SpreadPlayersCommand.java.patch  | 20 ------
 .../server/commands/SummonCommand.java.patch  | 19 ------
 .../commands/TeleportCommand.java.patch       | 55 ----------------
 .../server/commands/TimeCommand.java.patch    | 55 ----------------
 .../server/commands/WeatherCommand.java.patch | 34 ----------
 .../commands/WorldBorderCommand.java.patch    | 65 -------------------
 40 files changed, 460 insertions(+), 518 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/BanIpCommands.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/DeOpCommands.java.patch (85%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/DefaultGameModeCommands.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/GameModeCommand.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/KickCommand.java.patch (89%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/OpCommand.java.patch (83%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/ReloadCommand.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/BanPlayerCommands.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/DifficultyCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/EffectCommands.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/GameRuleCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/GiveCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/ListPlayersCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/LootCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/PlaceCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/ScheduleCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/SetSpawnCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/SummonCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/TeleportCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/TimeCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/WeatherCommand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/commands/WorldBorderCommand.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/BanIpCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/BanIpCommands.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch
index be620cd58a..3a29620e92 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/BanIpCommands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/BanIpCommands.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/commands/BanIpCommands.java
 +++ b/net/minecraft/server/commands/BanIpCommands.java
-@@ -66,7 +66,7 @@
+@@ -68,7 +_,7 @@
              }
  
-             for (ServerPlayer serverPlayer : list) {
+             for (ServerPlayer serverPlayer : playersWithAddress) {
 -                serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"));
 +                serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"), org.bukkit.event.player.PlayerKickEvent.Cause.IP_BANNED); // Paper - kick event cause
              }
  
-             return list.size();
+             return playersWithAddress.size();
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch
new file mode 100644
index 0000000000..9c2282ee36
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/BanPlayerCommands.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/commands/BanPlayerCommands.java
++++ b/net/minecraft/server/commands/BanPlayerCommands.java
+@@ -55,7 +_,7 @@
+                 );
+                 ServerPlayer player = source.getServer().getPlayerList().getPlayer(gameProfile.getId());
+                 if (player != null) {
+-                    player.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"));
++                    player.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause
+                 }
+             }
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/DeOpCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch
similarity index 85%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/DeOpCommands.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch
index 5009b1bdcb..8f0c19b86d 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/DeOpCommands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/DeOpCommands.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/server/commands/DeOpCommands.java
 +++ b/net/minecraft/server/commands/DeOpCommands.java
-@@ -35,7 +35,7 @@
+@@ -35,7 +_,7 @@
              if (playerList.isOp(gameProfile)) {
                  playerList.deop(gameProfile);
                  i++;
--                source.sendSuccess(() -> Component.translatable("commands.deop.success", targets.iterator().next().getName()), true);
+-                source.sendSuccess(() -> Component.translatable("commands.deop.success", players.iterator().next().getName()), true);
 +                source.sendSuccess(() -> Component.translatable("commands.deop.success", gameProfile.getName()), true); // Paper - fixes MC-253721
              }
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/DefaultGameModeCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/DefaultGameModeCommands.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch
index dfb815a54e..4f48d7dc70 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/DefaultGameModeCommands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/server/commands/DefaultGameModeCommands.java
 +++ b/net/minecraft/server/commands/DefaultGameModeCommands.java
-@@ -28,9 +28,13 @@
-         GameType gameType = minecraftServer.getForcedGameType();
-         if (gameType != null) {
-             for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) {
--                if (serverPlayer.setGameMode(gameType)) {
+@@ -28,9 +_,13 @@
+         GameType forcedGameType = server.getForcedGameType();
+         if (forcedGameType != null) {
+             for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
+-                if (serverPlayer.setGameMode(forcedGameType)) {
 -                    i++;
 +                // Paper start - Expand PlayerGameModeChangeEvent
-+                org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty());
++                org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty());
 +                if (event != null && event.isCancelled()) {
-+                    source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false);
++                    commandSource.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false);
                  }
 +                // Paper end - Expand PlayerGameModeChangeEvent
 +                    i++;
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
new file mode 100644
index 0000000000..e17945d462
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/server/commands/DifficultyCommand.java
++++ b/net/minecraft/server/commands/DifficultyCommand.java
+@@ -31,10 +_,11 @@
+ 
+     public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
+         MinecraftServer server = source.getServer();
+-        if (server.getWorldData().getDifficulty() == difficulty) {
++        net.minecraft.server.level.ServerLevel serverLevel = source.getLevel(); // CraftBukkit
++        if (serverLevel.getDifficulty() == difficulty) { // CraftBukkit
+             throw ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
+         } else {
+-            server.setDifficulty(difficulty, true);
++            server.setDifficulty(serverLevel, difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix)
+             source.sendSuccess(() -> Component.translatable("commands.difficulty.success", difficulty.getDisplayName()), true);
+             return 0;
+         }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch
new file mode 100644
index 0000000000..b634208b16
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/EffectCommands.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/server/commands/EffectCommands.java
++++ b/net/minecraft/server/commands/EffectCommands.java
+@@ -180,7 +_,7 @@
+         for (Entity entity : targets) {
+             if (entity instanceof LivingEntity) {
+                 MobEffectInstance mobEffectInstance = new MobEffectInstance(effect, i1, amplifier, false, showParticles);
+-                if (((LivingEntity)entity).addEffect(mobEffectInstance, source.getEntity())) {
++                if (((LivingEntity)entity).addEffect(mobEffectInstance, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                     i++;
+                 }
+             }
+@@ -210,7 +_,7 @@
+         int i = 0;
+ 
+         for (Entity entity : targets) {
+-            if (entity instanceof LivingEntity && ((LivingEntity)entity).removeAllEffects()) {
++            if (entity instanceof LivingEntity && ((LivingEntity)entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                 i++;
+             }
+         }
+@@ -235,7 +_,7 @@
+         int i = 0;
+ 
+         for (Entity entity : targets) {
+-            if (entity instanceof LivingEntity && ((LivingEntity)entity).removeEffect(effect)) {
++            if (entity instanceof LivingEntity && ((LivingEntity)entity).removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                 i++;
+             }
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/GameModeCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/GameModeCommand.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch
index 5984de9d0f..51e5a4909d 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/GameModeCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch
@@ -1,17 +1,17 @@
 --- a/net/minecraft/server/commands/GameModeCommand.java
 +++ b/net/minecraft/server/commands/GameModeCommand.java
-@@ -60,9 +60,14 @@
+@@ -54,9 +_,14 @@
          int i = 0;
  
-         for (ServerPlayer serverPlayer : targets) {
--            if (serverPlayer.setGameMode(gameMode)) {
+         for (ServerPlayer serverPlayer : players) {
+-            if (serverPlayer.setGameMode(gameType)) {
 +            // Paper start - Expand PlayerGameModeChangeEvent
-+            org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty());
++            org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty());
 +            if (event != null && !event.isCancelled()) {
-                 logGamemodeChange(context.getSource(), serverPlayer, gameMode);
+                 logGamemodeChange(source.getSource(), serverPlayer, gameType);
                  i++;
 +            } else if (event != null && event.cancelMessage() != null) {
-+                context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true);
++                source.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true);
 +                // Paper end - Expand PlayerGameModeChangeEvent
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch
new file mode 100644
index 0000000000..1f8178fb56
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/GameRuleCommand.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/server/commands/GameRuleCommand.java
++++ b/net/minecraft/server/commands/GameRuleCommand.java
+@@ -30,14 +_,14 @@
+ 
+     static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> source, GameRules.Key<T> gameRule) {
+         CommandSourceStack commandSourceStack = source.getSource();
+-        T rule = commandSourceStack.getServer().getGameRules().getRule(gameRule);
+-        rule.setFromArgument(source, "value");
++        T rule = commandSourceStack.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
++        rule.setFromArgument(source, "value", gameRule); // Paper - Add WorldGameRuleChangeEvent
+         commandSourceStack.sendSuccess(() -> Component.translatable("commands.gamerule.set", gameRule.getId(), rule.toString()), true);
+         return rule.getCommandResult();
+     }
+ 
+     static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack source, GameRules.Key<T> gameRule) {
+-        T rule = source.getServer().getGameRules().getRule(gameRule);
++        T rule = source.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
+         source.sendSuccess(() -> Component.translatable("commands.gamerule.query", gameRule.getId(), rule.toString()), false);
+         return rule.getCommandResult();
+     }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch
new file mode 100644
index 0000000000..be76cc3572
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/GiveCommand.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/server/commands/GiveCommand.java
++++ b/net/minecraft/server/commands/GiveCommand.java
+@@ -51,6 +_,7 @@
+ 
+     private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
+         ItemStack itemStack = item.createItemStack(1, false);
++        final Component displayName = itemStack.getDisplayName(); // Paper - get display name early
+         int maxStackSize = itemStack.getMaxStackSize();
+         int i = maxStackSize * 100;
+         if (count > i) {
+@@ -66,7 +_,7 @@
+                     ItemStack itemStack1 = item.createItemStack(min, false);
+                     boolean flag = serverPlayer.getInventory().add(itemStack1);
+                     if (flag && itemStack1.isEmpty()) {
+-                        ItemEntity itemEntity = serverPlayer.drop(itemStack, false);
++                        ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
+                         if (itemEntity != null) {
+                             itemEntity.makeFakeItem();
+                         }
+@@ -95,11 +_,11 @@
+ 
+             if (targets.size() == 1) {
+                 source.sendSuccess(
+-                    () -> Component.translatable("commands.give.success.single", count, itemStack.getDisplayName(), targets.iterator().next().getDisplayName()),
++                    () -> Component.translatable("commands.give.success.single", count, displayName, targets.iterator().next().getDisplayName()), // Paper - use cached display name
+                     true
+                 );
+             } else {
+-                source.sendSuccess(() -> Component.translatable("commands.give.success.single", count, itemStack.getDisplayName(), targets.size()), true);
++                source.sendSuccess(() -> Component.translatable("commands.give.success.single", count, displayName, targets.size()), true); // Paper - use cached display name
+             }
+ 
+             return targets.size();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/KickCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch
similarity index 89%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/KickCommand.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch
index 8138965b1c..f76fe82415 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/KickCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/KickCommand.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/server/commands/KickCommand.java
 +++ b/net/minecraft/server/commands/KickCommand.java
-@@ -48,7 +48,7 @@
+@@ -48,7 +_,7 @@
  
-             for (ServerPlayer serverPlayer : targets) {
+             for (ServerPlayer serverPlayer : players) {
                  if (!source.getServer().isSingleplayerOwner(serverPlayer.getGameProfile())) {
 -                    serverPlayer.connection.disconnect(reason);
 +                    serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch
new file mode 100644
index 0000000000..feda570539
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/ListPlayersCommand.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/server/commands/ListPlayersCommand.java
++++ b/net/minecraft/server/commands/ListPlayersCommand.java
+@@ -32,7 +_,14 @@
+ 
+     private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameExtractor) {
+         PlayerList playerList = source.getServer().getPlayerList();
+-        List<ServerPlayer> players = playerList.getPlayers();
++        // CraftBukkit start
++        List<ServerPlayer> playersTemp = playerList.getPlayers();
++        if (source.getBukkitSender() instanceof org.bukkit.entity.Player) {
++            org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender();
++            playersTemp = playersTemp.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList());
++        }
++        final List<ServerPlayer> players = playersTemp;
++        // CraftBukkit end
+         Component component = ComponentUtils.formatList(players, nameExtractor);
+         source.sendSuccess(() -> Component.translatable("commands.list.players", players.size(), playerList.getMaxPlayers(), component), false);
+         return players.size();
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch
new file mode 100644
index 0000000000..f83d4e298c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/server/commands/LootCommand.java
++++ b/net/minecraft/server/commands/LootCommand.java
+@@ -395,6 +_,7 @@
+ 
+     private static int dropInWorld(CommandSourceStack source, Vec3 pos, List<ItemStack> items, LootCommand.Callback callback) throws CommandSyntaxException {
+         ServerLevel level = source.getLevel();
++        items.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem
+         items.forEach(itemStack -> {
+             ItemEntity itemEntity = new ItemEntity(level, pos.x, pos.y, pos.z, itemStack.copy());
+             itemEntity.setDefaultPickUpDelay();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/OpCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch
similarity index 83%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/OpCommand.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch
index 3ca43d3777..bb5525dd82 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/OpCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/OpCommand.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/server/commands/OpCommand.java
 +++ b/net/minecraft/server/commands/OpCommand.java
-@@ -46,7 +46,7 @@
+@@ -46,7 +_,7 @@
              if (!playerList.isOp(gameProfile)) {
                  playerList.op(gameProfile);
                  i++;
--                source.sendSuccess(() -> Component.translatable("commands.op.success", targets.iterator().next().getName()), true);
+-                source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfiles.iterator().next().getName()), true);
 +                source.sendSuccess(() -> Component.translatable("commands.op.success", gameProfile.getName()), true); // Paper - fixes MC-253721
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch
new file mode 100644
index 0000000000..ec190202c4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/server/commands/PlaceCommand.java
++++ b/net/minecraft/server/commands/PlaceCommand.java
+@@ -280,6 +_,7 @@
+         if (!structureStart.isValid()) {
+             throw ERROR_STRUCTURE_FAILED.create();
+         } else {
++            structureStart.generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.COMMAND; // CraftBukkit - set AsyncStructureGenerateEvent.Cause.COMMAND as generation cause
+             BoundingBox boundingBox = structureStart.getBoundingBox();
+             ChunkPos chunkPos = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ()));
+             ChunkPos chunkPos1 = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ()));
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/ReloadCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/ReloadCommand.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
index f0b9cdfcce..f1221352b7 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/ReloadCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
@@ -1,18 +1,19 @@
 --- a/net/minecraft/server/commands/ReloadCommand.java
 +++ b/net/minecraft/server/commands/ReloadCommand.java
-@@ -20,7 +20,7 @@
-     public ReloadCommand() {}
+@@ -16,7 +_,7 @@
+     private static final Logger LOGGER = LogUtils.getLogger();
  
-     public static void reloadPacks(Collection<String> dataPacks, CommandSourceStack source) {
--        source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> {
-+        source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent
-             ReloadCommand.LOGGER.warn("Failed to execute reload", throwable);
+     public static void reloadPacks(Collection<String> selectedIds, CommandSourceStack source) {
+-        source.getServer().reloadResources(selectedIds).exceptionally(throwable -> {
++        source.getServer().reloadResources(selectedIds, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally(throwable -> { // Paper - Add ServerResourcesReloadedEvent
+             LOGGER.warn("Failed to execute reload", throwable);
              source.sendFailure(Component.translatable("commands.reload.failure"));
              return null;
-@@ -44,6 +44,16 @@
-         return collection1;
-     }
+@@ -36,6 +_,16 @@
  
+         return list;
+     }
++
 +    // CraftBukkit start
 +    public static void reload(MinecraftServer minecraftserver) {
 +        PackRepository resourcepackrepository = minecraftserver.getPackRepository();
@@ -22,7 +23,6 @@
 +        minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent
 +    }
 +    // CraftBukkit end
-+
+ 
      public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
-         dispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("reload").requires((commandlistenerwrapper) -> {
-             return commandlistenerwrapper.hasPermission(2);
+         dispatcher.register(Commands.literal("reload").requires(source -> source.hasPermission(2)).executes(context -> {
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch
new file mode 100644
index 0000000000..bd614fe2f3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/ScheduleCommand.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/server/commands/ScheduleCommand.java
++++ b/net/minecraft/server/commands/ScheduleCommand.java
+@@ -32,7 +_,7 @@
+     );
+     private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro"));
+     private static final SuggestionProvider<CommandSourceStack> SUGGEST_SCHEDULE = (context, builder) -> SharedSuggestionProvider.suggest(
+-        context.getSource().getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), builder
++        context.getSource().getLevel().serverLevelData.getScheduledEvents().getEventsIds(), builder // Paper - Make schedule command per-world
+     );
+ 
+     public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+@@ -101,7 +_,7 @@
+         } else {
+             long l = source.getLevel().getGameTime() + time;
+             ResourceLocation resourceLocation = function.getFirst();
+-            TimerQueue<MinecraftServer> scheduledEvents = source.getServer().getWorldData().overworldData().getScheduledEvents();
++            TimerQueue<MinecraftServer> scheduledEvents = source.getLevel().serverLevelData.overworldData().getScheduledEvents(); // CraftBukkit - SPIGOT-6667: Use world specific function timer
+             Optional<CommandFunction<CommandSourceStack>> optional = function.getSecond().left();
+             if (optional.isPresent()) {
+                 if (optional.get() instanceof MacroFunction) {
+@@ -132,7 +_,7 @@
+     }
+ 
+     private static int remove(CommandSourceStack source, String function) throws CommandSyntaxException {
+-        int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(function);
++        int i = source.getLevel().serverLevelData.overworldData().getScheduledEvents().remove(function); // Paper - Make schedule command per-world
+         if (i == 0) {
+             throw ERROR_CANT_REMOVE.create(function);
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch
new file mode 100644
index 0000000000..a2a722311b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/server/commands/SetSpawnCommand.java
++++ b/net/minecraft/server/commands/SetSpawnCommand.java
+@@ -66,24 +_,34 @@
+     private static int setSpawn(CommandSourceStack source, Collection<ServerPlayer> targets, BlockPos pos, float angle) {
+         ResourceKey<Level> resourceKey = source.getLevel().dimension();
+ 
++        final Collection<ServerPlayer> actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent
+         for (ServerPlayer serverPlayer : targets) {
+-            serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false);
+-        }
++            // Paper start - Add PlayerSetSpawnEvent
++            if (serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) {
++                actualTargets.add(serverPlayer);
++            }
++            // Paper end - Add PlayerSetSpawnEvent
++        }
++        // Paper start - Add PlayerSetSpawnEvent
++        if (actualTargets.isEmpty()) {
++            return 0;
++        }
++        // Paper end - Add PlayerSetSpawnEvent
+ 
+         String string = resourceKey.location().toString();
+-        if (targets.size() == 1) {
++        if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent
+             source.sendSuccess(
+                 () -> Component.translatable(
+-                    "commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, string, targets.iterator().next().getDisplayName()
++                    "commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, string, actualTargets.iterator().next().getDisplayName() // Paper - Add PlayerSetSpawnEvent
+                 ),
+                 true
+             );
+         } else {
+             source.sendSuccess(
+-                () -> Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, string, targets.size()), true
++                () -> Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, string, actualTargets.size()), true // Paper - Add PlayerSetSpawnEvent
+             );
+         }
+ 
+-        return targets.size();
++        return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch
rename to paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch
index 5e3b2e9493..c947d07f33 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/SetWorldSpawnCommand.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/commands/SetWorldSpawnCommand.java
 +++ b/net/minecraft/server/commands/SetWorldSpawnCommand.java
-@@ -30,7 +30,7 @@
-     private static int setSpawn(CommandSourceStack source, BlockPos pos, float angle) {
-         ServerLevel worldserver = source.getLevel();
+@@ -33,7 +_,7 @@
  
--        if (worldserver.dimension() != Level.OVERWORLD) {
-+        if (false && worldserver.dimension() != Level.OVERWORLD) { // CraftBukkit - SPIGOT-7649: allow in all worlds
+     private static int setSpawn(CommandSourceStack source, BlockPos pos, float angle) {
+         ServerLevel level = source.getLevel();
+-        if (level.dimension() != Level.OVERWORLD) {
++        if (false && level.dimension() != Level.OVERWORLD) { // CraftBukkit - SPIGOT-7649: allow in all worlds
              source.sendFailure(Component.translatable("commands.setworldspawn.failure.not_overworld"));
              return 0;
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
new file mode 100644
index 0000000000..a76793b039
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/server/commands/SpreadPlayersCommand.java
++++ b/net/minecraft/server/commands/SpreadPlayersCommand.java
+@@ -255,6 +_,7 @@
+                 entity.getYRot(),
+                 entity.getXRot(),
+                 true
++                , org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND // CraftBukkit - handle teleport reason
+             );
+             double d1 = Double.MAX_VALUE;
+ 
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch
new file mode 100644
index 0000000000..e35d0b0328
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/SummonCommand.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/server/commands/SummonCommand.java
++++ b/net/minecraft/server/commands/SummonCommand.java
+@@ -82,6 +_,7 @@
+             ServerLevel level = source.getLevel();
+             Entity entity = EntityType.loadEntityRecursive(compoundTag, level, EntitySpawnReason.COMMAND, entity1 -> {
+                 entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot());
++                entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason
+                 return entity1;
+             });
+             if (entity == null) {
+@@ -92,7 +_,7 @@
+                         .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, null);
+                 }
+ 
+-                if (!level.tryAddFreshEntityWithPassengers(entity)) {
++                if (!level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND"
+                     throw ERROR_DUPLICATE_UUID.create();
+                 } else {
+                     return entity;
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
new file mode 100644
index 0000000000..4513244012
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/server/commands/TeleportCommand.java
++++ b/net/minecraft/server/commands/TeleportCommand.java
+@@ -20,6 +_,7 @@
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.network.chat.Component;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.Mth;
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.LivingEntity;
+@@ -290,7 +_,31 @@
+             float f1 = relatives.contains(Relative.X_ROT) ? xRot - target.getXRot() : xRot;
+             float f2 = Mth.wrapDegrees(f);
+             float f3 = Mth.wrapDegrees(f1);
+-            if (target.teleportTo(level, d, d1, d2, relatives, f2, f3, true)) {
++            // CraftBukkit start - Teleport event
++            boolean result;
++            if (target instanceof ServerPlayer player) {
++                result = player.teleportTo(level, d, d1, d2, relatives, f2, f3, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND);
++            } else {
++                org.bukkit.Location to = new org.bukkit.Location(level.getWorld(), d, d1, d2, f2, f3);
++                org.bukkit.event.entity.EntityTeleportEvent event = new org.bukkit.event.entity.EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to);
++                level.getCraftServer().getPluginManager().callEvent(event);
++                if (event.isCancelled() || event.getTo() == null) { // Paper
++                    return;
++                }
++                to = event.getTo(); // Paper - actually track new location
++
++                d = to.getX();
++                d1 = to.getY();
++                d2 = to.getZ();
++                f2 = to.getYaw();
++                f3 = to.getPitch();
++                level = ((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle();
++
++                result = target.teleportTo(level, d, d1, d2, relatives, f2, f3, true);
++            }
++
++            if (result) {
++                // CraftBukkit end
+                 if (lookAt != null) {
+                     lookAt.perform(source, target);
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch
new file mode 100644
index 0000000000..efc275401c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/TimeCommand.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/server/commands/TimeCommand.java
++++ b/net/minecraft/server/commands/TimeCommand.java
+@@ -56,8 +_,15 @@
+     }
+ 
+     public static int setTime(CommandSourceStack source, int time) {
+-        for (ServerLevel serverLevel : source.getServer().getAllLevels()) {
+-            serverLevel.setDayTime(time);
++        for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
++            // serverLevel.setDayTime(time);
++            // CraftBukkit start
++            org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(serverLevel.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, time - serverLevel.getDayTime());
++            org.bukkit.Bukkit.getPluginManager().callEvent(event);
++            if (!event.isCancelled()) {
++                serverLevel.setDayTime(serverLevel.getDayTime() + event.getSkipAmount());
++            }
++            // CraftBukkit end
+         }
+ 
+         source.getServer().forceTimeSynchronization();
+@@ -66,8 +_,14 @@
+     }
+ 
+     public static int addTime(CommandSourceStack source, int amount) {
+-        for (ServerLevel serverLevel : source.getServer().getAllLevels()) {
+-            serverLevel.setDayTime(serverLevel.getDayTime() + amount);
++        for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
++            // CraftBukkit start
++            org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(serverLevel.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, amount);
++            org.bukkit.Bukkit.getPluginManager().callEvent(event);
++            if (!event.isCancelled()) {
++                serverLevel.setDayTime(serverLevel.getDayTime() + event.getSkipAmount());
++            }
++            // CraftBukkit end
+         }
+ 
+         source.getServer().forceTimeSynchronization();
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch
new file mode 100644
index 0000000000..d5d445aa1e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/server/commands/WeatherCommand.java
++++ b/net/minecraft/server/commands/WeatherCommand.java
+@@ -44,23 +_,23 @@
+     }
+ 
+     private static int getDuration(CommandSourceStack source, int time, IntProvider timeProvider) {
+-        return time == -1 ? timeProvider.sample(source.getServer().overworld().getRandom()) : time;
++        return time == -1 ? timeProvider.sample(source.getLevel().getRandom()) : time; // CraftBukkit - SPIGOT-7680: per-world
+     }
+ 
+     private static int setClear(CommandSourceStack source, int time) {
+-        source.getServer().overworld().setWeatherParameters(getDuration(source, time, ServerLevel.RAIN_DELAY), 0, false, false);
++        source.getLevel().setWeatherParameters(getDuration(source, time, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world
+         source.sendSuccess(() -> Component.translatable("commands.weather.set.clear"), true);
+         return time;
+     }
+ 
+     private static int setRain(CommandSourceStack source, int time) {
+-        source.getServer().overworld().setWeatherParameters(0, getDuration(source, time, ServerLevel.RAIN_DURATION), true, false);
++        source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world
+         source.sendSuccess(() -> Component.translatable("commands.weather.set.rain"), true);
+         return time;
+     }
+ 
+     private static int setThunder(CommandSourceStack source, int time) {
+-        source.getServer().overworld().setWeatherParameters(0, getDuration(source, time, ServerLevel.THUNDER_DURATION), true, true);
++        source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world
+         source.sendSuccess(() -> Component.translatable("commands.weather.set.thunder"), true);
+         return time;
+     }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch
new file mode 100644
index 0000000000..26d5ded309
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/server/commands/WorldBorderCommand.java
++++ b/net/minecraft/server/commands/WorldBorderCommand.java
+@@ -135,7 +_,7 @@
+     }
+ 
+     private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         if (worldBorder.getDamageSafeZone() == distance) {
+             throw ERROR_SAME_DAMAGE_BUFFER.create();
+         } else {
+@@ -146,7 +_,7 @@
+     }
+ 
+     private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         if (worldBorder.getDamagePerBlock() == damagePerBlock) {
+             throw ERROR_SAME_DAMAGE_AMOUNT.create();
+         } else {
+@@ -159,7 +_,7 @@
+     }
+ 
+     private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         if (worldBorder.getWarningTime() == time) {
+             throw ERROR_SAME_WARNING_TIME.create();
+         } else {
+@@ -170,7 +_,7 @@
+     }
+ 
+     private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         if (worldBorder.getWarningBlocks() == distance) {
+             throw ERROR_SAME_WARNING_DISTANCE.create();
+         } else {
+@@ -181,13 +_,13 @@
+     }
+ 
+     private static int getSize(CommandSourceStack source) {
+-        double size = source.getServer().overworld().getWorldBorder().getSize();
++        double size = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
+         source.sendSuccess(() -> Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", size)), false);
+         return Mth.floor(size + 0.5);
+     }
+ 
+     private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         if (worldBorder.getCenterX() == pos.x && worldBorder.getCenterZ() == pos.y) {
+             throw ERROR_SAME_CENTER.create();
+         } else if (!(Math.abs(pos.x) > 2.9999984E7) && !(Math.abs(pos.y) > 2.9999984E7)) {
+@@ -205,7 +_,7 @@
+     }
+ 
+     private static int setSize(CommandSourceStack source, double newSize, long time) throws CommandSyntaxException {
+-        WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
++        WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit
+         double size = worldBorder.getSize();
+         if (size == newSize) {
+             throw ERROR_SAME_SIZE.create();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/BanPlayerCommands.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/BanPlayerCommands.java.patch
deleted file mode 100644
index a6e8c45f71..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/BanPlayerCommands.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/server/commands/BanPlayerCommands.java
-+++ b/net/minecraft/server/commands/BanPlayerCommands.java
-@@ -55,7 +55,7 @@
-                 );
-                 ServerPlayer serverPlayer = source.getServer().getPlayerList().getPlayer(gameProfile.getId());
-                 if (serverPlayer != null) {
--                    serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"));
-+                    serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause
-                 }
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/DifficultyCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/DifficultyCommand.java.patch
deleted file mode 100644
index 0898d7e23d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/DifficultyCommand.java.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- a/net/minecraft/server/commands/DifficultyCommand.java
-+++ b/net/minecraft/server/commands/DifficultyCommand.java
-@@ -44,11 +44,12 @@
- 
-     public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
-         MinecraftServer minecraftserver = source.getServer();
-+        net.minecraft.server.level.ServerLevel worldServer = source.getLevel(); // CraftBukkit
- 
--        if (minecraftserver.getWorldData().getDifficulty() == difficulty) {
-+        if (worldServer.getDifficulty() == difficulty) { // CraftBukkit
-             throw DifficultyCommand.ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
-         } else {
--            minecraftserver.setDifficulty(difficulty, true);
-+            minecraftserver.setDifficulty(worldServer, difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix)
-             source.sendSuccess(() -> {
-                 return Component.translatable("commands.difficulty.success", difficulty.getDisplayName());
-             }, true);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/EffectCommands.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/EffectCommands.java.patch
deleted file mode 100644
index 94c98f3597..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/EffectCommands.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/server/commands/EffectCommands.java
-+++ b/net/minecraft/server/commands/EffectCommands.java
-@@ -84,7 +84,7 @@
-             if (entity instanceof LivingEntity) {
-                 MobEffectInstance mobeffect = new MobEffectInstance(statusEffect, k, amplifier, false, showParticles);
- 
--                if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity())) {
-+                if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
-                     ++j;
-                 }
-             }
-@@ -114,7 +114,7 @@
-         while (iterator.hasNext()) {
-             Entity entity = (Entity) iterator.next();
- 
--            if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects()) {
-+            if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
-                 ++i;
-             }
-         }
-@@ -144,7 +144,7 @@
-         while (iterator.hasNext()) {
-             Entity entity = (Entity) iterator.next();
- 
--            if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect)) {
-+            if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(statusEffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
-                 ++i;
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/GameRuleCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/GameRuleCommand.java.patch
deleted file mode 100644
index 66f9df2b3b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/GameRuleCommand.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/server/commands/GameRuleCommand.java
-+++ b/net/minecraft/server/commands/GameRuleCommand.java
-@@ -34,9 +34,9 @@
- 
-     static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> context, GameRules.Key<T> key) {
-         CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource();
--        T t0 = commandlistenerwrapper.getServer().getGameRules().getRule(key);
-+        T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit
- 
--        t0.setFromArgument(context, "value");
-+        t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent
-         commandlistenerwrapper.sendSuccess(() -> {
-             return Component.translatable("commands.gamerule.set", key.getId(), t0.toString());
-         }, true);
-@@ -44,7 +44,7 @@
-     }
- 
-     static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack source, GameRules.Key<T> key) {
--        T t0 = source.getServer().getGameRules().getRule(key);
-+        T t0 = source.getLevel().getGameRules().getRule(key); // CraftBukkit
- 
-         source.sendSuccess(() -> {
-             return Component.translatable("commands.gamerule.query", key.getId(), t0.toString());
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/GiveCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/GiveCommand.java.patch
deleted file mode 100644
index 025f1543d5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/GiveCommand.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/server/commands/GiveCommand.java
-+++ b/net/minecraft/server/commands/GiveCommand.java
-@@ -38,6 +38,7 @@
- 
-     private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
-         ItemStack itemstack = item.createItemStack(1, false);
-+        final Component displayName = itemstack.getDisplayName(); // Paper - get display name early
-         int j = itemstack.getMaxStackSize();
-         int k = j * 100;
- 
-@@ -60,7 +61,7 @@
-                     ItemEntity entityitem;
- 
-                     if (flag && itemstack1.isEmpty()) {
--                        entityitem = entityplayer.drop(itemstack, false);
-+                        entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
-                         if (entityitem != null) {
-                             entityitem.makeFakeItem();
-                         }
-@@ -79,11 +80,11 @@
- 
-             if (targets.size() == 1) {
-                 source.sendSuccess(() -> {
--                    return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName());
-+                    return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name
-                 }, true);
-             } else {
-                 source.sendSuccess(() -> {
--                    return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size());
-+                    return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name
-                 }, true);
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/ListPlayersCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/ListPlayersCommand.java.patch
deleted file mode 100644
index 93aec260e3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/ListPlayersCommand.java.patch
+++ /dev/null
@@ -1,18 +0,0 @@
---- a/net/minecraft/server/commands/ListPlayersCommand.java
-+++ b/net/minecraft/server/commands/ListPlayersCommand.java
-@@ -35,7 +35,14 @@
- 
-     private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameProvider) {
-         PlayerList playerlist = source.getServer().getPlayerList();
--        List<ServerPlayer> list = playerlist.getPlayers();
-+        // CraftBukkit start
-+        List<ServerPlayer> players = playerlist.getPlayers();
-+        if (source.getBukkitSender() instanceof org.bukkit.entity.Player) {
-+            org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender();
-+            players = players.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList());
-+        }
-+        List<ServerPlayer> list = players;
-+        // CraftBukkit end
-         Component ichatbasecomponent = ComponentUtils.formatList(list, nameProvider);
- 
-         source.sendSuccess(() -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/LootCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/LootCommand.java.patch
deleted file mode 100644
index c579471341..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/LootCommand.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/server/commands/LootCommand.java
-+++ b/net/minecraft/server/commands/LootCommand.java
-@@ -95,7 +95,7 @@
-     }
- 
-     private static <T extends ArgumentBuilder<CommandSourceStack, T>> T addTargets(T rootArgument, LootCommand.TailProvider sourceConstructor) {
--        return rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> {
-+        return (T) rootArgument.then(((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("replace").then(net.minecraft.commands.Commands.literal("entity").then(net.minecraft.commands.Commands.argument("entities", EntityArgument.entities()).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("slot", SlotArgument.slot()), (commandcontext, list, commandloot_a) -> { // CraftBukkit - decompile error
-             return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), list.size(), list, commandloot_a);
-         }).then(sourceConstructor.construct(net.minecraft.commands.Commands.argument("count", IntegerArgumentType.integer(0)), (commandcontext, list, commandloot_a) -> {
-             return LootCommand.entityReplace(EntityArgument.getEntities(commandcontext, "entities"), SlotArgument.getSlot(commandcontext, "slot"), IntegerArgumentType.getInteger(commandcontext, "count"), list, commandloot_a);
-@@ -250,6 +250,7 @@
-     private static int dropInWorld(CommandSourceStack source, Vec3 pos, List<ItemStack> stacks, LootCommand.Callback messageSender) throws CommandSyntaxException {
-         ServerLevel worldserver = source.getLevel();
- 
-+        stacks.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem
-         stacks.forEach((itemstack) -> {
-             ItemEntity entityitem = new ItemEntity(worldserver, pos.x, pos.y, pos.z, itemstack.copy());
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/PlaceCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/PlaceCommand.java.patch
deleted file mode 100644
index 1c1e4cacb4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/PlaceCommand.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/server/commands/PlaceCommand.java
-+++ b/net/minecraft/server/commands/PlaceCommand.java
-@@ -132,6 +132,7 @@
-         if (!structurestart.isValid()) {
-             throw PlaceCommand.ERROR_STRUCTURE_FAILED.create();
-         } else {
-+            structurestart.generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.COMMAND; // CraftBukkit - set AsyncStructureGenerateEvent.Cause.COMMAND as generation cause
-             BoundingBox structureboundingbox = structurestart.getBoundingBox();
-             ChunkPos chunkcoordintpair = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.minX()), SectionPos.blockToSectionCoord(structureboundingbox.minZ()));
-             ChunkPos chunkcoordintpair1 = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.maxX()), SectionPos.blockToSectionCoord(structureboundingbox.maxZ()));
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/ScheduleCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/ScheduleCommand.java.patch
deleted file mode 100644
index 885d99adba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/ScheduleCommand.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/server/commands/ScheduleCommand.java
-+++ b/net/minecraft/server/commands/ScheduleCommand.java
-@@ -33,7 +33,7 @@
-     });
-     private static final SimpleCommandExceptionType ERROR_MACRO = new SimpleCommandExceptionType(Component.translatableEscape("commands.schedule.macro"));
-     private static final SuggestionProvider<CommandSourceStack> SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> {
--        return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder);
-+        return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - Make schedule command per-world
-     };
- 
-     public ScheduleCommand() {}
-@@ -58,7 +58,7 @@
-         } else {
-             long j = source.getLevel().getGameTime() + (long) time;
-             ResourceLocation minecraftkey = (ResourceLocation) function.getFirst();
--            TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue = source.getServer().getWorldData().overworldData().getScheduledEvents();
-+            TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue = source.getLevel().serverLevelData.overworldData().getScheduledEvents(); // CraftBukkit - SPIGOT-6667: Use world specific function timer
-             Optional<net.minecraft.commands.functions.CommandFunction<CommandSourceStack>> optional = ((Either) function.getSecond()).left();
-             String s;
- 
-@@ -93,7 +93,7 @@
-     }
- 
-     private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException {
--        int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName);
-+        int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - Make schedule command per-world
- 
-         if (i == 0) {
-             throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/SetSpawnCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/SetSpawnCommand.java.patch
deleted file mode 100644
index f4a5c32f80..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/SetSpawnCommand.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/server/commands/SetSpawnCommand.java
-+++ b/net/minecraft/server/commands/SetSpawnCommand.java
-@@ -38,24 +38,34 @@
-         ResourceKey<Level> resourcekey = source.getLevel().dimension();
-         Iterator iterator = targets.iterator();
- 
-+        final Collection<ServerPlayer> actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
--            entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false);
-+            // Paper start - Add PlayerSetSpawnEvent
-+            if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) {
-+                actualTargets.add(entityplayer);
-+            }
-+            // Paper end - Add PlayerSetSpawnEvent
-         }
-+        // Paper start - Add PlayerSetSpawnEvent
-+        if (actualTargets.isEmpty()) {
-+            return 0;
-+        }
-+        // Paper end - Add PlayerSetSpawnEvent
- 
-         String s = resourcekey.location().toString();
- 
--        if (targets.size() == 1) {
-+        if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent
-             source.sendSuccess(() -> {
--                return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName());
-+                return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent
-             }, true);
-         } else {
-             source.sendSuccess(() -> {
--                return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size());
-+                return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent
-             }, true);
-         }
- 
--        return targets.size();
-+        return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
deleted file mode 100644
index 1827c81c69..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/server/commands/SpreadPlayersCommand.java
-+++ b/net/minecraft/server/commands/SpreadPlayersCommand.java
-@@ -93,7 +93,7 @@
-             if (entity instanceof Player) {
-                 set.add(entity.getTeam());
-             } else {
--                set.add((Object) null);
-+                set.add((Team) null); // CraftBukkit - decompile error
-             }
-         }
- 
-@@ -203,7 +203,7 @@
-                 commandspreadplayers_a = piles[j++];
-             }
- 
--            entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true);
-+            entity.teleportTo(world, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(world, maxY), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); // CraftBukkit - handle teleport reason
-             d1 = Double.MAX_VALUE;
-             SpreadPlayersCommand.Position[] acommandspreadplayers_a1 = piles;
-             int k = piles.length;
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/SummonCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/SummonCommand.java.patch
deleted file mode 100644
index 5a74f92631..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/SummonCommand.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/server/commands/SummonCommand.java
-+++ b/net/minecraft/server/commands/SummonCommand.java
-@@ -57,6 +57,7 @@
-             ServerLevel worldserver = source.getLevel();
-             Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, EntitySpawnReason.COMMAND, (entity1) -> {
-                 entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot());
-+                entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason
-                 return entity1;
-             });
- 
-@@ -67,7 +68,7 @@
-                     ((Mob) entity).finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, (SpawnGroupData) null);
-                 }
- 
--                if (!worldserver.tryAddFreshEntityWithPassengers(entity)) {
-+                if (!worldserver.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND"
-                     throw SummonCommand.ERROR_DUPLICATE_UUID.create();
-                 } else {
-                     return entity;
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/TeleportCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/TeleportCommand.java.patch
deleted file mode 100644
index 0126e4d505..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/TeleportCommand.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/server/commands/TeleportCommand.java
-+++ b/net/minecraft/server/commands/TeleportCommand.java
-@@ -22,6 +22,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.network.chat.Component;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.LivingEntity;
-@@ -30,6 +31,11 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.phys.Vec2;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.event.entity.EntityTeleportEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+// CraftBukkit end
- 
- public class TeleportCommand {
- 
-@@ -167,7 +173,31 @@
-             float f4 = Mth.wrapDegrees(f2);
-             float f5 = Mth.wrapDegrees(f3);
- 
--            if (target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true)) {
-+            // CraftBukkit start - Teleport event
-+            boolean result;
-+            if (target instanceof ServerPlayer player) {
-+                result = player.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true, PlayerTeleportEvent.TeleportCause.COMMAND);
-+            } else {
-+                Location to = new Location(world.getWorld(), d3, d4, d5, f4, f5);
-+                EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to);
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+                if (event.isCancelled() || event.getTo() == null) { // Paper
-+                    return;
-+                }
-+                to = event.getTo(); // Paper - actually track new location
-+
-+                d3 = to.getX();
-+                d4 = to.getY();
-+                d5 = to.getZ();
-+                f4 = to.getYaw();
-+                f5 = to.getPitch();
-+                world = ((CraftWorld) to.getWorld()).getHandle();
-+
-+                result = target.teleportTo(world, d3, d4, d5, movementFlags, f4, f5, true);
-+            }
-+
-+            if (result) {
-+                // CraftBukkit end
-                 if (facingLocation != null) {
-                     facingLocation.perform(source, target);
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/TimeCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/TimeCommand.java.patch
deleted file mode 100644
index 52c7c73aa7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/TimeCommand.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/server/commands/TimeCommand.java
-+++ b/net/minecraft/server/commands/TimeCommand.java
-@@ -8,6 +8,10 @@
- import net.minecraft.commands.arguments.TimeArgument;
- import net.minecraft.network.chat.Component;
- import net.minecraft.server.level.ServerLevel;
-+// CraftBukkit start
-+import org.bukkit.Bukkit;
-+import org.bukkit.event.world.TimeSkipEvent;
-+// CraftBukkit end
- 
- public class TimeCommand {
- 
-@@ -49,12 +53,18 @@
-     }
- 
-     public static int setTime(CommandSourceStack source, int time) {
--        Iterator iterator = source.getServer().getAllLevels().iterator();
-+        Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
- 
-         while (iterator.hasNext()) {
-             ServerLevel worldserver = (ServerLevel) iterator.next();
- 
--            worldserver.setDayTime((long) time);
-+            // CraftBukkit start
-+            TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime());
-+            Bukkit.getPluginManager().callEvent(event);
-+            if (!event.isCancelled()) {
-+                worldserver.setDayTime((long) worldserver.getDayTime() + event.getSkipAmount());
-+            }
-+            // CraftBukkit end
-         }
- 
-         source.getServer().forceTimeSynchronization();
-@@ -65,12 +75,18 @@
-     }
- 
-     public static int addTime(CommandSourceStack source, int time) {
--        Iterator iterator = source.getServer().getAllLevels().iterator();
-+        Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
- 
-         while (iterator.hasNext()) {
-             ServerLevel worldserver = (ServerLevel) iterator.next();
- 
--            worldserver.setDayTime(worldserver.getDayTime() + (long) time);
-+            // CraftBukkit start
-+            TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time);
-+            Bukkit.getPluginManager().callEvent(event);
-+            if (!event.isCancelled()) {
-+                worldserver.setDayTime(worldserver.getDayTime() + event.getSkipAmount());
-+            }
-+            // CraftBukkit end
-         }
- 
-         source.getServer().forceTimeSynchronization();
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/WeatherCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/WeatherCommand.java.patch
deleted file mode 100644
index 2f916fdf48..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/WeatherCommand.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/server/commands/WeatherCommand.java
-+++ b/net/minecraft/server/commands/WeatherCommand.java
-@@ -34,11 +34,11 @@
-     }
- 
-     private static int getDuration(CommandSourceStack source, int duration, IntProvider provider) {
--        return duration == -1 ? provider.sample(source.getServer().overworld().getRandom()) : duration;
-+        return duration == -1 ? provider.sample(source.getLevel().getRandom()) : duration; // CraftBukkit - SPIGOT-7680: per-world
-     }
- 
-     private static int setClear(CommandSourceStack source, int duration) {
--        source.getServer().overworld().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false);
-+        source.getLevel().setWeatherParameters(WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world
-         source.sendSuccess(() -> {
-             return Component.translatable("commands.weather.set.clear");
-         }, true);
-@@ -46,7 +46,7 @@
-     }
- 
-     private static int setRain(CommandSourceStack source, int duration) {
--        source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false);
-+        source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world
-         source.sendSuccess(() -> {
-             return Component.translatable("commands.weather.set.rain");
-         }, true);
-@@ -54,7 +54,7 @@
-     }
- 
-     private static int setThunder(CommandSourceStack source, int duration) {
--        source.getServer().overworld().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true);
-+        source.getLevel().setWeatherParameters(0, WeatherCommand.getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world
-         source.sendSuccess(() -> {
-             return Component.translatable("commands.weather.set.thunder");
-         }, true);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/commands/WorldBorderCommand.java.patch b/paper-server/patches/unapplied/net/minecraft/server/commands/WorldBorderCommand.java.patch
deleted file mode 100644
index d64cdd2c7d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/commands/WorldBorderCommand.java.patch
+++ /dev/null
@@ -1,65 +0,0 @@
---- a/net/minecraft/server/commands/WorldBorderCommand.java
-+++ b/net/minecraft/server/commands/WorldBorderCommand.java
-@@ -57,7 +57,7 @@
-     }
- 
-     private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
- 
-         if (worldborder.getDamageSafeZone() == (double) distance) {
-             throw WorldBorderCommand.ERROR_SAME_DAMAGE_BUFFER.create();
-@@ -71,7 +71,7 @@
-     }
- 
-     private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
- 
-         if (worldborder.getDamagePerBlock() == (double) damagePerBlock) {
-             throw WorldBorderCommand.ERROR_SAME_DAMAGE_AMOUNT.create();
-@@ -85,7 +85,7 @@
-     }
- 
-     private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
- 
-         if (worldborder.getWarningTime() == time) {
-             throw WorldBorderCommand.ERROR_SAME_WARNING_TIME.create();
-@@ -99,7 +99,7 @@
-     }
- 
-     private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
- 
-         if (worldborder.getWarningBlocks() == distance) {
-             throw WorldBorderCommand.ERROR_SAME_WARNING_DISTANCE.create();
-@@ -113,7 +113,7 @@
-     }
- 
-     private static int getSize(CommandSourceStack source) {
--        double d0 = source.getServer().overworld().getWorldBorder().getSize();
-+        double d0 = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
- 
-         source.sendSuccess(() -> {
-             return Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", d0));
-@@ -122,7 +122,7 @@
-     }
- 
-     private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
- 
-         if (worldborder.getCenterX() == (double) pos.x && worldborder.getCenterZ() == (double) pos.y) {
-             throw WorldBorderCommand.ERROR_SAME_CENTER.create();
-@@ -138,7 +138,7 @@
-     }
- 
-     private static int setSize(CommandSourceStack source, double distance, long time) throws CommandSyntaxException {
--        WorldBorder worldborder = source.getServer().overworld().getWorldBorder();
-+        WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
-         double d1 = worldborder.getSize();
- 
-         if (d1 == distance) {

From 9524c006d744d3f8c0428d4b3016bb489592becd Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 13:44:30 -0800
Subject: [PATCH 118/285] net.minecraft.world.entity.ai.village

---
 .../entity/ai/village/VillageSiege.java.patch    | 16 ++++++++++++++++
 .../entity/ai/village/VillageSiege.java.patch    | 16 ----------------
 2 files changed, 16 insertions(+), 16 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/village/VillageSiege.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
new file mode 100644
index 0000000000..983754df6f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/ai/village/VillageSiege.java
++++ b/net/minecraft/world/entity/ai/village/VillageSiege.java
+@@ -101,11 +_,12 @@
+                 zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.EVENT, null);
+             } catch (Exception var5) {
+                 LOGGER.warn("Failed to create zombie for village siege at {}", vec3, var5);
++                com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var5); // Paper - ServerExceptionEvent
+                 return;
+             }
+ 
+             zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F);
+-            level.addFreshEntityWithPassengers(zombie);
++            level.addFreshEntityWithPassengers(zombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
deleted file mode 100644
index 4b4a523928..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/entity/ai/village/VillageSiege.java
-+++ b/net/minecraft/world/entity/ai/village/VillageSiege.java
-@@ -117,11 +117,12 @@
-                 entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), EntitySpawnReason.EVENT, (SpawnGroupData) null);
-             } catch (Exception exception) {
-                 VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception);
-+                com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent
-                 return;
-             }
- 
-             entityzombie.moveTo(vec3d.x, vec3d.y, vec3d.z, world.random.nextFloat() * 360.0F, 0.0F);
--            world.addFreshEntityWithPassengers(entityzombie);
-+            world.addFreshEntityWithPassengers(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit
-         }
-     }
- 

From 4707f46b253b02767f2796c884507a203624fc9e Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:49:54 -0500
Subject: [PATCH 119/285] net/minecraft/world/level/material

---
 .../level/material/FlowingFluid.java.patch    | 137 +++++++++++++++
 .../level/material/FluidState.java.patch      |  12 +-
 .../world/level/material/LavaFluid.java.patch |  42 ++---
 .../level/material/WaterFluid.java.patch      |  20 +--
 .../level/material/FlowingFluid.java.patch    | 157 ------------------
 5 files changed, 174 insertions(+), 194 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/material/FluidState.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/material/LavaFluid.java.patch (50%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/material/WaterFluid.java.patch (65%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/material/FlowingFluid.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
new file mode 100644
index 0000000000..4ade7f8dfa
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
@@ -0,0 +1,137 @@
+--- a/net/minecraft/world/level/material/FlowingFluid.java
++++ b/net/minecraft/world/level/material/FlowingFluid.java
+@@ -118,6 +_,15 @@
+                 FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState1);
+                 Fluid type = newLiquid.getType();
+                 if (fluidState1.canBeReplacedWith(level, blockPos, type, Direction.DOWN) && canHoldSpecificFluid(level, blockPos, blockState1, type)) {
++                    // CraftBukkit start
++                    org.bukkit.block.Block source = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                    org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(source, org.bukkit.block.BlockFace.DOWN);
++                    level.getCraftServer().getPluginManager().callEvent(event);
++
++                    if (event.isCancelled()) {
++                        return;
++                    }
++                    // CraftBukkit end
+                     this.spreadTo(level, blockPos, blockState1, Direction.DOWN, newLiquid);
+                     if (this.sourceNeighborCount(level, pos) >= 3) {
+                         this.spreadToSides(level, pos, fluidState, blockState);
+@@ -146,7 +_,18 @@
+                 Direction direction = entry.getKey();
+                 FluidState fluidState1 = entry.getValue();
+                 BlockPos blockPos = pos.relative(direction);
+-                this.spreadTo(level, blockPos, level.getBlockState(blockPos), direction, fluidState1);
++                final BlockState blockStateIfLoaded = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing
++                if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing
++                // CraftBukkit start
++                org.bukkit.block.Block source = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(direction));
++                level.getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    continue;
++                }
++                // CraftBukkit end
++                this.spreadTo(level, blockPos, blockStateIfLoaded, direction, fluidState1); // Paper - Prevent chunk loading from fluid flowing
+             }
+         }
+     }
+@@ -158,7 +_,8 @@
+ 
+         for (Direction direction : Direction.Plane.HORIZONTAL) {
+             BlockPos blockPos = mutableBlockPos.setWithOffset(pos, direction);
+-            BlockState blockState = level.getBlockState(blockPos);
++            BlockState blockState = level.getBlockStateIfLoaded(mutableBlockPos); // Paper - Prevent chunk loading from fluid flowing
++            if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing
+             FluidState fluidState = blockState.getFluidState();
+             if (fluidState.getType().isSame(this) && canPassThroughWall(direction, level, pos, state, blockPos, blockState)) {
+                 if (fluidState.isSource()) {
+@@ -252,13 +_,14 @@
+             liquidBlockContainer.placeLiquid(level, pos, blockState, fluidState);
+         } else {
+             if (!blockState.isAir()) {
+-                this.beforeDestroyingBlock(level, pos, blockState);
++                this.beforeDestroyingBlock(level, pos, blockState, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent
+             }
+ 
+             level.setBlock(pos, fluidState.createLegacyBlock(), 3);
+         }
+     }
+ 
++    protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent
+     protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state);
+ 
+     protected int getSlopeDistance(LevelReader level, BlockPos pos, int depth, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadContext) {
+@@ -267,7 +_,8 @@
+         for (Direction direction1 : Direction.Plane.HORIZONTAL) {
+             if (direction1 != direction) {
+                 BlockPos blockPos = pos.relative(direction1);
+-                BlockState blockState = spreadContext.getBlockState(blockPos);
++                BlockState blockState = spreadContext.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing
++                if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing
+                 FluidState fluidState = blockState.getFluidState();
+                 if (this.canPassThrough(level, this.getFlowing(), pos, state, direction1, blockPos, blockState, fluidState)) {
+                     if (spreadContext.isHole(blockPos)) {
+@@ -334,7 +_,8 @@
+ 
+         for (Direction direction : Direction.Plane.HORIZONTAL) {
+             BlockPos blockPos = pos.relative(direction);
+-            BlockState blockState = level.getBlockState(blockPos);
++            BlockState blockState = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing
++            if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing
+             FluidState fluidState = blockState.getFluidState();
+             if (this.canMaybePassThrough(level, pos, state, direction, blockPos, blockState, fluidState)) {
+                 FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState);
+@@ -405,10 +_,24 @@
+             if (newLiquid.isEmpty()) {
+                 fluidState = newLiquid;
+                 blockState = Blocks.AIR.defaultBlockState();
++                // CraftBukkit start
++                org.bukkit.event.block.FluidLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFluidLevelChangeEvent(level, pos, blockState);
++                if (event.isCancelled()) {
++                    return;
++                }
++                blockState = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getNewData()).getState();
++                // CraftBukkit end
+                 level.setBlock(pos, blockState, 3);
+             } else if (!newLiquid.equals(fluidState)) {
+                 fluidState = newLiquid;
+                 blockState = newLiquid.createLegacyBlock();
++                // CraftBukkit start
++                org.bukkit.event.block.FluidLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFluidLevelChangeEvent(level, pos, blockState);
++                if (event.isCancelled()) {
++                    return;
++                }
++                blockState = ((org.bukkit.craftbukkit.block.data.CraftBlockData) event.getNewData()).getState();
++                // CraftBukkit end
+                 level.setBlock(pos, blockState, 3);
+                 level.scheduleTick(pos, newLiquid.getType(), spreadDelay);
+             }
+@@ -476,9 +_,26 @@
+         public BlockState getBlockState(BlockPos pos) {
+             return this.getBlockState(pos, this.getCacheKey(pos));
+         }
++        // Paper start - Prevent chunk loading from fluid flowing
++        public @javax.annotation.Nullable BlockState getBlockStateIfLoaded(BlockPos pos) {
++            return this.getBlockState(pos, this.getCacheKey(pos), false);
++        }
++        // Paper end - Prevent chunk loading from fluid flowing
+ 
+         private BlockState getBlockState(BlockPos pos, short cacheKey) {
+-            return this.stateCache.computeIfAbsent(cacheKey, s -> this.level.getBlockState(pos));
++        // Paper start - Prevent chunk loading from fluid flowing
++            return getBlockState(pos, cacheKey, true);
++        }
++        private @javax.annotation.Nullable BlockState getBlockState(BlockPos pos, short packed, boolean load) {
++            BlockState blockState = this.stateCache.get(packed);
++            if (blockState == null) {
++                blockState = load ? level.getBlockState(pos) : level.getBlockStateIfLoaded(pos);
++                if (blockState != null) {
++                    this.stateCache.put(packed, blockState);
++                }
++            }
++            return blockState;
++        // Paper end - Prevent chunk loading from fluid flowing
+         }
+ 
+         public boolean isHole(BlockPos pos) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/material/FluidState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/level/material/FluidState.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch
index 78b4ee0a64..c37c2e8733 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/material/FluidState.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/material/FluidState.java.patch
@@ -1,18 +1,18 @@
 --- a/net/minecraft/world/level/material/FluidState.java
 +++ b/net/minecraft/world/level/material/FluidState.java
-@@ -26,9 +26,11 @@
+@@ -26,9 +_,11 @@
      public static final Codec<FluidState> CODEC = codec(BuiltInRegistries.FLUID.byNameCodec(), Fluid::defaultFluidState).stable();
      public static final int AMOUNT_MAX = 9;
      public static final int AMOUNT_FULL = 8;
 +    protected final boolean isEmpty; // Paper - Perf: moved from isEmpty()
  
-     public FluidState(Fluid fluid, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<FluidState> codec) {
-         super(fluid, propertyMap, codec);
-+        this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty()
+     public FluidState(Fluid owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<FluidState> propertiesCodec) {
+         super(owner, values, propertiesCodec);
++        this.isEmpty = owner.isEmpty(); // Paper - Perf: moved from isEmpty()
      }
  
      public Fluid getType() {
-@@ -44,7 +46,7 @@
+@@ -44,7 +_,7 @@
      }
  
      public boolean isEmpty() {
@@ -20,4 +20,4 @@
 +        return this.isEmpty; // Paper - Perf: moved into constructor
      }
  
-     public float getHeight(BlockGetter world, BlockPos pos) {
+     public float getHeight(BlockGetter level, BlockPos pos) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/material/LavaFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/level/material/LavaFluid.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
index de343949f4..ce90c9af15 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/material/LavaFluid.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/material/LavaFluid.java.patch
@@ -1,48 +1,48 @@
 --- a/net/minecraft/world/level/material/LavaFluid.java
 +++ b/net/minecraft/world/level/material/LavaFluid.java
-@@ -85,6 +85,13 @@
- 
-                     if (iblockdata.isAir()) {
-                         if (this.hasFlammableNeighbours(world, blockposition1)) {
+@@ -88,6 +_,13 @@
+                     BlockState blockState = level.getBlockState(blockPos);
+                     if (blockState.isAir()) {
+                         if (this.hasFlammableNeighbours(level, blockPos)) {
 +                            // CraftBukkit start - Prevent lava putting something on fire
-+                            if (world.getBlockState(blockposition1).getBlock() != Blocks.FIRE) {
-+                                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition1, pos).isCancelled()) {
++                            if (level.getBlockState(blockPos).getBlock() != Blocks.FIRE) {
++                                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, blockPos, pos).isCancelled()) {
 +                                    continue;
 +                                }
 +                            }
 +                            // CraftBukkit end
-                             world.setBlockAndUpdate(blockposition1, BaseFireBlock.getState(world, blockposition1));
+                             level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(level, blockPos));
                              return;
                          }
-@@ -101,6 +108,14 @@
+@@ -103,6 +_,14 @@
                      }
  
-                     if (world.isEmptyBlock(blockposition2.above()) && this.isFlammable(world, blockposition2)) {
+                     if (level.isEmptyBlock(blockPos1.above()) && this.isFlammable(level, blockPos1)) {
 +                        // CraftBukkit start - Prevent lava putting something on fire
-+                        BlockPos up = blockposition2.above();
-+                        if (world.getBlockState(up).getBlock() != Blocks.FIRE) {
-+                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, up, pos).isCancelled()) {
++                        BlockPos up = blockPos1.above();
++                        if (level.getBlockState(up).getBlock() != Blocks.FIRE) {
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, up, pos).isCancelled()) {
 +                                continue;
 +                            }
 +                        }
 +                        // CraftBukkit end
-                         world.setBlockAndUpdate(blockposition2.above(), BaseFireBlock.getState(world, blockposition2));
+                         level.setBlockAndUpdate(blockPos1.above(), BaseFireBlock.getState(level, blockPos1));
                      }
                  }
-@@ -196,7 +211,11 @@
- 
-             if (this.is(FluidTags.LAVA) && fluid1.is(FluidTags.WATER)) {
-                 if (state.getBlock() instanceof LiquidBlock) {
--                    world.setBlock(pos, Blocks.STONE.defaultBlockState(), 3);
+@@ -195,7 +_,11 @@
+             FluidState fluidState1 = level.getFluidState(pos);
+             if (this.is(FluidTags.LAVA) && fluidState1.is(FluidTags.WATER)) {
+                 if (blockState.getBlock() instanceof LiquidBlock) {
+-                    level.setBlock(pos, Blocks.STONE.defaultBlockState(), 3);
 +                    // CraftBukkit start
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world.getMinecraftWorld(), pos, Blocks.STONE.defaultBlockState(), 3)) {
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level.getMinecraftWorld(), pos, Blocks.STONE.defaultBlockState(), 3)) {
 +                        return;
 +                    }
 +                    // CraftBukkit end
                  }
  
-                 this.fizz(world, pos);
-@@ -214,7 +233,7 @@
+                 this.fizz(level, pos);
+@@ -213,7 +_,7 @@
  
      @Override
      protected float getExplosionResistance() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/material/WaterFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/world/level/material/WaterFluid.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch
index f615848dea..59826b7446 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/material/WaterFluid.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/material/WaterFluid.java.patch
@@ -1,21 +1,21 @@
 --- a/net/minecraft/world/level/material/WaterFluid.java
 +++ b/net/minecraft/world/level/material/WaterFluid.java
-@@ -81,7 +81,14 @@
-         return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION);
+@@ -74,7 +_,13 @@
+     protected boolean canConvertToSource(ServerLevel level) {
+         return level.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION);
      }
- 
+-
 +    // Paper start - Add BlockBreakBlockEvent
-     @Override
-+    protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state,  BlockPos source) {
++     @Override
++    protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) {
 +        BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
 +        Block.dropResources(state, world, pos, tileentity, source);
 +    }
 +    // Paper end - Add BlockBreakBlockEvent
-+    @Override
-     protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) {
-         BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
-         Block.dropResources(state, world, pos, blockEntity);
-@@ -119,7 +126,7 @@
+     @Override
+     protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state) {
+         BlockEntity blockEntity = state.hasBlockEntity() ? level.getBlockEntity(pos) : null;
+@@ -113,7 +_,7 @@
  
      @Override
      protected float getExplosionResistance() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/material/FlowingFluid.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/material/FlowingFluid.java.patch
deleted file mode 100644
index cfc052e612..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/material/FlowingFluid.java.patch
+++ /dev/null
@@ -1,157 +0,0 @@
---- a/net/minecraft/world/level/material/FlowingFluid.java
-+++ b/net/minecraft/world/level/material/FlowingFluid.java
-@@ -31,6 +31,14 @@
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.block.BlockFace;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.block.data.CraftBlockData;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.block.BlockFromToEvent;
-+import org.bukkit.event.block.FluidLevelChangeEvent;
-+// CraftBukkit end
- 
- public abstract class FlowingFluid extends Fluid {
- 
-@@ -135,6 +143,15 @@
-                 Fluid fluidtype = fluid2.getType();
- 
-                 if (fluid1.canBeReplacedWith(world, blockposition1, fluidtype, Direction.DOWN) && FlowingFluid.canHoldSpecificFluid(world, blockposition1, iblockdata1, fluidtype)) {
-+                    // CraftBukkit start
-+                    org.bukkit.block.Block source = CraftBlock.at(world, fluidPos);
-+                    BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN);
-+                    world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (event.isCancelled()) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     this.spreadTo(world, blockposition1, iblockdata1, Direction.DOWN, fluid2);
-                     if (this.sourceNeighborCount(world, fluidPos) >= 3) {
-                         this.spreadToSides(world, fluidPos, fluidState, blockState);
-@@ -167,8 +184,19 @@
-                 Direction enumdirection = (Direction) entry.getKey();
-                 FluidState fluid1 = (FluidState) entry.getValue();
-                 BlockPos blockposition1 = pos.relative(enumdirection);
-+                final BlockState blockStateIfLoaded = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing
-+                if (blockStateIfLoaded == null) continue; // Paper - Prevent chunk loading from fluid flowing
- 
--                this.spreadTo(world, blockposition1, world.getBlockState(blockposition1), enumdirection, fluid1);
-+                // CraftBukkit start
-+                org.bukkit.block.Block source = CraftBlock.at(world, pos);
-+                BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection));
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    continue;
-+                }
-+                // CraftBukkit end
-+                this.spreadTo(world, blockposition1, blockStateIfLoaded, enumdirection, fluid1); // Paper - Prevent chunk loading from fluid flowing
-             }
- 
-         }
-@@ -183,7 +211,8 @@
-         while (iterator.hasNext()) {
-             Direction enumdirection = (Direction) iterator.next();
-             BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.setWithOffset(pos, enumdirection);
--            BlockState iblockdata1 = world.getBlockState(blockposition_mutableblockposition1);
-+            BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition_mutableblockposition1); // Paper - Prevent chunk loading from fluid flowing
-+            if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing
-             FluidState fluid = iblockdata1.getFluidState();
- 
-             if (fluid.getType().isSame(this) && FlowingFluid.canPassThroughWall(enumdirection, world, pos, state, blockposition_mutableblockposition1, iblockdata1)) {
-@@ -287,7 +316,7 @@
-             ifluidcontainer.placeLiquid(world, pos, state, fluidState);
-         } else {
-             if (!state.isAir()) {
--                this.beforeDestroyingBlock(world, pos, state);
-+                this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent
-             }
- 
-             world.setBlock(pos, fluidState.createLegacyBlock(), 3);
-@@ -295,6 +324,7 @@
- 
-     }
- 
-+    protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent
-     protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state);
- 
-     protected int getSlopeDistance(LevelReader world, BlockPos pos, int i, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadCache) {
-@@ -306,7 +336,8 @@
- 
-             if (enumdirection1 != direction) {
-                 BlockPos blockposition1 = pos.relative(enumdirection1);
--                BlockState iblockdata1 = spreadCache.getBlockState(blockposition1);
-+                BlockState iblockdata1 = spreadCache.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing
-+                if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing
-                 FluidState fluid = iblockdata1.getFluidState();
- 
-                 if (this.canPassThrough(world, this.getFlowing(), pos, state, enumdirection1, blockposition1, iblockdata1, fluid)) {
-@@ -372,7 +403,8 @@
-         while (iterator.hasNext()) {
-             Direction enumdirection = (Direction) iterator.next();
-             BlockPos blockposition1 = pos.relative(enumdirection);
--            BlockState iblockdata1 = world.getBlockState(blockposition1);
-+            BlockState iblockdata1 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent chunk loading from fluid flowing
-+            if (iblockdata1 == null) continue; // Paper - Prevent chunk loading from fluid flowing
-             FluidState fluid = iblockdata1.getFluidState();
- 
-             if (this.canMaybePassThrough(world, pos, state, enumdirection, blockposition1, iblockdata1, fluid)) {
-@@ -444,10 +476,24 @@
-             if (fluid1.isEmpty()) {
-                 fluidState = fluid1;
-                 blockState = Blocks.AIR.defaultBlockState();
-+                // CraftBukkit start
-+                FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(world, pos, blockState);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+                blockState = ((CraftBlockData) event.getNewData()).getState();
-+                // CraftBukkit end
-                 world.setBlock(pos, blockState, 3);
-             } else if (!fluid1.equals(fluidState)) {
-                 fluidState = fluid1;
-                 blockState = fluid1.createLegacyBlock();
-+                // CraftBukkit start
-+                FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(world, pos, blockState);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+                blockState = ((CraftBlockData) event.getNewData()).getState();
-+                // CraftBukkit end
-                 world.setBlock(pos, blockState, 3);
-                 world.scheduleTick(pos, fluid1.getType(), i);
-             }
-@@ -524,12 +570,27 @@
-         public BlockState getBlockState(BlockPos pos) {
-             return this.getBlockState(pos, this.getCacheKey(pos));
-         }
-+        // Paper start - Prevent chunk loading from fluid flowing
-+        public @javax.annotation.Nullable BlockState getBlockStateIfLoaded(BlockPos pos) {
-+            return this.getBlockState(pos, this.getCacheKey(pos), false);
-+        }
-+        // Paper end - Prevent chunk loading from fluid flowing
- 
-         private BlockState getBlockState(BlockPos pos, short packed) {
--            return (BlockState) this.stateCache.computeIfAbsent(packed, (short1) -> {
--                return this.level.getBlockState(pos);
--            });
-+        // Paper start - Prevent chunk loading from fluid flowing
-+            return getBlockState(pos, packed, true);
-         }
-+        private @javax.annotation.Nullable BlockState getBlockState(BlockPos pos, short packed, boolean load) {
-+            BlockState blockState = this.stateCache.get(packed);
-+            if (blockState == null) {
-+                blockState = load ? level.getBlockState(pos) : level.getBlockStateIfLoaded(pos);
-+                if (blockState != null) {
-+                    this.stateCache.put(packed, blockState);
-+                }
-+            }
-+            return blockState;
-+        // Paper end - Prevent chunk loading from fluid flowing
-+        }
- 
-         public boolean isHole(BlockPos pos) {
-             return this.holeCache.computeIfAbsent(this.getCacheKey(pos), (short0) -> {

From d096e6baaf3085f7107f5f73a05617ed34ad2edb Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 17:25:17 -0500
Subject: [PATCH 120/285] net/minecraft/world/entity/ai/behavior/

---
 .../entity/ai/behavior/AcquirePoi.java.patch  | 10 +++
 .../AssignProfessionFromJobSite.java.patch    | 18 +++++
 .../ai/behavior/BabyFollowAdult.java.patch    | 23 ++++++
 .../entity/ai/behavior/Behavior.java.patch    | 34 ++++-----
 .../ai/behavior/BehaviorUtils.java.patch      | 24 ++++++
 .../ai/behavior/GateBehavior.java.patch       |  2 +-
 .../ai/behavior/GoToWantedItem.java.patch     | 24 ++++++
 .../ai/behavior/HarvestFarmland.java.patch    | 24 ++++++
 .../ai/behavior/InteractWithDoor.java.patch   | 16 ++++
 .../PrepareRamNearestTarget.java.patch        | 27 +++++++
 .../entity/ai/behavior/RamTarget.java.patch   | 11 +++
 .../ai/behavior/ResetProfession.java.patch    | 18 +++++
 .../ai/behavior/ShufflingList.java.patch      | 12 +--
 .../entity/ai/behavior/SleepInBed.java.patch  |  8 +-
 .../ai/behavior/StartAttacking.java.patch     | 20 +++++
 .../StopAttackingIfTargetInvalid.java.patch   | 33 +++++++++
 .../TryLaySpawnOnWaterNearLand.java.patch     | 15 ++++
 .../behavior/VillagerGoalPackages.java.patch  |  8 +-
 .../ai/behavior/VillagerMakeLove.java.patch   | 23 ++++++
 .../ai/behavior/WorkAtComposter.java.patch    | 12 +--
 .../entity/ai/behavior/AcquirePoi.java.patch  | 10 ---
 .../AssignProfessionFromJobSite.java.patch    | 31 --------
 .../ai/behavior/BabyFollowAdult.java.patch    | 37 ----------
 .../ai/behavior/BehaviorUtils.java.patch      | 64 ----------------
 .../ai/behavior/GoToWantedItem.java.patch     | 24 ------
 .../ai/behavior/HarvestFarmland.java.patch    | 63 ----------------
 .../ai/behavior/InteractWithDoor.java.patch   | 39 ----------
 .../PrepareRamNearestTarget.java.patch        | 73 -------------------
 .../entity/ai/behavior/RamTarget.java.patch   | 11 ---
 .../ai/behavior/ResetProfession.java.patch    | 31 --------
 .../ai/behavior/StartAttacking.java.patch     | 36 ---------
 .../StopAttackingIfTargetInvalid.java.patch   | 46 ------------
 .../TryLaySpawnOnWaterNearLand.java.patch     | 15 ----
 .../ai/behavior/VillagerMakeLove.java.patch   | 42 -----------
 34 files changed, 324 insertions(+), 560 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/Behavior.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch (97%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch (92%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch (58%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch (72%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch (54%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
new file mode 100644
index 0000000000..512a8a9953
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+@@ -70,6 +_,7 @@
+                             return false;
+                         } else {
+                             mutableLong.setValue(time + 20L + level.getRandom().nextInt(20));
++                            if (mob.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck
+                             PoiManager poiManager = level.getPoiManager();
+                             map.long2ObjectEntrySet().removeIf(entry -> !entry.getValue().isStillValid(time));
+                             Predicate<BlockPos> predicate1 = pos -> {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
new file mode 100644
index 0000000000..60e6ed37af
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
++++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
+@@ -38,7 +_,14 @@
+                                             .findFirst()
+                                     )
+                                     .ifPresent(profession -> {
+-                                        villager.setVillagerData(villager.getVillagerData().setProfession(profession));
++                                        // CraftBukkit start - Fire VillagerCareerChangeEvent where Villager gets employed
++                                        org.bukkit.event.entity.VillagerCareerChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callVillagerCareerChangeEvent(villager, org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.minecraftToBukkit(profession), org.bukkit.event.entity.VillagerCareerChangeEvent.ChangeReason.EMPLOYED);
++                                        if (event.isCancelled()) {
++                                            return;
++                                        }
++
++                                        villager.setVillagerData(villager.getVillagerData().setProfession(org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
++                                        // CraftBukkit end
+                                         villager.refreshBrain(level);
+                                     });
+                                 return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
new file mode 100644
index 0000000000..0695aae31f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
++++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
+@@ -24,8 +_,19 @@
+                     if (!mob.isBaby()) {
+                         return false;
+                     } else {
+-                        AgeableMob ageableMob = instance.get(nearestVisibleAdult);
++                        LivingEntity ageableMob = instance.get(nearestVisibleAdult); // CraftBukkit - type
+                         if (mob.closerThan(ageableMob, followRange.getMaxValue() + 1) && !mob.closerThan(ageableMob, followRange.getMinValue())) {
++                            // CraftBukkit start
++                            org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(mob, ageableMob, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER);
++                            if (event.isCancelled()) {
++                                return false;
++                            }
++                            if (event.getTarget() == null) {
++                                nearestVisibleAdult.erase();
++                                return true;
++                            }
++                            ageableMob = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++                            // CraftBukkit end
+                             WalkTarget walkTarget1 = new WalkTarget(
+                                 new EntityTracker(ageableMob, false), speedModifier.apply(mob), followRange.getMinValue() - 1
+                             );
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/Behavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
index dab10a1e20..d205203f19 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/behavior/Behavior.java
 +++ b/net/minecraft/world/entity/ai/behavior/Behavior.java
-@@ -14,6 +14,9 @@
+@@ -14,6 +_,9 @@
      private long endTimestamp;
      private final int minDuration;
      private final int maxDuration;
@@ -8,12 +8,12 @@
 +    private final String configKey;
 +    // Paper end - configurable behavior tick rate and timings
  
-     public Behavior(Map<MemoryModuleType<?>, MemoryStatus> requiredMemoryState) {
-         this(requiredMemoryState, 60);
-@@ -27,6 +30,14 @@
-         this.minDuration = minRunTime;
-         this.maxDuration = maxRunTime;
-         this.entryCondition = requiredMemoryState;
+     public Behavior(Map<MemoryModuleType<?>, MemoryStatus> entryCondition) {
+         this(entryCondition, 60);
+@@ -27,6 +_,14 @@
+         this.minDuration = minDuration;
+         this.maxDuration = maxDuration;
+         this.entryCondition = entryCondition;
 +        // Paper start - configurable behavior tick rate and timings
 +        String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName();
 +        int lastSeparator = key.lastIndexOf('.');
@@ -25,16 +25,16 @@
      }
  
      @Override
-@@ -36,6 +47,12 @@
+@@ -36,6 +_,12 @@
  
      @Override
-     public final boolean tryStart(ServerLevel world, E entity, long time) {
-+        // Paper start - configurable behavior tick rate and timings
-+        int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1);
-+        if (tickRate > -1 && time < this.endTimestamp + tickRate) {
-+            return false;
-+        }
-+        // Paper end - configurable behavior tick rate and timings
-         if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) {
+     public final boolean tryStart(ServerLevel level, E owner, long gameTime) {
++       // Paper start - configurable behavior tick rate and timings
++       int tickRate = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.behavior.get(owner.getType(), this.configKey), -1);
++       if (tickRate > -1 && gameTime < this.endTimestamp + tickRate) {
++           return false;
++       }
++       // Paper end - configurable behavior tick rate and timings
+         if (this.hasRequiredMemories(owner) && this.checkExtraStartConditions(level, owner)) {
              this.status = Behavior.Status.RUNNING;
-             int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);
+             int i = this.minDuration + level.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
new file mode 100644
index 0000000000..77f8530cc4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
++++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+@@ -80,6 +_,7 @@
+     }
+ 
+     public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 offset, Vec3 speedMultiplier, float yOffset) {
++        if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-4940: no empty loot
+         double d = entity.getEyeY() - yOffset;
+         ItemEntity itemEntity = new ItemEntity(entity.level(), entity.getX(), d, entity.getZ(), stack);
+         itemEntity.setThrower(entity);
+@@ -87,6 +_,13 @@
+         vec3 = vec3.normalize().multiply(speedMultiplier.x, speedMultiplier.y, speedMultiplier.z);
+         itemEntity.setDeltaMovement(vec3);
+         itemEntity.setDefaultPickUpDelay();
++        // CraftBukkit start
++        org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
++        itemEntity.level().getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return;
++        }
++        // CraftBukkit end
+         entity.level().addFreshEntity(itemEntity);
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch
similarity index 97%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch
index fdb4490626..d7a1d368e0 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GateBehavior.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/behavior/GateBehavior.java
 +++ b/net/minecraft/world/entity/ai/behavior/GateBehavior.java
-@@ -18,7 +18,7 @@
+@@ -18,7 +_,7 @@
      private final Set<MemoryModuleType<?>> exitErasedMemories;
      private final GateBehavior.OrderPolicy orderPolicy;
      private final GateBehavior.RunningPolicy runningPolicy;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
new file mode 100644
index 0000000000..ca1948e8a9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
++++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
+@@ -35,6 +_,21 @@
+                                 && itemEntity.closerThan(entity, maxDistToWalk)
+                                 && entity.level().getWorldBorder().isWithinBounds(itemEntity.blockPosition())
+                                 && entity.canPickUpLoot()) {
++                                // CraftBukkit start
++                                if (entity instanceof net.minecraft.world.entity.animal.allay.Allay) {
++                                    org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetEvent(entity, itemEntity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++
++                                    if (event.isCancelled()) {
++                                        return false;
++                                    }
++                                    if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets
++                                        nearestVisibleWantedItem.erase();
++                                        return false; // Paper - only erase allay memory on non-item targets
++                                    }
++
++                                    itemEntity = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
++                                }
++                                // CraftBukkit end
+                                 WalkTarget walkTarget1 = new WalkTarget(new EntityTracker(itemEntity, false), speedModifier, 0);
+                                 lookTarget.set(new EntityTracker(itemEntity, true));
+                                 walkTarget.set(walkTarget1);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
new file mode 100644
index 0000000000..32e15ef453
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
++++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+@@ -110,7 +_,9 @@
+                 Block block = blockState.getBlock();
+                 Block block1 = level.getBlockState(this.aboveFarmlandPos.below()).getBlock();
+                 if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState)) {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
+                     level.destroyBlock(this.aboveFarmlandPos, true, owner);
++                    } // CraftBukkit
+                 }
+ 
+                 if (blockState.isAir() && block1 instanceof FarmBlock && owner.hasFarmSeeds()) {
+@@ -121,9 +_,11 @@
+                         boolean flag = false;
+                         if (!item.isEmpty() && item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) && item.getItem() instanceof BlockItem blockItem) {
+                             BlockState blockState1 = blockItem.getBlock().defaultBlockState();
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, blockState1)) { // CraftBukkit
+                             level.setBlockAndUpdate(this.aboveFarmlandPos, blockState1);
+                             level.gameEvent(GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(owner, blockState1));
+                             flag = true;
++                            } // CraftBukkit
+                         }
+ 
+                         if (flag) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
new file mode 100644
index 0000000000..a5139c9b02
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
++++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+@@ -58,6 +_,13 @@
+                             if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+                                 DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
+                                 if (!doorBlock.isOpen(blockState)) {
++                                // CraftBukkit start - entities opening doors
++                                org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), blockPos));
++                                    entity.level().getCraftServer().getPluginManager().callEvent(event);
++                                if (event.isCancelled()) {
++                                    return false;
++                                }
++                                // CraftBukkit end
+                                     doorBlock.setOpen(entity, level, blockState, blockPos, true);
+                                 }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
new file mode 100644
index 0000000000..239d6f934e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
++++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
+@@ -10,6 +_,7 @@
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.sounds.SoundEvent;
+ import net.minecraft.sounds.SoundSource;
+ import net.minecraft.util.Mth;
+@@ -75,6 +_,16 @@
+             .flatMap(
+                 nearestVisibleLivingEntities -> nearestVisibleLivingEntities.findClosest(livingEntity -> this.ramTargeting.test(level, entity, livingEntity))
+             )
++            // CraftBukkit start
++            .map((entityliving) -> {
++                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++                if (event.isCancelled() || event.getTarget() == null) {
++                    return null;
++                }
++                entityliving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++                return entityliving;
++            })
++            // CraftBukkit end
+             .ifPresent(entity1 -> this.chooseRamPosition(entity, entity1));
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
new file mode 100644
index 0000000000..0f29b936ec
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ai/behavior/RamTarget.java
++++ b/net/minecraft/world/entity/ai/behavior/RamTarget.java
+@@ -89,7 +_,7 @@
+             float f = 0.25F * (i - i1);
+             float f1 = Mth.clamp(owner.getSpeed() * 1.65F, 0.2F, 3.0F) + f;
+             float f2 = livingEntity.isDamageSourceBlocked(level.damageSources().mobAttack(owner)) ? 0.5F : 1.0F;
+-            livingEntity.knockback(f2 * f1 * this.getKnockbackForce.applyAsDouble(owner), this.ramDirection.x(), this.ramDirection.z());
++            livingEntity.knockback(f2 * f1 * this.getKnockbackForce.applyAsDouble(owner), this.ramDirection.x(), this.ramDirection.z(), owner, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+             this.finishRam(level, owner);
+             level.playSound(null, owner, this.getImpactSound.apply(owner), SoundSource.NEUTRAL, 1.0F, 1.0F);
+         } else if (this.hasRammedHornBreakingBlock(level, owner)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
new file mode 100644
index 0000000000..580871ed59
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java
++++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java
+@@ -18,7 +_,14 @@
+                             && villagerData.getProfession() != VillagerProfession.NITWIT
+                             && villager.getVillagerXp() == 0
+                             && villagerData.getLevel() <= 1) {
+-                            villager.setVillagerData(villager.getVillagerData().setProfession(VillagerProfession.NONE));
++                            // CraftBukkit start
++                            org.bukkit.event.entity.VillagerCareerChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callVillagerCareerChangeEvent(villager, org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.minecraftToBukkit(VillagerProfession.NONE), org.bukkit.event.entity.VillagerCareerChangeEvent.ChangeReason.LOSING_JOB);
++                            if (event.isCancelled()) {
++                                return false;
++                            }
++
++                            villager.setVillagerData(villager.getVillagerData().setProfession(org.bukkit.craftbukkit.entity.CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
++                            // CraftBukkit end
+                             villager.refreshBrain(level);
+                             return true;
+                         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch
similarity index 92%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch
index 3f1d97092e..ddad0d3698 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/ShufflingList.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/behavior/ShufflingList.java
 +++ b/net/minecraft/world/entity/ai/behavior/ShufflingList.java
-@@ -16,12 +16,25 @@
+@@ -16,12 +_,25 @@
  public class ShufflingList<U> implements Iterable<U> {
      protected final List<ShufflingList.WeightedEntry<U>> entries;
      private final RandomSource random = RandomSource.create();
@@ -16,17 +16,17 @@
          this.entries = Lists.newArrayList();
      }
  
-     private ShufflingList(List<ShufflingList.WeightedEntry<U>> list) {
+     private ShufflingList(List<ShufflingList.WeightedEntry<U>> entries) {
 +        // Paper start - Fix Concurrency issue in ShufflingList during worldgen
-+        this(list, true);
++        this(entries, true);
 +    }
-+    private ShufflingList(List<ShufflingList.WeightedEntry<U>> list, boolean isUnsafe) {
++    private ShufflingList(List<ShufflingList.WeightedEntry<U>> entries, boolean isUnsafe) {
 +        this.isUnsafe = isUnsafe;
 +        // Paper end - Fix Concurrency issue in ShufflingList during worldgen
-         this.entries = Lists.newArrayList(list);
+         this.entries = Lists.newArrayList(entries);
      }
  
-@@ -35,9 +48,12 @@
+@@ -35,9 +_,12 @@
      }
  
      public ShufflingList<U> shuffle() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch
index d905f5c1d7..cfe4ec0579 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/SleepInBed.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/entity/ai/behavior/SleepInBed.java
 +++ b/net/minecraft/world/entity/ai/behavior/SleepInBed.java
-@@ -42,7 +42,8 @@
+@@ -42,7 +_,8 @@
                      }
                  }
  
--                BlockState blockState = world.getBlockState(globalPos.pos());
-+                BlockState blockState = world.getBlockStateIfLoaded(globalPos.pos()); // Paper - Prevent sync chunk loads when villagers try to find beds
+-                BlockState blockState = level.getBlockState(globalPos.pos());
++                BlockState blockState = level.getBlockStateIfLoaded(globalPos.pos()); // Paper - Prevent sync chunk loads when villagers try to find beds
 +                if (blockState == null) { return false; } // Paper - Prevent sync chunk loads when villagers try to find beds
-                 return globalPos.pos().closerToCenterThan(entity.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
+                 return globalPos.pos().closerToCenterThan(owner.position(), 2.0) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED);
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
new file mode 100644
index 0000000000..4915d1fd6b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
++++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
+@@ -27,6 +_,17 @@
+                             if (!entity.canAttack(livingEntity)) {
+                                 return false;
+                             } else {
++                                // CraftBukkit start
++                                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, livingEntity, (livingEntity instanceof net.minecraft.server.level.ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++                                if (event.isCancelled()) {
++                                    return false;
++                                }
++                                if (event.getTarget() == null) {
++                                    memoryAccessor.erase();
++                                    return true;
++                                }
++                                livingEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++                                // CraftBukkit end
+                                 memoryAccessor.set(livingEntity);
+                                 memoryAccessor1.erase();
+                                 return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
new file mode 100644
index 0000000000..24a503eca0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
++++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
+@@ -40,6 +_,30 @@
+                             && !canStopAttacking.test(level, livingEntity)) {
+                             return true;
+                         } else {
++                        // Paper start - better track target change reason
++                        final org.bukkit.event.entity.EntityTargetEvent.TargetReason reason;
++                        if (!entity.canAttack(livingEntity)) {
++                            reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID;
++                        } else if (canGrowTiredOfTryingToReachTarget && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entity, instance.tryGet(memoryAccessor1))) {
++                            reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET;
++                        } else if (!livingEntity.isAlive()) {
++                            reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED;
++                        } else if (livingEntity.level() != entity.level()) {
++                            reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL;
++                        } else {
++                            reason = org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID;
++                        }
++                        // Paper end
++                        // CraftBukkit start
++                        org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, null, reason); // Paper
++                        if (event.isCancelled()) {
++                            return false;
++                        }
++                        if (event.getTarget() != null) {
++                            entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle());
++                            return true;
++                        }
++                        // CraftBukkit end
+                             onStopAttacking.accept(level, entity, livingEntity);
+                             memoryAccessor.erase();
+                             return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
new file mode 100644
index 0000000000..da9c516844
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
++++ b/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
+@@ -33,6 +_,12 @@
+                                     BlockPos blockPos2 = blockPos1.above();
+                                     if (level.getBlockState(blockPos2).isAir()) {
+                                         BlockState blockState = spawnBlock.defaultBlockState();
++                                        // CraftBukkit start
++                                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos2, blockState)) {
++                                            isPregnant.erase();
++                                            return true;
++                                        }
++                                        // CraftBukkit end
+                                         level.setBlock(blockPos2, blockState, 3);
+                                         level.gameEvent(GameEvent.BLOCK_PLACE, blockPos2, GameEvent.Context.of(entity, blockState));
+                                         level.playSound(null, entity, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch
similarity index 72%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch
index c8260c2305..cac3171662 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
 +++ b/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
-@@ -42,7 +42,7 @@
+@@ -42,7 +_,7 @@
              Pair.of(1, new MoveToTargetSink()),
              Pair.of(2, PoiCompetitorScan.create()),
-             Pair.of(3, new LookAndFollowTradingPlayerSink(speed)),
--            Pair.of(5, GoToWantedItem.create(speed, false, 4)),
-+            Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speed, false, 4)), // Paper - Fix MC-157464
+             Pair.of(3, new LookAndFollowTradingPlayerSink(speedModifier)),
+-            Pair.of(5, GoToWantedItem.create(speedModifier, false, 4)),
++            Pair.of(5, GoToWantedItem.create(villager -> !villager.isSleeping(), speedModifier, false, 4)), // Paper - Fix MC-157464
              Pair.of(
                  6,
                  AcquirePoi.create(
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
new file mode 100644
index 0000000000..303c80235c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
++++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+@@ -111,11 +_,17 @@
+         if (breedOffspring == null) {
+             return Optional.empty();
+         } else {
+-            parent.setAge(6000);
+-            partner.setAge(6000);
+             breedOffspring.setAge(-24000);
+             breedOffspring.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F);
+-            level.addFreshEntityWithPassengers(breedOffspring);
++            // CraftBukkit start - call EntityBreedEvent
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(breedOffspring, parent, partner, null, null, 0).isCancelled()) {
++                return Optional.empty();
++            }
++            // Move age setting down
++            parent.setAge(6000);
++            partner.setAge(6000);
++            level.addFreshEntityWithPassengers(breedOffspring, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
++            // CraftBukkit end
+             level.broadcastEntityEvent(breedOffspring, (byte)12);
+             return Optional.of(breedOffspring);
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
index 3d8d9ce4bd..3556764a3b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
 +++ b/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
-@@ -86,7 +86,9 @@
-                 simpleContainer.removeItemType(Items.WHEAT, m);
-                 ItemStack itemStack = simpleContainer.addItem(new ItemStack(Items.BREAD, l));
+@@ -86,7 +_,9 @@
+                 inventory.removeItemType(Items.WHEAT, i3);
+                 ItemStack itemStack = inventory.addItem(new ItemStack(Items.BREAD, min));
                  if (!itemStack.isEmpty()) {
-+                    villager.forceDrops = true; // Paper - Add missing forceDrop toggles
-                     villager.spawnAtLocation(world, itemStack, 0.5F);
-+                    villager.forceDrops = false; // Paper - Add missing forceDrop toggles
++                    villager.forceDrops = true; // Paper - Add missing forceDrop toggle
+                     villager.spawnAtLocation(level, itemStack, 0.5F);
++                    villager.forceDrops = false; // Paper - Add missing forceDrop toggle
                  }
              }
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
deleted file mode 100644
index 64bab57963..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
-+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
-@@ -70,6 +70,7 @@
-                                     return false;
-                                 } else {
-                                     mutableLong.setValue(time + 20L + (long)world.getRandom().nextInt(20));
-+                                    if (entity.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck
-                                     PoiManager poiManager = world.getPoiManager();
-                                     long2ObjectMap.long2ObjectEntrySet().removeIf(entry -> !entry.getValue().isStillValid(time));
-                                     Predicate<BlockPos> predicate2 = pos -> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
deleted file mode 100644
index a3aec14777..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
-+++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
-@@ -9,6 +9,12 @@
- import net.minecraft.world.entity.npc.Villager;
- import net.minecraft.world.entity.npc.VillagerProfession;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftVillager;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.VillagerCareerChangeEvent;
-+// CraftBukkit end
-+
- public class AssignProfessionFromJobSite {
- 
-     public AssignProfessionFromJobSite() {}
-@@ -37,7 +43,14 @@
-                                     return villagerprofession.heldJobSite().test(holder);
-                                 }).findFirst();
-                             }).ifPresent((villagerprofession) -> {
--                                entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(villagerprofession));
-+                                // CraftBukkit start - Fire VillagerCareerChangeEvent where Villager gets employed
-+                                VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(villagerprofession), VillagerCareerChangeEvent.ChangeReason.EMPLOYED);
-+                                if (event.isCancelled()) {
-+                                    return;
-+                                }
-+
-+                                entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
-+                                // CraftBukkit end
-                                 entityvillager.refreshBrain(worldserver);
-                             });
-                             return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
deleted file mode 100644
index fe0de69d00..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
-+++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
-@@ -7,6 +7,12 @@
- import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
- import net.minecraft.world.entity.ai.memory.MemoryModuleType;
- import net.minecraft.world.entity.ai.memory.WalkTarget;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
-+// CraftBukkit end
- 
- public class BabyFollowAdult {
- 
-@@ -25,9 +31,20 @@
-                     if (!entityageable.isBaby()) {
-                         return false;
-                     } else {
--                        AgeableMob entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor);
-+                        LivingEntity entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor); // CraftBukkit - type
- 
-                         if (entityageable.closerThan(entityageable1, (double) (executionRange.getMaxValue() + 1)) && !entityageable.closerThan(entityageable1, (double) executionRange.getMinValue())) {
-+                            // CraftBukkit start
-+                            EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityageable, entityageable1, EntityTargetEvent.TargetReason.FOLLOW_LEADER);
-+                            if (event.isCancelled()) {
-+                                return false;
-+                            }
-+                            if (event.getTarget() == null) {
-+                                memoryaccessor.erase();
-+                                return true;
-+                            }
-+                            entityageable1 = ((CraftLivingEntity) event.getTarget()).getHandle();
-+                            // CraftBukkit end
-                             WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityageable1, false), (Float) speed.apply(entityageable), executionRange.getMinValue() - 1);
- 
-                             memoryaccessor1.set(new EntityTracker(entityageable1, true));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
deleted file mode 100644
index 35afe8f0a4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
+++ /dev/null
@@ -1,64 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
-+++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
-@@ -60,7 +60,7 @@
-     }
- 
-     public static void lookAtEntity(LivingEntity entity, LivingEntity target) {
--        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new EntityTracker(target, true)));
-+        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new EntityTracker(target, true))); // CraftBukkit - decompile error
-     }
- 
-     private static void setWalkAndLookTargetMemoriesToEachOther(LivingEntity first, LivingEntity second, float speed, int completionRange) {
-@@ -79,8 +79,8 @@
-     public static void setWalkAndLookTargetMemories(LivingEntity entity, PositionTracker target, float speed, int completionRange) {
-         WalkTarget memorytarget = new WalkTarget(target, speed, completionRange);
- 
--        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) target);
--        entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) memorytarget);
-+        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, target); // CraftBukkit - decompile error
-+        entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, memorytarget); // CraftBukkit - decompile error
-     }
- 
-     public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 targetLocation) {
-@@ -90,6 +90,7 @@
-     }
- 
-     public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 targetLocation, Vec3 velocityFactor, float yOffset) {
-+        if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-4940: no empty loot
-         double d0 = entity.getEyeY() - (double) yOffset;
-         ItemEntity entityitem = new ItemEntity(entity.level(), entity.getX(), d0, entity.getZ(), stack);
- 
-@@ -99,12 +100,19 @@
-         vec3d2 = vec3d2.normalize().multiply(velocityFactor.x, velocityFactor.y, velocityFactor.z);
-         entityitem.setDeltaMovement(vec3d2);
-         entityitem.setDefaultPickUpDelay();
-+        // CraftBukkit start
-+        org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
-+        entityitem.level().getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return;
-+        }
-+        // CraftBukkit end
-         entity.level().addFreshEntity(entityitem);
-     }
- 
-     public static SectionPos findSectionClosestToVillage(ServerLevel world, SectionPos center, int radius) {
-         int j = world.sectionsToVillage(center);
--        Stream stream = SectionPos.cube(center, radius).filter((sectionposition1) -> {
-+        Stream<SectionPos> stream = SectionPos.cube(center, radius).filter((sectionposition1) -> { // CraftBukkit - decompile error
-             return world.sectionsToVillage(sectionposition1) < j;
-         });
- 
-@@ -161,10 +169,10 @@
- 
-         return optional.map((uuid) -> {
-             return ((ServerLevel) entity.level()).getEntity(uuid);
--        }).map((entity) -> {
-+        }).map((entity1) -> { // Paper - remap fix
-             LivingEntity entityliving1;
- 
--            if (entity instanceof LivingEntity entityliving2) {
-+            if (entity1 instanceof LivingEntity entityliving2) { // Paper - remap fix
-                 entityliving1 = entityliving2;
-             } else {
-                 entityliving1 = null;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
deleted file mode 100644
index d9692605cb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
-+++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
-@@ -28,6 +28,21 @@
-                     ItemEntity entityitem = (ItemEntity) behaviorbuilder_b.get(memoryaccessor2);
- 
-                     if (behaviorbuilder_b.tryGet(memoryaccessor3).isEmpty() && startCondition.test(entityliving) && entityitem.closerThan(entityliving, (double) radius) && entityliving.level().getWorldBorder().isWithinBounds(entityitem.blockPosition()) && entityliving.canPickUpLoot()) {
-+                        // CraftBukkit start
-+                        if (entityliving instanceof net.minecraft.world.entity.animal.allay.Allay) {
-+                            org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetEvent(entityliving, entityitem, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
-+
-+                            if (event.isCancelled()) {
-+                                return false;
-+                            }
-+                            if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets
-+                                memoryaccessor2.erase();
-+                                return false; // Paper - only erase allay memory on non-item targets
-+                            }
-+
-+                            entityitem = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
-+                        }
-+                        // CraftBukkit end
-                         WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityitem, false), speed, 0);
- 
-                         memoryaccessor.set(new EntityTracker(entityitem, true));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
deleted file mode 100644
index 066bfd1863..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
+++ /dev/null
@@ -1,63 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
-+++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
-@@ -22,10 +22,15 @@
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.gameevent.GameEvent;
-+
-+// CraftBukkit start
-+import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.CropBlock;
- import net.minecraft.world.level.block.FarmBlock;
- import net.minecraft.world.level.block.state.BlockState;
--import net.minecraft.world.level.gameevent.GameEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class HarvestFarmland extends Behavior<Villager> {
- 
-@@ -82,8 +87,8 @@
- 
-     protected void start(ServerLevel worldserver, Villager entityvillager, long i) {
-         if (i > this.nextOkStartTime && this.aboveFarmlandPos != null) {
--            entityvillager.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BlockPosTracker(this.aboveFarmlandPos)));
--            entityvillager.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1)));
-+            entityvillager.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new BlockPosTracker(this.aboveFarmlandPos))); // CraftBukkit - decompile error
-+            entityvillager.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); // CraftBukkit - decompile error
-         }
- 
-     }
-@@ -103,7 +108,9 @@
-                 Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock();
- 
-                 if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) {
-+                    if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
-                     world.destroyBlock(this.aboveFarmlandPos, true, entity);
-+                    } // CraftBukkit
-                 }
- 
-                 if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) {
-@@ -120,9 +127,11 @@
-                                 BlockItem itemblock = (BlockItem) item;
-                                 BlockState iblockdata1 = itemblock.getBlock().defaultBlockState();
- 
-+                                if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata1)) { // CraftBukkit
-                                 world.setBlockAndUpdate(this.aboveFarmlandPos, iblockdata1);
-                                 world.gameEvent((Holder) GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(entity, iblockdata1));
-                                 flag = true;
-+                                } // CraftBukkit
-                             }
-                         }
- 
-@@ -142,8 +151,8 @@
-                     this.aboveFarmlandPos = this.getValidFarmland(world);
-                     if (this.aboveFarmlandPos != null) {
-                         this.nextOkStartTime = time + 20L;
--                        entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1)));
--                        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new BlockPosTracker(this.aboveFarmlandPos)));
-+                        entity.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(new BlockPosTracker(this.aboveFarmlandPos), 0.5F, 1))); // CraftBukkit - decompile error
-+                        entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new BlockPosTracker(this.aboveFarmlandPos))); // CraftBukkit - decompile error
-                     }
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
deleted file mode 100644
index 7348471be5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-+++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-@@ -61,6 +61,13 @@
-                             DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
- 
-                             if (!blockdoor.isOpen(iblockdata)) {
-+                                // CraftBukkit start - entities opening doors
-+                                org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition));
-+                                entityliving.level().getCraftServer().getPluginManager().callEvent(event);
-+                                if (event.isCancelled()) {
-+                                    return false;
-+                                }
-+                                // CraftBukkit end
-                                 blockdoor.setOpen(entityliving, worldserver, iblockdata, blockposition, true);
-                             }
- 
-@@ -76,6 +83,13 @@
-                             DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock();
- 
-                             if (!blockdoor1.isOpen(iblockdata1)) {
-+                                // CraftBukkit start - entities opening doors
-+                                org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition1));
-+                                entityliving.level().getCraftServer().getPluginManager().callEvent(event);
-+                                if (event.isCancelled()) {
-+                                    return false;
-+                                }
-+                                // CraftBukkit end
-                                 blockdoor1.setOpen(entityliving, worldserver, iblockdata1, blockposition1, true);
-                                 optional = InteractWithDoor.rememberDoorToClose(memoryaccessor1, optional, worldserver, blockposition1);
-                             }
-@@ -129,7 +143,7 @@
-     }
- 
-     private static boolean areOtherMobsComingThroughDoor(LivingEntity entity, BlockPos pos, Optional<List<LivingEntity>> otherMobs) {
--        return otherMobs.isEmpty() ? false : ((List) otherMobs.get()).stream().filter((entityliving1) -> {
-+        return otherMobs.isEmpty() ? false : (otherMobs.get()).stream().filter((entityliving1) -> { // CraftBukkit - decompile error
-             return entityliving1.getType() == entity.getType();
-         }).filter((entityliving1) -> {
-             return pos.closerToCenterThan(entityliving1.position(), 2.0D);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
deleted file mode 100644
index 18aef70f25..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
+++ /dev/null
@@ -1,73 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
-+++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
-@@ -13,6 +13,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundSource;
- import net.minecraft.util.Mth;
-@@ -30,6 +31,10 @@
- import net.minecraft.world.level.pathfinder.Path;
- import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public class PrepareRamNearestTarget<E extends PathfinderMob> extends Behavior<E> {
- 
-@@ -63,6 +68,13 @@
-                 return this.ramTargeting.test(worldserver, entitycreature, entityliving);
-             });
-         }).ifPresent((entityliving) -> {
-+            // CraftBukkit start
-+            EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entitycreature, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
-+            if (event.isCancelled() || event.getTarget() == null) {
-+                return;
-+            }
-+            entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
-+            // CraftBukkit end
-             this.chooseRamPosition(entitycreature, entityliving);
-         });
-     }
-@@ -72,7 +84,7 @@
- 
-         if (!behaviorcontroller.hasMemoryValue(MemoryModuleType.RAM_TARGET)) {
-             world.broadcastEntityEvent(entity, (byte) 59);
--            behaviorcontroller.setMemory(MemoryModuleType.RAM_COOLDOWN_TICKS, (Object) this.getCooldownOnFail.applyAsInt(entity));
-+            behaviorcontroller.setMemory(MemoryModuleType.RAM_COOLDOWN_TICKS, this.getCooldownOnFail.applyAsInt(entity)); // CraftBukkit - decompile error
-         }
- 
-     }
-@@ -83,8 +95,8 @@
- 
-     protected void tick(ServerLevel worldserver, E e0, long i) {
-         if (!this.ramCandidate.isEmpty()) {
--            e0.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (Object) (new WalkTarget(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getStartPosition(), this.walkSpeed, 0)));
--            e0.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (Object) (new EntityTracker(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget(), true)));
-+            e0.getBrain().setMemory(MemoryModuleType.WALK_TARGET, (new WalkTarget(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getStartPosition(), this.walkSpeed, 0))); // CraftBukkit - decompile error
-+            e0.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, (new EntityTracker(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget(), true))); // CraftBukkit - decompile error
-             boolean flag = !((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTarget().blockPosition().equals(((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition());
- 
-             if (flag) {
-@@ -101,7 +113,7 @@
-                     }
- 
-                     if (i - (Long) this.reachedRamPositionTimestamp.get() >= (long) this.ramPrepareTime) {
--                        e0.getBrain().setMemory(MemoryModuleType.RAM_TARGET, (Object) this.getEdgeOfBlock(blockposition, ((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition()));
-+                        e0.getBrain().setMemory(MemoryModuleType.RAM_TARGET, this.getEdgeOfBlock(blockposition, ((PrepareRamNearestTarget.RamCandidate) this.ramCandidate.get()).getTargetPosition())); // CraftBukkit - decompile error
-                         worldserver.playSound((Player) null, (Entity) e0, (SoundEvent) this.getPrepareRamSound.apply(e0), SoundSource.NEUTRAL, 1.0F, e0.getVoicePitch());
-                         this.ramCandidate = Optional.empty();
-                     }
-@@ -153,7 +165,7 @@
-             }
- 
-             PathNavigation navigationabstract = entity.getNavigation();
--            Stream stream = list.stream();
-+            Stream<BlockPos> stream = list.stream(); // CraftBukkit - decompile error
-             BlockPos blockposition1 = entity.blockPosition();
- 
-             Objects.requireNonNull(blockposition1);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
deleted file mode 100644
index 3c23484d4f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/RamTarget.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/RamTarget.java
-+++ b/net/minecraft/world/entity/ai/behavior/RamTarget.java
-@@ -89,7 +89,7 @@
-             float f = 0.25F * (float)(i - j);
-             float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f;
-             float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F;
--            livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z());
-+            livingEntity.knockback(h * g * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-             this.finishRam(world, entity);
-             world.playSound(null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F);
-         } else if (this.hasRammedHornBreakingBlock(world, entity)) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
deleted file mode 100644
index f5dc7ba366..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java
-+++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java
-@@ -6,6 +6,12 @@
- import net.minecraft.world.entity.npc.VillagerData;
- import net.minecraft.world.entity.npc.VillagerProfession;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftVillager;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.VillagerCareerChangeEvent;
-+// CraftBukkit end
-+
- public class ResetProfession {
- 
-     public ResetProfession() {}
-@@ -17,7 +23,14 @@
-                     VillagerData villagerdata = entityvillager.getVillagerData();
- 
-                     if (villagerdata.getProfession() != VillagerProfession.NONE && villagerdata.getProfession() != VillagerProfession.NITWIT && entityvillager.getVillagerXp() == 0 && villagerdata.getLevel() <= 1) {
--                        entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(VillagerProfession.NONE));
-+                        // CraftBukkit start
-+                        VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(VillagerProfession.NONE), VillagerCareerChangeEvent.ChangeReason.LOSING_JOB);
-+                        if (event.isCancelled()) {
-+                            return false;
-+                        }
-+
-+                        entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
-+                        // CraftBukkit end
-                         entityvillager.refreshBrain(worldserver);
-                         return true;
-                     } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
deleted file mode 100644
index 0a02e4eb2c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
+++ /dev/null
@@ -1,36 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
-+++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
-@@ -2,10 +2,15 @@
- 
- import java.util.Optional;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
- import net.minecraft.world.entity.ai.memory.MemoryModuleType;
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
- 
- public class StartAttacking {
- 
-@@ -34,6 +39,17 @@
-                             if (!entityinsentient.canAttack(entityliving)) {
-                                 return false;
-                             } else {
-+                                // CraftBukkit start
-+                                EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
-+                                if (event.isCancelled()) {
-+                                    return false;
-+                                }
-+                                if (event.getTarget() == null) {
-+                                    memoryaccessor.erase();
-+                                    return true;
-+                                }
-+                                entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
-+                                // CraftBukkit end
-                                 memoryaccessor.set(entityliving);
-                                 memoryaccessor1.erase();
-                                 return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
deleted file mode 100644
index c156a35921..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
-+++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
-@@ -7,6 +7,12 @@
- import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
- import net.minecraft.world.entity.ai.memory.MemoryModuleType;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+// CraftBukkit end
-+
- public class StopAttackingIfTargetInvalid {
- 
-     private static final int TIMEOUT_TO_GET_WITHIN_ATTACK_RANGE = 200;
-@@ -40,6 +46,30 @@
-                     if (entityinsentient.canAttack(entityliving) && (!shouldForgetIfTargetUnreachable || !StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) && entityliving.isAlive() && entityliving.level() == entityinsentient.level() && !condition.test(worldserver, entityliving)) {
-                         return true;
-                     } else {
-+                        // Paper start - better track target change reason
-+                        final EntityTargetEvent.TargetReason reason;
-+                        if (!entityinsentient.canAttack(entityliving)) {
-+                            reason = EntityTargetEvent.TargetReason.TARGET_INVALID;
-+                        } else if (shouldForgetIfTargetUnreachable && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) {
-+                            reason = EntityTargetEvent.TargetReason.FORGOT_TARGET;
-+                        } else if (!entityliving.isAlive()) {
-+                            reason = EntityTargetEvent.TargetReason.TARGET_DIED;
-+                        } else if (entityliving.level() != entityinsentient.level()) {
-+                            reason = EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL;
-+                        } else {
-+                            reason = EntityTargetEvent.TargetReason.TARGET_INVALID;
-+                        }
-+                        // Paper end
-+                        // CraftBukkit start
-+                        EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, reason); // Paper
-+                        if (event.isCancelled()) {
-+                            return false;
-+                        }
-+                        if (event.getTarget() != null) {
-+                            entityinsentient.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle());
-+                            return true;
-+                        }
-+                        // CraftBukkit end
-                         callback.accept(worldserver, entityinsentient, entityliving);
-                         memoryaccessor.erase();
-                         return true;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
deleted file mode 100644
index e0fa77caef..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
-+++ b/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java
-@@ -39,6 +39,12 @@
-                                 if (worldserver.getBlockState(blockposition2).isAir()) {
-                                     BlockState iblockdata = frogSpawn.defaultBlockState();
- 
-+                                    // CraftBukkit start
-+                                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityliving, blockposition2, iblockdata)) {
-+                                        memoryaccessor2.erase();
-+                                        return true;
-+                                    }
-+                                    // CraftBukkit end
-                                     worldserver.setBlock(blockposition2, iblockdata, 3);
-                                     worldserver.gameEvent((Holder) GameEvent.BLOCK_PLACE, blockposition2, GameEvent.Context.of(entityliving, iblockdata));
-                                     worldserver.playSound((Player) null, (Entity) entityliving, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
deleted file mode 100644
index 9875c853b9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
-+++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
-@@ -17,6 +17,10 @@
- import net.minecraft.world.entity.ai.village.poi.PoiTypes;
- import net.minecraft.world.entity.npc.Villager;
- import net.minecraft.world.level.pathfinder.Path;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+// CraftBukkit end
- 
- public class VillagerMakeLove extends Behavior<Villager> {
- 
-@@ -114,11 +118,17 @@
-         if (entityvillager2 == null) {
-             return Optional.empty();
-         } else {
--            parent.setAge(6000);
--            partner.setAge(6000);
-             entityvillager2.setAge(-24000);
-             entityvillager2.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F);
--            world.addFreshEntityWithPassengers(entityvillager2);
-+            // CraftBukkit start - call EntityBreedEvent
-+            if (CraftEventFactory.callEntityBreedEvent(entityvillager2, parent, partner, null, null, 0).isCancelled()) {
-+                return Optional.empty();
-+            }
-+            // Move age setting down
-+            parent.setAge(6000);
-+            partner.setAge(6000);
-+            world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING);
-+            // CraftBukkit end
-             world.broadcastEntityEvent(entityvillager2, (byte) 12);
-             return Optional.of(entityvillager2);
-         }
-@@ -127,6 +137,6 @@
-     private void giveBedToChild(ServerLevel world, Villager child, BlockPos pos) {
-         GlobalPos globalpos = GlobalPos.of(world.dimension(), pos);
- 
--        child.getBrain().setMemory(MemoryModuleType.HOME, (Object) globalpos);
-+        child.getBrain().setMemory(MemoryModuleType.HOME, globalpos); // CraftBukkit - decompile error
-     }
- }

From f2f3b061794d7a432ddabd9bdc0626016f7bad32 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 17:29:33 -0500
Subject: [PATCH 121/285] remove already applied
 net/minecraft/world/level/storage/loot

---
 .../storage/loot/LootDataType.java.patch      | 22 -----
 .../level/storage/loot/LootTable.java.patch   | 85 -------------------
 2 files changed, 107 deletions(-)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootDataType.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootTable.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootDataType.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootDataType.java.patch
deleted file mode 100644
index c9f13ba298..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootDataType.java.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- a/net/minecraft/world/level/storage/loot/LootDataType.java
-+++ b/net/minecraft/world/level/storage/loot/LootDataType.java
-@@ -9,6 +9,11 @@
- import net.minecraft.world.level.storage.loot.functions.LootItemFunctions;
- import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.CraftLootTable;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
-+// CraftBukkit end
-+
- public record LootDataType<T>(ResourceKey<Registry<T>> registryKey, Codec<T> codec, LootDataType.Validator<T> validator) {
- 
-     public static final LootDataType<LootItemCondition> PREDICATE = new LootDataType<>(Registries.PREDICATE, LootItemCondition.DIRECT_CODEC, createSimpleValidator());
-@@ -32,6 +37,7 @@
-     private static LootDataType.Validator<LootTable> createLootTableValidator() {
-         return (lootcollector, resourcekey, loottable) -> {
-             loottable.validate(lootcollector.setContextKeySet(loottable.getParamSet()).enterElement("{" + String.valueOf(resourcekey.registry()) + "/" + String.valueOf(resourcekey.location()) + "}", resourcekey));
-+            loottable.craftLootTable = new CraftLootTable(CraftNamespacedKey.fromMinecraft(resourcekey.location()), loottable); // CraftBukkit
-         };
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootTable.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootTable.java.patch
deleted file mode 100644
index 29133b2303..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/loot/LootTable.java.patch
+++ /dev/null
@@ -1,85 +0,0 @@
---- a/net/minecraft/world/level/storage/loot/LootTable.java
-+++ b/net/minecraft/world/level/storage/loot/LootTable.java
-@@ -31,6 +31,13 @@
- import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.CraftLootTable;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.world.LootGenerateEvent;
-+// CraftBukkit end
-+
- public class LootTable {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -54,6 +61,7 @@
-     private final List<LootPool> pools;
-     private final List<LootItemFunction> functions;
-     private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
-+    public CraftLootTable craftLootTable; // CraftBukkit
- 
-     LootTable(ContextKeySet type, Optional<ResourceLocation> randomSequenceId, List<LootPool> pools, List<LootItemFunction> functions) {
-         this.paramSet = type;
-@@ -64,9 +72,10 @@
-     }
- 
-     public static Consumer<ItemStack> createStackSplitter(ServerLevel world, Consumer<ItemStack> consumer) {
-+        boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items
-         return (itemstack) -> {
-             if (itemstack.isItemEnabled(world.enabledFeatures())) {
--                if (itemstack.getCount() < itemstack.getMaxStackSize()) {
-+                if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items
-                     consumer.accept(itemstack);
-                 } else {
-                     int i = itemstack.getCount();
-@@ -157,10 +166,23 @@
-     }
- 
-     public void fill(Container inventory, LootParams parameters, long seed) {
--        LootContext loottableinfo = (new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence);
-+        // CraftBukkit start
-+        this.fillInventory(inventory, parameters, seed, false);
-+    }
-+
-+    public void fillInventory(Container iinventory, LootParams lootparams, long i, boolean plugin) {
-+        // CraftBukkit end
-+        LootContext loottableinfo = (new LootContext.Builder(lootparams)).withOptionalRandomSeed(i).create(this.randomSequence);
-         ObjectArrayList<ItemStack> objectarraylist = this.getRandomItems(loottableinfo);
-         RandomSource randomsource = loottableinfo.getRandom();
--        List<Integer> list = this.getAvailableSlots(inventory, randomsource);
-+        // CraftBukkit start
-+        LootGenerateEvent event = CraftEventFactory.callLootGenerateEvent(iinventory, this, loottableinfo, objectarraylist, plugin);
-+        if (event.isCancelled()) {
-+            return;
-+        }
-+        objectarraylist = event.getLoot().stream().map(CraftItemStack::asNMSCopy).collect(ObjectArrayList.toList());
-+        // CraftBukkit end
-+        List<Integer> list = this.getAvailableSlots(iinventory, randomsource);
- 
-         this.shuffleAndSplitItems(objectarraylist, list.size(), randomsource);
-         ObjectListIterator objectlistiterator = objectarraylist.iterator();
-@@ -174,9 +196,9 @@
-             }
- 
-             if (itemstack.isEmpty()) {
--                inventory.setItem((Integer) list.remove(list.size() - 1), ItemStack.EMPTY);
-+                iinventory.setItem((Integer) list.remove(list.size() - 1), ItemStack.EMPTY);
-             } else {
--                inventory.setItem((Integer) list.remove(list.size() - 1), itemstack);
-+                iinventory.setItem((Integer) list.remove(list.size() - 1), itemstack);
-             }
-         }
- 
-@@ -238,8 +260,8 @@
- 
-     public static class Builder implements FunctionUserBuilder<LootTable.Builder> {
- 
--        private final Builder<LootPool> pools = ImmutableList.builder();
--        private final Builder<LootItemFunction> functions = ImmutableList.builder();
-+        private final ImmutableList.Builder<LootPool> pools = ImmutableList.builder();
-+        private final ImmutableList.Builder<LootItemFunction> functions = ImmutableList.builder();
-         private ContextKeySet paramSet;
-         private Optional<ResourceLocation> randomSequence;
- 

From 1dd7ab92034016fa03bfecc3405e23c47ad29eed Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 17:35:55 -0500
Subject: [PATCH 122/285] net/minecraft/nbt

---
 .../net/minecraft/nbt/ByteArrayTag.java.patch | 15 +++++++
 .../net/minecraft/nbt/CompoundTag.java.patch  | 12 +++---
 .../net/minecraft/nbt/IntArrayTag.java.patch  | 16 ++++----
 .../net/minecraft/nbt/NbtIo.java.patch        | 14 +++----
 .../net/minecraft/nbt/NbtUtils.java.patch     | 12 +++---
 .../net/minecraft/nbt/TagParser.java.patch    | 39 ++++++-------------
 .../net/minecraft/nbt/ByteArrayTag.java.patch | 15 -------
 7 files changed, 54 insertions(+), 69 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/nbt/CompoundTag.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/nbt/IntArrayTag.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/nbt/NbtIo.java.patch (62%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/nbt/NbtUtils.java.patch (65%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/nbt/TagParser.java.patch (57%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/nbt/ByteArrayTag.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
new file mode 100644
index 0000000000..6fd3bac562
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/nbt/ByteArrayTag.java
++++ b/net/minecraft/nbt/ByteArrayTag.java
+@@ -1,3 +_,4 @@
++// mc-dev import
+ package net.minecraft.nbt;
+ 
+ import java.io.DataInput;
+@@ -23,6 +_,7 @@
+         private static byte[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
+             accounter.accountBytes(24L);
+             int _int = input.readInt();
++            com.google.common.base.Preconditions.checkArgument( _int < 1 << 24); // Spigot
+             accounter.accountBytes(1L, _int);
+             byte[] bytes = new byte[_int];
+             input.readFully(bytes);
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/CompoundTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/nbt/CompoundTag.java.patch
rename to paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch
index 1cb8cb2d7b..837aed26f3 100644
--- a/paper-server/patches/unapplied/net/minecraft/nbt/CompoundTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/CompoundTag.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/nbt/CompoundTag.java
 +++ b/net/minecraft/nbt/CompoundTag.java
-@@ -49,7 +49,7 @@
+@@ -49,7 +_,7 @@
  
-         private static CompoundTag loadCompound(DataInput input, NbtAccounter tracker) throws IOException {
-             tracker.accountBytes(48L);
+         private static CompoundTag loadCompound(DataInput input, NbtAccounter nbtAccounter) throws IOException {
+             nbtAccounter.accountBytes(48L);
 -            Map<String, Tag> map = Maps.newHashMap();
 +            it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<String, Tag> map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - Reduce memory footprint of CompoundTag
  
              byte b;
              while ((b = input.readByte()) != 0) {
-@@ -166,7 +166,7 @@
+@@ -166,7 +_,7 @@
      }
  
      public CompoundTag() {
@@ -18,7 +18,7 @@
      }
  
      @Override
-@@ -232,14 +232,34 @@
+@@ -232,14 +_,34 @@
      }
  
      public void putUUID(String key, UUID value) {
@@ -53,7 +53,7 @@
          Tag tag = this.get(key);
          return tag != null && tag.getType() == IntArrayTag.TYPE && ((IntArrayTag)tag).getAsIntArray().length == 4;
      }
-@@ -477,8 +497,16 @@
+@@ -477,8 +_,16 @@
  
      @Override
      public CompoundTag copy() {
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/IntArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/nbt/IntArrayTag.java.patch
rename to paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
index 97872e3339..16917267c6 100644
--- a/paper-server/patches/unapplied/net/minecraft/nbt/IntArrayTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/nbt/IntArrayTag.java
 +++ b/net/minecraft/nbt/IntArrayTag.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.nbt;
  
  import java.io.DataInput;
-@@ -24,6 +25,7 @@
-         private static int[] readAccounted(DataInput input, NbtAccounter tracker) throws IOException {
-             tracker.accountBytes(24L);
-             int i = input.readInt();
-+            com.google.common.base.Preconditions.checkArgument( i < 1 << 24); // Spigot
+@@ -23,6 +_,7 @@
+         private static int[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
+             accounter.accountBytes(24L);
+             int _int = input.readInt();
++            com.google.common.base.Preconditions.checkArgument( _int < 1 << 24); // Spigot
+             accounter.accountBytes(4L, _int);
+             int[] ints = new int[_int];
  
-             tracker.accountBytes(4L, (long) i);
-             int[] aint = new int[i];
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/NbtIo.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/nbt/NbtIo.java.patch
rename to paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
index f6dc9e632c..752f9b8153 100644
--- a/paper-server/patches/unapplied/net/minecraft/nbt/NbtIo.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
@@ -1,20 +1,20 @@
 --- a/net/minecraft/nbt/NbtIo.java
 +++ b/net/minecraft/nbt/NbtIo.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// mc-dev import
  package net.minecraft.nbt;
  
  import java.io.BufferedOutputStream;
-@@ -324,6 +325,12 @@
+@@ -118,6 +_,12 @@
      }
  
-     public static CompoundTag read(DataInput input, NbtAccounter tracker) throws IOException {
+     public static CompoundTag read(DataInput input, NbtAccounter accounter) throws IOException {
 +        // Spigot start
 +        if ( input instanceof io.netty.buffer.ByteBufInputStream )
 +        {
-+            input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, tracker));
++            input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, accounter));
 +        }
 +        // Spigot end
-         Tag nbtbase = NbtIo.readUnnamedTag(input, tracker);
- 
-         if (nbtbase instanceof CompoundTag) {
+         Tag unnamedTag = readUnnamedTag(input, accounter);
+         if (unnamedTag instanceof CompoundTag) {
+             return (CompoundTag)unnamedTag;
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/NbtUtils.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch
similarity index 65%
rename from paper-server/patches/unapplied/net/minecraft/nbt/NbtUtils.java.patch
rename to paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch
index a126b25999..00ff16e9e8 100644
--- a/paper-server/patches/unapplied/net/minecraft/nbt/NbtUtils.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/NbtUtils.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/nbt/NbtUtils.java
 +++ b/net/minecraft/nbt/NbtUtils.java
-@@ -149,8 +149,10 @@
-         if (!nbt.contains("Name", 8)) {
+@@ -143,8 +_,10 @@
+         if (!tag.contains("Name", 8)) {
              return Blocks.AIR.defaultBlockState();
          } else {
--            ResourceLocation resourceLocation = ResourceLocation.parse(nbt.getString("Name"));
--            Optional<? extends Holder<Block>> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation));
+-            ResourceLocation resourceLocation = ResourceLocation.parse(tag.getString("Name"));
+-            Optional<? extends Holder<Block>> optional = blockGetter.get(ResourceKey.create(Registries.BLOCK, resourceLocation));
 +            // Paper start - Validate resource location
-+            ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name"));
-+            Optional<? extends Holder<Block>> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty();
++            ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getString("Name"));
++            Optional<? extends Holder<Block>> optional = resourceLocation != null ? blockGetter.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty();
 +            // Paper end - Validate resource location
              if (optional.isEmpty()) {
                  return Blocks.AIR.defaultBlockState();
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/TagParser.java.patch b/paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/nbt/TagParser.java.patch
rename to paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch
index dd6b67ed47..8d8938c690 100644
--- a/paper-server/patches/unapplied/net/minecraft/nbt/TagParser.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/TagParser.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/nbt/TagParser.java
 +++ b/net/minecraft/nbt/TagParser.java
-@@ -49,6 +49,7 @@
+@@ -49,6 +_,7 @@
      }, CompoundTag::toString);
      public static final Codec<CompoundTag> LENIENT_CODEC = Codec.withAlternative(AS_CODEC, CompoundTag.CODEC);
      private final StringReader reader;
 +    private int depth; // Paper
  
-     public static CompoundTag parseTag(String string) throws CommandSyntaxException {
-         return new TagParser(new StringReader(string)).readSingleStruct();
-@@ -159,6 +160,7 @@
+     public static CompoundTag parseTag(String text) throws CommandSyntaxException {
+         return new TagParser(new StringReader(text)).readSingleStruct();
+@@ -159,6 +_,7 @@
  
      public CompoundTag readStruct() throws CommandSyntaxException {
          this.expect('{');
@@ -16,7 +16,7 @@
          CompoundTag compoundTag = new CompoundTag();
          this.reader.skipWhitespace();
  
-@@ -182,6 +184,7 @@
+@@ -182,6 +_,7 @@
          }
  
          this.expect('}');
@@ -24,7 +24,7 @@
          return compoundTag;
      }
  
-@@ -191,6 +194,7 @@
+@@ -191,6 +_,7 @@
          if (!this.reader.canRead()) {
              throw ERROR_EXPECTED_VALUE.createWithContext(this.reader);
          } else {
@@ -32,7 +32,7 @@
              ListTag listTag = new ListTag();
              TagType<?> tagType = null;
  
-@@ -216,6 +220,7 @@
+@@ -216,6 +_,7 @@
              }
  
              this.expect(']');
@@ -40,30 +40,15 @@
              return listTag;
          }
      }
-@@ -253,11 +258,11 @@
-             }
- 
-             if (typeReader == ByteTag.TYPE) {
--                list.add((T)((NumericTag)tag).getAsByte());
-+                list.add((T)(Byte)((NumericTag)tag).getAsByte()); // Paper - decompile fix
-             } else if (typeReader == LongTag.TYPE) {
--                list.add((T)((NumericTag)tag).getAsLong());
-+                list.add((T)(Long)((NumericTag)tag).getAsLong()); // Paper - decompile fix
-             } else {
--                list.add((T)((NumericTag)tag).getAsInt());
-+                list.add((T)(Integer)((NumericTag)tag).getAsInt()); // Paper - decompile fix
-             }
- 
-             if (!this.hasElementSeparator()) {
-@@ -288,4 +293,11 @@
+@@ -287,5 +_,11 @@
+     private void expect(char expected) throws CommandSyntaxException {
          this.reader.skipWhitespace();
-         this.reader.expect(c);
-     }
-+
+         this.reader.expect(expected);
++    }
 +    private void increaseDepth() throws CommandSyntaxException {
 +        this.depth++;
 +        if (this.depth > 512) {
 +            throw new io.papermc.paper.brigadier.TagParseCommandSyntaxException("NBT tag is too complex, depth > 512");
 +        }
-+    }
+     }
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/nbt/ByteArrayTag.java.patch b/paper-server/patches/unapplied/net/minecraft/nbt/ByteArrayTag.java.patch
deleted file mode 100644
index d7c707925b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/nbt/ByteArrayTag.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/nbt/ByteArrayTag.java
-+++ b/net/minecraft/nbt/ByteArrayTag.java
-@@ -1,3 +1,4 @@
-+// mc-dev import
- package net.minecraft.nbt;
- 
- import java.io.DataInput;
-@@ -24,6 +25,7 @@
-         private static byte[] readAccounted(DataInput input, NbtAccounter tracker) throws IOException {
-             tracker.accountBytes(24L);
-             int i = input.readInt();
-+            com.google.common.base.Preconditions.checkArgument( i < 1 << 24); // Spigot
- 
-             tracker.accountBytes(1L, (long) i);
-             byte[] abyte = new byte[i];

From 2546348b9d769c491c0770f3b7f5e443e459831b Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 14:36:48 -0800
Subject: [PATCH 123/285] net.minecraft.world.level.chunk.storage

---
 .../chunk/storage/ChunkStorage.java.patch     | 148 ++++++++++++
 .../level/chunk/storage/RegionFile.java.patch |  73 ++++++
 .../storage/RegionFileStorage.java.patch      |  61 +++++
 .../storage/RegionFileVersion.java.patch      |   4 +-
 .../storage/SerializableChunkData.java.patch  | 177 ++++++++++++++
 .../chunk/storage/ChunkStorage.java.patch     | 158 -------------
 .../level/chunk/storage/RegionFile.java.patch | 127 ----------
 .../storage/RegionFileStorage.java.patch      |  67 ------
 .../storage/SerializableChunkData.java.patch  | 216 ------------------
 9 files changed, 461 insertions(+), 570 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch (93%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
new file mode 100644
index 0000000000..f7f4038d0e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
@@ -0,0 +1,148 @@
+--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+@@ -15,10 +_,16 @@
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.nbt.NbtUtils;
+ import net.minecraft.resources.ResourceKey;
++import net.minecraft.server.level.ServerChunkCache;
++import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.util.datafix.DataFixTypes;
+ import net.minecraft.world.level.ChunkPos;
+-import net.minecraft.world.level.Level;
++import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.chunk.ChunkGenerator;
++// CraftBukkit start
++import java.util.concurrent.ExecutionException;
++import net.minecraft.world.level.chunk.status.ChunkStatus;
++import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+ 
+@@ -38,17 +_,63 @@
+         return this.worker.isOldChunkAround(pos, radius);
+     }
+ 
++    // CraftBukkit start
++    private boolean check(ServerChunkCache cps, int x, int z) {
++        if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
++        ChunkPos pos = new ChunkPos(x, z);
++        if (cps != null) {
++            com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
++            if (cps.hasChunk(x, z)) {
++                return true;
++            }
++        }
++
++        CompoundTag nbt;
++        try {
++            nbt = this.read(pos).get().orElse(null);
++        } catch (InterruptedException | ExecutionException ex) {
++            throw new RuntimeException(ex);
++        }
++        if (nbt != null) {
++            CompoundTag level = nbt.getCompound("Level");
++            if (level.getBoolean("TerrainPopulated")) {
++                return true;
++            }
++
++            ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
++            if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) {
++                return true;
++            }
++        }
++
++        return false;
++    }
++
+     public CompoundTag upgradeChunkTag(
+-        ResourceKey<Level> levelKey,
++        ResourceKey<LevelStem> levelKey,
+         Supplier<DimensionDataStorage> storage,
+         CompoundTag chunkData,
+-        Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
++        Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
++        ChunkPos pos,
++        @Nullable LevelAccessor generatoraccess
++        // CraftBukkit end
+     ) {
+         int version = getVersion(chunkData);
+         if (version == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
+             return chunkData;
+         } else {
+             try {
++                // CraftBukkit start
++                if (version < 1466) {
++                    CompoundTag level = chunkData.getCompound("Level");
++                    if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
++                        ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
++                        if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) {
++                            level.putBoolean("LightPopulated", true);
++                        }
++                    }
++                }
++                // CraftBukkit end
+                 if (version < 1493) {
+                     chunkData = DataFixTypes.CHUNK.update(this.fixerUpper, chunkData, version, 1493);
+                     if (chunkData.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+@@ -57,8 +_,22 @@
+                     }
+                 }
+ 
++                // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty
++                boolean stopBelowZero = false;
++                boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks;
++
++                if (version <= 2730 && !belowZeroGenerationInExistingChunks) {
++                    stopBelowZero = "full".equals(chunkData.getCompound("Level").getString("Status"));
++                }
++                // Spigot end
++
+                 injectDatafixingContext(chunkData, levelKey, chunkGeneratorKey);
+                 chunkData = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, chunkData, Math.max(1493, version));
++                // Spigot start
++                if (stopBelowZero) {
++                    chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
++                }
++                // Spigot end
+                 removeDatafixingContext(chunkData);
+                 NbtUtils.addCurrentDataVersion(chunkData);
+                 return chunkData;
+@@ -71,7 +_,7 @@
+         }
+     }
+ 
+-    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, Supplier<DimensionDataStorage> storage) {
++    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
+         LegacyStructureDataHandler legacyStructureDataHandler = this.legacyStructureHandler;
+         if (legacyStructureDataHandler == null) {
+             synchronized (this) {
+@@ -86,7 +_,7 @@
+     }
+ 
+     public static void injectDatafixingContext(
+-        CompoundTag chunkData, ResourceKey<Level> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
++        CompoundTag chunkData, ResourceKey<LevelStem> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey // CraftBukkit
+     ) {
+         CompoundTag compoundTag = new CompoundTag();
+         compoundTag.putString("dimension", levelKey.location().toString());
+@@ -107,8 +_,19 @@
+     }
+ 
+     public CompletableFuture<Void> write(ChunkPos pos, Supplier<CompoundTag> tagSupplier) {
++        // Paper start - guard against possible chunk pos desync
++        final Supplier<CompoundTag> guardedPosCheck = () -> {
++            CompoundTag nbt = tagSupplier.get();
++            if (nbt != null && !pos.equals(SerializableChunkData.getChunkCoordinate(nbt))) {
++                final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null;
++                throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos
++                    + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world)));
++            }
++            return nbt;
++        };
++        // Paper end - guard against possible chunk pos desync
+         this.handleLegacyStructureIndex(pos);
+-        return this.worker.store(pos, tagSupplier);
++        return this.worker.store(pos, guardedPosCheck); // Paper - guard against possible chunk pos desync
+     }
+ 
+     protected void handleLegacyStructureIndex(ChunkPos chunkPos) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
new file mode 100644
index 0000000000..0a25ddead5
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -1,3 +_,4 @@
++// mc-dev import
+ package net.minecraft.world.level.chunk.storage;
+ 
+ import com.google.common.annotations.VisibleForTesting;
+@@ -46,7 +_,7 @@
+     protected final RegionBitmap usedSectors = new RegionBitmap();
+ 
+     public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, boolean sync) throws IOException {
+-        this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync);
++        this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
+     }
+ 
+     public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, RegionFileVersion version, boolean sync) throws IOException {
+@@ -82,6 +_,14 @@
+                     if (i2 != 0) {
+                         int sectorNumber = getSectorNumber(i2);
+                         int numSectors = getNumSectors(i2);
++                        // Spigot start
++                        if (numSectors == 255) {
++                            // We're maxed out, so we need to read the proper length from the section
++                            ByteBuffer realLen = ByteBuffer.allocate(4);
++                            this.file.read(realLen, sectorNumber * 4096);
++                            numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
++                        }
++                        // Spigot end
+                         if (sectorNumber < 2) {
+                             LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i1, sectorNumber);
+                             this.offsets.put(i1, 0);
+@@ -117,6 +_,13 @@
+         } else {
+             int sectorNumber = getSectorNumber(offset);
+             int numSectors = getNumSectors(offset);
++            // Spigot start
++            if (numSectors == 255) {
++                ByteBuffer realLen = ByteBuffer.allocate(4);
++                this.file.read(realLen, sectorNumber * 4096);
++                numSectors = (realLen.getInt(0) + 4) / 4096 + 1;
++            }
++            // Spigot end
+             int i = numSectors * 4096;
+             ByteBuffer byteBuffer = ByteBuffer.allocate(i);
+             this.file.read(byteBuffer, sectorNumber * 4096);
+@@ -260,6 +_,7 @@
+                     return true;
+                 }
+             } catch (IOException var9) {
++                com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var9); // Paper - ServerExceptionEvent
+                 return false;
+             }
+         }
+@@ -331,13 +_,18 @@
+         try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
+             chunkData.position(5);
+             fileChannel.write(chunkData);
++            // Paper start - ServerExceptionEvent
++        } catch (Throwable throwable) {
++            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable);
++            throw throwable;
++            // Paper end - ServerExceptionEvent
+         }
+ 
+         return () -> Files.move(path, externalChunkFile, StandardCopyOption.REPLACE_EXISTING);
+     }
+ 
+     private void writeHeader() throws IOException {
+-        this.header.position(0);
++        ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error
+         this.file.write(this.header, 0L);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
new file mode 100644
index 0000000000..fadb8b6cdb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
@@ -0,0 +1,61 @@
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -28,18 +_,19 @@
+         this.info = info;
+     }
+ 
+-    private RegionFile getRegionFile(ChunkPos chunkPos) throws IOException {
++    @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit
+         long packedChunkPos = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
+         RegionFile regionFile = this.regionCache.getAndMoveToFirst(packedChunkPos);
+         if (regionFile != null) {
+             return regionFile;
+         } else {
+-            if (this.regionCache.size() >= 256) {
++            if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable
+                 this.regionCache.removeLast().close();
+             }
+ 
+             FileUtil.createDirectoriesSafe(this.folder);
+             Path path = this.folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
++            if (existingOnly && !java.nio.file.Files.exists(path)) return null; // CraftBukkit
+             RegionFile regionFile1 = new RegionFile(this.info, path, this.folder, this.sync);
+             this.regionCache.putAndMoveToFirst(packedChunkPos, regionFile1);
+             return regionFile1;
+@@ -48,7 +_,12 @@
+ 
+     @Nullable
+     public CompoundTag read(ChunkPos chunkPos) throws IOException {
+-        RegionFile regionFile = this.getRegionFile(chunkPos);
++        // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
++        RegionFile regionFile = this.getRegionFile(chunkPos, true);
++        if (regionFile == null) {
++            return null;
++        }
++        // CraftBukkit end
+ 
+         CompoundTag var4;
+         try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) {
+@@ -63,7 +_,12 @@
+     }
+ 
+     public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
+-        RegionFile regionFile = this.getRegionFile(chunkPos);
++        // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
++        RegionFile regionFile = this.getRegionFile(chunkPos, true);
++        if (regionFile == null) {
++            return;
++        }
++        // CraftBukkit end
+ 
+         try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) {
+             if (chunkDataInputStream != null) {
+@@ -73,7 +_,7 @@
+     }
+ 
+     protected void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException {
+-        RegionFile regionFile = this.getRegionFile(chunkPos);
++        RegionFile regionFile = this.getRegionFile(chunkPos, false); // CraftBukkit
+         if (chunkData == null) {
+             regionFile.clear(chunkPos);
+         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch
similarity index 93%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch
index 65bf070d12..ce1088103a 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
-@@ -58,6 +58,15 @@
+@@ -61,6 +_,15 @@
      private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
      private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
  
@@ -15,4 +15,4 @@
 +    // Paper end - Configurable region compression format
      private RegionFileVersion(
          int id,
-         @Nullable String name,
+         @Nullable String optionName,
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch
new file mode 100644
index 0000000000..56133882ab
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch
@@ -0,0 +1,177 @@
+--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+@@ -91,6 +_,7 @@
+     List<CompoundTag> entities,
+     List<CompoundTag> blockEntities,
+     CompoundTag structureData
++    , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer
+ ) {
+     public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(
+         Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()
+@@ -107,12 +_,39 @@
+     public static final String BLOCK_LIGHT_TAG = "BlockLight";
+     public static final String SKY_LIGHT_TAG = "SkyLight";
+ 
++    // Paper start - guard against serializing mismatching coordinates
++    // TODO Note: This needs to be re-checked each update
++    public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) {
++        final int dataVersion = ChunkStorage.getVersion(chunkData);
++        if (dataVersion < 2842) { // Level tag is removed after this version
++            final CompoundTag levelData = chunkData.getCompound("Level");
++            return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
++        } else {
++            return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
++        }
++    }
++    // Paper end - guard against serializing mismatching coordinates
++
++    // Paper start - Do not let the server load chunks from newer versions
++    private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
++    private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
++    // Paper end - Do not let the server load chunks from newer versions
++
+     @Nullable
+     public static SerializableChunkData parse(LevelHeightAccessor levelHeightAccessor, RegistryAccess registries, CompoundTag tag) {
+         if (!tag.contains("Status", 8)) {
+             return null;
+         } else {
+-            ChunkPos chunkPos = new ChunkPos(tag.getInt("xPos"), tag.getInt("zPos"));
++            // Paper start - Do not let the server load chunks from newer versions
++            if (tag.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
++                final int dataVersion = tag.getInt("DataVersion");
++                if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
++                    new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
++                    System.exit(1);
++                }
++            }
++            // Paper end - Do not let the server load chunks from newer versions
++            ChunkPos chunkPos = new ChunkPos(tag.getInt("xPos"), tag.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate
+             long _long = tag.getLong("LastUpdate");
+             long _long1 = tag.getLong("InhabitedTime");
+             ChunkStatus chunkStatus = ChunkStatus.byName(tag.getString("Status"));
+@@ -181,7 +_,7 @@
+             ListTag list7 = tag.getList("sections", 10);
+             List<SerializableChunkData.SectionData> list8 = new ArrayList<>(list7.size());
+             Registry<Biome> registry = registries.lookupOrThrow(Registries.BIOME);
+-            Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
++            Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(registry); // CraftBukkit - read/write
+ 
+             for (int i2 = 0; i2 < list7.size(); i2++) {
+                 CompoundTag compound2 = list7.getCompound(i2);
+@@ -199,7 +_,7 @@
+                         );
+                     }
+ 
+-                    PalettedContainerRO<Holder<Biome>> palettedContainerRo;
++                    PalettedContainer<Holder<Biome>> palettedContainerRo; // CraftBukkit - read/write
+                     if (compound2.contains("biomes", 10)) {
+                         palettedContainerRo = codec.parse(NbtOps.INSTANCE, compound2.getCompound("biomes"))
+                             .promotePartial(string -> logErrors(chunkPos, _byte, string))
+@@ -239,6 +_,7 @@
+                 list5,
+                 list6,
+                 compound1
++                , tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues
+             );
+         }
+     }
+@@ -316,6 +_,12 @@
+             }
+         }
+ 
++        // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
++        if (this.persistentDataContainer instanceof CompoundTag) {
++            chunkAccess.persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer);
++        }
++        // CraftBukkit end
++
+         chunkAccess.setLightCorrect(this.lightCorrect);
+         EnumSet<Heightmap.Types> set = EnumSet.noneOf(Heightmap.Types.class);
+ 
+@@ -346,6 +_,13 @@
+             }
+ 
+             for (CompoundTag compoundTag : this.blockEntities) {
++                // Paper start - do not read tile entities positioned outside the chunk
++                final BlockPos blockposition = BlockEntity.getPosFromTag(compoundTag);
++                if ((blockposition.getX() >> 4) != this.chunkPos.x || (blockposition.getZ() >> 4) != this.chunkPos.z) {
++                    LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", this.chunkPos, level.getWorld().getName(), blockposition);
++                    continue;
++                }
++                // Paper end - do not read tile entities positioned outside the chunk
+                 protoChunk1.setBlockEntityNbt(compoundTag);
+             }
+ 
+@@ -370,6 +_,12 @@
+         );
+     }
+ 
++    // CraftBukkit start - read/write
++    private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
++        return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS));
++    }
++    // CraftBukkit end
++
+     public static SerializableChunkData copyOf(ServerLevel level, ChunkAccess chunk) {
+         if (!chunk.canBeSerialized()) {
+             throw new IllegalArgumentException("Chunk can't be serialized: " + chunk);
+@@ -428,6 +_,12 @@
+             CompoundTag compoundTag = packStructureData(
+                 StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences()
+             );
++            // CraftBukkit start - store chunk persistent data in nbt
++            CompoundTag persistentDataContainer = null;
++            if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
++                persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
++            }
++            // CraftBukkit end
+             return new SerializableChunkData(
+                 level.registryAccess().lookupOrThrow(Registries.BIOME),
+                 pos,
+@@ -447,6 +_,7 @@
+                 list2,
+                 list1,
+                 compoundTag
++                , persistentDataContainer // CraftBukkit - persistentDataContainer
+             );
+         }
+     }
+@@ -525,6 +_,11 @@
+         this.heightmaps.forEach((types, longs) -> compoundTag2.put(types.getSerializationKey(), new LongArrayTag(longs)));
+         compoundTag.put("Heightmaps", compoundTag2);
+         compoundTag.put("structures", this.structureData);
++        // CraftBukkit start - store chunk persistent data in nbt
++        if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
++            compoundTag.put("ChunkBukkitValues", this.persistentDataContainer);
++        }
++        // CraftBukkit end
+         return compoundTag;
+     }
+ 
+@@ -562,6 +_,13 @@
+                     chunk.setBlockEntityNbt(compoundTag);
+                 } else {
+                     BlockPos posFromTag = BlockEntity.getPosFromTag(compoundTag);
++                    // Paper start - do not read tile entities positioned outside the chunk
++                    ChunkPos chunkPos = chunk.getPos();
++                    if ((posFromTag.getX() >> 4) != chunkPos.x || (posFromTag.getZ() >> 4) != chunkPos.z) {
++                        LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + level.getWorld().getName() + "' positioned at " + posFromTag + " is located outside of the chunk");
++                        continue;
++                    }
++                    // Paper end - do not read tile entities positioned outside the chunk
+                     BlockEntity blockEntity = BlockEntity.loadStatic(posFromTag, chunk.getBlockState(posFromTag), compoundTag, level.registryAccess());
+                     if (blockEntity != null) {
+                         chunk.setBlockEntity(blockEntity);
+@@ -610,6 +_,12 @@
+             } else {
+                 StructureStart structureStart = StructureStart.loadStaticStart(context, compound.getCompound(string), seed);
+                 if (structureStart != null) {
++                    // CraftBukkit start - load persistent data for structure start
++                    net.minecraft.nbt.Tag persistentBase = compound.getCompound(string).get("StructureBukkitValues");
++                    if (persistentBase instanceof CompoundTag) {
++                        structureStart.persistentDataContainer.putAll((CompoundTag) persistentBase);
++                    }
++                    // CraftBukkit end
+                     map.put(structure, structureStart);
+                 }
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
deleted file mode 100644
index b82d242d2c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
+++ /dev/null
@@ -1,158 +0,0 @@
---- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-+++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -15,10 +15,16 @@
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtUtils;
- import net.minecraft.resources.ResourceKey;
-+import net.minecraft.server.level.ServerChunkCache;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.util.datafix.DataFixTypes;
- import net.minecraft.world.level.ChunkPos;
--import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.chunk.ChunkGenerator;
-+// CraftBukkit start
-+import java.util.concurrent.ExecutionException;
-+import net.minecraft.world.level.chunk.status.ChunkStatus;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- 
-@@ -39,27 +45,86 @@
-         return this.worker.isOldChunkAround(chunkPos, checkRadius);
-     }
- 
--    public CompoundTag upgradeChunkTag(ResourceKey<Level> worldKey, Supplier<DimensionDataStorage> persistentStateManagerFactory, CompoundTag nbt, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) {
--        int i = ChunkStorage.getVersion(nbt);
-+    // CraftBukkit start
-+    private boolean check(ServerChunkCache cps, int x, int z) {
-+        if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
-+        ChunkPos pos = new ChunkPos(x, z);
-+        if (cps != null) {
-+            com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
-+            if (cps.hasChunk(x, z)) {
-+                return true;
-+            }
-+        }
- 
-+        CompoundTag nbt;
-+        try {
-+            nbt = this.read(pos).get().orElse(null);
-+        } catch (InterruptedException | ExecutionException ex) {
-+            throw new RuntimeException(ex);
-+        }
-+        if (nbt != null) {
-+            CompoundTag level = nbt.getCompound("Level");
-+            if (level.getBoolean("TerrainPopulated")) {
-+                return true;
-+            }
-+
-+            ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
-+            if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) {
-+                return true;
-+            }
-+        }
-+
-+        return false;
-+    }
-+
-+    public CompoundTag upgradeChunkTag(ResourceKey<LevelStem> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag nbttagcompound, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) {
-+        // CraftBukkit end
-+        int i = ChunkStorage.getVersion(nbttagcompound);
-+
-         if (i == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
--            return nbt;
-+            return nbttagcompound;
-         } else {
-             try {
-+                // CraftBukkit start
-+                if (i < 1466) {
-+                    CompoundTag level = nbttagcompound.getCompound("Level");
-+                    if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
-+                        ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
-+                        if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) {
-+                            level.putBoolean("LightPopulated", true);
-+                        }
-+                    }
-+                }
-+                // CraftBukkit end
-+
-                 if (i < 1493) {
--                    nbt = DataFixTypes.CHUNK.update(this.fixerUpper, nbt, i, 1493);
--                    if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) {
--                        LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(worldKey, persistentStateManagerFactory);
-+                    nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
-+                    if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
-+                        LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
- 
--                        nbt = persistentstructurelegacy.updateFromLegacy(nbt);
-+                        nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound);
-                     }
-                 }
- 
--                ChunkStorage.injectDatafixingContext(nbt, worldKey, generatorCodecKey);
--                nbt = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbt, Math.max(1493, i));
--                ChunkStorage.removeDatafixingContext(nbt);
--                NbtUtils.addCurrentDataVersion(nbt);
--                return nbt;
-+                // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty
-+                boolean stopBelowZero = false;
-+                boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks;
-+
-+                if (i <= 2730 && !belowZeroGenerationInExistingChunks) {
-+                    stopBelowZero = "full".equals(nbttagcompound.getCompound("Level").getString("Status"));
-+                }
-+                // Spigot end
-+
-+                ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
-+                nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
-+                // Spigot start
-+                if (stopBelowZero) {
-+                    nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
-+                }
-+                // Spigot end
-+                ChunkStorage.removeDatafixingContext(nbttagcompound);
-+                NbtUtils.addCurrentDataVersion(nbttagcompound);
-+                return nbttagcompound;
-             } catch (Exception exception) {
-                 CrashReport crashreport = CrashReport.forThrowable(exception, "Updated chunk");
-                 CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Updated chunk details");
-@@ -70,7 +135,7 @@
-         }
-     }
- 
--    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) {
-+    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) { // CraftBukkit
-         LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
- 
-         if (persistentstructurelegacy == null) {
-@@ -85,7 +150,7 @@
-         return persistentstructurelegacy;
-     }
- 
--    public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<Level> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) {
-+    public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<LevelStem> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) { // CraftBukkit
-         CompoundTag nbttagcompound1 = new CompoundTag();
- 
-         nbttagcompound1.putString("dimension", worldKey.location().toString());
-@@ -108,8 +173,19 @@
-     }
- 
-     public CompletableFuture<Void> write(ChunkPos chunkPos, Supplier<CompoundTag> nbtSupplier) {
-+        // Paper start - guard against possible chunk pos desync
-+        final Supplier<CompoundTag> guardedPosCheck = () -> {
-+            CompoundTag nbt = nbtSupplier.get();
-+            if (nbt != null && !chunkPos.equals(SerializableChunkData.getChunkCoordinate(nbt))) {
-+                final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null;
-+                throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos
-+                    + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world)));
-+            }
-+            return nbt;
-+        };
-+        // Paper end - guard against possible chunk pos desync
-         this.handleLegacyStructureIndex(chunkPos);
--        return this.worker.store(chunkPos, nbtSupplier);
-+        return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync
-     }
- 
-     protected void handleLegacyStructureIndex(ChunkPos chunkPos) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
deleted file mode 100644
index 3955a59294..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
+++ /dev/null
@@ -1,127 +0,0 @@
---- a/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -1,3 +1,4 @@
-+// mc-dev import
- package net.minecraft.world.level.chunk.storage;
- 
- import com.google.common.annotations.VisibleForTesting;
-@@ -49,7 +50,7 @@
-     protected final RegionBitmap usedSectors;
- 
-     public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
--        this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync);
-+        this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
-     }
- 
-     public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException {
-@@ -63,8 +64,8 @@
-         } else {
-             this.externalFileDir = directory;
-             this.offsets = this.header.asIntBuffer();
--            this.offsets.limit(1024);
--            this.header.position(4096);
-+            ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error
-+            ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error
-             this.timestamps = this.header.asIntBuffer();
-             if (dsync) {
-                 this.file = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.DSYNC);
-@@ -73,7 +74,7 @@
-             }
- 
-             this.usedSectors.force(0, 2);
--            this.header.position(0);
-+            ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error
-             int i = this.file.read(this.header, 0L);
- 
-             if (i != -1) {
-@@ -89,6 +90,14 @@
-                     if (l != 0) {
-                         int i1 = RegionFile.getSectorNumber(l);
-                         int j1 = RegionFile.getNumSectors(l);
-+                        // Spigot start
-+                        if (j1 == 255) {
-+                            // We're maxed out, so we need to read the proper length from the section
-+                            ByteBuffer realLen = ByteBuffer.allocate(4);
-+                            this.file.read(realLen, i1 * 4096);
-+                            j1 = (realLen.getInt(0) + 4) / 4096 + 1;
-+                        }
-+                        // Spigot end
- 
-                         if (i1 < 2) {
-                             RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1});
-@@ -128,11 +137,18 @@
-         } else {
-             int j = RegionFile.getSectorNumber(i);
-             int k = RegionFile.getNumSectors(i);
-+            // Spigot start
-+            if (k == 255) {
-+                ByteBuffer realLen = ByteBuffer.allocate(4);
-+                this.file.read(realLen, j * 4096);
-+                k = (realLen.getInt(0) + 4) / 4096 + 1;
-+            }
-+            // Spigot end
-             int l = k * 4096;
-             ByteBuffer bytebuffer = ByteBuffer.allocate(l);
- 
-             this.file.read(bytebuffer, (long) (j * 4096));
--            bytebuffer.flip();
-+            ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
-             if (bytebuffer.remaining() < 5) {
-                 RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
-                 return null;
-@@ -246,7 +262,7 @@
- 
-             try {
-                 this.file.read(bytebuffer, (long) (j * 4096));
--                bytebuffer.flip();
-+                ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
-                 if (bytebuffer.remaining() != 5) {
-                     return false;
-                 } else {
-@@ -280,6 +296,7 @@
-                     return true;
-                 }
-             } catch (IOException ioexception) {
-+                com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper - ServerExceptionEvent
-                 return false;
-             }
-         }
-@@ -349,7 +366,7 @@
- 
-         bytebuffer.putInt(1);
-         bytebuffer.put((byte) (this.version.getId() | 128));
--        bytebuffer.flip();
-+        ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
-         return bytebuffer;
-     }
- 
-@@ -358,9 +375,10 @@
-         FileChannel filechannel = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
- 
-         try {
--            buf.position(5);
-+            ((java.nio.Buffer) buf).position(5); // CraftBukkit - decompile error
-             filechannel.write(buf);
-         } catch (Throwable throwable) {
-+            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper - ServerExceptionEvent
-             if (filechannel != null) {
-                 try {
-                     filechannel.close();
-@@ -382,7 +400,7 @@
-     }
- 
-     private void writeHeader() throws IOException {
--        this.header.position(0);
-+        ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error
-         this.file.write(this.header, 0L);
-     }
- 
-@@ -418,7 +436,7 @@
-         if (i != j) {
-             ByteBuffer bytebuffer = RegionFile.PADDING_BUFFER.duplicate();
- 
--            bytebuffer.position(0);
-+            ((java.nio.Buffer) bytebuffer).position(0); // CraftBukkit - decompile error
-             this.file.write(bytebuffer, (long) (j - 1));
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
deleted file mode 100644
index 4e4fd07bda..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
+++ /dev/null
@@ -1,67 +0,0 @@
---- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -32,21 +32,22 @@
-         this.info = storageKey;
-     }
- 
--    private RegionFile getRegionFile(ChunkPos pos) throws IOException {
--        long i = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ());
-+    private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
-+        long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
-         RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
- 
-         if (regionfile != null) {
-             return regionfile;
-         } else {
--            if (this.regionCache.size() >= 256) {
-+            if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable
-                 ((RegionFile) this.regionCache.removeLast()).close();
-             }
- 
-             FileUtil.createDirectoriesSafe(this.folder);
-             Path path = this.folder;
--            int j = pos.getRegionX();
--            Path path1 = path.resolve("r." + j + "." + pos.getRegionZ() + ".mca");
-+            int j = chunkcoordintpair.getRegionX();
-+            Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
-+            if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
-             RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync);
- 
-             this.regionCache.putAndMoveToFirst(i, regionfile1);
-@@ -56,7 +57,12 @@
- 
-     @Nullable
-     public CompoundTag read(ChunkPos pos) throws IOException {
--        RegionFile regionfile = this.getRegionFile(pos);
-+        // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
-+        RegionFile regionfile = this.getRegionFile(pos, true);
-+        if (regionfile == null) {
-+            return null;
-+        }
-+        // CraftBukkit end
-         DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
- 
-         CompoundTag nbttagcompound;
-@@ -96,7 +102,12 @@
-     }
- 
-     public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException {
--        RegionFile regionfile = this.getRegionFile(chunkPos);
-+        // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
-+        RegionFile regionfile = this.getRegionFile(chunkPos, true);
-+        if (regionfile == null) {
-+            return;
-+        }
-+        // CraftBukkit end
-         DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos);
- 
-         try {
-@@ -122,7 +133,7 @@
-     }
- 
-     protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
--        RegionFile regionfile = this.getRegionFile(pos);
-+        RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
- 
-         if (nbt == null) {
-             regionfile.clear(pos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch
deleted file mode 100644
index 15c1561f05..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch
+++ /dev/null
@@ -1,216 +0,0 @@
---- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -76,7 +76,8 @@
- import net.minecraft.world.ticks.SavedTick;
- import org.slf4j.Logger;
- 
--public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData) {
-+// CraftBukkit - persistentDataContainer
-+public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) {
- 
-     public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -90,13 +91,39 @@
-     public static final String SECTIONS_TAG = "sections";
-     public static final String BLOCK_LIGHT_TAG = "BlockLight";
-     public static final String SKY_LIGHT_TAG = "SkyLight";
-+    // Paper start - guard against serializing mismatching coordinates
-+    // TODO Note: This needs to be re-checked each update
-+    public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) {
-+        final int dataVersion = ChunkStorage.getVersion(chunkData);
-+        if (dataVersion < 2842) { // Level tag is removed after this version
-+            final CompoundTag levelData = chunkData.getCompound("Level");
-+            return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
-+        } else {
-+            return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
-+        }
-+    }
-+    // Paper end - guard against serializing mismatching coordinates
- 
-+    // Paper start - Do not let the server load chunks from newer versions
-+    private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
-+    private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
-+    // Paper end - Do not let the server load chunks from newer versions
-+
-     @Nullable
-     public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) {
-         if (!nbt.contains("Status", 8)) {
-             return null;
-         } else {
--            ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
-+            // Paper start - Do not let the server load chunks from newer versions
-+            if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
-+                final int dataVersion = nbt.getInt("DataVersion");
-+                if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
-+                    new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
-+                    System.exit(1);
-+                }
-+            }
-+            // Paper end - Do not let the server load chunks from newer versions
-+            ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate
-             long i = nbt.getLong("LastUpdate");
-             long j = nbt.getLong("InhabitedTime");
-             ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
-@@ -110,7 +137,7 @@
-                 dataresult = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("blending_data"));
-                 logger = SerializableChunkData.LOGGER;
-                 Objects.requireNonNull(logger);
--                blendingdata_d = (BlendingData.Packed) dataresult.resultOrPartial(logger::error).orElse((Object) null);
-+                blendingdata_d = (BlendingData.Packed) ((DataResult<BlendingData.Packed>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
-             } else {
-                 blendingdata_d = null;
-             }
-@@ -121,7 +148,7 @@
-                 dataresult = BelowZeroRetrogen.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen"));
-                 logger = SerializableChunkData.LOGGER;
-                 Objects.requireNonNull(logger);
--                belowzeroretrogen = (BelowZeroRetrogen) dataresult.resultOrPartial(logger::error).orElse((Object) null);
-+                belowzeroretrogen = (BelowZeroRetrogen) ((DataResult<BelowZeroRetrogen>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
-             } else {
-                 belowzeroretrogen = null;
-             }
-@@ -178,7 +205,7 @@
-             ListTag nbttaglist2 = nbt.getList("sections", 10);
-             List<SerializableChunkData.SectionData> list4 = new ArrayList(nbttaglist2.size());
-             Registry<Biome> iregistry = registryManager.lookupOrThrow(Registries.BIOME);
--            Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(iregistry);
-+            Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
- 
-             for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
-                 CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1);
-@@ -196,17 +223,17 @@
-                         datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
-                     }
- 
--                    Object object;
-+                    PalettedContainer object; // CraftBukkit - read/write
- 
-                     if (nbttagcompound3.contains("biomes", 10)) {
--                        object = (PalettedContainerRO) codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> {
-+                        object = codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { // CraftBukkit - read/write
-                             logErrors(chunkcoordintpair, b0, s1);
-                         }).getOrThrow(SerializableChunkData.ChunkReadException::new);
-                     } else {
-                         object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
-                     }
- 
--                    chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainerRO) object);
-+                    chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
-                 } else {
-                     chunksection = null;
-                 }
-@@ -217,7 +244,8 @@
-                 list4.add(new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1));
-             }
- 
--            return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2);
-+            // CraftBukkit - ChunkBukkitValues
-+            return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2, nbt.get("ChunkBukkitValues"));
-         }
-     }
- 
-@@ -287,7 +315,13 @@
-             if (this.chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
-                 protochunk.setLightEngine(levellightengine);
-             }
-+        }
-+
-+        // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
-+        if (this.persistentDataContainer instanceof CompoundTag) {
-+            ((ChunkAccess) object).persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer);
-         }
-+        // CraftBukkit end
- 
-         ((ChunkAccess) object).setLightCorrect(this.lightCorrect);
-         EnumSet<Heightmap.Types> enumset = EnumSet.noneOf(Heightmap.Types.class);
-@@ -329,6 +363,13 @@
- 
-             while (iterator2.hasNext()) {
-                 nbttagcompound = (CompoundTag) iterator2.next();
-+                // Paper start - do not read tile entities positioned outside the chunk
-+                final BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound);
-+                if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
-+                    LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", chunkPos, world.getWorld().getName(), blockposition);
-+                    continue;
-+                }
-+                // Paper end - do not read tile entities positioned outside the chunk
-                 protochunk1.setBlockEntityNbt(nbttagcompound);
-             }
- 
-@@ -348,6 +389,12 @@
-         return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS));
-     }
- 
-+    // CraftBukkit start - read/write
-+    private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
-+        return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS));
-+    }
-+    // CraftBukkit end
-+
-     public static SerializableChunkData copyOf(ServerLevel world, ChunkAccess chunk) {
-         if (!chunk.canBeSerialized()) {
-             throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk));
-@@ -419,7 +466,14 @@
-             });
-             CompoundTag nbttagcompound1 = packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences());
- 
--            return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1);
-+            // CraftBukkit start - store chunk persistent data in nbt
-+            CompoundTag persistentDataContainer = null;
-+            if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
-+                persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
-+            }
-+
-+            return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1, persistentDataContainer);
-+            // CraftBukkit end
-         }
-     }
- 
-@@ -432,7 +486,7 @@
-         nbttagcompound.putLong("LastUpdate", this.lastUpdateTime);
-         nbttagcompound.putLong("InhabitedTime", this.inhabitedTime);
-         nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
--        DataResult dataresult;
-+        DataResult<Tag> dataresult; // CraftBukkit - decompile error
-         Logger logger;
- 
-         if (this.blendingData != null) {
-@@ -513,6 +567,11 @@
-         });
-         nbttagcompound.put("Heightmaps", nbttagcompound2);
-         nbttagcompound.put("structures", this.structureData);
-+        // CraftBukkit start - store chunk persistent data in nbt
-+        if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
-+            nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer);
-+        }
-+        // CraftBukkit end
-         return nbttagcompound;
-     }
- 
-@@ -564,6 +623,13 @@
-                     chunk.setBlockEntityNbt(nbttagcompound);
-                 } else {
-                     BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound);
-+                    // Paper start - do not read tile entities positioned outside the chunk
-+                    ChunkPos chunkPos = chunk.getPos();
-+                    if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
-+                        LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
-+                        continue;
-+                    }
-+                    // Paper end - do not read tile entities positioned outside the chunk
-                     BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound, world.registryAccess());
- 
-                     if (tileentity != null) {
-@@ -623,6 +689,12 @@
-                 StructureStart structurestart = StructureStart.loadStaticStart(context, nbttagcompound1.getCompound(s), worldSeed);
- 
-                 if (structurestart != null) {
-+                    // CraftBukkit start - load persistent data for structure start
-+                    net.minecraft.nbt.Tag persistentBase = nbttagcompound1.getCompound(s).get("StructureBukkitValues");
-+                    if (persistentBase instanceof CompoundTag) {
-+                        structurestart.persistentDataContainer.putAll((CompoundTag) persistentBase);
-+                    }
-+                    // CraftBukkit end
-                     map.put(structure, structurestart);
-                 }
-             }

From 630c815714d7b416a55067718555f05bb7d58f2f Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 14:45:10 -0800
Subject: [PATCH 124/285] net.minecraft.world.item.trading

---
 .../world/item/trading/Merchant.java.patch    |  4 +-
 .../item/trading/MerchantOffer.java.patch     | 87 ++++++++++++++++++
 .../item/trading/MerchantOffer.java.patch     | 90 -------------------
 3 files changed, 89 insertions(+), 92 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/item/trading/Merchant.java.patch (94%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/item/trading/MerchantOffer.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/trading/Merchant.java.patch b/paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/net/minecraft/world/item/trading/Merchant.java.patch
rename to paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch
index a8626b121e..36c879841f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/item/trading/Merchant.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/trading/Merchant.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/trading/Merchant.java
 +++ b/net/minecraft/world/item/trading/Merchant.java
-@@ -20,6 +20,7 @@
+@@ -19,6 +_,7 @@
  
      void overrideOffers(MerchantOffers offers);
  
@@ -8,7 +8,7 @@
      void notifyTrade(MerchantOffer offer);
  
      void notifyTradeUpdated(ItemStack stack);
-@@ -54,4 +55,6 @@
+@@ -50,4 +_,6 @@
      boolean isClientSide();
  
      boolean stillValid(Player player);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch b/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch
new file mode 100644
index 0000000000..05eb5597d3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/trading/MerchantOffer.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/item/trading/MerchantOffer.java
++++ b/net/minecraft/world/item/trading/MerchantOffer.java
+@@ -21,6 +_,7 @@
+                 Codec.INT.lenientOptionalFieldOf("demand", Integer.valueOf(0)).forGetter(merchantOffer -> merchantOffer.demand),
+                 Codec.FLOAT.lenientOptionalFieldOf("priceMultiplier", Float.valueOf(0.0F)).forGetter(merchantOffer -> merchantOffer.priceMultiplier),
+                 Codec.INT.lenientOptionalFieldOf("xp", Integer.valueOf(1)).forGetter(merchantOffer -> merchantOffer.xp)
++                , Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter(merchantOffer -> merchantOffer.ignoreDiscounts) // Paper
+             )
+             .apply(instance, MerchantOffer::new)
+     );
+@@ -37,6 +_,21 @@
+     public int demand;
+     public float priceMultiplier;
+     public int xp;
++    public boolean ignoreDiscounts; // Paper - Add ignore discounts API
++
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.inventory.@org.jspecify.annotations.Nullable CraftMerchantRecipe bukkitHandle;
++
++    public org.bukkit.craftbukkit.inventory.CraftMerchantRecipe asBukkit() {
++        return (this.bukkitHandle == null) ? this.bukkitHandle = new org.bukkit.craftbukkit.inventory.CraftMerchantRecipe(this) : this.bukkitHandle;
++    }
++
++    public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, org.bukkit.craftbukkit.inventory.CraftMerchantRecipe bukkit) { // Paper
++        this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand);
++        this.ignoreDiscounts = ignoreDiscounts; // Paper
++        this.bukkitHandle = bukkit;
++    }
++    // CraftBukkit end
+ 
+     private MerchantOffer(
+         ItemCost baseCostA,
+@@ -49,6 +_,7 @@
+         int demand,
+         float priceMultiplier,
+         int xp
++        , final boolean ignoreDiscounts // Paper
+     ) {
+         this.baseCostA = baseCostA;
+         this.costB = costB;
+@@ -60,6 +_,7 @@
+         this.demand = demand;
+         this.priceMultiplier = priceMultiplier;
+         this.xp = xp;
++        this.ignoreDiscounts = ignoreDiscounts; // Paper
+     }
+ 
+     public MerchantOffer(ItemCost baseCostA, ItemStack result, int maxUses, int xp, float priceMultiplier) {
+@@ -75,7 +_,7 @@
+     }
+ 
+     public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int _uses, int maxUses, int xp, float priceMultiplier, int demand) {
+-        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp);
++        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp, false); // Paper
+     }
+ 
+     private MerchantOffer(MerchantOffer other) {
+@@ -90,6 +_,7 @@
+             other.demand,
+             other.priceMultiplier,
+             other.xp
++            , other.ignoreDiscounts // Paper
+         );
+     }
+ 
+@@ -124,7 +_,7 @@
+     }
+ 
+     public void updateDemand() {
+-        this.demand = this.demand + this.uses - (this.maxUses - this.uses);
++        this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962
+     }
+ 
+     public ItemStack assemble() {
+@@ -205,7 +_,11 @@
+         if (!this.satisfiedBy(playerOfferA, playerOfferB)) {
+             return false;
+         } else {
+-            playerOfferA.shrink(this.getCostA().getCount());
++            // CraftBukkit start
++            if (!this.getCostA().isEmpty()) {
++                playerOfferA.shrink(this.getCostA().getCount());
++            }
++            // CraftBukkit end
+             if (!this.getCostB().isEmpty()) {
+                 playerOfferB.shrink(this.getCostB().getCount());
+             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/item/trading/MerchantOffer.java.patch b/paper-server/patches/unapplied/net/minecraft/world/item/trading/MerchantOffer.java.patch
deleted file mode 100644
index bf956ac4ae..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/item/trading/MerchantOffer.java.patch
+++ /dev/null
@@ -1,90 +0,0 @@
---- a/net/minecraft/world/item/trading/MerchantOffer.java
-+++ b/net/minecraft/world/item/trading/MerchantOffer.java
-@@ -8,6 +8,8 @@
- import net.minecraft.util.Mth;
- import net.minecraft.world.item.ItemStack;
- 
-+import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; // CraftBukkit
-+
- public class MerchantOffer {
- 
-     public static final Codec<MerchantOffer> CODEC = RecordCodecBuilder.create((instance) -> {
-@@ -31,6 +33,10 @@
-             return merchantrecipe.priceMultiplier;
-         }), Codec.INT.lenientOptionalFieldOf("xp", 1).forGetter((merchantrecipe) -> {
-             return merchantrecipe.xp;
-+        // Paper start
-+        }), Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter((merchantrecipe) -> {
-+            return merchantrecipe.ignoreDiscounts;
-+        // Paper end
-         })).apply(instance, MerchantOffer::new);
-     });
-     public static final StreamCodec<RegistryFriendlyByteBuf, MerchantOffer> STREAM_CODEC = StreamCodec.of(MerchantOffer::writeToStream, MerchantOffer::createFromStream);
-@@ -44,8 +50,22 @@
-     public int demand;
-     public float priceMultiplier;
-     public int xp;
-+    public boolean ignoreDiscounts; // Paper - Add ignore discounts API
-+    // CraftBukkit start
-+    private CraftMerchantRecipe bukkitHandle;
- 
--    private MerchantOffer(ItemCost firstBuyItem, Optional<ItemCost> secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience) {
-+    public CraftMerchantRecipe asBukkit() {
-+        return (this.bukkitHandle == null) ? this.bukkitHandle = new CraftMerchantRecipe(this) : this.bukkitHandle;
-+    }
-+
-+    public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { // Paper
-+        this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand);
-+        this.ignoreDiscounts = ignoreDiscounts; // Paper
-+        this.bukkitHandle = bukkit;
-+    }
-+    // CraftBukkit end
-+
-+    private MerchantOffer(ItemCost firstBuyItem, Optional<ItemCost> secondBuyItem, ItemStack sellItem, int uses, int maxUses, boolean rewardingPlayerExperience, int specialPrice, int demandBonus, float priceMultiplier, int merchantExperience, final boolean ignoreDiscounts) { // Paper
-         this.baseCostA = firstBuyItem;
-         this.costB = secondBuyItem;
-         this.result = sellItem;
-@@ -56,6 +76,7 @@
-         this.demand = demandBonus;
-         this.priceMultiplier = priceMultiplier;
-         this.xp = merchantExperience;
-+        this.ignoreDiscounts = ignoreDiscounts; // Paper
-     }
- 
-     public MerchantOffer(ItemCost buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) {
-@@ -71,11 +92,11 @@
-     }
- 
-     public MerchantOffer(ItemCost firstBuyItem, Optional<ItemCost> secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) {
--        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience);
-+        this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, true, 0, demandBonus, priceMultiplier, merchantExperience, false); // Paper
-     }
- 
-     private MerchantOffer(MerchantOffer offer) {
--        this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp);
-+        this(offer.baseCostA, offer.costB, offer.result.copy(), offer.uses, offer.maxUses, offer.rewardExp, offer.specialPriceDiff, offer.demand, offer.priceMultiplier, offer.xp, offer.ignoreDiscounts); // Paper
-     }
- 
-     public ItemStack getBaseCostA() {
-@@ -110,7 +131,7 @@
-     }
- 
-     public void updateDemand() {
--        this.demand = this.demand + this.uses - (this.maxUses - this.uses);
-+        this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962
-     }
- 
-     public ItemStack assemble() {
-@@ -185,7 +206,11 @@
-         if (!this.satisfiedBy(firstBuyStack, secondBuyStack)) {
-             return false;
-         } else {
--            firstBuyStack.shrink(this.getCostA().getCount());
-+            // CraftBukkit start
-+            if (!this.getCostA().isEmpty()) {
-+                firstBuyStack.shrink(this.getCostA().getCount());
-+            }
-+            // CraftBukkit end
-             if (!this.getCostB().isEmpty()) {
-                 secondBuyStack.shrink(this.getCostB().getCount());
-             }

From 5b17ebbd30fc624edbba59369c2066390831adc2 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 23:57:57 +0100
Subject: [PATCH 125/285] net/minecraft/server/dedicated

---
 build-data/paper.at                           |   1 +
 .../dedicated/DedicatedPlayerList.java.patch  |   8 +-
 .../dedicated/DedicatedServer.java.patch      | 274 ++++++++----------
 .../DedicatedServerProperties.java.patch      |  65 +++++
 .../DedicatedServerSettings.java.patch        |  17 ++
 .../server/dedicated/Settings.java.patch      | 128 ++++++++
 .../DedicatedServerProperties.java.patch      | 106 -------
 .../DedicatedServerSettings.java.patch        |  27 --
 .../server/dedicated/Settings.java.patch      | 179 ------------
 9 files changed, 334 insertions(+), 471 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch (62%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/dedicated/DedicatedServer.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/dedicated/Settings.java.patch

diff --git a/build-data/paper.at b/build-data/paper.at
index 91c5e8792f..9b69a12479 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -680,6 +680,7 @@ public-f net.minecraft.server.MinecraftServer storageSource
 public-f net.minecraft.server.ReloadableServerResources commands
 public-f net.minecraft.server.dedicated.DedicatedServer serverLinks
 public-f net.minecraft.server.dedicated.DedicatedServer settings
+public-f net.minecraft.server.dedicated.DedicatedServerProperties pauseWhenEmptySeconds
 public-f net.minecraft.server.level.TicketType timeout
 public-f net.minecraft.server.players.PlayerList maxPlayers
 public-f net.minecraft.world.entity.LivingEntity combatTracker
diff --git a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch
rename to paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch
index 17d45a61ee..758bc8ca8d 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedPlayerList.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/server/dedicated/DedicatedPlayerList.java
 +++ b/net/minecraft/server/dedicated/DedicatedPlayerList.java
-@@ -18,6 +18,11 @@
-         this.setViewDistance(dedicatedServerProperties.viewDistance);
-         this.setSimulationDistance(dedicatedServerProperties.simulationDistance);
-         super.setUsingWhiteList(dedicatedServerProperties.whiteList.get());
+@@ -18,6 +_,11 @@
+         this.setViewDistance(properties.viewDistance);
+         this.setSimulationDistance(properties.simulationDistance);
+         super.setUsingWhiteList(properties.whiteList.get());
 +        // Paper start - fix converting txt to json file; moved from constructor
 +    }
 +    @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServer.java.patch
rename to paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 0780484bbb..2deca3f0de 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -1,28 +1,6 @@
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -54,20 +54,31 @@
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.GameType;
--import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.entity.SkullBlockEntity;
- import net.minecraft.world.level.storage.LevelStorageSource;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import net.minecraft.server.WorldLoader;
-+import org.apache.logging.log4j.Level;
-+import org.apache.logging.log4j.LogManager;
-+import org.apache.logging.log4j.io.IoBuilder;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.craftbukkit.util.TerminalCompletionHandler;
-+import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread;
-+import org.bukkit.event.server.ServerCommandEvent;
-+import org.bukkit.event.server.RemoteServerCommandEvent;
-+// CraftBukkit end
-+
- public class DedicatedServer extends MinecraftServer implements ServerInterface {
- 
+@@ -63,10 +_,10 @@
      static final Logger LOGGER = LogUtils.getLogger();
      private static final int CONVERSION_RETRY_DELAY_MS = 5000;
      private static final int CONVERSION_RETRIES = 2;
@@ -31,44 +9,47 @@
      @Nullable
      private QueryThreadGs4 queryThreadGs4;
 -    private final RconConsoleSource rconConsoleSource;
-+    // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field
++    // private final RconConsoleSource rconConsoleSource; // CraftBukkit - remove field
      @Nullable
      private RconThread rconThread;
      public DedicatedServerSettings settings;
-@@ -81,41 +92,117 @@
+@@ -80,19 +_,12 @@
      private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker;
      public ServerLinks serverLinks;
  
--    public DedicatedServer(Thread serverThread, LevelStorageSource.LevelStorageAccess session, PackRepository dataPackManager, WorldStem saveLoader, DedicatedServerSettings propertiesLoader, DataFixer dataFixer, Services apiServices, ChunkProgressListenerFactory worldGenerationProgressListenerFactory) {
--        super(serverThread, session, dataPackManager, saveLoader, Proxy.NO_PROXY, dataFixer, apiServices, worldGenerationProgressListenerFactory);
--        this.settings = propertiesLoader;
--        this.rconConsoleSource = new RconConsoleSource(this);
--        this.serverTextFilter = ServerTextFilter.createFromConfig(propertiesLoader.getProperties());
--        this.serverLinks = DedicatedServer.createServerLinks(propertiesLoader);
+-    public DedicatedServer(
+-        Thread serverThread,
+-        LevelStorageSource.LevelStorageAccess storageSource,
+-        PackRepository packRepository,
+-        WorldStem worldStem,
+-        DedicatedServerSettings settings,
+-        DataFixer fixerUpper,
+-        Services services,
+-        ChunkProgressListenerFactory progressListenerFactory
+-    ) {
+-        super(serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory);
 +    // CraftBukkit start - Signature changed
-+    public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
++    public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings settings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
 +        super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
 +        // CraftBukkit end
-+        this.settings = dedicatedserversettings;
-+        // this.rconConsoleSource = new RemoteControlCommandListener(this); // CraftBukkit - remove field
-+        this.serverTextFilter = ServerTextFilter.createFromConfig(dedicatedserversettings.getProperties());
-+        this.serverLinks = DedicatedServer.createServerLinks(dedicatedserversettings);
+         this.settings = settings;
+-        this.rconConsoleSource = new RconConsoleSource(this);
++        //this.rconConsoleSource = new RconConsoleSource(this); // CraftBukkit - remove field
+         this.serverTextFilter = ServerTextFilter.createFromConfig(settings.getProperties());
+         this.serverLinks = createServerLinks(settings);
      }
- 
-     @Override
-     public boolean initServer() throws IOException {
+@@ -102,26 +_,95 @@
          Thread thread = new Thread("Server console handler") {
+             @Override
              public void run() {
--                BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+-
 +                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.Main.useConsole) {
-+                    return;
-+                }
++                if (!org.bukkit.craftbukkit.Main.useConsole) return;
 +                // Paper start - Use TerminalConsoleAppender
 +                new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
 +                /*
 +                jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
- 
 +                // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
 +                try {
 +                    System.in.available();
@@ -76,22 +57,20 @@
 +                    return;
 +                }
 +                // CraftBukkit end
-+
-                 String s;
- 
+                 String string1;
                  try {
--                    while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (s = bufferedreader.readLine()) != null) {
--                        DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack());
+-                    while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (string1 = bufferedReader.readLine()) != null) {
+-                        DedicatedServer.this.handleConsoleInput(string1, DedicatedServer.this.createCommandSourceStack());
 +                    // CraftBukkit start - JLine disabling compatibility
 +                    while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) {
 +                        if (org.bukkit.craftbukkit.Main.useJline) {
-+                            s = bufferedreader.readLine(">", null);
++                            string1 = bufferedreader.readLine(">", null);
 +                        } else {
-+                            s = bufferedreader.readLine();
++                            string1 = bufferedreader.readLine();
 +                        }
 +
 +                        // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null
-+                        if (s == null) {
++                        if (string1 == null) {
 +                            try {
 +                                Thread.sleep(50L);
 +                            } catch (InterruptedException ex) {
@@ -99,20 +78,18 @@
 +                            }
 +                            continue;
 +                        }
-+                        if (s.trim().length() > 0) { // Trim to filter lines which are just spaces
++                        if (string1.trim().length() > 0) { // Trim to filter lines which are just spaces
 +                            DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener());
 +                        }
 +                        // CraftBukkit end
                      }
-                 } catch (IOException ioexception) {
-                     DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
+                 } catch (IOException var4) {
+                     DedicatedServer.LOGGER.error("Exception handling console input", (Throwable)var4);
                  }
- 
 +                */
 +                // Paper end
              }
          };
- 
 +        // CraftBukkit start - TODO: handle command-line logging arguments
 +        java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
 +        global.setUseParentHandlers(false);
@@ -122,7 +99,7 @@
 +        global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
 +
 +        // Paper start - Not needed with TerminalConsoleAppender
-+        final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger();
++        final org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getRootLogger();
 +        /*
 +        final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
 +        for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
@@ -137,17 +114,16 @@
 +        */
 +        // Paper end - Not needed with TerminalConsoleAppender
 +
-+        System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream());
-+        System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream());
++        System.setOut(org.apache.logging.log4j.io.IoBuilder.forLogger(logger).setLevel(org.apache.logging.log4j.Level.INFO).buildPrintStream());
++        System.setErr(org.apache.logging.log4j.io.IoBuilder.forLogger(logger).setLevel(org.apache.logging.log4j.Level.WARN).buildPrintStream());
 +        // CraftBukkit end
-+
          thread.setDaemon(true);
-         thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
+         thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
 -        thread.start();
 +        // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down
-         DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
+         LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
          if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
-             DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
+             LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
          }
  
 +        // Paper start - detect running as root
@@ -160,13 +136,13 @@
 +        }
 +        // Paper end - detect running as root
 +
-         DedicatedServer.LOGGER.info("Loading properties");
-         DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties();
- 
-@@ -126,14 +213,51 @@
-             this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
-             this.setLocalIp(dedicatedserverproperties.serverIp);
+         LOGGER.info("Loading properties");
+         DedicatedServerProperties properties = this.settings.getProperties();
+         if (this.isSingleplayer()) {
+@@ -132,13 +_,51 @@
+             this.setLocalIp(properties.serverIp);
          }
+ 
 +        // Spigot start
 +        this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
 +        org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
@@ -190,15 +166,15 @@
 +        this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
 +        com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
 +        com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
- 
-         this.setPvpAllowed(dedicatedserverproperties.pvp);
-         this.setFlightAllowed(dedicatedserverproperties.allowFlight);
-         this.setMotd(dedicatedserverproperties.motd);
-         super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get());
-         this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
--        this.worldData.setGameType(dedicatedserverproperties.gamemode);
-+        // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
-         DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
++
+         this.setPvpAllowed(properties.pvp);
+         this.setFlightAllowed(properties.allowFlight);
+         this.setMotd(properties.motd);
+         super.setPlayerIdleTimeout(properties.playerIdleTimeout.get());
+         this.setEnforceWhitelist(properties.enforceWhitelist);
+-        this.worldData.setGameType(properties.gamemode);
++        // this.worldData.setGameType(properties.gamemode); // CraftBukkit - moved to world loading
+         LOGGER.info("Default game type: {}", properties.gamemode);
 +        // Paper start - Unix domain socket support
 +        java.net.SocketAddress bindAddress;
 +        if (this.getLocalIp().startsWith("unix:")) {
@@ -213,28 +189,28 @@
 +            }
 +            bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length()));
 +        } else {
-         InetAddress inetaddress = null;
- 
+         InetAddress inetAddress = null;
          if (!this.getLocalIp().isEmpty()) {
-@@ -143,34 +267,55 @@
+             inetAddress = InetAddress.getByName(this.getLocalIp());
+@@ -147,36 +_,62 @@
          if (this.getPort() < 0) {
-             this.setPort(dedicatedserverproperties.serverPort);
+             this.setPort(properties.serverPort);
          }
-+        bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort());
++        bindAddress = new java.net.InetSocketAddress(inetAddress, this.getPort());
 +        }
 +        // Paper end - Unix domain socket support
  
          this.initializeKeyPair();
-         DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
+         LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
  
          try {
--            this.getConnection().startTcpServerListener(inetaddress, this.getPort());
+-            this.getConnection().startTcpServerListener(inetAddress, this.getPort());
 +            this.getConnection().bind(bindAddress); // Paper - Unix domain socket support
-         } catch (IOException ioexception) {
-             DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!");
-             DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString());
-             DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
-+            if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error
+         } catch (IOException var10) {
+             LOGGER.warn("**** FAILED TO BIND TO PORT!");
+             LOGGER.warn("The exception was: {}", var10.toString());
+             LOGGER.warn("Perhaps a server is already running on that port?");
++            if (true) throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error
              return false;
          }
  
@@ -250,25 +226,31 @@
 +        String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/";
 +        // Paper end - Add Velocity IP Forwarding Support
          if (!this.usesAuthentication()) {
-             DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
-             DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
--            DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
+             LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
+             LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
+-            LOGGER.warn(
+-                "While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."
+-            );
 +            // Spigot start
 +            // Paper start - Add Velocity IP Forwarding Support
 +            if (usingProxy) {
 +                DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose.");
 +                DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information.");
-+            // Paper end - Add Velocity IP Forwarding Support
++                // Paper end - Add Velocity IP Forwarding Support
 +            } else {
 +                DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
 +            }
 +            // Spigot end
-             DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
+             LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
          }
  
--        if (this.convertOldUsers()) {
--            this.getProfileCache().save();
--        }
++        // CraftBukkit start
++        /*
+         if (this.convertOldUsers()) {
+             this.getProfileCache().save();
+         }
++        */
++        // CraftBukkit end
  
          if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
              return false;
@@ -276,34 +258,33 @@
 -            this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
 +            // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // CraftBukkit - moved up
              this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
-             this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
-             long i = Util.getNanos();
-@@ -178,13 +323,13 @@
+             this.tickTimeLogger = new RemoteSampleLogger(
+                 TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME
+@@ -185,12 +_,12 @@
              SkullBlockEntity.setup(this.services, this);
              GameProfileCache.setUsesAuthentication(this.usesAuthentication());
-             DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
+             LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
 -            this.loadLevel();
 +            this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
-             long j = Util.getNanos() - i;
-             String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
- 
-             DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
-             if (dedicatedserverproperties.announcePlayerAchievements != null) {
--                ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this);
-+                ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
+             long l = Util.getNanos() - nanos;
+             String string = String.format(Locale.ROOT, "%.3fs", l / 1.0E9);
+             LOGGER.info("Done ({})! For help, type \"help\"", string);
+             if (properties.announcePlayerAchievements != null) {
+-                this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this);
++                this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
              }
  
-             if (dedicatedserverproperties.enableQuery) {
-@@ -197,7 +342,7 @@
+             if (properties.enableQuery) {
+@@ -203,7 +_,7 @@
                  this.rconThread = RconThread.create(this);
              }
  
 -            if (this.getMaxTickLength() > 0L) {
-+            if (false && this.getMaxTickLength() > 0L) {  // Spigot - disable
++            if (false && this.getMaxTickLength() > 0L) { // Spigot - disable
                  Thread thread1 = new Thread(new ServerWatchdog(this));
- 
-                 thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
-@@ -215,6 +360,12 @@
+                 thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER));
+                 thread1.setName("Server Watchdog");
+@@ -220,6 +_,12 @@
          }
      }
  
@@ -316,7 +297,7 @@
      @Override
      public boolean isSpawningMonsters() {
          return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters();
-@@ -227,7 +378,7 @@
+@@ -232,7 +_,7 @@
  
      @Override
      public void forceDifficulty() {
@@ -325,7 +306,7 @@
      }
  
      @Override
-@@ -286,13 +437,14 @@
+@@ -271,12 +_,14 @@
          }
  
          if (this.rconThread != null) {
@@ -337,37 +318,29 @@
 -            this.queryThreadGs4.stop();
 +            // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
          }
- 
++
 +        System.exit(0); // CraftBukkit
      }
  
      @Override
-@@ -302,19 +454,29 @@
+@@ -291,13 +_,23 @@
      }
  
-     @Override
--    public boolean isLevelEnabled(Level world) {
--        return world.dimension() == Level.NETHER ? this.getProperties().allowNether : true;
-+    public boolean isLevelEnabled(net.minecraft.world.level.Level world) {
-+        return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
-     }
- 
-     public void handleConsoleInput(String command, CommandSourceStack commandSource) {
--        this.consoleInput.add(new ConsoleInput(command, commandSource));
-+        this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue
+     public void handleConsoleInput(String msg, CommandSourceStack source) {
+-        this.consoleInput.add(new ConsoleInput(msg, source));
++        this.serverCommandQueue.add(new ConsoleInput(msg, source)); // Paper - Perf: use proper queue
      }
  
      public void handleConsoleInputs() {
 -        while (!this.consoleInput.isEmpty()) {
--            ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0);
+-            ConsoleInput consoleInput = this.consoleInput.remove(0);
+-            this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg);
 +        // Paper start - Perf: use proper queue
 +        ConsoleInput servercommand;
 +        while ((servercommand = this.serverCommandQueue.poll()) != null) {
 +            // Paper end - Perf: use proper queue
- 
--            this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg);
 +            // CraftBukkit start - ServerCommand for preprocessing
-+            ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg);
++            org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(this.console, servercommand.msg);
 +            this.server.getPluginManager().callEvent(event);
 +            if (event.isCancelled()) continue;
 +            servercommand = new ConsoleInput(event.getCommand(), servercommand.source);
@@ -376,31 +349,22 @@
 +            this.server.dispatchServerCommand(this.console, servercommand);
 +            // CraftBukkit end
          }
- 
      }
-@@ -383,7 +545,7 @@
  
+@@ -430,7 +_,11 @@
      @Override
-     public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
--        if (world.dimension() != Level.OVERWORLD) {
-+        if (world.dimension() != net.minecraft.world.level.Level.OVERWORLD) {
-             return false;
-         } else if (this.getPlayerList().getOps().isEmpty()) {
-             return false;
-@@ -453,7 +615,11 @@
      public boolean enforceSecureProfile() {
-         DedicatedServerProperties dedicatedserverproperties = this.getProperties();
- 
--        return dedicatedserverproperties.enforceSecureProfile && dedicatedserverproperties.onlineMode && this.services.canValidateProfileKeys();
+         DedicatedServerProperties properties = this.getProperties();
+-        return properties.enforceSecureProfile && properties.onlineMode && this.services.canValidateProfileKeys();
 +        // Paper start - Add setting for proxy online mode status
-+        return dedicatedserverproperties.enforceSecureProfile
++        return properties.enforceSecureProfile
 +            && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
 +            && this.services.canValidateProfileKeys();
 +        // Paper end - Add setting for proxy online mode status
      }
  
      @Override
-@@ -541,16 +707,52 @@
+@@ -515,14 +_,52 @@
  
      @Override
      public String getPluginNames() {
@@ -434,30 +398,30 @@
      @Override
      public String runCommand(String command) {
 -        this.rconConsoleSource.prepareForCommand();
+-        this.executeBlocking(() -> this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command));
+-        return this.rconConsoleSource.getCommandResponse();
 +        // CraftBukkit start - fire RemoteServerCommandEvent
 +        throw new UnsupportedOperationException("Not supported - remote source required.");
 +    }
 +
 +    public String runCommand(RconConsoleSource rconConsoleSource, String s) {
 +        rconConsoleSource.prepareForCommand();
-         this.executeBlocking(() -> {
--            this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command);
++        this.executeBlocking(() -> {
 +            CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
-+            RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
++            org.bukkit.event.server.RemoteServerCommandEvent event = new org.bukkit.event.server.RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
 +            this.server.getPluginManager().callEvent(event);
 +            if (event.isCancelled()) {
 +                return;
 +            }
 +            ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
 +            this.server.dispatchServerCommand(event.getSender(), serverCommand);
-         });
--        return this.rconConsoleSource.getCommandResponse();
++        });
 +        return rconConsoleSource.getCommandResponse();
 +        // CraftBukkit end
      }
  
-     public void storeUsingWhiteList(boolean useWhitelist) {
-@@ -660,4 +862,15 @@
+     public void storeUsingWhiteList(boolean isStoreUsingWhiteList) {
+@@ -626,4 +_,15 @@
              }
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
new file mode 100644
index 0000000000..9e44f8227b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
+@@ -45,6 +_,7 @@
+     static final Logger LOGGER = LogUtils.getLogger();
+     private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
+     private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults();
++    public final boolean debug = this.get("debug", false); // CraftBukkit
+     public final boolean onlineMode = this.get("online-mode", true);
+     public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
+     public final String serverIp = this.get("server-ip", "");
+@@ -85,7 +_,7 @@
+     public final boolean broadcastRconToOps = this.get("broadcast-rcon-to-ops", true);
+     public final boolean broadcastConsoleToOps = this.get("broadcast-console-to-ops", true);
+     public final int maxWorldSize = this.get("max-world-size", property -> Mth.clamp(property, 1, 29999984), 29999984);
+-    public final boolean syncChunkWrites = this.get("sync-chunk-writes", true);
++    public final boolean syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag
+     public final String regionFileComression = this.get("region-file-compression", "deflate");
+     public final boolean enableJmxMonitoring = this.get("enable-jmx-monitoring", false);
+     public final boolean enableStatus = this.get("enable-status", true);
+@@ -99,13 +_,16 @@
+     public final Settings<DedicatedServerProperties>.MutableValue<Boolean> whiteList = this.getMutable("white-list", false);
+     public final boolean enforceSecureProfile = this.get("enforce-secure-profile", true);
+     public final boolean logIPs = this.get("log-ips", true);
+-    public int pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", 60);
++    public int pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", -1); // Paper - disable tick sleeping by default 
+     private final DedicatedServerProperties.WorldDimensionData worldDimensionData;
+     public final WorldOptions worldOptions;
+     public boolean acceptsTransfers = this.get("accepts-transfers", false);
++    public final String rconIp; // Paper - Configurable rcon ip
+ 
+-    public DedicatedServerProperties(Properties properties) {
+-        super(properties);
++    // CraftBukkit start
++    public DedicatedServerProperties(Properties properties, joptsimple.OptionSet optionset) {
++        super(properties, optionset);
++        // CraftBukkit end
+         String string = this.get("level-seed", "");
+         boolean flag = this.get("generate-structures", true);
+         long l = WorldOptions.parseSeed(string).orElse(WorldOptions.randomSeed());
+@@ -126,15 +_,21 @@
+             this.get("initial-enabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getEnabled())),
+             this.get("initial-disabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getDisabled()))
+         );
++        // Paper start - Configurable rcon ip
++        final String rconIp = this.getStringRaw("rcon.ip");
++        this.rconIp = rconIp == null ? this.serverIp : rconIp;
++        // Paper end - Configurable rcon ip
+     }
+ 
+-    public static DedicatedServerProperties fromFile(Path path) {
+-        return new DedicatedServerProperties(loadFromFile(path));
++    // CraftBukkit start
++    public static DedicatedServerProperties fromFile(Path path, joptsimple.OptionSet optionset) {
++        return new DedicatedServerProperties(loadFromFile(path), optionset);
+     }
+ 
+     @Override
+-    protected DedicatedServerProperties reload(RegistryAccess registryAccess, Properties properties) {
+-        return new DedicatedServerProperties(properties);
++    public DedicatedServerProperties reload(RegistryAccess registryAccess, Properties properties, joptsimple.OptionSet options) {
++        return new DedicatedServerProperties(properties, options);
++        // CraftBukkit end
+     }
+ 
+     @Nullable
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
new file mode 100644
index 0000000000..acd4a0443e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerSettings.java
++++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java
+@@ -7,9 +_,11 @@
+     private final Path source;
+     private DedicatedServerProperties properties;
+ 
+-    public DedicatedServerSettings(Path source) {
+-        this.source = source;
+-        this.properties = DedicatedServerProperties.fromFile(source);
++    // CraftBukkit start
++    public DedicatedServerSettings(joptsimple.OptionSet optionset) {
++        this.source = ((java.io.File) optionset.valueOf("config")).toPath();
++        this.properties = DedicatedServerProperties.fromFile(this.source, optionset);
++        // CraftBukkit end
+     }
+ 
+     public DedicatedServerProperties getProperties() {
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
new file mode 100644
index 0000000000..33fd250afb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/server/dedicated/Settings.java
++++ b/net/minecraft/server/dedicated/Settings.java
+@@ -26,12 +_,26 @@
+ public abstract class Settings<T extends Settings<T>> {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     public final Properties properties;
++    private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments
++    // CraftBukkit start
++    private joptsimple.OptionSet options = null;
+ 
+-    public Settings(Properties properties) {
++    public Settings(Properties properties, final joptsimple.OptionSet options) {
+         this.properties = properties;
++        this.options = options;
++    }
++
++    private String getOverride(String name, String value) {
++        if ((this.options != null) && (this.options.has(name))) {
++            return String.valueOf(this.options.valueOf(name));
++        }
++
++        return value;
++        // CraftBukkit end
+     }
+ 
+     public static Properties loadFromFile(Path path) {
++        if (!path.toFile().exists()) return new Properties(); // CraftBukkit - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
+         try {
+             try {
+                 Properties var13;
+@@ -65,7 +_,53 @@
+     }
+ 
+     public void store(Path path) {
+-        try (Writer bufferedWriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
++        try /*(Writer bufferedWriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8))*/ { // Paper
++            // CraftBukkit start - Don't attempt writing to file if it's read only
++            if (path.toFile().exists() && !path.toFile().canWrite()) {
++                Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper - log message file is read-only
++                return;
++            }
++            // CraftBukkit end
++            // Paper start - allow skipping server.properties comments
++            java.io.OutputStream outputstream = Files.newOutputStream(path);
++            java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) {
++                private boolean isRightAfterNewline = true; // If last written char was newline
++                private boolean isComment = false; // Are we writing comment currently?
++
++                @Override
++                public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException {
++                    this.write(b, 0, b.length);
++                }
++
++                @Override
++                public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException {
++                    int latest_offset = off; // The latest offset, updated when comment ends
++                    for (int index = off; index < off + len; ++index ) {
++                        byte c = bbuf[index];
++                        boolean isNewline = (c == '\n' || c == '\r');
++                        if (isNewline && this.isComment) {
++                            // Comment has ended
++                            this.isComment = false;
++                            latest_offset = index+1;
++                        }
++                        if (c == '#' && this.isRightAfterNewline) {
++                            this.isComment = true;
++                            if (index != latest_offset) {
++                                // We got some non-comment data earlier
++                                super.write(bbuf, latest_offset, index-latest_offset);
++                            }
++                        }
++                        this.isRightAfterNewline = isNewline; // Store for next iteration
++
++                    }
++                    if (latest_offset < off+len && !this.isComment) {
++                        // We have some unwritten data, that isn't part of a comment
++                        super.write(bbuf, latest_offset, (off + len) - latest_offset);
++                    }
++                }
++            };
++            java.io.BufferedWriter bufferedWriter = new java.io.BufferedWriter(new java.io.OutputStreamWriter(bufferedOutputStream, java.nio.charset.StandardCharsets.UTF_8.newEncoder()));
++            // Paper end - allow skipping server.properties comments
+             this.properties.store(bufferedWriter, "Minecraft server properties");
+         } catch (IOException var7) {
+             LOGGER.error("Failed to store properties to file: {}", path);
+@@ -94,7 +_,7 @@
+ 
+     @Nullable
+     public String getStringRaw(String key) {
+-        return (String)this.properties.get(key);
++        return (String)this.getOverride(key, this.properties.getProperty(key)); // CraftBukkit
+     }
+ 
+     @Nullable
+@@ -109,6 +_,15 @@
+     }
+ 
+     protected <V> V get(String key, Function<String, V> serializer, Function<V, String> deserializer, V defaultValue) {
++        // CraftBukkit start
++        try {
++            return this.get0(key, serializer, deserializer, defaultValue);
++        } catch (Exception ex) {
++            throw new RuntimeException("Could not load invalidly configured property '" + key + "'", ex);
++        }
++    }
++    private <V> V get0(String key, Function<String, V> serializer, Function<V, String> deserializer, V defaultValue) {
++        // CraftBukkit end
+         String stringRaw = this.getStringRaw(key);
+         V object = MoreObjects.firstNonNull(stringRaw != null ? serializer.apply(stringRaw) : null, defaultValue);
+         this.properties.put(key, deserializer.apply(object));
+@@ -181,7 +_,7 @@
+         return map;
+     }
+ 
+-    protected abstract T reload(RegistryAccess registryAccess, Properties properties);
++    protected abstract T reload(RegistryAccess registryAccess, Properties properties, joptsimple.OptionSet optionset); // CraftBukkit
+ 
+     public class MutableValue<V> implements Supplier<V> {
+         private final String key;
+@@ -202,7 +_,7 @@
+         public T update(RegistryAccess registryAccess, V newValue) {
+             Properties map = Settings.this.cloneProperties();
+             map.put(this.key, this.serializer.apply(newValue));
+-            return Settings.this.reload(registryAccess, map);
++            return Settings.this.reload(registryAccess, properties, Settings.this.options); // CraftBukkit
+         }
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
deleted file mode 100644
index 2020814765..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
+++ /dev/null
@@ -1,106 +0,0 @@
---- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
-+++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
-@@ -43,11 +43,16 @@
- import net.minecraft.world.level.levelgen.presets.WorldPresets;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import joptsimple.OptionSet;
-+// CraftBukkit end
-+
- public class DedicatedServerProperties extends Settings<DedicatedServerProperties> {
- 
-     static final Logger LOGGER = LogUtils.getLogger();
-     private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
-     private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults();
-+    public final boolean debug = this.get("debug", false); // CraftBukkit
-     public final boolean onlineMode = this.get("online-mode", true);
-     public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
-     public final String serverIp = this.get("server-ip", "");
-@@ -100,13 +105,17 @@
-     public final Settings<DedicatedServerProperties>.MutableValue<Boolean> whiteList;
-     public final boolean enforceSecureProfile;
-     public final boolean logIPs;
--    public final int pauseWhenEmptySeconds;
-+    public int pauseWhenEmptySeconds;
-     private final DedicatedServerProperties.WorldDimensionData worldDimensionData;
-     public final WorldOptions worldOptions;
-     public boolean acceptsTransfers;
- 
--    public DedicatedServerProperties(Properties properties) {
--        super(properties);
-+    public final String rconIp; // Paper - Configurable rcon ip
-+
-+    // CraftBukkit start
-+    public DedicatedServerProperties(Properties properties, OptionSet optionset) {
-+        super(properties, optionset);
-+        // CraftBukkit end
-         this.difficulty = (Difficulty) this.get("difficulty", dispatchNumberOrString(Difficulty::byId, Difficulty::byName), Difficulty::getKey, Difficulty.EASY);
-         this.gamemode = (GameType) this.get("gamemode", dispatchNumberOrString(GameType::byId, GameType::byName), GameType::getName, GameType.SURVIVAL);
-         this.levelName = this.get("level-name", "world");
-@@ -137,7 +146,7 @@
-         this.maxWorldSize = this.get("max-world-size", (integer) -> {
-             return Mth.clamp(integer, 1, 29999984);
-         }, 29999984);
--        this.syncChunkWrites = this.get("sync-chunk-writes", true);
-+        this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - Hide sync chunk writes behind flag
-         this.regionFileComression = this.get("region-file-compression", "deflate");
-         this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false);
-         this.enableStatus = this.get("enable-status", true);
-@@ -151,7 +160,7 @@
-         this.whiteList = this.getMutable("white-list", false);
-         this.enforceSecureProfile = this.get("enforce-secure-profile", true);
-         this.logIPs = this.get("log-ips", true);
--        this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", 60);
-+        this.pauseWhenEmptySeconds = this.get("pause-when-empty-seconds", -1); // Paper - disable tick sleeping by default
-         this.acceptsTransfers = this.get("accepts-transfers", false);
-         String s = this.get("level-seed", "");
-         boolean flag = this.get("generate-structures", true);
-@@ -165,15 +174,21 @@
-         }, WorldPresets.NORMAL.location().toString()));
-         this.serverResourcePackInfo = DedicatedServerProperties.getServerPackInfo(this.get("resource-pack-id", ""), this.get("resource-pack", ""), this.get("resource-pack-sha1", ""), this.getLegacyString("resource-pack-hash"), this.get("require-resource-pack", false), this.get("resource-pack-prompt", ""));
-         this.initialDataPackConfiguration = DedicatedServerProperties.getDatapackConfig(this.get("initial-enabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getEnabled())), this.get("initial-disabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getDisabled())));
-+        // Paper start - Configurable rcon ip
-+        final String rconIp = this.getStringRaw("rcon.ip");
-+        this.rconIp = rconIp == null ? this.serverIp : rconIp;
-+        // Paper end - Configurable rcon ip
-     }
- 
--    public static DedicatedServerProperties fromFile(Path path) {
--        return new DedicatedServerProperties(loadFromFile(path));
-+    // CraftBukkit start
-+    public static DedicatedServerProperties fromFile(Path path, OptionSet optionset) {
-+        return new DedicatedServerProperties(loadFromFile(path), optionset);
-     }
- 
-     @Override
--    protected DedicatedServerProperties reload(RegistryAccess registryManager, Properties properties) {
--        return new DedicatedServerProperties(properties);
-+    public DedicatedServerProperties reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset) {
-+        return new DedicatedServerProperties(properties, optionset);
-+        // CraftBukkit end
-     }
- 
-     @Nullable
-@@ -254,10 +269,10 @@
-             }).orElseThrow(() -> {
-                 return new IllegalStateException("Invalid datapack contents: can't find default preset");
-             });
--            Optional optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> {
-+            Optional<ResourceKey<WorldPreset>> optional = Optional.ofNullable(ResourceLocation.tryParse(this.levelType)).map((minecraftkey) -> { // CraftBukkit - decompile error
-                 return ResourceKey.create(Registries.WORLD_PRESET, minecraftkey);
-             }).or(() -> {
--                return Optional.ofNullable((ResourceKey) DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType));
-+                return Optional.ofNullable(DedicatedServerProperties.WorldDimensionData.LEGACY_PRESET_NAMES.get(this.levelType)); // CraftBukkit - decompile error
-             });
- 
-             Objects.requireNonNull(holderlookup);
-@@ -269,7 +284,7 @@
- 
-             if (holder.is(WorldPresets.FLAT)) {
-                 RegistryOps<JsonElement> registryops = registries.createSerializationContext(JsonOps.INSTANCE);
--                DataResult dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings()));
-+                DataResult<FlatLevelGeneratorSettings> dataresult = FlatLevelGeneratorSettings.CODEC.parse(new Dynamic(registryops, this.generatorSettings())); // CraftBukkit - decompile error
-                 Logger logger = DedicatedServerProperties.LOGGER;
- 
-                 Objects.requireNonNull(logger);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch b/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
deleted file mode 100644
index 0aaaffd34b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- a/net/minecraft/server/dedicated/DedicatedServerSettings.java
-+++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java
-@@ -3,14 +3,21 @@
- import java.nio.file.Path;
- import java.util.function.UnaryOperator;
- 
-+// CraftBukkit start
-+import java.io.File;
-+import joptsimple.OptionSet;
-+// CraftBukkit end
-+
- public class DedicatedServerSettings {
- 
-     private final Path source;
-     private DedicatedServerProperties properties;
- 
--    public DedicatedServerSettings(Path path) {
--        this.source = path;
--        this.properties = DedicatedServerProperties.fromFile(path);
-+    // CraftBukkit start
-+    public DedicatedServerSettings(OptionSet optionset) {
-+        this.source = ((File) optionset.valueOf("config")).toPath();
-+        this.properties = DedicatedServerProperties.fromFile(this.source, optionset);
-+        // CraftBukkit end
-     }
- 
-     public DedicatedServerProperties getProperties() {
diff --git a/paper-server/patches/unapplied/net/minecraft/server/dedicated/Settings.java.patch b/paper-server/patches/unapplied/net/minecraft/server/dedicated/Settings.java.patch
deleted file mode 100644
index 300eeaa029..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/dedicated/Settings.java.patch
+++ /dev/null
@@ -1,179 +0,0 @@
---- a/net/minecraft/server/dedicated/Settings.java
-+++ b/net/minecraft/server/dedicated/Settings.java
-@@ -20,20 +20,41 @@
- import java.util.function.Supplier;
- import java.util.function.UnaryOperator;
- import javax.annotation.Nullable;
--import net.minecraft.core.RegistryAccess;
- import org.slf4j.Logger;
- 
-+import joptsimple.OptionSet; // CraftBukkit
-+import net.minecraft.core.RegistryAccess;
-+
- public abstract class Settings<T extends Settings<T>> {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-     public final Properties properties;
-+    private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments
-+    // CraftBukkit start
-+    private OptionSet options = null;
- 
--    public Settings(Properties properties) {
-+    public Settings(Properties properties, final OptionSet options) {
-         this.properties = properties;
-+
-+        this.options = options;
-     }
- 
-+    private String getOverride(String name, String value) {
-+        if ((this.options != null) && (this.options.has(name))) {
-+            return String.valueOf(this.options.valueOf(name));
-+        }
-+
-+        return value;
-+        // CraftBukkit end
-+    }
-+
-     public static Properties loadFromFile(Path path) {
-         try {
-+            // CraftBukkit start - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
-+            if (!path.toFile().exists()) {
-+                return new Properties();
-+            }
-+            // CraftBukkit end
-             Properties properties;
-             Properties properties1;
- 
-@@ -97,8 +118,53 @@
- 
-     public void store(Path path) {
-         try {
--            BufferedWriter bufferedwriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8);
-+            // CraftBukkit start - Don't attempt writing to file if it's read only
-+            if (path.toFile().exists() && !path.toFile().canWrite()) {
-+                Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper - log message file is read-only
-+                return;
-+            }
-+            // CraftBukkit end
-+            // Paper start - allow skipping server.properties comments
-+            java.io.OutputStream outputstream = Files.newOutputStream(path);
-+            java.io.BufferedOutputStream bufferedOutputStream =  !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) {
-+                private boolean isRightAfterNewline = true; // If last written char was newline
-+                private boolean isComment = false; // Are we writing comment currently?
-+
-+                @Override
-+                public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException {
-+                    this.write(b, 0, b.length);
-+                }
-+
-+                @Override
-+                public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException {
-+                    int latest_offset = off; // The latest offset, updated when comment ends
-+                    for (int index = off; index < off + len; ++index ) {
-+                        byte c = bbuf[index];
-+                        boolean isNewline = (c == '\n' || c == '\r');
-+                        if (isNewline && this.isComment) {
-+                            // Comment has ended
-+                            this.isComment = false;
-+                            latest_offset = index+1;
-+                        }
-+                        if (c == '#' && this.isRightAfterNewline) {
-+                            this.isComment = true;
-+                            if (index != latest_offset) {
-+                                // We got some non-comment data earlier
-+                                super.write(bbuf, latest_offset, index-latest_offset);
-+                            }
-+                        }
-+                        this.isRightAfterNewline = isNewline; // Store for next iteration
- 
-+                    }
-+                    if (latest_offset < off+len && !this.isComment) {
-+                        // We have some unwritten data, that isn't part of a comment
-+                        super.write(bbuf, latest_offset, (off + len) - latest_offset);
-+                    }
-+                }
-+            };
-+            BufferedWriter bufferedwriter = new BufferedWriter(new java.io.OutputStreamWriter(bufferedOutputStream, java.nio.charset.StandardCharsets.UTF_8.newEncoder()));
-+            // Paper end - allow skipping server.properties comments
-+
-             try {
-                 this.properties.store(bufferedwriter, "Minecraft server properties");
-             } catch (Throwable throwable) {
-@@ -125,7 +191,7 @@
-     private static <V extends Number> Function<String, V> wrapNumberDeserializer(Function<String, V> parser) {
-         return (s) -> {
-             try {
--                return (Number) parser.apply(s);
-+                return (V) parser.apply(s); // CraftBukkit - decompile error
-             } catch (NumberFormatException numberformatexception) {
-                 return null;
-             }
-@@ -144,7 +210,7 @@
- 
-     @Nullable
-     public String getStringRaw(String key) {
--        return (String) this.properties.get(key);
-+        return (String) this.getOverride(key, this.properties.getProperty(key)); // CraftBukkit
-     }
- 
-     @Nullable
-@@ -160,10 +226,20 @@
-     }
- 
-     protected <V> V get(String key, Function<String, V> parser, Function<V, String> stringifier, V fallback) {
--        String s1 = this.getStringRaw(key);
--        V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
-+        // CraftBukkit start
-+        try {
-+            return this.get0(key, parser, stringifier, fallback);
-+        } catch (Exception ex) {
-+            throw new RuntimeException("Could not load invalidly configured property '" + key + "'", ex);
-+        }
-+    }
- 
--        this.properties.put(key, stringifier.apply(v1));
-+    private <V> V get0(String s, Function<String, V> function, Function<V, String> function1, V v0) {
-+        // CraftBukkit end
-+        String s1 = this.getStringRaw(s);
-+        V v1 = MoreObjects.firstNonNull(s1 != null ? function.apply(s1) : null, v0);
-+
-+        this.properties.put(s, function1.apply(v1));
-         return v1;
-     }
- 
-@@ -172,7 +248,7 @@
-         V v1 = MoreObjects.firstNonNull(s1 != null ? parser.apply(s1) : null, fallback);
- 
-         this.properties.put(key, stringifier.apply(v1));
--        return new Settings.MutableValue<>(key, v1, stringifier);
-+        return new Settings.MutableValue(key, v1, stringifier); // CraftBukkit - decompile error
-     }
- 
-     protected <V> V get(String key, Function<String, V> parser, UnaryOperator<V> parsedTransformer, Function<V, String> stringifier, V fallback) {
-@@ -236,7 +312,7 @@
-         return properties;
-     }
- 
--    protected abstract T reload(RegistryAccess registryManager, Properties properties);
-+    protected abstract T reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset); // CraftBukkit
- 
-     public class MutableValue<V> implements Supplier<V> {
- 
-@@ -244,7 +320,7 @@
-         private final V value;
-         private final Function<V, String> serializer;
- 
--        MutableValue(final String s, final Object object, final Function function) {
-+        MutableValue(final String s, final V object, final Function function) { // CraftBukkit - decompile error
-             this.key = s;
-             this.value = object;
-             this.serializer = function;
-@@ -258,7 +334,7 @@
-             Properties properties = Settings.this.cloneProperties();
- 
-             properties.put(this.key, this.serializer.apply(value));
--            return Settings.this.reload(registryManager, properties);
-+            return Settings.this.reload(registryManager, properties, Settings.this.options); // CraftBukkit
-         }
-     }
- }

From c9fd2a7ad53bbd4ce51016a7badca00529022e20 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sat, 14 Dec 2024 23:58:03 +0100
Subject: [PATCH 126/285] cleanup wrong at

---
 .../level/dimension/end/EndDragonFight.java.patch     | 11 -----------
 1 file changed, 11 deletions(-)
 delete mode 100644 paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
deleted file mode 100644
index e6ba6d525f..0000000000
--- a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
-+++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
-@@ -92,7 +_,7 @@
-     @Nullable
-     public BlockPos portalLocation;
-     @Nullable
--    public DragonRespawnAnimation respawnStage;
-+    public DragonRespawnAnimation respawnStage;// Paper-At: public net.minecraft.world.level.dimension.end.EndDragonFight respawnStage
-     private int respawnTime;
-     @Nullable
-     public List<EndCrystal> respawnCrystals;

From d1369750e249c152ad8cca893386d5aab697748d Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 15:00:16 -0800
Subject: [PATCH 127/285] net.minecraft.world.level.levelgen

---
 .../levelgen/DensityFunctions.java.patch      | 48 +++++-----
 .../level/levelgen/FlatLevelSource.java.patch | 28 ++++++
 .../NoiseBasedChunkGenerator.java.patch       |  2 +-
 .../level/levelgen/PatrolSpawner.java.patch   | 91 +++++++++++++++++++
 .../level/levelgen/PhantomSpawner.java.patch  | 67 ++++++++++++++
 .../level/levelgen/FlatLevelSource.java.patch | 38 --------
 .../level/levelgen/PatrolSpawner.java.patch   | 79 ----------------
 .../level/levelgen/PhantomSpawner.java.patch  | 68 --------------
 8 files changed, 211 insertions(+), 210 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/levelgen/DensityFunctions.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch (94%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/DensityFunctions.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
index 3f778b5424..96d7029c39 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/levelgen/DensityFunctions.java
 +++ b/net/minecraft/world/level/levelgen/DensityFunctions.java
-@@ -509,6 +509,16 @@
+@@ -506,6 +_,16 @@
          );
          private static final float ISLAND_THRESHOLD = -0.9F;
          private final SimplexNoise islandNoise;
@@ -17,36 +17,36 @@
  
          public EndIslandDensityFunction(long seed) {
              RandomSource randomSource = new LegacyRandomSource(seed);
-@@ -521,15 +531,29 @@
-             int j = z / 2;
-             int k = x % 2;
-             int l = z % 2;
--            float f = 100.0F - Mth.sqrt((float)(x * x + z * z)) * 8.0F;
-+            float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow
+@@ -518,15 +_,29 @@
+             int i1 = z / 2;
+             int i2 = x % 2;
+             int i3 = z % 2;
+-            float f = 100.0F - Mth.sqrt(x * x + z * z) * 8.0F;
++            float f = 100.0F - Mth.sqrt((long)x * (long)x + (long)z * (long)z) * 8.0F; // Paper - cast ints to long to avoid integer overflow
              f = Mth.clamp(f, -100.0F, 80.0F);
  
-+            NoiseCache cache = noiseCache.get().computeIfAbsent(sampler, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation
-             for (int m = -12; m <= 12; m++) {
-                 for (int n = -12; n <= 12; n++) {
-                     long o = (long)(i + m);
-                     long p = (long)(j + n);
--                    if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < -0.9F) {
--                        float g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F;
++            NoiseCache cache = noiseCache.get().computeIfAbsent(noise, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation
+             for (int i4 = -12; i4 <= 12; i4++) {
+                 for (int i5 = -12; i5 <= 12; i5++) {
+                     long l = i + i4;
+                     long l1 = i1 + i5;
+-                    if (l * l + l1 * l1 > 4096L && noise.getValue(l, l1) < -0.9F) {
+-                        float f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
 +                    // Paper start - Perf: Optimize end generation by using a noise cache
-+                    long key = net.minecraft.world.level.ChunkPos.asLong((int) o, (int) p);
++                    long key = net.minecraft.world.level.ChunkPos.asLong((int) l, (int) l);
 +                    int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191;
-+                    float g = Float.MIN_VALUE;
++                    float f1 = Float.MIN_VALUE;
 +                    if (cache.keys[index] == key) {
-+                        g = cache.values[index];
++                        f1 = cache.values[index];
 +                    } else {
-+                        if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < -0.9F) {
-+                            g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F;
++                        if (l * l + l1 * l1 > 4096L && noise.getValue((double)l, (double)l1) < -0.9F) {
++                            f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
 +                        }
 +                        cache.keys[index] = key;
-+                        cache.values[index] = g;
++                        cache.values[index] = f1;
 +                    }
-+                    if (g != Float.MIN_VALUE) {
++                    if (f1 != Float.MIN_VALUE) {
 +                        // Paper end - Perf: Optimize end generation
-                         float h = (float)(k - m * 2);
-                         float q = (float)(l - n * 2);
-                         float r = 100.0F - Mth.sqrt(h * h + q * q) * g;
+                         float f2 = i2 - i4 * 2;
+                         float f3 = i3 - i5 * 2;
+                         float f4 = 100.0F - Mth.sqrt(f2 * f2 + f3 * f3) * f1;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
new file mode 100644
index 0000000000..f1cd4a895b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/levelgen/FlatLevelSource.java
++++ b/net/minecraft/world/level/levelgen/FlatLevelSource.java
+@@ -33,17 +_,22 @@
+     private final FlatLevelGeneratorSettings settings;
+ 
+     public FlatLevelSource(FlatLevelGeneratorSettings settings) {
+-        super(new FixedBiomeSource(settings.getBiome()), Util.memoize(settings::adjustGenerationSettings));
++        // CraftBukkit start
++        this(settings, new FixedBiomeSource(settings.getBiome()));
++    }
++    public FlatLevelSource(FlatLevelGeneratorSettings settings, net.minecraft.world.level.biome.BiomeSource biomeSource) {
++        super(biomeSource, Util.memoize(settings::adjustGenerationSettings));
++        // CraftBukkit end
+         this.settings = settings;
+     }
+ 
+     @Override
+-    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed) {
++    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed, org.spigotmc.SpigotWorldConfig conf) { // Spigot
+         Stream<Holder<StructureSet>> stream = this.settings
+             .structureOverrides()
+             .map(HolderSet::stream)
+             .orElseGet(() -> structureSetLookup.listElements().map(reference -> (Holder<StructureSet>)reference));
+-        return ChunkGeneratorStructureState.createForFlat(randomState, seed, this.biomeSource, stream);
++        return ChunkGeneratorStructureState.createForFlat(randomState, seed, this.biomeSource, stream, conf); // Spigot
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
index e3e53c8dde..18943c3caa 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 +++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-@@ -1,3 +1,4 @@
+@@ -1,3 +_,4 @@
 +// keep
  package net.minecraft.world.level.levelgen;
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
new file mode 100644
index 0000000000..0e1624f5dc
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/level/levelgen/PatrolSpawner.java
++++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java
+@@ -20,28 +_,66 @@
+ 
+     @Override
+     public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) {
++        if (level.paperConfig().entities.behavior.pillagerPatrols.disable || level.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options
+         if (!spawnEnemies) {
+             return 0;
+         } else if (!level.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) {
+             return 0;
+         } else {
+             RandomSource randomSource = level.random;
+-            this.nextTick--;
+-            if (this.nextTick > 0) {
+-                return 0;
+-            } else {
+-                this.nextTick = this.nextTick + 12000 + randomSource.nextInt(1200);
+-                long l = level.getDayTime() / 24000L;
+-                if (l < 5L || !level.isDay()) {
+-                    return 0;
+-                } else if (randomSource.nextInt(5) != 0) {
+-                    return 0;
+-                } else {
+-                    int size = level.players().size();
++            // this.nextTick--;
++            // if (this.nextTick > 0) {
++            //     return 0;
++            // } else {
++            //     this.nextTick = this.nextTick + 12000 + randomSource.nextInt(1200);
++            //     long l = level.getDayTime() / 24000L;
++            //     if (l < 5L || !level.isDay()) {
++            //         return 0;
++            //     } else if (randomSource.nextInt(5) != 0) {
++            // Paper start - Pillager patrol spawn settings and per player options
++            // Random player selection moved up for per player spawning and configuration
++            int size = level.players().size();
++            if (size < 1) {
++                return 0;
++            }
++
++            net.minecraft.server.level.ServerPlayer player = level.players().get(randomSource.nextInt(size));
++            if (player.isSpectator()) {
++                return 0;
++            }
++
++            int patrolSpawnDelay;
++            if (level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) {
++                --player.patrolSpawnDelay;
++                patrolSpawnDelay = player.patrolSpawnDelay;
++            } else {
++                this.nextTick--;
++                patrolSpawnDelay = this.nextTick;
++            }
++            if (patrolSpawnDelay > 0) {
++                return 0;
++            } else {
++                long days;
++                if (level.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) {
++                    days = player.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang
++                } else {
++                    days = level.getDayTime() / 24000L;
++                }
++                if (level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) {
++                    player.patrolSpawnDelay += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200);
++                } else {
++                    this.nextTick += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200);
++                }
++
++                if (days < level.paperConfig().entities.behavior.pillagerPatrols.start.day || !level.isDay()) {
++                    return 0;
++                } else if (randomSource.nextDouble() >= level.paperConfig().entities.behavior.pillagerPatrols.spawnChance) {
++                    // Paper end - Pillager patrol spawn settings and per player options
++                    return 0;
++                } else {
+                     if (size < 1) {
+                         return 0;
+                     } else {
+-                        Player player = level.players().get(randomSource.nextInt(size));
+                         if (player.isSpectator()) {
+                             return 0;
+                         } else if (level.isCloseToVillage(player.blockPosition(), 2)) {
+@@ -104,7 +_,7 @@
+ 
+                 patrollingMonster.setPos(pos.getX(), pos.getY(), pos.getZ());
+                 patrollingMonster.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), EntitySpawnReason.PATROL, null);
+-                level.addFreshEntityWithPassengers(patrollingMonster);
++                level.addFreshEntityWithPassengers(patrollingMonster, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PATROL); // CraftBukkit
+                 return true;
+             } else {
+                 return false;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
new file mode 100644
index 0000000000..80e3eeee2e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java
++++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java
+@@ -28,19 +_,28 @@
+         } else if (!level.getGameRules().getBoolean(GameRules.RULE_DOINSOMNIA)) {
+             return 0;
+         } else {
++            // Paper start - Ability to control player's insomnia and phantoms
++            if (level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) {
++                return 0;
++            }
++            // Paper end - Ability to control player's insomnia and phantoms
+             RandomSource randomSource = level.random;
+             this.nextTick--;
+             if (this.nextTick > 0) {
+                 return 0;
+             } else {
+-                this.nextTick = this.nextTick + (60 + randomSource.nextInt(60)) * 20;
++                // Paper start - Ability to control player's insomnia and phantoms
++                int spawnAttemptMinSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds;
++                int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds;
++                this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20;
++                // Paper end - Ability to control player's insomnia and phantoms
+                 if (level.getSkyDarken() < 5 && level.dimensionType().hasSkyLight()) {
+                     return 0;
+                 } else {
+                     int i = 0;
+ 
+                     for (ServerPlayer serverPlayer : level.players()) {
+-                        if (!serverPlayer.isSpectator()) {
++                        if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls
+                             BlockPos blockPos = serverPlayer.blockPosition();
+                             if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) {
+                                 DifficultyInstance currentDifficultyAt = level.getCurrentDifficultyAt(blockPos);
+@@ -48,7 +_,7 @@
+                                     ServerStatsCounter stats = serverPlayer.getStats();
+                                     int i1 = Mth.clamp(stats.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
+                                     int i2 = 24000;
+-                                    if (randomSource.nextInt(i1) >= 72000) {
++                                    if (randomSource.nextInt(i1) >= level.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms
+                                         BlockPos blockPos1 = blockPos.above(20 + randomSource.nextInt(15))
+                                             .east(-10 + randomSource.nextInt(21))
+                                             .south(-10 + randomSource.nextInt(21));
+@@ -59,13 +_,23 @@
+                                             int i3 = 1 + randomSource.nextInt(currentDifficultyAt.getDifficulty().getId() + 1);
+ 
+                                             for (int i4 = 0; i4 < i3; i4++) {
++                                                // Paper start - PhantomPreSpawnEvent
++                                                com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(io.papermc.paper.util.MCUtil.toLocation(level, blockPos1), serverPlayer.getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL);
++                                                if (!event.callEvent()) {
++                                                    if (event.shouldAbortSpawn()) {
++                                                        break;
++                                                    }
++                                                    continue;
++                                                }
++                                                // Paper end - PhantomPreSpawnEvent
+                                                 Phantom phantom = EntityType.PHANTOM.create(level, EntitySpawnReason.NATURAL);
+                                                 if (phantom != null) {
++                                                    phantom.setSpawningEntity(serverPlayer.getUUID()); // Paper - PhantomPreSpawnEvent
+                                                     phantom.moveTo(blockPos1, 0.0F, 0.0F);
+                                                     spawnGroupData = phantom.finalizeSpawn(
+                                                         level, currentDifficultyAt, EntitySpawnReason.NATURAL, spawnGroupData
+                                                     );
+-                                                    level.addFreshEntityWithPassengers(phantom);
++                                                    level.addFreshEntityWithPassengers(phantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
+                                                     i++;
+                                                 }
+                                             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
deleted file mode 100644
index da8a1b29d5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/world/level/levelgen/FlatLevelSource.java
-+++ b/net/minecraft/world/level/levelgen/FlatLevelSource.java
-@@ -34,22 +34,28 @@
-     private final FlatLevelGeneratorSettings settings;
- 
-     public FlatLevelSource(FlatLevelGeneratorSettings config) {
--        FixedBiomeSource worldchunkmanagerhell = new FixedBiomeSource(config.getBiome());
-+        // CraftBukkit start
-+        // WorldChunkManagerHell worldchunkmanagerhell = new WorldChunkManagerHell(generatorsettingsflat.getBiome());
- 
--        Objects.requireNonNull(config);
--        super(worldchunkmanagerhell, Util.memoize(config::adjustGenerationSettings));
--        this.settings = config;
-+        // Objects.requireNonNull(generatorsettingsflat);
-+        this(config, new FixedBiomeSource(config.getBiome()));
-     }
- 
-+    public FlatLevelSource(FlatLevelGeneratorSettings generatorsettingsflat, net.minecraft.world.level.biome.BiomeSource worldchunkmanager) {
-+        super(worldchunkmanager, Util.memoize(generatorsettingsflat::adjustGenerationSettings));
-+        // CraftBukkit end
-+        this.settings = generatorsettingsflat;
-+    }
-+
-     @Override
--    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetRegistry, RandomState noiseConfig, long seed) {
-+    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i, org.spigotmc.SpigotWorldConfig conf) { // Spigot
-         Stream<Holder<StructureSet>> stream = (Stream) this.settings.structureOverrides().map(HolderSet::stream).orElseGet(() -> {
--            return structureSetRegistry.listElements().map((holder_c) -> {
-+            return holderlookup.listElements().map((holder_c) -> {
-                 return holder_c;
-             });
-         });
- 
--        return ChunkGeneratorStructureState.createForFlat(noiseConfig, seed, this.biomeSource, stream);
-+        return ChunkGeneratorStructureState.createForFlat(randomstate, i, this.biomeSource, stream, conf); // Spigot
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
deleted file mode 100644
index 9d044b3016..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
+++ /dev/null
@@ -1,79 +0,0 @@
---- a/net/minecraft/world/level/levelgen/PatrolSpawner.java
-+++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java
-@@ -24,6 +24,7 @@
- 
-     @Override
-     public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) {
-+        if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options
-         if (!spawnMonsters) {
-             return 0;
-         } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) {
-@@ -31,23 +32,51 @@
-         } else {
-             RandomSource randomsource = world.random;
- 
--            --this.nextTick;
--            if (this.nextTick > 0) {
-+            // Paper start - Pillager patrol spawn settings and per player options
-+            // Random player selection moved up for per player spawning and configuration
-+            int j = world.players().size();
-+            if (j < 1) {
-                 return 0;
-+            }
-+
-+            net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j));
-+            if (entityhuman.isSpectator()) {
-+                return 0;
-+            }
-+
-+            int patrolSpawnDelay;
-+            if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) {
-+                --entityhuman.patrolSpawnDelay;
-+                patrolSpawnDelay = entityhuman.patrolSpawnDelay;
-             } else {
--                this.nextTick += 12000 + randomsource.nextInt(1200);
--                long i = world.getDayTime() / 24000L;
-+                this.nextTick--;
-+                patrolSpawnDelay = this.nextTick;
-+            }
- 
--                if (i >= 5L && world.isDay()) {
--                    if (randomsource.nextInt(5) != 0) {
-+            if (patrolSpawnDelay > 0) {
-+                return 0;
-+            } else {
-+                long days;
-+                if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) {
-+                    days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang
-+                } else {
-+                    days = world.getDayTime() / 24000L;
-+                }
-+                if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) {
-+                    entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200);
-+                } else {
-+                    this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200);
-+                }
-+
-+                if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) {
-+                    if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) {
-+                        // Paper end - Pillager patrol spawn settings and per player options
-                         return 0;
-                     } else {
--                        int j = world.players().size();
- 
-                         if (j < 1) {
-                             return 0;
-                         } else {
--                            Player entityhuman = (Player) world.players().get(randomsource.nextInt(j));
- 
-                             if (entityhuman.isSpectator()) {
-                                 return 0;
-@@ -116,7 +145,7 @@
- 
-                 entitymonsterpatrolling.setPos((double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
-                 entitymonsterpatrolling.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.PATROL, (SpawnGroupData) null);
--                world.addFreshEntityWithPassengers(entitymonsterpatrolling);
-+                world.addFreshEntityWithPassengers(entitymonsterpatrolling, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PATROL); // CraftBukkit
-                 return true;
-             } else {
-                 return false;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
deleted file mode 100644
index e3cf03e4f8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
+++ /dev/null
@@ -1,68 +0,0 @@
---- a/net/minecraft/world/level/levelgen/PhantomSpawner.java
-+++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java
-@@ -32,13 +32,22 @@
-         } else if (!world.getGameRules().getBoolean(GameRules.RULE_DOINSOMNIA)) {
-             return 0;
-         } else {
-+            // Paper start - Ability to control player's insomnia and phantoms
-+            if (world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds <= 0) {
-+                return 0;
-+            }
-+            // Paper end - Ability to control player's insomnia and phantoms
-             RandomSource randomsource = world.random;
- 
-             --this.nextTick;
-             if (this.nextTick > 0) {
-                 return 0;
-             } else {
--                this.nextTick += (60 + randomsource.nextInt(60)) * 20;
-+                // Paper start - Ability to control player's insomnia and phantoms
-+                int spawnAttemptMinSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds;
-+                int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds;
-+                this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20;
-+                // Paper end - Ability to control player's insomnia and phantoms
-                 if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) {
-                     return 0;
-                 } else {
-@@ -48,7 +57,7 @@
-                     while (iterator.hasNext()) {
-                         ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
--                        if (!entityplayer.isSpectator()) {
-+                        if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls
-                             BlockPos blockposition = entityplayer.blockPosition();
- 
-                             if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) {
-@@ -59,7 +68,7 @@
-                                     int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
-                                     boolean flag2 = true;
- 
--                                    if (randomsource.nextInt(j) >= 72000) {
-+                                    if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms
-                                         BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21));
-                                         BlockState iblockdata = world.getBlockState(blockposition1);
-                                         FluidState fluid = world.getFluidState(blockposition1);
-@@ -69,12 +78,22 @@
-                                             int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1);
- 
-                                             for (int l = 0; l < k; ++l) {
-+                                                // Paper start - PhantomPreSpawnEvent
-+                                                com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(io.papermc.paper.util.MCUtil.toLocation(world, blockposition1), entityplayer.getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL);
-+                                                if (!event.callEvent()) {
-+                                                    if (event.shouldAbortSpawn()) {
-+                                                        break;
-+                                                    }
-+                                                    continue;
-+                                                }
-+                                                // Paper end - PhantomPreSpawnEvent
-                                                 Phantom entityphantom = (Phantom) EntityType.PHANTOM.create(world, EntitySpawnReason.NATURAL);
- 
-                                                 if (entityphantom != null) {
-+                                                    entityphantom.setSpawningEntity(entityplayer.getUUID()); // Paper - PhantomPreSpawnEvent
-                                                     entityphantom.moveTo(blockposition1, 0.0F, 0.0F);
-                                                     groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, EntitySpawnReason.NATURAL, groupdataentity);
--                                                    world.addFreshEntityWithPassengers(entityphantom);
-+                                                    world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
-                                                     ++i;
-                                                 }
-                                             }

From 6a85106951852cfe609d7f71d783ca163200fa66 Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sun, 15 Dec 2024 00:03:03 +0100
Subject: [PATCH 128/285] net/minecraft/world/entity/ambient

---
 .../world/entity/ambient/Bat.java.patch       | 45 +++++++++++++++
 .../world/entity/ambient/Bat.java.patch       | 55 -------------------
 2 files changed, 45 insertions(+), 55 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ambient/Bat.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch
new file mode 100644
index 0000000000..2b20441f48
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ambient/Bat.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/entity/ambient/Bat.java
++++ b/net/minecraft/world/entity/ambient/Bat.java
+@@ -85,7 +_,7 @@
+     }
+ 
+     @Override
+-    public boolean isPushable() {
++    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
+         return false;
+     }
+ 
+@@ -139,13 +_,13 @@
+                     this.yHeadRot = this.random.nextInt(360);
+                 }
+ 
+-                if (level.getNearestPlayer(BAT_RESTING_TARGETING, this) != null) {
++                if (level.getNearestPlayer(BAT_RESTING_TARGETING, this) != null && org.bukkit.craftbukkit.event.CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+                     this.setResting(false);
+                     if (!isSilent) {
+                         level.levelEvent(null, 1025, blockPos, 0);
+                     }
+                 }
+-            } else {
++            } else if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+                 this.setResting(false);
+                 if (!isSilent) {
+                     level.levelEvent(null, 1025, blockPos, 0);
+@@ -178,7 +_,7 @@
+             float f1 = Mth.wrapDegrees(f - this.getYRot());
+             this.zza = 0.5F;
+             this.setYRot(this.getYRot() + f1);
+-            if (this.random.nextInt(100) == 0 && level.getBlockState(blockPos1).isRedstoneConductor(level, blockPos1)) {
++            if (this.random.nextInt(100) == 0 && level.getBlockState(blockPos1).isRedstoneConductor(level, blockPos1) && org.bukkit.craftbukkit.event.CraftEventFactory.handleBatToggleSleepEvent(this, false)) { // CraftBukkit - Call BatToggleSleepEvent
+                 this.setResting(true);
+             }
+         }
+@@ -203,7 +_,7 @@
+         if (this.isInvulnerableTo(level, damageSource)) {
+             return false;
+         } else {
+-            if (this.isResting()) {
++            if (this.isResting() && org.bukkit.craftbukkit.event.CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+                 this.setResting(false);
+             }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ambient/Bat.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ambient/Bat.java.patch
deleted file mode 100644
index acfaa378fe..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ambient/Bat.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/world/entity/ambient/Bat.java
-+++ b/net/minecraft/world/entity/ambient/Bat.java
-@@ -29,6 +29,9 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.levelgen.Heightmap;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class Bat extends AmbientCreature {
- 
-@@ -88,7 +91,7 @@
-     }
- 
-     @Override
--    public boolean isPushable() {
-+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
-         return false;
-     }
- 
-@@ -144,13 +147,13 @@
-                     this.yHeadRot = (float) this.random.nextInt(360);
-                 }
- 
--                if (world.getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null) {
-+                if (world.getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
-                     this.setResting(false);
-                     if (!flag) {
-                         world.levelEvent((Player) null, 1025, blockposition, 0);
-                     }
-                 }
--            } else {
-+            } else if (CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
-                 this.setResting(false);
-                 if (!flag) {
-                     world.levelEvent((Player) null, 1025, blockposition, 0);
-@@ -177,7 +180,7 @@
- 
-             this.zza = 0.5F;
-             this.setYRot(this.getYRot() + f1);
--            if (this.random.nextInt(100) == 0 && world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1)) {
-+            if (this.random.nextInt(100) == 0 && world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1) && CraftEventFactory.handleBatToggleSleepEvent(this, false)) { // CraftBukkit - Call BatToggleSleepEvent
-                 this.setResting(true);
-             }
-         }
-@@ -202,7 +205,7 @@
-         if (this.isInvulnerableTo(world, source)) {
-             return false;
-         } else {
--            if (this.isResting()) {
-+            if (this.isResting() && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
-                 this.setResting(false);
-             }
- 

From 3fbf7aa159f7d07224ed98080fe9d18f35f652ce Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sun, 15 Dec 2024 00:09:40 +0100
Subject: [PATCH 129/285] net/minecraft/world/level/levelgen/feature

---
 .../feature/EndPlatformFeature.java.patch     | 52 ++++++++++++++
 .../levelgen/feature/SpikeFeature.java.patch  | 10 +++
 .../feature/EndPlatformFeature.java.patch     | 72 -------------------
 .../levelgen/feature/SpikeFeature.java.patch  | 10 ---
 4 files changed, 62 insertions(+), 82 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
new file mode 100644
index 0000000000..bfd494df87
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
@@ -0,0 +1,52 @@
+--- a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java
++++ b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java
+@@ -19,6 +_,12 @@
+     }
+ 
+     public static void createEndPlatform(ServerLevelAccessor level, BlockPos pos, boolean dropBlocks) {
++        // CraftBukkit start
++        createEndPlatform(level, pos, dropBlocks, null);
++    }
++    public static void createEndPlatform(ServerLevelAccessor level, BlockPos pos, boolean dropBlocks, net.minecraft.world.entity.Entity entity) {
++        org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level);
++        // CraftBukkit end
+         BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
+ 
+         for (int i = -2; i <= 2; i++) {
+@@ -26,15 +_,33 @@
+                 for (int i2 = -1; i2 < 3; i2++) {
+                     BlockPos blockPos = mutableBlockPos.set(pos).move(i1, i2, i);
+                     Block block = i2 == -1 ? Blocks.OBSIDIAN : Blocks.AIR;
+-                    if (!level.getBlockState(blockPos).is(block)) {
++                    // CraftBukkit start
++                    if (!blockList.getBlockState(blockPos).is(block)) {
+                         if (dropBlocks) {
+-                            level.destroyBlock(blockPos, true, null);
++                            blockList.destroyBlock(blockPos, true, null);
+                         }
+ 
+-                        level.setBlock(blockPos, block.defaultBlockState(), 3);
++                        blockList.setBlock(blockPos, block.defaultBlockState(), 3);
++                        // CraftBukkit end
+                     }
+                 }
+             }
+         }
++
++        // CraftBukkit start
++        // SPIGOT-7746: Entity will only be null during world generation, which is async, so just generate without event
++        if (entity != null) {
++            org.bukkit.World bworld = level.getLevel().getWorld();
++            org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) blockList.getList(), bworld, entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
++            level.getLevel().getCraftServer().getPluginManager().callEvent(portalEvent);
++            if (portalEvent.isCancelled()) return;
++        }
++
++        // SPIGOT-7856: End platform not dropping items after replacing blocks
++        if (dropBlocks) {
++            blockList.getList().forEach((state) -> level.destroyBlock(state.getPosition(), true, null));
++        }
++        blockList.updateList();
++        // CraftBukkit end
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch
new file mode 100644
index 0000000000..9de3934ace
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
++++ b/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
+@@ -113,6 +_,7 @@
+             endCrystal.setBeamTarget(config.getCrystalBeamTarget());
+             endCrystal.setInvulnerable(config.isCrystalInvulnerable());
+             endCrystal.moveTo(spike.getCenterX() + 0.5, spike.getHeight() + 1, spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F);
++            endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals
+             level.addFreshEntity(endCrystal);
+             BlockPos blockPosx = endCrystal.blockPosition();
+             this.setBlock(level, blockPosx.below(), Blocks.BEDROCK.defaultBlockState());
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
deleted file mode 100644
index d2cdde6d47..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch
+++ /dev/null
@@ -1,72 +0,0 @@
---- a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java
-+++ b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java
-@@ -7,6 +7,11 @@
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.block.BlockState;
-+import org.bukkit.event.world.PortalCreateEvent;
-+// CraftBukkit end
- 
- public class EndPlatformFeature extends Feature<NoneFeatureConfiguration> {
- 
-@@ -21,24 +26,51 @@
-     }
- 
-     public static void createEndPlatform(ServerLevelAccessor world, BlockPos pos, boolean breakBlocks) {
--        BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
-+        EndPlatformFeature.createEndPlatform(world, pos, breakBlocks, null);
-+        // CraftBukkit start
-+    }
- 
-+    public static void createEndPlatform(ServerLevelAccessor worldaccess, BlockPos blockposition, boolean flag, Entity entity) {
-+        org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(worldaccess);
-+        // CraftBukkit end
-+        BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
-+
-         for (int i = -2; i <= 2; ++i) {
-             for (int j = -2; j <= 2; ++j) {
-                 for (int k = -1; k < 3; ++k) {
--                    BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(pos).move(j, k, i);
-+                    BlockPos.MutableBlockPos blockposition_mutableblockposition1 = blockposition_mutableblockposition.set(blockposition).move(j, k, i);
-                     Block block = k == -1 ? Blocks.OBSIDIAN : Blocks.AIR;
- 
--                    if (!world.getBlockState(blockposition_mutableblockposition1).is(block)) {
--                        if (breakBlocks) {
--                            world.destroyBlock(blockposition_mutableblockposition1, true, (Entity) null);
-+                    // CraftBukkit start
-+                    if (!blockList.getBlockState(blockposition_mutableblockposition1).is(block)) {
-+                        if (flag) {
-+                            blockList.destroyBlock(blockposition_mutableblockposition1, true, (Entity) null);
-                         }
- 
--                        world.setBlock(blockposition_mutableblockposition1, block.defaultBlockState(), 3);
-+                        blockList.setBlock(blockposition_mutableblockposition1, block.defaultBlockState(), 3);
-+                        // CraftBukkit end
-                     }
-                 }
-             }
-         }
-+        // CraftBukkit start
-+        // SPIGOT-7746: Entity will only be null during world generation, which is async, so just generate without event
-+        if (entity != null) {
-+            org.bukkit.World bworld = worldaccess.getLevel().getWorld();
-+            PortalCreateEvent portalEvent = new PortalCreateEvent((List<BlockState>) (List) blockList.getList(), bworld, entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
- 
-+            worldaccess.getLevel().getCraftServer().getPluginManager().callEvent(portalEvent);
-+            if (portalEvent.isCancelled()) {
-+                return;
-+            }
-+        }
-+
-+        // SPIGOT-7856: End platform not dropping items after replacing blocks
-+        if (flag) {
-+            blockList.getList().forEach((state) -> worldaccess.destroyBlock(state.getPosition(), true, null));
-+        }
-+        blockList.updateList();
-+        // CraftBukkit end
-+
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch
deleted file mode 100644
index 3db119d7ed..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/levelgen/feature/SpikeFeature.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
-+++ b/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
-@@ -115,6 +115,7 @@
-             endCrystal.moveTo(
-                 (double)spike.getCenterX() + 0.5, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F
-             );
-+            endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals
-             world.addFreshEntity(endCrystal);
-             BlockPos blockPos2 = endCrystal.blockPosition();
-             this.setBlock(world, blockPos2.below(), Blocks.BEDROCK.defaultBlockState());

From 363d5bcf57a8afe3d075704fa0bdc41dd2afb76c Mon Sep 17 00:00:00 2001
From: Noah van der Aa <ndvdaa@gmail.com>
Date: Sun, 15 Dec 2024 00:16:28 +0100
Subject: [PATCH 130/285] com.mojang.{authlib,serialization}

---
 .../yggdrasil/YggdrasilGameProfileRepository.java.patch       | 4 ++--
 .../com/mojang/serialization/Dynamic.java.patch               | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
 rename paper-server/patches/{unapplied => sources}/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/com/mojang/serialization/Dynamic.java.patch (96%)

diff --git a/paper-server/patches/unapplied/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch b/paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch
rename to paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch
index 30f05bd5aa..3d5dbbd314 100644
--- a/paper-server/patches/unapplied/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch
+++ b/paper-server/patches/sources/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java
 +++ b/com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java
-@@ -44,6 +44,7 @@
+@@ -44,6 +_,7 @@
              .collect(Collectors.toSet());
  
          final int page = 0;
@@ -8,7 +8,7 @@
  
          for (final List<String> request : Iterables.partition(criteria, ENTRIES_PER_PAGE)) {
              final List<String> normalizedRequest = request.stream().map(YggdrasilGameProfileRepository::normalizeName).toList();
-@@ -75,6 +76,12 @@
+@@ -75,6 +_,12 @@
                          LOGGER.debug("Couldn't find profile {}", name);
                          callback.onProfileLookupFailed(name, new ProfileNotFoundException("Server did not find the requested profile"));
                      }
diff --git a/paper-server/patches/unapplied/com/mojang/serialization/Dynamic.java.patch b/paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/com/mojang/serialization/Dynamic.java.patch
rename to paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch
index cdd24c2f21..98fe692497 100644
--- a/paper-server/patches/unapplied/com/mojang/serialization/Dynamic.java.patch
+++ b/paper-server/patches/sources/com/mojang/serialization/Dynamic.java.patch
@@ -1,6 +1,6 @@
 --- a/com/mojang/serialization/Dynamic.java
 +++ b/com/mojang/serialization/Dynamic.java
-@@ -19,6 +19,7 @@
+@@ -19,6 +_,7 @@
  
  @SuppressWarnings("unused")
  public class Dynamic<T> extends DynamicLike<T> {
@@ -8,7 +8,7 @@
      private final T value;
  
      public Dynamic(final DynamicOps<T> ops) {
-@@ -120,7 +121,7 @@
+@@ -120,7 +_,7 @@
          return new OptionalDynamic<>(ops, ops.getMap(value).flatMap(m -> {
              final T value = m.get(key);
              if (value == null) {

From 5deb3e967119a1be07c05c929994bdaff546b0d4 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 15:38:34 -0800
Subject: [PATCH 131/285] net.minecraft.world.entity.decoration

---
 .../entity/decoration/ArmorStand.java.patch   | 376 +++++++++++++
 .../decoration/BlockAttachedEntity.java.patch | 114 ++++
 .../entity/decoration/ItemFrame.java.patch    | 127 +++++
 .../LeashFenceKnotEntity.java.patch           |  44 +-
 .../entity/decoration/Painting.java.patch     |  41 ++
 .../entity/decoration/ArmorStand.java.patch   | 513 ------------------
 .../decoration/BlockAttachedEntity.java.patch | 140 -----
 .../entity/decoration/ItemFrame.java.patch    | 150 -----
 .../entity/decoration/Painting.java.patch     |  54 --
 9 files changed, 667 insertions(+), 892 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ArmorStand.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ItemFrame.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/decoration/Painting.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
new file mode 100644
index 0000000000..146db06871
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
@@ -0,0 +1,376 @@
+--- a/net/minecraft/world/entity/decoration/ArmorStand.java
++++ b/net/minecraft/world/entity/decoration/ArmorStand.java
+@@ -86,9 +_,17 @@
+     public Rotations rightArmPose = DEFAULT_RIGHT_ARM_POSE;
+     public Rotations leftLegPose = DEFAULT_LEFT_LEG_POSE;
+     public Rotations rightLegPose = DEFAULT_RIGHT_LEG_POSE;
++    public boolean canMove = true; // Paper
++    // Paper start - Allow ArmorStands not to tick
++    public boolean canTick = true;
++    public boolean canTickSetByAPI = false;
++    private boolean noTickPoseDirty = false;
++    private boolean noTickEquipmentDirty = false;
++    // Paper end - Allow ArmorStands not to tick
+ 
+     public ArmorStand(EntityType<? extends ArmorStand> entityType, Level level) {
+         super(entityType, level);
++        if (level != null) this.canTick = level.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick
+     }
+ 
+     public ArmorStand(Level level, double x, double y, double z) {
+@@ -100,6 +_,13 @@
+         return createLivingAttributes().add(Attributes.STEP_HEIGHT, 0.0);
+     }
+ 
++    // CraftBukkit start - SPIGOT-3607, SPIGOT-3637
++    @Override
++    public float getBukkitYaw() {
++        return this.getYRot();
++    }
++    // CraftBukkit end
++
+     @Override
+     public void refreshDimensions() {
+         double x = this.getX();
+@@ -159,14 +_,22 @@
+ 
+     @Override
+     public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
++        // CraftBukkit start
++        this.setItemSlot(slot, stack, false);
++    }
++
++    @Override
++    public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack, boolean silent) {
++        // CraftBukkit end
+         this.verifyEquippedItem(stack);
+         switch (slot.getType()) {
+             case HAND:
+-                this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack);
++                this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack, silent); // CraftBukkit
+                 break;
+             case HUMANOID_ARMOR:
+-                this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack);
++                this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack, silent); // CraftBukkit
+         }
++        this.noTickEquipmentDirty = true; // Paper - Allow ArmorStands not to tick; Still update equipment
+     }
+ 
+     @Override
+@@ -196,6 +_,7 @@
+         }
+ 
+         compound.put("Pose", this.writePose());
++        if (this.canTickSetByAPI) compound.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick
+     }
+ 
+     @Override
+@@ -226,6 +_,12 @@
+         this.setNoBasePlate(compound.getBoolean("NoBasePlate"));
+         this.setMarker(compound.getBoolean("Marker"));
+         this.noPhysics = !this.hasPhysics();
++        // Paper start - Allow ArmorStands not to tick
++        if (compound.contains("Paper.CanTickOverride")) {
++            this.canTick = compound.getBoolean("Paper.CanTickOverride");
++            this.canTickSetByAPI = true;
++        }
++        // Paper end - Allow ArmorStands not to tick
+         CompoundTag compound2 = compound.getCompound("Pose");
+         this.readPose(compound2);
+     }
+@@ -275,7 +_,7 @@
+     }
+ 
+     @Override
+-    public boolean isPushable() {
++    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
+         return false;
+     }
+ 
+@@ -285,6 +_,7 @@
+ 
+     @Override
+     protected void pushEntities() {
++        if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
+         for (Entity entity : this.level().getEntities(this, this.getBoundingBox(), RIDABLE_MINECARTS)) {
+             if (this.distanceToSqr(entity) <= 0.2) {
+                 entity.push(this);
+@@ -357,7 +_,25 @@
+             return false;
+         } else if (itemBySlot.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(16)) != 0) {
+             return false;
+-        } else if (player.hasInfiniteMaterials() && itemBySlot.isEmpty() && !stack.isEmpty()) {
++            // CraftBukkit start
++        } else {
++            org.bukkit.inventory.ItemStack armorStandItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemBySlot);
++            org.bukkit.inventory.ItemStack playerHeldItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack);
++
++            org.bukkit.entity.Player player1 = (org.bukkit.entity.Player) player.getBukkitEntity();
++            org.bukkit.entity.ArmorStand self = (org.bukkit.entity.ArmorStand) this.getBukkitEntity();
++
++            org.bukkit.inventory.EquipmentSlot slot1 = org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(slot);
++            org.bukkit.inventory.EquipmentSlot hand1 = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand);
++            org.bukkit.event.player.PlayerArmorStandManipulateEvent armorStandManipulateEvent = new org.bukkit.event.player.PlayerArmorStandManipulateEvent(player1, self, playerHeldItem, armorStandItem, slot1, hand1);
++            this.level().getCraftServer().getPluginManager().callEvent(armorStandManipulateEvent);
++
++            if (armorStandManipulateEvent.isCancelled()) {
++                return true;
++            }
++
++        if (player.hasInfiniteMaterials() && itemBySlot.isEmpty() && !stack.isEmpty()) {
++            // CraftBukkit end
+             this.setItemSlot(slot, stack.copyWithCount(1));
+             return true;
+         } else if (stack.isEmpty() || stack.getCount() <= 1) {
+@@ -370,6 +_,7 @@
+             this.setItemSlot(slot, stack.split(1));
+             return true;
+         }
++        } // CraftBukkit
+     }
+ 
+     @Override
+@@ -379,15 +_,32 @@
+         } else if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) {
+             return false;
+         } else if (damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
+-            this.kill(level);
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount)) {
++                return false;
++            }
++            this.kill(level, damageSource); // CraftBukkit
++            // CraftBukkit end
+             return false;
+-        } else if (this.isInvulnerableTo(level, damageSource) || this.invisible || this.isMarker()) {
++        } else if (this.isInvulnerableTo(level, damageSource) /*|| this.invisible*/ || this.isMarker()) { // CraftBukkit
+             return false;
+         } else if (damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
+-            this.brokenByAnything(level, damageSource);
+-            this.kill(level);
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
++                return false;
++            }
++            // CraftBukkit end
++            // Paper start - avoid duplicate event call
++            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
++            if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit
++            // Paper end
+             return false;
+         } else if (damageSource.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
++                return false;
++            }
++            // CraftBukkit end
+             if (this.isOnFire()) {
+                 this.causeDamage(level, damageSource, 0.15F);
+             } else {
+@@ -396,9 +_,19 @@
+ 
+             return false;
+         } else if (damageSource.is(DamageTypeTags.BURNS_ARMOR_STANDS) && this.getHealth() > 0.5F) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
++                return false;
++            }
++            // CraftBukkit end
+             this.causeDamage(level, damageSource, 4.0F);
+             return false;
+         } else {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
++                return false;
++            }
++            // CraftBukkit end
+             boolean isCanBreakArmorStand = damageSource.is(DamageTypeTags.CAN_BREAK_ARMOR_STAND);
+             boolean isAlwaysKillsArmorStands = damageSource.is(DamageTypeTags.ALWAYS_KILLS_ARMOR_STANDS);
+             if (!isCanBreakArmorStand && !isAlwaysKillsArmorStands) {
+@@ -408,7 +_,7 @@
+             } else if (damageSource.isCreativePlayer()) {
+                 this.playBrokenSound();
+                 this.showBreakingParticles();
+-                this.kill(level);
++                this.kill(level, damageSource); // CraftBukkit
+                 return true;
+             } else {
+                 long gameTime = level.getGameTime();
+@@ -417,9 +_,9 @@
+                     this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
+                     this.lastHit = gameTime;
+                 } else {
+-                    this.brokenByPlayer(level, damageSource);
++                    org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(level, damageSource); // Paper
+                     this.showBreakingParticles();
+-                    this.kill(level);
++                    if (!event.isCancelled()) this.kill(damageSource, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...)
+                 }
+ 
+                 return true;
+@@ -472,28 +_,31 @@
+         health -= damageAmount;
+         if (health <= 0.5F) {
+             this.brokenByAnything(level, damageSource);
+-            this.kill(level);
++            // Paper start - avoid duplicate event call
++            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
++            if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit
++            // Paper end
+         } else {
+             this.setHealth(health);
+             this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
+         }
+     }
+ 
+-    private void brokenByPlayer(ServerLevel level, DamageSource damageSource) {
++    private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel level, DamageSource damageSource) { // Paper
+         ItemStack itemStack = new ItemStack(Items.ARMOR_STAND);
+         itemStack.set(DataComponents.CUSTOM_NAME, this.getCustomName());
+-        Block.popResource(this.level(), this.blockPosition(), itemStack);
+-        this.brokenByAnything(level, damageSource);
++        this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior
++        return this.brokenByAnything(level, damageSource); // Paper
+     }
+ 
+-    private void brokenByAnything(ServerLevel level, DamageSource damageSource) {
++    private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel level, DamageSource damageSource) { // Paper
+         this.playBrokenSound();
+-        this.dropAllDeathLoot(level, damageSource);
++        // this.dropAllDeathLoot(level, damageSource); // CraftBukkit - moved down
+ 
+         for (int i = 0; i < this.handItems.size(); i++) {
+             ItemStack itemStack = this.handItems.get(i);
+             if (!itemStack.isEmpty()) {
+-                Block.popResource(this.level(), this.blockPosition().above(), itemStack);
++                this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly
+                 this.handItems.set(i, ItemStack.EMPTY);
+             }
+         }
+@@ -501,10 +_,11 @@
+         for (int ix = 0; ix < this.armorItems.size(); ix++) {
+             ItemStack itemStack = this.armorItems.get(ix);
+             if (!itemStack.isEmpty()) {
+-                Block.popResource(this.level(), this.blockPosition().above(), itemStack);
++                this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly
+                 this.armorItems.set(ix, ItemStack.EMPTY);
+             }
+         }
++        return this.dropAllDeathLoot(level, damageSource); // CraftBukkit - moved from above // Paper
+     }
+ 
+     private void playBrokenSound() {
+@@ -539,7 +_,28 @@
+ 
+     @Override
+     public void tick() {
++        // Paper start - Allow ArmorStands not to tick
++        if (!this.canTick) {
++            if (this.noTickPoseDirty) {
++                this.noTickPoseDirty = false;
++                this.updatePose();
++            }
++
++            if (this.noTickEquipmentDirty) {
++                this.noTickEquipmentDirty = false;
++                this.detectEquipmentUpdatesPublic();
++            }
++
++            return;
++        }
++        // Paper end - Allow ArmorStands not to tick
+         super.tick();
++        // Paper start - Allow ArmorStands not to tick
++        this.updatePose();
++    }
++
++    public void updatePose() {
++        // Paper end - Allow ArmorStands not to tick
+         Rotations rotations = this.entityData.get(DATA_HEAD_POSE);
+         if (!this.headPose.equals(rotations)) {
+             this.setHeadPose(rotations);
+@@ -587,9 +_,31 @@
+         return this.isSmall();
+     }
+ 
++    // CraftBukkit start
++     @Override
++    public boolean shouldDropExperience() {
++        return true; // MC-157395, SPIGOT-5193 even baby (small) armor stands should drop
++    }
++    // CraftBukkit end
++
+     @Override
+     public void kill(ServerLevel level) {
+-        this.remove(Entity.RemovalReason.KILLED);
++        // CraftBukkit start - pass DamageSource for kill
++        this.kill(level, null);
++    }
++
++    public void kill(ServerLevel level, @Nullable DamageSource damageSource) {
++        // Paper start - make cancellable
++        this.kill(damageSource, true);
++    }
++    public void kill(@Nullable DamageSource damageSource, boolean callEvent) {
++        if (callEvent) {
++            org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event
++            if (event.isCancelled()) return;
++        }
++        // Paper end
++        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
++        // CraftBukkit end
+         this.gameEvent(GameEvent.ENTITY_DIE);
+     }
+ 
+@@ -653,31 +_,37 @@
+     public void setHeadPose(Rotations headPose) {
+         this.headPose = headPose;
+         this.entityData.set(DATA_HEAD_POSE, headPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public void setBodyPose(Rotations bodyPose) {
+         this.bodyPose = bodyPose;
+         this.entityData.set(DATA_BODY_POSE, bodyPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public void setLeftArmPose(Rotations leftArmPose) {
+         this.leftArmPose = leftArmPose;
+         this.entityData.set(DATA_LEFT_ARM_POSE, leftArmPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public void setRightArmPose(Rotations rightArmPose) {
+         this.rightArmPose = rightArmPose;
+         this.entityData.set(DATA_RIGHT_ARM_POSE, rightArmPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public void setLeftLegPose(Rotations leftLegPose) {
+         this.leftLegPose = leftLegPose;
+         this.entityData.set(DATA_LEFT_LEG_POSE, leftLegPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public void setRightLegPose(Rotations rightLegPose) {
+         this.rightLegPose = rightLegPose;
+         this.entityData.set(DATA_RIGHT_LEG_POSE, rightLegPose);
++        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
+     }
+ 
+     public Rotations getHeadPose() {
+@@ -809,4 +_,13 @@
+     public boolean canBeSeenByAnyone() {
+         return !this.isInvisible() && !this.isMarker();
+     }
++
++    // Paper start
++    @Override
++    public void move(net.minecraft.world.entity.MoverType type, Vec3 movement) {
++        if (this.canMove) {
++            super.move(type, movement);
++        }
++    }
++    // Paper end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
new file mode 100644
index 0000000000..a5583937ae
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
@@ -0,0 +1,114 @@
+--- a/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
++++ b/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
+@@ -20,7 +_,7 @@
+ 
+ public abstract class BlockAttachedEntity extends Entity {
+     private static final Logger LOGGER = LogUtils.getLogger();
+-    private int checkInterval;
++    private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking
+     protected BlockPos pos;
+ 
+     protected BlockAttachedEntity(EntityType<? extends BlockAttachedEntity> entityType, Level level) {
+@@ -38,10 +_,29 @@
+     public void tick() {
+         if (this.level() instanceof ServerLevel serverLevel) {
+             this.checkBelowWorld();
+-            if (this.checkInterval++ == 100) {
++            if (this.checkInterval++ == this.level().spigotConfig.hangingTickFrequency) { // Spigot
+                 this.checkInterval = 0;
+                 if (!this.isRemoved() && !this.survives()) {
+-                    this.discard();
++                    // this.discard();
++                    // CraftBukkit start - fire break events
++                    net.minecraft.world.level.block.state.BlockState material = this.level().getBlockState(this.blockPosition());
++                    org.bukkit.event.hanging.HangingBreakEvent.RemoveCause cause;
++
++                    if (!material.isAir()) {
++                        // TODO: This feels insufficient to catch 100% of suffocation cases
++                        cause = org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.OBSTRUCTION;
++                    } else {
++                        cause = org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.PHYSICS;
++                    }
++
++                    org.bukkit.event.hanging.HangingBreakEvent event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), cause);
++                    this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                    if (this.isRemoved() || event.isCancelled()) {
++                        return;
++                    }
++                    // CraftBukkit end
++                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
+                     this.dropItem(serverLevel, null);
+                 }
+             }
+@@ -74,6 +_,21 @@
+             return false;
+         } else {
+             if (!this.isRemoved()) {
++                // CraftBukkit start - fire break events
++                Entity damager = (!damageSource.isDirect() && damageSource.getEntity() != null) ? damageSource.getEntity() : damageSource.getDirectEntity(); // Paper - fix DamageSource API
++                org.bukkit.event.hanging.HangingBreakEvent event;
++                if (damager != null) {
++                    event = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.ENTITY);
++                } else {
++                    event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION) ? org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.EXPLOSION : org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.DEFAULT);
++                }
++
++                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (this.isRemoved() || event.isCancelled()) {
++                    return true;
++                }
++                // CraftBukkit end
+                 this.kill(level);
+                 this.markHurt();
+                 this.dropItem(level, damageSource.getEntity());
+@@ -91,18 +_,36 @@
+     @Override
+     public void move(MoverType type, Vec3 movement) {
+         if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && movement.lengthSqr() > 0.0) {
+-            this.kill(serverLevel);
+-            this.dropItem(serverLevel, null);
+-        }
+-    }
+-
+-    @Override
+-    public void push(double x, double y, double z) {
+-        if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && x * x + y * y + z * z > 0.0) {
+-            this.kill(serverLevel);
+-            this.dropItem(serverLevel, null);
+-        }
+-    }
++            // CraftBukkit start - fire break events
++            // TODO - Does this need its own cause? Seems to only be triggered by pistons
++            org.bukkit.event.hanging.HangingBreakEvent event = new org.bukkit.event.hanging.HangingBreakEvent((org.bukkit.entity.Hanging) this.getBukkitEntity(), org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.PHYSICS);
++            this.level().getCraftServer().getPluginManager().callEvent(event);
++
++            if (this.isRemoved() || event.isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
++            this.kill(serverLevel);
++            this.dropItem(serverLevel, null);
++        }
++    }
++
++    @Override
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - override correct overload
++        if (false && this.level() instanceof ServerLevel serverLevel && !this.isRemoved() && x * x + y * y + z * z > 0.0) { // CraftBukkit - not needed
++            this.kill(serverLevel);
++            this.dropItem(serverLevel, null);
++        }
++    }
++
++    // CraftBukkit start - selectively save tile position
++    @Override
++    public void addAdditionalSaveData(CompoundTag nbt, boolean includeAll) {
++        if (includeAll) {
++            this.addAdditionalSaveData(nbt);
++        }
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public void addAdditionalSaveData(CompoundTag tag) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch
new file mode 100644
index 0000000000..f6480c2be0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch
@@ -0,0 +1,127 @@
+--- a/net/minecraft/world/entity/decoration/ItemFrame.java
++++ b/net/minecraft/world/entity/decoration/ItemFrame.java
+@@ -49,6 +_,7 @@
+     private static final float HEIGHT = 0.75F;
+     public float dropChance = 1.0F;
+     public boolean fixed;
++    public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames
+ 
+     public ItemFrame(EntityType<? extends ItemFrame> entityType, Level level) {
+         super(entityType, level);
+@@ -88,6 +_,12 @@
+ 
+     @Override
+     protected AABB calculateBoundingBox(BlockPos pos, Direction direction) {
++        // CraftBukkit start - break out BB calc into own method
++        return ItemFrame.calculateBoundingBoxStatic(pos, direction);
++    }
++
++    public static AABB calculateBoundingBoxStatic(BlockPos pos, Direction direction) {
++        // CraftBukkit end
+         float f = 0.46875F;
+         Vec3 vec3 = Vec3.atCenterOf(pos).relative(direction, -0.46875);
+         Direction.Axis axis = direction.getAxis();
+@@ -118,9 +_,9 @@
+     }
+ 
+     @Override
+-    public void push(double x, double y, double z) {
++    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - add push source entity param
+         if (!this.fixed) {
+-            super.push(x, y, z);
++            super.push(x, y, z, pushingEntity); // Paper - add push source entity param
+         }
+     }
+ 
+@@ -149,6 +_,18 @@
+             if (this.isInvulnerableToBase(damageSource)) {
+                 return false;
+             } else if (this.shouldDamageDropItem(damageSource)) {
++                // CraftBukkit start - fire EntityDamageEvent
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false) || this.isRemoved()) {
++                    return true;
++                }
++                // CraftBukkit end
++                // Paper start - Add PlayerItemFrameChangeEvent
++                if (damageSource.getEntity() instanceof Player player) {
++                    var event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE);
++                    if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change
++                    this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false);
++                }
++                // Paper end - Add PlayerItemFrameChangeEvent
+                 this.dropItem(level, damageSource.getEntity(), false);
+                 this.gameEvent(GameEvent.BLOCK_CHANGE, damageSource.getEntity());
+                 this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
+@@ -234,6 +_,14 @@
+         return this.getEntityData().get(DATA_ITEM);
+     }
+ 
++    // Paper start - Fix MC-123848 (spawn item frame drops above block)
++    @Nullable
++    @Override
++    public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ServerLevel serverLevel, ItemStack stack) {
++        return this.spawnAtLocation(serverLevel, stack, this.getDirection() == Direction.DOWN ? -0.6F : 0.0F);
++    }
++    // Paper end
++
+     @Nullable
+     public MapId getFramedMapId(ItemStack stack) {
+         return stack.get(DataComponents.MAP_ID);
+@@ -248,13 +_,19 @@
+     }
+ 
+     public void setItem(ItemStack stack, boolean updateNeighbours) {
++        // CraftBukkit start
++        this.setItem(stack, updateNeighbours, true);
++    }
++
++    public void setItem(ItemStack stack, boolean updateNeighbours, boolean playSound) {
++        // CraftBukkit end
+         if (!stack.isEmpty()) {
+             stack = stack.copyWithCount(1);
+         }
+ 
+         this.onItemChanged(stack);
+         this.getEntityData().set(DATA_ITEM, stack);
+-        if (!stack.isEmpty()) {
++        if (!stack.isEmpty() && updateNeighbours && playSound) { // CraftBukkit // Paper - only play sound when update flag is set
+             this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
+         }
+ 
+@@ -280,6 +_,7 @@
+     }
+ 
+     private void onItemChanged(ItemStack item) {
++        this.cachedMapId = item.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames
+         if (!item.isEmpty() && item.getFrame() != this) {
+             item.setEntityRepresentation(this);
+         }
+@@ -359,7 +_,13 @@
+                     if (savedData != null && savedData.isTrackedCountOverLimit(256)) {
+                         return InteractionResult.FAIL;
+                     } else {
+-                        this.setItem(itemInHand);
++                        // Paper start - Add PlayerItemFrameChangeEvent
++                        io.papermc.paper.event.player.PlayerItemFrameChangeEvent event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemInHand.asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE);
++                        if (!event.callEvent()) {
++                            return InteractionResult.FAIL;
++                        }
++                        this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()));
++                        // Paper end - Add PlayerItemFrameChangeEvent
+                         this.gameEvent(GameEvent.BLOCK_CHANGE, player);
+                         itemInHand.consume(1, player);
+                         return InteractionResult.SUCCESS;
+@@ -368,6 +_,13 @@
+                     return InteractionResult.PASS;
+                 }
+             } else {
++                // Paper start - Add PlayerItemFrameChangeEvent
++                io.papermc.paper.event.player.PlayerItemFrameChangeEvent event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE);
++                if (!event.callEvent()) {
++                    return InteractionResult.FAIL;
++                }
++                setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false);
++                // Paper end - Add PlayerItemFrameChangeEvent
+                 this.playSound(this.getRotateItemSound(), 1.0F, 1.0F);
+                 this.setRotation(this.getRotation() + 1);
+                 this.gameEvent(GameEvent.BLOCK_CHANGE, player);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
index c569c75fde..56d3dea16c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
@@ -1,35 +1,13 @@
 --- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
 +++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
-@@ -8,9 +8,11 @@
- import net.minecraft.network.protocol.Packet;
- import net.minecraft.network.protocol.game.ClientGamePacketListener;
- import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
- import net.minecraft.network.syncher.SynchedEntityData;
- import net.minecraft.server.level.ServerEntity;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.tags.BlockTags;
- import net.minecraft.world.InteractionHand;
-@@ -26,6 +28,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+// CraftBukkit end
- 
- public class LeashFenceKnotEntity extends BlockAttachedEntity {
- 
-@@ -85,6 +90,15 @@
-                 Leashable leashable = (Leashable) iterator.next();
+@@ -81,6 +_,15 @@
  
+             for (Leashable leashable : list) {
                  if (leashable.getLeashHolder() == player) {
 +                    // CraftBukkit start
 +                    if (leashable instanceof Entity leashed) {
-+                        if (CraftEventFactory.callPlayerLeashEntityEvent(leashed, this, player, hand).isCancelled()) {
-+                            ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(leashed, leashable.getLeashHolder()));
++                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(leashed, this, player, hand).isCancelled()) {
++                            ((net.minecraft.server.level.ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(leashed, leashable.getLeashHolder()));
 +                            flag = true; // Also set true when the event is cancelled otherwise it tries to unleash the entities
 +                            continue;
 +                        }
@@ -38,9 +16,9 @@
                      leashable.setLeashedTo(this, true);
                      flag = true;
                  }
-@@ -93,18 +107,43 @@
-             boolean flag1 = false;
+@@ -88,14 +_,39 @@
  
+             boolean flag1 = false;
              if (!flag) {
 -                this.discard();
 -                if (player.getAbilities().instabuild) {
@@ -49,18 +27,14 @@
 +                boolean die = true;
 +                // CraftBukkit end
 +                if (true || player.getAbilities().instabuild) { // CraftBukkit - Process for non-creative as well
-                     Iterator iterator1 = list.iterator();
- 
-                     while (iterator1.hasNext()) {
-                         Leashable leashable1 = (Leashable) iterator1.next();
- 
+                     for (Leashable leashable1 : list) {
                          if (leashable1.isLeashed() && leashable1.getLeashHolder() == this) {
 -                            leashable1.removeLeash();
 +                            // CraftBukkit start
 +                            boolean dropLeash = !player.hasInfiniteMaterials();
 +                            if (leashable1 instanceof Entity leashed) {
 +                                // Paper start - Expand EntityUnleashEvent
-+                                org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash);
++                                org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(leashed, player, hand, dropLeash);
 +                                dropLeash = event.isDropLeash();
 +                                if (event.isCancelled()) {
 +                                    // Paper end - Expand EntityUnleashEvent
@@ -79,7 +53,7 @@
                      }
 +                    // CraftBukkit start
 +                    if (die) {
-+                        this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
++                        this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
 +                    }
 +                    // CraftBukkit end
                  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch
new file mode 100644
index 0000000000..97a17e6cd7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/Painting.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/entity/decoration/Painting.java
++++ b/net/minecraft/world/entity/decoration/Painting.java
+@@ -129,21 +_,31 @@
+ 
+     @Override
+     protected AABB calculateBoundingBox(BlockPos pos, Direction direction) {
++        // CraftBukkit start
++        PaintingVariant variant = (PaintingVariant) this.getVariant().value();
++        return Painting.calculateBoundingBoxStatic(pos, direction, variant.width(), variant.height());
++    }
++
++    public static AABB calculateBoundingBoxStatic(BlockPos pos, Direction direction, int width, int height) {
++        // CraftBukkit end
+         float f = 0.46875F;
+         Vec3 vec3 = Vec3.atCenterOf(pos).relative(direction, -0.46875);
+-        PaintingVariant paintingVariant = this.getVariant().value();
+-        double d = this.offsetForPaintingSize(paintingVariant.width());
+-        double d1 = this.offsetForPaintingSize(paintingVariant.height());
++        // CraftBukkit start
++        double d = Painting.offsetForPaintingSize(width);
++        double d1 = Painting.offsetForPaintingSize(height);
++        // CraftBukkit end
+         Direction counterClockWise = direction.getCounterClockWise();
+         Vec3 vec31 = vec3.relative(counterClockWise, d).relative(Direction.UP, d1);
+         Direction.Axis axis = direction.getAxis();
+-        double d2 = axis == Direction.Axis.X ? 0.0625 : paintingVariant.width();
+-        double d3 = paintingVariant.height();
+-        double d4 = axis == Direction.Axis.Z ? 0.0625 : paintingVariant.width();
++        // CraftBukkit start
++        double d2 = axis == Direction.Axis.X ? 0.0625 : width;
++        double d3 = height;
++        double d4 = axis == Direction.Axis.Z ? 0.0625 : width;
++        // CraftBukkit end
+         return AABB.ofSize(vec31, d2, d3, d4);
+     }
+ 
+-    private double offsetForPaintingSize(int size) {
++    private static double offsetForPaintingSize(int size) { // CraftBukkit - static
+         return size % 2 == 0 ? 0.5 : 0.0;
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ArmorStand.java.patch
deleted file mode 100644
index edb69ea66e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ArmorStand.java.patch
+++ /dev/null
@@ -1,513 +0,0 @@
---- a/net/minecraft/world/entity/decoration/ArmorStand.java
-+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
-@@ -25,7 +25,6 @@
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityDimensions;
- import net.minecraft.world.entity.EntityType;
--import net.minecraft.world.entity.EquipmentSlot;
- import net.minecraft.world.entity.HumanoidArm;
- import net.minecraft.world.entity.LightningBolt;
- import net.minecraft.world.entity.LivingEntity;
-@@ -33,7 +32,6 @@
- import net.minecraft.world.entity.Pose;
- import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
- import net.minecraft.world.entity.ai.attributes.Attributes;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.vehicle.AbstractMinecart;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
-@@ -47,6 +45,14 @@
- import net.minecraft.world.level.material.PushReaction;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.inventory.EquipmentSlot;
-+import org.bukkit.craftbukkit.CraftEquipmentSlot;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
-+// CraftBukkit end
- 
- public class ArmorStand extends LivingEntity {
- 
-@@ -101,9 +107,17 @@
-     public Rotations rightArmPose;
-     public Rotations leftLegPose;
-     public Rotations rightLegPose;
-+    public boolean canMove = true; // Paper
-+    // Paper start - Allow ArmorStands not to tick
-+    public boolean canTick = true;
-+    public boolean canTickSetByAPI = false;
-+    private boolean noTickPoseDirty = false;
-+    private boolean noTickEquipmentDirty = false;
-+    // Paper end - Allow ArmorStands not to tick
- 
-     public ArmorStand(EntityType<? extends ArmorStand> type, Level world) {
-         super(type, world);
-+        if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick
-         this.handItems = NonNullList.withSize(2, ItemStack.EMPTY);
-         this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY);
-         this.headPose = ArmorStand.DEFAULT_HEAD_POSE;
-@@ -123,7 +137,14 @@
-         return createLivingAttributes().add(Attributes.STEP_HEIGHT, 0.0D);
-     }
- 
-+    // CraftBukkit start - SPIGOT-3607, SPIGOT-3637
-     @Override
-+    public float getBukkitYaw() {
-+        return this.getYRot();
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public void refreshDimensions() {
-         double d0 = this.getX();
-         double d1 = this.getY();
-@@ -165,7 +186,7 @@
-     }
- 
-     @Override
--    public ItemStack getItemBySlot(EquipmentSlot slot) {
-+    public ItemStack getItemBySlot(net.minecraft.world.entity.EquipmentSlot slot) {
-         switch (slot.getType()) {
-             case HAND:
-                 return (ItemStack) this.handItems.get(slot.getIndex());
-@@ -177,21 +198,29 @@
-     }
- 
-     @Override
--    public boolean canUseSlot(EquipmentSlot slot) {
--        return slot != EquipmentSlot.BODY && !this.isDisabled(slot);
-+    public boolean canUseSlot(net.minecraft.world.entity.EquipmentSlot slot) {
-+        return slot != net.minecraft.world.entity.EquipmentSlot.BODY && !this.isDisabled(slot);
-+    }
-+
-+    @Override
-+    public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack) {
-+        // CraftBukkit start
-+        this.setItemSlot(slot, stack, false);
-     }
- 
-     @Override
--    public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
--        this.verifyEquippedItem(stack);
--        switch (slot.getType()) {
-+    public void setItemSlot(net.minecraft.world.entity.EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
-+        // CraftBukkit end
-+        this.verifyEquippedItem(itemstack);
-+        switch (enumitemslot.getType()) {
-             case HAND:
--                this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack);
-+                this.onEquipItem(enumitemslot, (ItemStack) this.handItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
-                 break;
-             case HUMANOID_ARMOR:
--                this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack);
-+                this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
-         }
- 
-+        this.noTickEquipmentDirty = true; // Paper - Allow ArmorStands not to tick; Still update equipment
-     }
- 
-     @Override
-@@ -227,6 +256,7 @@
-         }
- 
-         nbt.put("Pose", this.writePose());
-+        if (this.canTickSetByAPI) nbt.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick
-     }
- 
-     @Override
-@@ -261,6 +291,12 @@
-         this.setNoBasePlate(nbt.getBoolean("NoBasePlate"));
-         this.setMarker(nbt.getBoolean("Marker"));
-         this.noPhysics = !this.hasPhysics();
-+        // Paper start - Allow ArmorStands not to tick
-+        if (nbt.contains("Paper.CanTickOverride")) {
-+            this.canTick = nbt.getBoolean("Paper.CanTickOverride");
-+            this.canTickSetByAPI = true;
-+        }
-+        // Paper end - Allow ArmorStands not to tick
-         CompoundTag nbttagcompound2 = nbt.getCompound("Pose");
- 
-         this.readPose(nbttagcompound2);
-@@ -318,7 +354,7 @@
-     }
- 
-     @Override
--    public boolean isPushable() {
-+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
-         return false;
-     }
- 
-@@ -327,6 +363,7 @@
- 
-     @Override
-     protected void pushEntities() {
-+        if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
-         List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS);
-         Iterator iterator = list.iterator();
- 
-@@ -341,7 +378,7 @@
-     }
- 
-     @Override
--    public InteractionResult interactAt(Player player, Vec3 hitPos, InteractionHand hand) {
-+    public InteractionResult interactAt(net.minecraft.world.entity.player.Player player, Vec3 hitPos, InteractionHand hand) {
-         ItemStack itemstack = player.getItemInHand(hand);
- 
-         if (!this.isMarker() && !itemstack.is(Items.NAME_TAG)) {
-@@ -350,11 +387,11 @@
-             } else if (player.level().isClientSide) {
-                 return InteractionResult.SUCCESS_SERVER;
-             } else {
--                EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack);
-+                net.minecraft.world.entity.EquipmentSlot enumitemslot = this.getEquipmentSlotForItem(itemstack);
- 
-                 if (itemstack.isEmpty()) {
--                    EquipmentSlot enumitemslot1 = this.getClickedSlot(hitPos);
--                    EquipmentSlot enumitemslot2 = this.isDisabled(enumitemslot1) ? enumitemslot : enumitemslot1;
-+                    net.minecraft.world.entity.EquipmentSlot enumitemslot1 = this.getClickedSlot(hitPos);
-+                    net.minecraft.world.entity.EquipmentSlot enumitemslot2 = this.isDisabled(enumitemslot1) ? enumitemslot : enumitemslot1;
- 
-                     if (this.hasItemInSlot(enumitemslot2) && this.swapItem(player, enumitemslot2, itemstack, hand)) {
-                         return InteractionResult.SUCCESS_SERVER;
-@@ -364,7 +401,7 @@
-                         return InteractionResult.FAIL;
-                     }
- 
--                    if (enumitemslot.getType() == EquipmentSlot.Type.HAND && !this.showArms()) {
-+                    if (enumitemslot.getType() == net.minecraft.world.entity.EquipmentSlot.Type.HAND && !this.showArms()) {
-                         return InteractionResult.FAIL;
-                     }
- 
-@@ -380,39 +417,57 @@
-         }
-     }
- 
--    private EquipmentSlot getClickedSlot(Vec3 hitPos) {
--        EquipmentSlot enumitemslot = EquipmentSlot.MAINHAND;
-+    private net.minecraft.world.entity.EquipmentSlot getClickedSlot(Vec3 hitPos) {
-+        net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.world.entity.EquipmentSlot.MAINHAND;
-         boolean flag = this.isSmall();
-         double d0 = hitPos.y / (double) (this.getScale() * this.getAgeScale());
--        EquipmentSlot enumitemslot1 = EquipmentSlot.FEET;
-+        net.minecraft.world.entity.EquipmentSlot enumitemslot1 = net.minecraft.world.entity.EquipmentSlot.FEET;
- 
-         if (d0 >= 0.1D && d0 < 0.1D + (flag ? 0.8D : 0.45D) && this.hasItemInSlot(enumitemslot1)) {
--            enumitemslot = EquipmentSlot.FEET;
--        } else if (d0 >= 0.9D + (flag ? 0.3D : 0.0D) && d0 < 0.9D + (flag ? 1.0D : 0.7D) && this.hasItemInSlot(EquipmentSlot.CHEST)) {
--            enumitemslot = EquipmentSlot.CHEST;
--        } else if (d0 >= 0.4D && d0 < 0.4D + (flag ? 1.0D : 0.8D) && this.hasItemInSlot(EquipmentSlot.LEGS)) {
--            enumitemslot = EquipmentSlot.LEGS;
--        } else if (d0 >= 1.6D && this.hasItemInSlot(EquipmentSlot.HEAD)) {
--            enumitemslot = EquipmentSlot.HEAD;
--        } else if (!this.hasItemInSlot(EquipmentSlot.MAINHAND) && this.hasItemInSlot(EquipmentSlot.OFFHAND)) {
--            enumitemslot = EquipmentSlot.OFFHAND;
-+            enumitemslot = net.minecraft.world.entity.EquipmentSlot.FEET;
-+        } else if (d0 >= 0.9D + (flag ? 0.3D : 0.0D) && d0 < 0.9D + (flag ? 1.0D : 0.7D) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.CHEST)) {
-+            enumitemslot = net.minecraft.world.entity.EquipmentSlot.CHEST;
-+        } else if (d0 >= 0.4D && d0 < 0.4D + (flag ? 1.0D : 0.8D) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.LEGS)) {
-+            enumitemslot = net.minecraft.world.entity.EquipmentSlot.LEGS;
-+        } else if (d0 >= 1.6D && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.HEAD)) {
-+            enumitemslot = net.minecraft.world.entity.EquipmentSlot.HEAD;
-+        } else if (!this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND) && this.hasItemInSlot(net.minecraft.world.entity.EquipmentSlot.OFFHAND)) {
-+            enumitemslot = net.minecraft.world.entity.EquipmentSlot.OFFHAND;
-         }
- 
-         return enumitemslot;
-     }
- 
--    public boolean isDisabled(EquipmentSlot slot) {
--        return (this.disabledSlots & 1 << slot.getFilterBit(0)) != 0 || slot.getType() == EquipmentSlot.Type.HAND && !this.showArms();
-+    public boolean isDisabled(net.minecraft.world.entity.EquipmentSlot slot) {
-+        return (this.disabledSlots & 1 << slot.getFilterBit(0)) != 0 || slot.getType() == net.minecraft.world.entity.EquipmentSlot.Type.HAND && !this.showArms();
-     }
- 
--    private boolean swapItem(Player player, EquipmentSlot slot, ItemStack stack, InteractionHand hand) {
-+    private boolean swapItem(net.minecraft.world.entity.player.Player player, net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack, InteractionHand hand) {
-         ItemStack itemstack1 = this.getItemBySlot(slot);
- 
-         if (!itemstack1.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(8)) != 0) {
-             return false;
-         } else if (itemstack1.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(16)) != 0) {
-             return false;
--        } else if (player.hasInfiniteMaterials() && itemstack1.isEmpty() && !stack.isEmpty()) {
-+            // CraftBukkit start
-+        } else {
-+            org.bukkit.inventory.ItemStack armorStandItem = CraftItemStack.asCraftMirror(itemstack1);
-+            org.bukkit.inventory.ItemStack playerHeldItem = CraftItemStack.asCraftMirror(stack);
-+
-+            Player player1 = (Player) player.getBukkitEntity();
-+            org.bukkit.entity.ArmorStand self = (org.bukkit.entity.ArmorStand) this.getBukkitEntity();
-+
-+            EquipmentSlot slot1 = CraftEquipmentSlot.getSlot(slot);
-+            EquipmentSlot hand1 = CraftEquipmentSlot.getHand(hand);
-+            PlayerArmorStandManipulateEvent armorStandManipulateEvent = new PlayerArmorStandManipulateEvent(player1, self, playerHeldItem, armorStandItem, slot1, hand1);
-+            this.level().getCraftServer().getPluginManager().callEvent(armorStandManipulateEvent);
-+
-+            if (armorStandManipulateEvent.isCancelled()) {
-+                return true;
-+            }
-+
-+        if (player.hasInfiniteMaterials() && itemstack1.isEmpty() && !stack.isEmpty()) {
-+            // CraftBukkit end
-             this.setItemSlot(slot, stack.copyWithCount(1));
-             return true;
-         } else if (!stack.isEmpty() && stack.getCount() > 1) {
-@@ -427,6 +482,7 @@
-             player.setItemInHand(hand, itemstack1);
-             return true;
-         }
-+        } // CraftBukkit
-     }
- 
-     @Override
-@@ -436,12 +492,24 @@
-         } else if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && source.getEntity() instanceof Mob) {
-             return false;
-         } else if (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
--            this.kill(world);
-+            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) {
-+                return false;
-+            }
-+            this.kill(world, source); // CraftBukkit
-+            // CraftBukkit end
-             return false;
--        } else if (!this.isInvulnerableTo(world, source) && !this.invisible && !this.isMarker()) {
-+        } else if (!this.isInvulnerableTo(world, source) && (true || !this.invisible) && !this.isMarker()) { // CraftBukkit
-+            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, true, this.invisible)) {
-+                return false;
-+            }
-+            // CraftBukkit end
-             if (source.is(DamageTypeTags.IS_EXPLOSION)) {
--                this.brokenByAnything(world, source);
--                this.kill(world);
-+                // Paper start - avoid duplicate event call
-+                org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, source);
-+                if (!event.isCancelled()) this.kill(source, false); // CraftBukkit
-+                // Paper end
-                 return false;
-             } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
-                 if (this.isOnFire()) {
-@@ -463,8 +531,8 @@
-                 } else {
-                     Entity entity = source.getEntity();
- 
--                    if (entity instanceof Player) {
--                        Player entityhuman = (Player) entity;
-+                    if (entity instanceof net.minecraft.world.entity.player.Player) {
-+                        net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity;
- 
-                         if (!entityhuman.getAbilities().mayBuild) {
-                             return false;
-@@ -474,7 +542,7 @@
-                     if (source.isCreativePlayer()) {
-                         this.playBrokenSound();
-                         this.showBreakingParticles();
--                        this.kill(world);
-+                        this.kill(world, source); // CraftBukkit
-                         return true;
-                     } else {
-                         long i = world.getGameTime();
-@@ -484,9 +552,9 @@
-                             this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
-                             this.lastHit = i;
-                         } else {
--                            this.brokenByPlayer(world, source);
-+                            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(world, source); // Paper
-                             this.showBreakingParticles();
--                            this.kill(world);
-+                            if (!event.isCancelled()) this.kill(source, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...)
-                         }
- 
-                         return true;
-@@ -536,7 +604,10 @@
-         f1 -= amount;
-         if (f1 <= 0.5F) {
-             this.brokenByAnything(world, damageSource);
--            this.kill(world);
-+            // Paper start - avoid duplicate event call
-+            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(world, damageSource);
-+            if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit
-+            // Paper end
-         } else {
-             this.setHealth(f1);
-             this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
-@@ -544,17 +615,17 @@
- 
-     }
- 
--    private void brokenByPlayer(ServerLevel world, DamageSource damageSource) {
-+    private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper
-         ItemStack itemstack = new ItemStack(Items.ARMOR_STAND);
- 
-         itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName());
--        Block.popResource(this.level(), this.blockPosition(), itemstack);
--        this.brokenByAnything(world, damageSource);
-+        this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior
-+        return this.brokenByAnything(world, damageSource); // Paper
-     }
- 
--    private void brokenByAnything(ServerLevel world, DamageSource damageSource) {
-+    private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel world, DamageSource damageSource) { // Paper
-         this.playBrokenSound();
--        this.dropAllDeathLoot(world, damageSource);
-+        // this.dropAllDeathLoot(worldserver, damagesource); // CraftBukkit - moved down
- 
-         ItemStack itemstack;
-         int i;
-@@ -562,7 +633,7 @@
-         for (i = 0; i < this.handItems.size(); ++i) {
-             itemstack = (ItemStack) this.handItems.get(i);
-             if (!itemstack.isEmpty()) {
--                Block.popResource(this.level(), this.blockPosition().above(), itemstack);
-+                this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly
-                 this.handItems.set(i, ItemStack.EMPTY);
-             }
-         }
-@@ -570,15 +641,16 @@
-         for (i = 0; i < this.armorItems.size(); ++i) {
-             itemstack = (ItemStack) this.armorItems.get(i);
-             if (!itemstack.isEmpty()) {
--                Block.popResource(this.level(), this.blockPosition().above(), itemstack);
-+                this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly
-                 this.armorItems.set(i, ItemStack.EMPTY);
-             }
-         }
-+        return this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above // Paper
- 
-     }
- 
-     private void playBrokenSound() {
--        this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.ARMOR_STAND_BREAK, this.getSoundSource(), 1.0F, 1.0F);
-+        this.level().playSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.ARMOR_STAND_BREAK, this.getSoundSource(), 1.0F, 1.0F);
-     }
- 
-     @Override
-@@ -609,7 +681,29 @@
- 
-     @Override
-     public void tick() {
-+        // Paper start - Allow ArmorStands not to tick
-+        if (!this.canTick) {
-+            if (this.noTickPoseDirty) {
-+                this.noTickPoseDirty = false;
-+                this.updatePose();
-+            }
-+
-+            if (this.noTickEquipmentDirty) {
-+                this.noTickEquipmentDirty = false;
-+                this.detectEquipmentUpdatesPublic();
-+            }
-+
-+            return;
-+        }
-+        // Paper end - Allow ArmorStands not to tick
-+
-         super.tick();
-+        // Paper start - Allow ArmorStands not to tick
-+        updatePose();
-+    }
-+
-+    public void updatePose() {
-+        // Paper end - Allow ArmorStands not to tick
-         Rotations vector3f = (Rotations) this.entityData.get(ArmorStand.DATA_HEAD_POSE);
- 
-         if (!this.headPose.equals(vector3f)) {
-@@ -664,9 +758,31 @@
-         return this.isSmall();
-     }
- 
-+    // CraftBukkit start
-     @Override
-+    public boolean shouldDropExperience() {
-+        return true; // MC-157395, SPIGOT-5193 even baby (small) armor stands should drop
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public void kill(ServerLevel world) {
--        this.remove(Entity.RemovalReason.KILLED);
-+        // CraftBukkit start - pass DamageSource for kill
-+        this.kill(world, null);
-+    }
-+
-+    public void kill(ServerLevel worldserver, DamageSource damageSource) {
-+        // Paper start - make cancellable
-+        this.kill(damageSource, true);
-+    }
-+    public void kill(DamageSource damageSource, boolean callEvent) {
-+        if (callEvent) {
-+            org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event
-+            if (event.isCancelled()) return;
-+        }
-+        // Paper end
-+        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
-+        // CraftBukkit end
-         this.gameEvent(GameEvent.ENTITY_DIE);
-     }
- 
-@@ -730,31 +846,37 @@
-     public void setHeadPose(Rotations angle) {
-         this.headPose = angle;
-         this.entityData.set(ArmorStand.DATA_HEAD_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public void setBodyPose(Rotations angle) {
-         this.bodyPose = angle;
-         this.entityData.set(ArmorStand.DATA_BODY_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public void setLeftArmPose(Rotations angle) {
-         this.leftArmPose = angle;
-         this.entityData.set(ArmorStand.DATA_LEFT_ARM_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public void setRightArmPose(Rotations angle) {
-         this.rightArmPose = angle;
-         this.entityData.set(ArmorStand.DATA_RIGHT_ARM_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public void setLeftLegPose(Rotations angle) {
-         this.leftLegPose = angle;
-         this.entityData.set(ArmorStand.DATA_LEFT_LEG_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public void setRightLegPose(Rotations angle) {
-         this.rightLegPose = angle;
-         this.entityData.set(ArmorStand.DATA_RIGHT_LEG_POSE, angle);
-+        this.noTickPoseDirty = true; // Paper - Allow updates when not ticking
-     }
- 
-     public Rotations getHeadPose() {
-@@ -788,7 +910,7 @@
- 
-     @Override
-     public boolean skipAttackInteraction(Entity attacker) {
--        return attacker instanceof Player && !this.level().mayInteract((Player) attacker, this.blockPosition());
-+        return attacker instanceof net.minecraft.world.entity.player.Player && !this.level().mayInteract((net.minecraft.world.entity.player.Player) attacker, this.blockPosition());
-     }
- 
-     @Override
-@@ -882,4 +1004,13 @@
-     public boolean canBeSeenByAnyone() {
-         return !this.isInvisible() && !this.isMarker();
-     }
-+
-+    // Paper start
-+    @Override
-+    public void move(net.minecraft.world.entity.MoverType type, Vec3 movement) {
-+        if (this.canMove) {
-+            super.move(type, movement);
-+        }
-+    }
-+    // Paper end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
deleted file mode 100644
index 2f102c86b9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
+++ /dev/null
@@ -1,140 +0,0 @@
---- a/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
-+++ b/net/minecraft/world/entity/decoration/BlockAttachedEntity.java
-@@ -2,9 +2,6 @@
- 
- import com.mojang.logging.LogUtils;
- import javax.annotation.Nullable;
--import net.minecraft.core.BlockPos;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityType;
-@@ -15,13 +12,24 @@
- import net.minecraft.world.level.Explosion;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.server.level.ServerLevel;
-+// CraftBukkit start
-+import net.minecraft.tags.DamageTypeTags;
-+import org.bukkit.entity.Hanging;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
-+import org.bukkit.event.hanging.HangingBreakEvent;
-+// CraftBukkit end
- 
- public abstract class BlockAttachedEntity extends Entity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
--    private int checkInterval;
-+    private int checkInterval; { this.checkInterval = this.getId() % this.level().spigotConfig.hangingTickFrequency; } // Paper - Perf: offset item frame ticking
-     protected BlockPos pos;
- 
-     protected BlockAttachedEntity(EntityType<? extends BlockAttachedEntity> type, Level world) {
-@@ -41,10 +49,28 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             this.checkBelowWorld();
--            if (this.checkInterval++ == 100) {
-+            if (this.checkInterval++ == this.level().spigotConfig.hangingTickFrequency) { // Spigot
-                 this.checkInterval = 0;
-                 if (!this.isRemoved() && !this.survives()) {
--                    this.discard();
-+                    // CraftBukkit start - fire break events
-+                    BlockState material = this.level().getBlockState(this.blockPosition());
-+                    HangingBreakEvent.RemoveCause cause;
-+
-+                    if (!material.isAir()) {
-+                        // TODO: This feels insufficient to catch 100% of suffocation cases
-+                        cause = HangingBreakEvent.RemoveCause.OBSTRUCTION;
-+                    } else {
-+                        cause = HangingBreakEvent.RemoveCause.PHYSICS;
-+                    }
-+
-+                    HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), cause);
-+                    this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                    if (this.isRemoved() || event.isCancelled()) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-+                    this.discard(EntityRemoveEvent.Cause.DROP); // CraftBukkit - add Bukkit remove cause
-                     this.dropItem(worldserver, (Entity) null);
-                 }
-             }
-@@ -81,6 +107,22 @@
-             return false;
-         } else {
-             if (!this.isRemoved()) {
-+                // CraftBukkit start - fire break events
-+                Entity damager = (!source.isDirect() && source.getEntity() != null) ? source.getEntity() : source.getDirectEntity(); // Paper - fix DamageSource API
-+                HangingBreakEvent event;
-+                if (damager != null) {
-+                    event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY);
-+                } else {
-+                    event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.DEFAULT);
-+                }
-+
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (this.isRemoved() || event.isCancelled()) {
-+                    return true;
-+                }
-+                // CraftBukkit end
-+
-                 this.kill(world);
-                 this.markHurt();
-                 this.dropItem(world, source.getEntity());
-@@ -101,6 +143,16 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (!this.isRemoved() && movement.lengthSqr() > 0.0D) {
-+                // CraftBukkit start - fire break events
-+                // TODO - Does this need its own cause? Seems to only be triggered by pistons
-+                HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.PHYSICS);
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (this.isRemoved() || event.isCancelled()) {
-+                    return;
-+                }
-+                // CraftBukkit end
-+
-                 this.kill(worldserver);
-                 this.dropItem(worldserver, (Entity) null);
-             }
-@@ -109,11 +161,11 @@
-     }
- 
-     @Override
--    public void push(double deltaX, double deltaY, double deltaZ) {
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - override correct overload
-         Level world = this.level();
- 
-         if (world instanceof ServerLevel worldserver) {
--            if (!this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) {
-+            if (false && !this.isRemoved() && deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ > 0.0D) { // CraftBukkit - not needed
-                 this.kill(worldserver);
-                 this.dropItem(worldserver, (Entity) null);
-             }
-@@ -121,7 +173,16 @@
- 
-     }
- 
-+    // CraftBukkit start - selectively save tile position
-     @Override
-+    public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
-+        if (includeAll) {
-+            this.addAdditionalSaveData(nbttagcompound);
-+        }
-+    }
-+    // CraftBukkit end
-+
-+    @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         BlockPos blockposition = this.getPos();
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ItemFrame.java.patch
deleted file mode 100644
index f6d7c47bb4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/ItemFrame.java.patch
+++ /dev/null
@@ -1,150 +0,0 @@
---- a/net/minecraft/world/entity/decoration/ItemFrame.java
-+++ b/net/minecraft/world/entity/decoration/ItemFrame.java
-@@ -1,6 +1,7 @@
- package net.minecraft.world.entity.decoration;
- 
- import javax.annotation.Nullable;
-+import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
- import net.minecraft.core.component.DataComponents;
-@@ -50,6 +51,7 @@
-     private static final float HEIGHT = 0.75F;
-     public float dropChance;
-     public boolean fixed;
-+    public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames
- 
-     public ItemFrame(EntityType<? extends ItemFrame> type, Level world) {
-         super(type, world);
-@@ -91,9 +93,15 @@
- 
-     @Override
-     protected AABB calculateBoundingBox(BlockPos pos, Direction side) {
-+        // CraftBukkit start - break out BB calc into own method
-+        return ItemFrame.calculateBoundingBoxStatic(pos, side);
-+    }
-+
-+    public static AABB calculateBoundingBoxStatic(BlockPos blockposition, Direction enumdirection) {
-+        // CraftBukkit end
-         float f = 0.46875F;
--        Vec3 vec3d = Vec3.atCenterOf(pos).relative(side, -0.46875D);
--        Direction.Axis enumdirection_enumaxis = side.getAxis();
-+        Vec3 vec3d = Vec3.atCenterOf(blockposition).relative(enumdirection, -0.46875D);
-+        Direction.Axis enumdirection_enumaxis = enumdirection.getAxis();
-         double d0 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : 0.75D;
-         double d1 = enumdirection_enumaxis == Direction.Axis.Y ? 0.0625D : 0.75D;
-         double d2 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : 0.75D;
-@@ -123,9 +131,9 @@
-     }
- 
-     @Override
--    public void push(double deltaX, double deltaY, double deltaZ) {
-+    public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { // Paper - add push source entity param
-         if (!this.fixed) {
--            super.push(deltaX, deltaY, deltaZ);
-+            super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param
-         }
- 
-     }
-@@ -155,6 +163,18 @@
-             if (this.isInvulnerableToBase(source)) {
-                 return false;
-             } else if (this.shouldDamageDropItem(source)) {
-+                // CraftBukkit start - fire EntityDamageEvent
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false) || this.isRemoved()) {
-+                    return true;
-+                }
-+                // CraftBukkit end
-+                // Paper start - Add PlayerItemFrameChangeEvent
-+                if (source.getEntity() instanceof Player player) {
-+                    var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE);
-+                    if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change
-+                    this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false);
-+                }
-+                // Paper end - Add PlayerItemFrameChangeEvent
-                 this.dropItem(world, source.getEntity(), false);
-                 this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity());
-                 this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
-@@ -251,7 +271,15 @@
- 
-     public ItemStack getItem() {
-         return (ItemStack) this.getEntityData().get(ItemFrame.DATA_ITEM);
-+    }
-+
-+    // Paper start - Fix MC-123848 (spawn item frame drops above block)
-+    @Nullable
-+    @Override
-+    public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ServerLevel serverLevel, ItemStack stack) {
-+        return this.spawnAtLocation(serverLevel, stack, this.getDirection() == Direction.DOWN ? -0.6F : 0.0F);
-     }
-+    // Paper end
- 
-     @Nullable
-     public MapId getFramedMapId(ItemStack stack) {
-@@ -267,17 +295,23 @@
-     }
- 
-     public void setItem(ItemStack value, boolean update) {
--        if (!value.isEmpty()) {
--            value = value.copyWithCount(1);
-+        // CraftBukkit start
-+        this.setItem(value, update, true);
-+    }
-+
-+    public void setItem(ItemStack itemstack, boolean flag, boolean playSound) {
-+        // CraftBukkit end
-+        if (!itemstack.isEmpty()) {
-+            itemstack = itemstack.copyWithCount(1);
-         }
- 
--        this.onItemChanged(value);
--        this.getEntityData().set(ItemFrame.DATA_ITEM, value);
--        if (!value.isEmpty()) {
-+        this.onItemChanged(itemstack);
-+        this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack);
-+        if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set
-             this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
-         }
- 
--        if (update && this.pos != null) {
-+        if (flag && this.pos != null) {
-             this.level().updateNeighbourForOutputSignal(this.pos, Blocks.AIR);
-         }
- 
-@@ -301,6 +335,7 @@
-     }
- 
-     private void onItemChanged(ItemStack stack) {
-+        this.cachedMapId = stack.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames
-         if (!stack.isEmpty() && stack.getFrame() != this) {
-             stack.setEntityRepresentation(this);
-         }
-@@ -386,7 +421,13 @@
-                     if (worldmap != null && worldmap.isTrackedCountOverLimit(256)) {
-                         return InteractionResult.FAIL;
-                     } else {
--                        this.setItem(itemstack);
-+                        // Paper start - Add PlayerItemFrameChangeEvent
-+                        PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE);
-+                        if (!event.callEvent()) {
-+                            return InteractionResult.FAIL;
-+                        }
-+                        this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()));
-+                        // Paper end - Add PlayerItemFrameChangeEvent
-                         this.gameEvent(GameEvent.BLOCK_CHANGE, player);
-                         itemstack.consume(1, player);
-                         return InteractionResult.SUCCESS;
-@@ -395,6 +436,13 @@
-                     return InteractionResult.PASS;
-                 }
-             } else {
-+                // Paper start - Add PlayerItemFrameChangeEvent
-+                PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE);
-+                if (!event.callEvent()) {
-+                    return InteractionResult.FAIL;
-+                }
-+                setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false);
-+                // Paper end - Add PlayerItemFrameChangeEvent
-                 this.playSound(this.getRotateItemSound(), 1.0F, 1.0F);
-                 this.setRotation(this.getRotation() + 1);
-                 this.gameEvent(GameEvent.BLOCK_CHANGE, player);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/Painting.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/Painting.java.patch
deleted file mode 100644
index 122466b765..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/decoration/Painting.java.patch
+++ /dev/null
@@ -1,54 +0,0 @@
---- a/net/minecraft/world/entity/decoration/Painting.java
-+++ b/net/minecraft/world/entity/decoration/Painting.java
-@@ -72,7 +72,7 @@
-     public static Optional<Painting> create(Level world, BlockPos pos, Direction facing) {
-         Painting entitypainting = new Painting(world, pos);
-         List<Holder<PaintingVariant>> list = new ArrayList();
--        Iterable iterable = world.registryAccess().lookupOrThrow(Registries.PAINTING_VARIANT).getTagOrEmpty(PaintingVariantTags.PLACEABLE);
-+        Iterable<Holder<PaintingVariant>> iterable = world.registryAccess().lookupOrThrow(Registries.PAINTING_VARIANT).getTagOrEmpty(PaintingVariantTags.PLACEABLE); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(list);
-         iterable.forEach(list::add);
-@@ -138,22 +138,32 @@
- 
-     @Override
-     protected AABB calculateBoundingBox(BlockPos pos, Direction side) {
--        float f = 0.46875F;
--        Vec3 vec3d = Vec3.atCenterOf(pos).relative(side, -0.46875D);
-+        // CraftBukkit start
-         PaintingVariant paintingvariant = (PaintingVariant) this.getVariant().value();
--        double d0 = this.offsetForPaintingSize(paintingvariant.width());
--        double d1 = this.offsetForPaintingSize(paintingvariant.height());
--        Direction enumdirection1 = side.getCounterClockWise();
-+        return Painting.calculateBoundingBoxStatic(pos, side, paintingvariant.width(), paintingvariant.height());
-+    }
-+
-+    public static AABB calculateBoundingBoxStatic(BlockPos blockposition, Direction enumdirection, int width, int height) {
-+        // CraftBukkit end
-+        float f = 0.46875F;
-+        Vec3 vec3d = Vec3.atCenterOf(blockposition).relative(enumdirection, -0.46875D);
-+        // CraftBukkit start
-+        double d0 = Painting.offsetForPaintingSize(width);
-+        double d1 = Painting.offsetForPaintingSize(height);
-+        // CraftBukkit end
-+        Direction enumdirection1 = enumdirection.getCounterClockWise();
-         Vec3 vec3d1 = vec3d.relative(enumdirection1, d0).relative(Direction.UP, d1);
--        Direction.Axis enumdirection_enumaxis = side.getAxis();
--        double d2 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : (double) paintingvariant.width();
--        double d3 = (double) paintingvariant.height();
--        double d4 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : (double) paintingvariant.width();
-+        Direction.Axis enumdirection_enumaxis = enumdirection.getAxis();
-+        // CraftBukkit start
-+        double d2 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : (double) width;
-+        double d3 = (double) height;
-+        double d4 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : (double) width;
-+        // CraftBukkit end
- 
-         return AABB.ofSize(vec3d1, d2, d3, d4);
-     }
- 
--    private double offsetForPaintingSize(int length) {
-+    private static double offsetForPaintingSize(int length) { // CraftBukkit - static
-         return length % 2 == 0 ? 0.5D : 0.0D;
-     }
- 

From 767215bf9be7717e49bbb2226968fbff2377bd25 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 14 Dec 2024 23:42:27 +0000
Subject: [PATCH 132/285] net/minecraft/world/level

---
 .../world/level/BaseCommandBlock.java.patch   |  37 +-
 .../world/level/BaseSpawner.java.patch        | 156 ++++++++
 .../world/level/BlockGetter.java.patch        |  76 ++++
 .../minecraft/world/level/ChunkPos.java.patch |  25 +-
 .../world/level/ClipContext.java.patch        |  11 +
 .../world/level/EmptyBlockGetter.java.patch   |   7 +-
 .../world/level/EntityGetter.java.patch       |  19 +-
 .../world/level/GameRules.java.patch          | 298 +++++++++++++++
 .../minecraft/world/level/Level.java.patch    | 349 ++++++++----------
 .../world/level/LevelAccessor.java.patch      |   9 +
 .../world/level/LevelReader.java.patch        |   4 +-
 .../world/level/LevelWriter.java.patch        |   2 +-
 .../world/level/NaturalSpawner.java.patch     | 234 ++++++++++++
 .../level/PathNavigationRegion.java.patch     |  30 +-
 .../world/level/ServerExplosion.java.patch    | 236 ++++++------
 .../level/ServerLevelAccessor.java.patch      |   5 +-
 .../world/level/StructureManager.java.patch   |  17 +-
 .../world/level/BaseSpawner.java.patch        | 167 ---------
 .../world/level/BlockGetter.java.patch        |  90 -----
 .../world/level/ClipContext.java.patch        |  20 -
 .../world/level/GameRules.java.patch          | 321 ----------------
 .../world/level/LevelAccessor.java.patch      |   9 -
 .../world/level/NaturalSpawner.java.patch     | 212 -----------
 23 files changed, 1110 insertions(+), 1224 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/BaseCommandBlock.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/ChunkPos.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/EmptyBlockGetter.java.patch (95%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/EntityGetter.java.patch (88%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/Level.java.patch (70%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/LevelReader.java.patch (81%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/LevelWriter.java.patch (95%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/PathNavigationRegion.java.patch (71%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/ServerExplosion.java.patch (59%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/ServerLevelAccessor.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/StructureManager.java.patch (67%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/BaseSpawner.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/BlockGetter.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/ClipContext.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/GameRules.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/LevelAccessor.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/NaturalSpawner.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/BaseCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/level/BaseCommandBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
index 3c091f17a7..dc6271b85f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/BaseCommandBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/BaseCommandBlock.java
 +++ b/net/minecraft/world/level/BaseCommandBlock.java
-@@ -33,6 +33,10 @@
+@@ -32,6 +_,11 @@
      private String command = "";
      @Nullable
      private Component customName;
@@ -8,27 +8,28 @@
 +    @Override
 +    public abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper);
 +    // CraftBukkit end
++
  
-     public BaseCommandBlock() {}
- 
-@@ -132,7 +136,7 @@
- 
-                         });
- 
--                        minecraftserver.getCommands().performPrefixedCommand(commandlistenerwrapper, this.command);
-+                        minecraftserver.getCommands().dispatchServerCommand(commandlistenerwrapper, this.command); // CraftBukkit
-                     } catch (Throwable throwable) {
-                         CrashReport crashreport = CrashReport.forThrowable(throwable, "Executing command block");
-                         CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Command to be executed");
-@@ -174,6 +178,7 @@
+     public int getSuccessCount() {
+         return this.successCount;
+@@ -126,7 +_,7 @@
+                             this.successCount++;
+                         }
+                     });
+-                    server.getCommands().performPrefixedCommand(commandSourceStack, this.command);
++                    server.getCommands().dispatchServerCommand(commandSourceStack, this.command);  // CraftBukkit
+                 } catch (Throwable var6) {
+                     CrashReport crashReport = CrashReport.forThrowable(var6, "Executing command block");
+                     CrashReportCategory crashReportCategory = crashReport.addCategory("Command to be executed");
+@@ -162,6 +_,7 @@
      @Override
-     public void sendSystemMessage(Component message) {
+     public void sendSystemMessage(Component component) {
          if (this.trackOutput) {
 +            org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks
-             SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT;
-             Date date = new Date();
- 
-@@ -200,7 +205,7 @@
+             this.lastOutput = Component.literal("[" + TIME_FORMAT.format(new Date()) + "] ").append(component);
+             this.onUpdated();
+         }
+@@ -184,7 +_,7 @@
      }
  
      public InteractionResult usedBy(Player player) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
new file mode 100644
index 0000000000..b806243653
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
@@ -0,0 +1,156 @@
+--- a/net/minecraft/world/level/BaseSpawner.java
++++ b/net/minecraft/world/level/BaseSpawner.java
+@@ -44,9 +_,11 @@
+     public int maxNearbyEntities = 6;
+     public int requiredPlayerRange = 16;
+     public int spawnRange = 4;
++    private int tickDelay = 0; // Paper - Configurable mob spawner tick rate
+ 
+     public void setEntityId(EntityType<?> type, @Nullable Level level, RandomSource random, BlockPos pos) {
+         this.getOrCreateNextSpawnData(level, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());
++        this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282
+     }
+ 
+     public boolean isNearPlayer(Level level, BlockPos pos) {
+@@ -73,13 +_,19 @@
+     }
+ 
+     public void serverTick(ServerLevel serverLevel, BlockPos pos) {
++        if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick
++        // Paper start - Configurable mob spawner tick rate
++        if (spawnDelay > 0 && --tickDelay > 0) return;
++        tickDelay = serverLevel.paperConfig().tickRates.mobSpawner;
++        if (tickDelay == -1) { return; } // If disabled
++        // Paper end - Configurable mob spawner tick rate
+         if (this.isNearPlayer(serverLevel, pos)) {
+-            if (this.spawnDelay == -1) {
++            if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate
+                 this.delay(serverLevel, pos);
+             }
+ 
+             if (this.spawnDelay > 0) {
+-                this.spawnDelay--;
++                this.spawnDelay -= tickDelay; // Paper - Configurable mob spawner tick rate
+             } else {
+                 boolean flag = false;
+                 RandomSource random = serverLevel.getRandom();
+@@ -113,6 +_,21 @@
+                             continue;
+                         }
+ 
++                        // Paper start - PreCreatureSpawnEvent
++                        com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent(
++                            io.papermc.paper.util.MCUtil.toLocation(serverLevel, d, d1, d2),
++                            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()),
++                            io.papermc.paper.util.MCUtil.toLocation(serverLevel, pos)
++                        );
++                        if (!event.callEvent()) {
++                            flag = true;
++                            if (event.shouldAbortSpawn()) {
++                                break;
++                            }
++                            continue;
++                        }
++                        // Paper end - PreCreatureSpawnEvent
++
+                         Entity entity = EntityType.loadEntityRecursive(entityToSpawn, serverLevel, EntitySpawnReason.SPAWNER, entity1 -> {
+                             entity1.moveTo(d, d1, d2, entity1.getYRot(), entity1.getXRot());
+                             return entity1;
+@@ -133,6 +_,7 @@
+                             return;
+                         }
+ 
++                        entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag
+                         entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), random.nextFloat() * 360.0F, 0.0F);
+                         if (entity instanceof Mob mob) {
+                             if (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER)
+@@ -147,9 +_,22 @@
+                             }
+ 
+                             nextSpawnData.getEquipment().ifPresent(mob::equip);
++                            // Spigot Start
++                            if (mob.level().spigotConfig.nerfSpawnerMobs) {
++                                mob.aware = false;
++                            }
++                                                        // Spigot End
+                         }
+ 
+-                        if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
++                        entity.spawnedViaMobSpawner = true; // Paper
++                        entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason
++                        flag = true; // Paper
++                        // CraftBukkit start
++                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
++                                continue;
++                        }
++                        if (!serverLevel.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
++                            // CraftBukkit end
+                             this.delay(serverLevel, pos);
+                             return;
+                         }
+@@ -160,7 +_,7 @@
+                             ((Mob)entity).spawnAnim();
+                         }
+ 
+-                        flag = true;
++                        //flag = true; // Paper - moved up above cancellable event
+                     }
+                 }
+ 
+@@ -184,7 +_,13 @@
+     }
+ 
+     public void load(@Nullable Level level, BlockPos pos, CompoundTag tag) {
+-        this.spawnDelay = tag.getShort("Delay");
++        // Paper start - use larger int if set
++        if (tag.contains("Paper.Delay")) {
++            this.spawnDelay = tag.getInt("Paper.Delay");
++        } else {
++            this.spawnDelay = tag.getShort("Delay");
++        }
++        // Paper end
+         boolean flag = tag.contains("SpawnData", 10);
+         if (flag) {
+             SpawnData spawnData = SpawnData.CODEC
+@@ -205,9 +_,15 @@
+             this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData());
+         }
+ 
++        // Paper start - use ints if set
++        if (tag.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
++            this.minSpawnDelay = tag.getInt("Paper.MinSpawnDelay");
++            this.maxSpawnDelay = tag.getInt("Paper.MaxSpawnDelay");
++            this.spawnCount = tag.getShort("SpawnCount");
++        } else // Paper end
+         if (tag.contains("MinSpawnDelay", 99)) {
+-            this.minSpawnDelay = tag.getShort("MinSpawnDelay");
+-            this.maxSpawnDelay = tag.getShort("MaxSpawnDelay");
++            this.minSpawnDelay = tag.getInt("MinSpawnDelay"); // Paper - short -> int
++            this.maxSpawnDelay = tag.getInt("MaxSpawnDelay"); // Paper - short -> int
+             this.spawnCount = tag.getShort("SpawnCount");
+         }
+ 
+@@ -224,9 +_,20 @@
+     }
+ 
+     public CompoundTag save(CompoundTag tag) {
+-        tag.putShort("Delay", (short)this.spawnDelay);
+-        tag.putShort("MinSpawnDelay", (short)this.minSpawnDelay);
+-        tag.putShort("MaxSpawnDelay", (short)this.maxSpawnDelay);
++        // Paper start
++        if (spawnDelay > Short.MAX_VALUE) {
++            tag.putInt("Paper.Delay", this.spawnDelay);
++        }
++        tag.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay));
++
++        if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) {
++            tag.putInt("Paper.MinSpawnDelay", this.minSpawnDelay);
++            tag.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay);
++        }
++
++        tag.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay));
++        tag.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay));
++        // Paper end
+         tag.putShort("SpawnCount", (short)this.spawnCount);
+         tag.putShort("MaxNearbyEntities", (short)this.maxNearbyEntities);
+         tag.putShort("RequiredPlayerRange", (short)this.requiredPlayerRange);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
new file mode 100644
index 0000000000..4ba53de04b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/level/BlockGetter.java
++++ b/net/minecraft/world/level/BlockGetter.java
+@@ -11,6 +_,7 @@
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.util.Mth;
++import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+ import net.minecraft.world.level.block.entity.BlockEntityType;
+ import net.minecraft.world.level.block.state.BlockState;
+@@ -33,6 +_,16 @@
+ 
+     BlockState getBlockState(BlockPos pos);
+ 
++    // Paper start - if loaded util
++    @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition);
++
++    default @Nullable Block getBlockIfLoaded(BlockPos blockposition) {
++        BlockState type = this.getBlockStateIfLoaded(blockposition);
++        return type == null ? null : type.getBlock();
++    }
++    @Nullable FluidState getFluidIfLoaded(BlockPos blockposition);
++    // Paper end
++
+     FluidState getFluidState(BlockPos pos);
+ 
+     default int getLightEmission(BlockPos pos) {
+@@ -66,10 +_,25 @@
+         );
+     }
+ 
+-    default BlockHitResult clip(ClipContext context) {
+-        return traverseBlocks(context.getFrom(), context.getTo(), context, (traverseContext, traversePos) -> {
+-            BlockState blockState = this.getBlockState(traversePos);
+-            FluidState fluidState = this.getFluidState(traversePos);
++    // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
++    default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
++        // Paper start - Add predicate for blocks when raytracing
++        return clip(raytrace1, blockposition, null);
++    }
++
++    default BlockHitResult clip(ClipContext traverseContext, BlockPos traversePos, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
++        // Paper end - Add predicate for blocks when raytracing
++        // Paper start - Prevent raytrace from loading chunks
++        BlockState blockState = this.getBlockStateIfLoaded(traversePos);
++        if (blockState == null) {
++            // copied the last function parameter (listed below)
++            Vec3 vec3d = traverseContext.getFrom().subtract(traverseContext.getTo());
++
++            return BlockHitResult.miss(traverseContext.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
++        }
++        // Paper end - Prevent raytrace from loading chunks
++        if (blockState.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate
++            FluidState fluidState = blockState.getFluidState(); // Paper - Perf: don't need to go to world state again
+             Vec3 from = traverseContext.getFrom();
+             Vec3 to = traverseContext.getTo();
+             VoxelShape blockShape = traverseContext.getBlockShape(blockState, this, traversePos);
+@@ -79,6 +_,18 @@
+             double d = blockHitResult == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult.getLocation());
+             double d1 = blockHitResult1 == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult1.getLocation());
+             return d <= d1 ? blockHitResult : blockHitResult1;
++    }
++    // CraftBukkit end
++
++    default BlockHitResult clip(ClipContext context) {
++        // Paper start - Add predicate for blocks when raytracing
++        return clip(context, (java.util.function.Predicate<org.bukkit.block.Block>) null);
++    }
++
++    default BlockHitResult clip(ClipContext context, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
++        // Paper end - Add predicate for blocks when raytracing
++        return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> {
++            return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing
+         }, failContext -> {
+             Vec3 vec3 = failContext.getFrom().subtract(failContext.getTo());
+             return BlockHitResult.miss(failContext.getTo(), Direction.getApproximateNearest(vec3.x, vec3.y, vec3.z), BlockPos.containing(failContext.getTo()));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/ChunkPos.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/level/ChunkPos.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
index be7a951951..1b795bb313 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/ChunkPos.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/ChunkPos.java
 +++ b/net/minecraft/world/level/ChunkPos.java
-@@ -46,6 +46,7 @@
+@@ -46,6 +_,7 @@
      public static final int REGION_MAX_INDEX = 31;
      public final int x;
      public final int z;
@@ -8,10 +8,10 @@
      private static final int HASH_A = 1664525;
      private static final int HASH_C = 1013904223;
      private static final int HASH_Z_XOR = -559038737;
-@@ -53,16 +54,19 @@
-     public ChunkPos(int x, int z) {
+@@ -53,16 +_,19 @@
+     public ChunkPos(int x, int y) {
          this.x = x;
-         this.z = z;
+         this.z = y;
 +        this.longKey = asLong(this.x, this.z); // Paper
      }
  
@@ -21,19 +21,10 @@
 +        this.longKey = asLong(this.x, this.z); // Paper
      }
  
-     public ChunkPos(long pos) {
-         this.x = (int)pos;
-         this.z = (int)(pos >> 32);
+     public ChunkPos(long packedPos) {
+         this.x = (int)packedPos;
+         this.z = (int)(packedPos >> 32);
 +        this.longKey = asLong(this.x, this.z); // Paper
      }
  
-     public static ChunkPos minFromRegion(int x, int z) {
-@@ -74,7 +78,7 @@
-     }
- 
-     public long toLong() {
--        return asLong(this.x, this.z);
-+        return longKey; // Paper
-     }
- 
-     public static long asLong(int chunkX, int chunkZ) {
+     public static ChunkPos minFromRegion(int chunkX, int chunkZ) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch
new file mode 100644
index 0000000000..85d8652279
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/ClipContext.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/ClipContext.java
++++ b/net/minecraft/world/level/ClipContext.java
+@@ -21,7 +_,7 @@
+     private final CollisionContext collisionContext;
+ 
+     public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, Entity entity) {
+-        this(from, to, block, fluid, CollisionContext.of(entity));
++        this(from, to, block, fluid, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit
+     }
+ 
+     public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, CollisionContext collisionContext) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/EmptyBlockGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/level/EmptyBlockGetter.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch
index b4743ab862..dfb2f644ab 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/EmptyBlockGetter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockGetter.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/EmptyBlockGetter.java
 +++ b/net/minecraft/world/level/EmptyBlockGetter.java
-@@ -17,7 +17,19 @@
+@@ -17,6 +_,18 @@
          return null;
      }
  
 +    // Paper start - If loaded util
-     @Override
++    @Override
 +    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
 +        return Fluids.EMPTY.defaultFluidState();
 +    }
@@ -16,7 +16,6 @@
 +    }
 +    // Paper end
 +
-+    @Override
+     @Override
      public BlockState getBlockState(BlockPos pos) {
          return Blocks.AIR.defaultBlockState();
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/EntityGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch
similarity index 88%
rename from paper-server/patches/unapplied/net/minecraft/world/level/EntityGetter.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch
index 09a6133595..edf6c35510 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/EntityGetter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/EntityGetter.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/EntityGetter.java
 +++ b/net/minecraft/world/level/EntityGetter.java
-@@ -71,6 +71,11 @@
+@@ -71,6 +_,12 @@
          }
      }
  
@@ -9,10 +9,11 @@
 +        return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate);
 +    }
 +    // Paper end - Affects Spawning API
++
      @Nullable
-     default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate<Entity> targetPredicate) {
+     default Player getNearestPlayer(double x, double y, double z, double distance, @Nullable Predicate<Entity> predicate) {
          double d = -1.0;
-@@ -89,6 +94,28 @@
+@@ -89,6 +_,28 @@
          return player;
      }
  
@@ -39,10 +40,10 @@
 +    // Paper end
 +
      @Nullable
-     default Player getNearestPlayer(Entity entity, double maxDistance) {
-         return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, false);
-@@ -100,6 +127,20 @@
-         return this.getNearestPlayer(x, y, z, maxDistance, predicate);
+     default Player getNearestPlayer(Entity entity, double distance) {
+         return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), distance, false);
+@@ -100,6 +_,20 @@
+         return this.getNearestPlayer(x, y, z, distance, predicate);
      }
  
 +    // Paper start - Affects Spawning API
@@ -59,10 +60,10 @@
 +    }
 +    // Paper end - Affects Spawning API
 +
-     default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) {
+     default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) {
          for (Player player : this.players()) {
              if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
-@@ -124,4 +165,11 @@
+@@ -124,4 +_,11 @@
  
          return null;
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
new file mode 100644
index 0000000000..2fd51d80b6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
@@ -0,0 +1,298 @@
+--- a/net/minecraft/world/level/GameRules.java
++++ b/net/minecraft/world/level/GameRules.java
+@@ -32,6 +_,14 @@
+ import org.slf4j.Logger;
+ 
+ public class GameRules {
++    // Paper start - allow disabling gamerule limits
++    private static final boolean DISABLE_LIMITS = Boolean.getBoolean("paper.disableGameRuleLimits");
++
++    private static int limit(final int limit, final int unlimited) {
++        return DISABLE_LIMITS ? unlimited : limit;
++    }
++    // Paper end - allow disabling gamerule limits
++
+     public static final int DEFAULT_RANDOM_TICK_SPEED = 3;
+     static final Logger LOGGER = LogUtils.getLogger();
+     private static final Map<GameRules.Key<?>, GameRules.Type<?>> GAME_RULE_TYPES = Maps.newTreeMap(Comparator.comparing(entry -> entry.id));
+@@ -81,10 +_,10 @@
+         "sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)
+     );
+     public static final GameRules.Key<GameRules.BooleanValue> RULE_REDUCEDDEBUGINFO = register(
+-        "reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (server, value) -> {
++        "reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
+             byte b = (byte)(value.get() ? 22 : 23);
+ 
+-            for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
++            for (ServerPlayer serverPlayer : level.players()) {
+                 serverPlayer.connection.send(new ClientboundEntityEventPacket(serverPlayer, b));
+             }
+         })
+@@ -108,8 +_,8 @@
+         "doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)
+     );
+     public static final GameRules.Key<GameRules.BooleanValue> RULE_LIMITED_CRAFTING = register(
+-        "doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (server, value) -> {
+-            for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
++        "doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
++            for (ServerPlayer serverPlayer : level.players()) {
+                 serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LIMITED_CRAFTING, value.get() ? 1.0F : 0.0F));
+             }
+         })
+@@ -133,8 +_,8 @@
+         "doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)
+     );
+     public static final GameRules.Key<GameRules.BooleanValue> RULE_DO_IMMEDIATE_RESPAWN = register(
+-        "doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (server, value) -> {
+-            for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) {
++        "doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (level, value) -> { // Paper - rename param to match changes
++            for (ServerPlayer serverPlayer : level.players()) {
+                 serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, value.get() ? 1.0F : 0.0F));
+             }
+         })
+@@ -205,16 +_,17 @@
+     public static final GameRules.Key<GameRules.IntegerValue> RULE_MINECART_MAX_SPEED = register(
+         "minecartMaxSpeed",
+         GameRules.Category.MISC,
+-        GameRules.IntegerValue.create(8, 1, 1000, FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (server, value) -> {})
++        GameRules.IntegerValue.create(8, 1, limit(1000, Integer.MAX_VALUE), FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (server, value) -> {}) // Paper - allow disabling gamerule limits
+     );
+     public static final GameRules.Key<GameRules.IntegerValue> RULE_SPAWN_CHUNK_RADIUS = register(
+-        "spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, 32, FeatureFlagSet.of(), (server, value) -> {
+-            ServerLevel serverLevel = server.overworld();
++        "spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, limit(32, Integer.MAX_VALUE), FeatureFlagSet.of(), (level, value) -> { // Paper - allow disabling gamerule limits - also, rename param
++            ServerLevel serverLevel = level; // CraftBukkit - per-world
+             serverLevel.setDefaultSpawnPos(serverLevel.getSharedSpawnPos(), serverLevel.getSharedSpawnAngle());
+         })
+     );
+     private final Map<GameRules.Key<?>, GameRules.Value<?>> rules;
+     private final FeatureFlagSet enabledFeatures;
++    private final GameRules.Value<?>[] gameruleArray; // Paper - Perf: Use array for gamerule storage
+ 
+     private static <T extends GameRules.Value<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
+         GameRules.Key<T> key = new GameRules.Key<>(name, category);
+@@ -242,10 +_,21 @@
+     private GameRules(Map<GameRules.Key<?>, GameRules.Value<?>> rules, FeatureFlagSet enabledFeatures) {
+         this.rules = rules;
+         this.enabledFeatures = enabledFeatures;
++
++        // Paper start - Perf: Use array for gamerule storage
++        int arraySize = GameRules.Key.lastGameRuleIndex + 1;
++        GameRules.Value<?>[] values = new GameRules.Value[arraySize];
++
++        for (Entry<GameRules.Key<?>, GameRules.Value<?>> entry : rules.entrySet()) {
++            values[entry.getKey().gameRuleIndex] = entry.getValue();
++        }
++
++        this.gameruleArray = values;
++        // Paper end - Perf: Use array for gamerule storage
+     }
+ 
+     public <T extends GameRules.Value<T>> T getRule(GameRules.Key<T> key) {
+-        T value = (T)this.rules.get(key);
++        T value = key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage
+         if (value == null) {
+             throw new IllegalArgumentException("Tried to access invalid game rule");
+         } else {
+@@ -286,13 +_,13 @@
+         }
+     }
+ 
+-    public void assignFrom(GameRules rules, @Nullable MinecraftServer server) {
+-        rules.rules.keySet().forEach(key -> this.assignCap((GameRules.Key<?>)key, rules, server));
++    public void assignFrom(GameRules rules, @Nullable ServerLevel level) { // CraftBukkit - per-world
++        rules.rules.keySet().forEach(key -> this.assignCap((GameRules.Key<?>)key, rules, level)); // CraftBukkit - per-world
+     }
+ 
+-    private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> key, GameRules rules, @Nullable MinecraftServer server) {
++    private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> key, GameRules rules, @Nullable ServerLevel level) { // CraftBukkit - per-world
+         T rule = rules.getRule(key);
+-        this.<T>getRule(key).setFrom(rule, server);
++        this.<T>getRule(key).setFrom(rule, level); // CraftBukkit - per-world
+     }
+ 
+     public boolean getBoolean(GameRules.Key<GameRules.BooleanValue> key) {
+@@ -306,7 +_,7 @@
+     public static class BooleanValue extends GameRules.Value<GameRules.BooleanValue> {
+         private boolean value;
+ 
+-        static GameRules.Type<GameRules.BooleanValue> create(boolean defaultValue, BiConsumer<MinecraftServer, GameRules.BooleanValue> changeListener) {
++        static GameRules.Type<GameRules.BooleanValue> create(boolean defaultValue, BiConsumer<ServerLevel, GameRules.BooleanValue> changeListener) { // CraftBukkit - per-world
+             return new GameRules.Type<>(
+                 BoolArgumentType::bool,
+                 type -> new GameRules.BooleanValue(type, defaultValue),
+@@ -326,17 +_,21 @@
+         }
+ 
+         @Override
+-        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
+-            this.value = BoolArgumentType.getBool(context, paramName);
++        // Paper start - Add WorldGameRuleChangeEvent
++        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<BooleanValue> gameRuleKey) {
++                io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Boolean>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, paramName)));
++                if (!event.callEvent()) return;
++                this.value = Boolean.parseBoolean(event.getValue());
++        // Paper end - Add WorldGameRuleChangeEvent
+         }
+ 
+         public boolean get() {
+             return this.value;
+         }
+ 
+-        public void set(boolean value, @Nullable MinecraftServer server) {
++        public void set(boolean value, @Nullable ServerLevel level) {  // CraftBukkit - per-world
+             this.value = value;
+-            this.onChanged(server);
++            this.onChanged(level); // CraftBukkit - per-world
+         }
+ 
+         @Override
+@@ -345,7 +_,7 @@
+         }
+ 
+         @Override
+-        protected void deserialize(String value) {
++        public void deserialize(String value) { // PAIL - protected->public
+             this.value = Boolean.parseBoolean(value);
+         }
+ 
+@@ -365,9 +_,9 @@
+         }
+ 
+         @Override
+-        public void setFrom(GameRules.BooleanValue value, @Nullable MinecraftServer server) {
++        public void setFrom(GameRules.BooleanValue value, @Nullable ServerLevel level) {  // CraftBukkit - per-world
+             this.value = value.value;
+-            this.onChanged(server);
++            this.onChanged(level); // CraftBukkit - per-world
+         }
+     }
+ 
+@@ -405,7 +_,7 @@
+     public static class IntegerValue extends GameRules.Value<GameRules.IntegerValue> {
+         private int value;
+ 
+-        private static GameRules.Type<GameRules.IntegerValue> create(int defaultValue, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeListener) {
++        private static GameRules.Type<GameRules.IntegerValue> create(int defaultValue, BiConsumer<ServerLevel, GameRules.IntegerValue> changeListener) { // CraftBukkit - per-world
+             return new GameRules.Type<>(
+                 IntegerArgumentType::integer,
+                 type -> new GameRules.IntegerValue(type, defaultValue),
+@@ -416,7 +_,7 @@
+         }
+ 
+         static GameRules.Type<GameRules.IntegerValue> create(
+-            int defaultValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeListener
++            int defaultValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<ServerLevel, GameRules.IntegerValue> changeListener // CraftBukkit - per-world
+         ) {
+             return new GameRules.Type<>(
+                 () -> IntegerArgumentType.integer(min, max),
+@@ -437,17 +_,21 @@
+         }
+ 
+         @Override
+-        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
+-            this.value = IntegerArgumentType.getInteger(context, paramName);
++        // Paper start - Add WorldGameRuleChangeEvent
++        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<IntegerValue> gameRuleKey) {
++            io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Integer>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, paramName)));
++            if (!event.callEvent()) return;
++            this.value = Integer.parseInt(event.getValue());
++            // Paper end - Add WorldGameRuleChangeEvent
+         }
+ 
+         public int get() {
+             return this.value;
+         }
+ 
+-        public void set(int value, @Nullable MinecraftServer server) {
++        public void set(int value, @Nullable ServerLevel level) { // CraftBukkit - per-world
+             this.value = value;
+-            this.onChanged(server);
++            this.onChanged(level) ;// CraftBukkit - per-world
+         }
+ 
+         @Override
+@@ -456,7 +_,7 @@
+         }
+ 
+         @Override
+-        protected void deserialize(String value) {
++        public void deserialize(String value) { // PAIL - protected->public
+             this.value = safeParse(value);
+         }
+ 
+@@ -498,13 +_,17 @@
+         }
+ 
+         @Override
+-        public void setFrom(GameRules.IntegerValue value, @Nullable MinecraftServer server) {
++        public void setFrom(GameRules.IntegerValue value, @Nullable ServerLevel level) { // CraftBukkit - per-world
+             this.value = value.value;
+-            this.onChanged(server);
++            this.onChanged(level); // CraftBukkit - per-world
+         }
+     }
+ 
+     public static final class Key<T extends GameRules.Value<T>> {
++        // Paper start - Perf: Use array for gamerule storage
++        public static int lastGameRuleIndex = 0;
++        public final int gameRuleIndex = lastGameRuleIndex++;
++        // Paper end - Perf: Use array for gamerule storage
+         final String id;
+         private final GameRules.Category category;
+ 
+@@ -544,14 +_,14 @@
+     public static class Type<T extends GameRules.Value<T>> {
+         final Supplier<ArgumentType<?>> argument;
+         private final Function<GameRules.Type<T>, T> constructor;
+-        final BiConsumer<MinecraftServer, T> callback;
++        final BiConsumer<ServerLevel, T> callback; // CraftBukkit - per-world
+         private final GameRules.VisitorCaller<T> visitorCaller;
+         final FeatureFlagSet requiredFeatures;
+ 
+         Type(
+             Supplier<ArgumentType<?>> argument,
+             Function<GameRules.Type<T>, T> constructor,
+-            BiConsumer<MinecraftServer, T> callback,
++            BiConsumer<ServerLevel, T> callback, // CraftBukkit - per-world
+             GameRules.VisitorCaller<T> visitorCaller,
+             FeatureFlagSet requiredFeature
+         ) {
+@@ -586,20 +_,20 @@
+             this.type = type;
+         }
+ 
+-        protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName);
++        protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<T> gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
+ 
+-        public void setFromArgument(CommandContext<CommandSourceStack> context, String paramName) {
+-            this.updateFromArgument(context, paramName);
+-            this.onChanged(context.getSource().getServer());
++        public void setFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<T> gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent
++            this.updateFromArgument(context, paramName, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
++            this.onChanged(context.getSource().getLevel());
+         }
+ 
+-        public void onChanged(@Nullable MinecraftServer server) {
+-            if (server != null) {
+-                this.type.callback.accept(server, this.getSelf());
++        public void onChanged(@Nullable ServerLevel level) { // CraftBukkit - per-world
++            if (level != null) { // CraftBukkit - per-world
++                this.type.callback.accept(level, this.getSelf()); // CraftBukkit - per-world
+             }
+         }
+ 
+-        protected abstract void deserialize(String value);
++        public abstract void deserialize(String value); // PAIL - private->public
+ 
+         public abstract String serialize();
+ 
+@@ -614,7 +_,7 @@
+ 
+         protected abstract T copy();
+ 
+-        public abstract void setFrom(T value, @Nullable MinecraftServer server);
++        public abstract void setFrom(T value, @Nullable ServerLevel level); // CraftBukkit - per-world
+     }
+ 
+     interface VisitorCaller<T extends GameRules.Value<T>> {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
similarity index 70%
rename from paper-server/patches/unapplied/net/minecraft/world/level/Level.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index 6a3ac30d97..c9dd5027af 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -25,8 +25,10 @@
+@@ -24,8 +_,10 @@
  import net.minecraft.network.protocol.Packet;
  import net.minecraft.resources.ResourceKey;
  import net.minecraft.resources.ResourceLocation;
@@ -11,7 +11,7 @@
  import net.minecraft.sounds.SoundEvent;
  import net.minecraft.sounds.SoundEvents;
  import net.minecraft.sounds.SoundSource;
-@@ -43,6 +45,7 @@
+@@ -42,6 +_,7 @@
  import net.minecraft.world.entity.Entity;
  import net.minecraft.world.entity.boss.EnderDragonPart;
  import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
@@ -19,22 +19,7 @@
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.item.ItemStack;
  import net.minecraft.world.item.alchemy.PotionBrewing;
-@@ -57,12 +60,14 @@
- import net.minecraft.world.level.block.entity.FuelValues;
- import net.minecraft.world.level.block.entity.TickingBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.border.BorderChangeListener;
- import net.minecraft.world.level.border.WorldBorder;
- import net.minecraft.world.level.chunk.ChunkAccess;
- import net.minecraft.world.level.chunk.ChunkSource;
- import net.minecraft.world.level.chunk.LevelChunk;
- import net.minecraft.world.level.chunk.status.ChunkStatus;
- import net.minecraft.world.level.dimension.DimensionType;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.entity.EntityTypeTest;
- import net.minecraft.world.level.entity.LevelEntityGetter;
- import net.minecraft.world.level.gameevent.GameEvent;
-@@ -81,6 +86,25 @@
+@@ -79,6 +_,27 @@
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.scores.Scoreboard;
  
@@ -46,6 +31,8 @@
 +import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
 +import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
 +import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
++import net.minecraft.world.level.border.BorderChangeListener;
++import net.minecraft.world.level.dimension.LevelStem;
 +import org.bukkit.Bukkit;
 +import org.bukkit.craftbukkit.CraftServer;
 +import org.bukkit.craftbukkit.CraftWorld;
@@ -58,9 +45,9 @@
 +// CraftBukkit end
 +
  public abstract class Level implements LevelAccessor, AutoCloseable {
- 
      public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
-@@ -94,7 +118,7 @@
+     public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
+@@ -91,7 +_,7 @@
      public static final int TICKS_PER_DAY = 24000;
      public static final int MAX_ENTITY_SPAWN_Y = 20000000;
      public static final int MIN_ENTITY_SPAWN_Y = -20000000;
@@ -69,21 +56,15 @@
      protected final NeighborUpdater neighborUpdater;
      private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
      private boolean tickingBlockEntities;
-@@ -121,23 +145,91 @@
+@@ -117,6 +_,61 @@
      private final DamageSources damageSources;
      private long subTickCount;
  
--    protected Level(WritableLevelData properties, ResourceKey<Level> registryRef, RegistryAccess registryManager, Holder<DimensionType> dimensionEntry, boolean isClient, boolean debugWorld, long seed, int maxChainedNeighborUpdates) {
--        this.levelData = properties;
--        this.dimensionTypeRegistration = dimensionEntry;
--        final DimensionType dimensionmanager = (DimensionType) dimensionEntry.value();
 +    // CraftBukkit start Added the following
 +    private final CraftWorld world;
 +    public boolean pvpMode;
 +    public org.bukkit.generator.ChunkGenerator generator;
- 
--        this.dimension = registryRef;
--        this.isClientSide = isClient;
++
 +    public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
 +    public boolean captureBlockStates = false;
 +    public boolean captureTreeGeneration = false;
@@ -134,7 +115,21 @@
 +
 +    public abstract ResourceKey<LevelStem> getTypeKey();
 +
-+    protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config
+     protected Level(
+         WritableLevelData levelData,
+         ResourceKey<Level> dimension,
+@@ -125,8 +_,26 @@
+         boolean isClientSide,
+         boolean isDebug,
+         long biomeZoomSeed,
+-        int maxChainedNeighborUpdates
++        int maxChainedNeighborUpdates,
++        org.bukkit.generator.ChunkGenerator gen, // CraftBukkit
++        org.bukkit.generator.BiomeProvider biomeProvider, // CraftBukkit
++        org.bukkit.World.Environment env, // CraftBukkit
++        java.util.function.Function<org.spigotmc.SpigotWorldConfig, // Spigot - create per world config
++        io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator // Paper - create paper world config
+     ) {
 +        this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
 +        this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
 +        this.generator = gen;
@@ -148,42 +143,30 @@
 +        }
 +
 +        // CraftBukkit end
-+        this.levelData = worlddatamutable;
-+        this.dimensionTypeRegistration = holder;
-+        final DimensionType dimensionmanager = (DimensionType) holder.value();
-+
-+        this.dimension = resourcekey;
-+        this.isClientSide = flag;
-         if (dimensionmanager.coordinateScale() != 1.0D) {
--            this.worldBorder = new WorldBorder(this) {
-+            this.worldBorder = new WorldBorder() { // CraftBukkit - decompile error
+         this.levelData = levelData;
+         this.dimensionTypeRegistration = dimensionTypeRegistration;
+         final DimensionType dimensionType = dimensionTypeRegistration.value();
+@@ -136,12 +_,12 @@
+             this.worldBorder = new WorldBorder() {
                  @Override
                  public double getCenterX() {
--                    return super.getCenterX() / dimensionmanager.coordinateScale();
+-                    return super.getCenterX() / dimensionType.coordinateScale();
 +                    return super.getCenterX(); // CraftBukkit
                  }
  
                  @Override
                  public double getCenterZ() {
--                    return super.getCenterZ() / dimensionmanager.coordinateScale();
+-                    return super.getCenterZ() / dimensionType.coordinateScale();
 +                    return super.getCenterZ(); // CraftBukkit
                  }
              };
          } else {
-@@ -145,13 +237,90 @@
-         }
- 
-         this.thread = Thread.currentThread();
--        this.biomeManager = new BiomeManager(this, seed);
--        this.isDebug = debugWorld;
--        this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
--        this.registryAccess = registryManager;
--        this.damageSources = new DamageSources(registryManager);
-+        this.biomeManager = new BiomeManager(this, i);
-+        this.isDebug = flag1;
-+        this.neighborUpdater = new CollectingNeighborUpdater(this, j);
-+        this.registryAccess = iregistrycustom;
-+        this.damageSources = new DamageSources(iregistrycustom);
+@@ -154,7 +_,86 @@
+         this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
+         this.registryAccess = registryAccess;
+         this.damageSources = new DamageSources(registryAccess);
+-    }
++
 +        // CraftBukkit start
 +        this.getWorldBorder().world = (ServerLevel) this;
 +        // From PlayerList.setPlayerFileData
@@ -222,8 +205,8 @@
 +        // CraftBukkit end
 +        this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
 +        this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
-     }
- 
++    }
++
 +    // Paper start - Cancel hit for vanished players
 +    // ret true if no collision
 +    public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision,
@@ -263,10 +246,10 @@
 +        return true;
 +    }
 +    // Paper end - Cancel hit for vanished players
+ 
      @Override
      public boolean isClientSide() {
-         return this.isClientSide;
-@@ -163,6 +332,13 @@
+@@ -167,6 +_,13 @@
          return null;
      }
  
@@ -278,9 +261,9 @@
 +    // Paper end
 +
      public boolean isInWorldBounds(BlockPos pos) {
-         return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos);
+         return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
      }
-@@ -172,25 +348,87 @@
+@@ -176,25 +_,88 @@
      }
  
      private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
@@ -299,7 +282,7 @@
  
      @Override
 -    public LevelChunk getChunk(int chunkX, int chunkZ) {
--        return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
+-        return (LevelChunk)this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
 +    public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
 +        // Paper start - Perf: make sure loaded chunks get the inlined variant of this function
 +        net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
@@ -309,11 +292,11 @@
 +        }
 +        return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
 +        // Paper end - Perf: make sure loaded chunks get the inlined variant of this function
-     }
- 
++    }
++
 +    // Paper start - if loaded
-     @Nullable
-     @Override
++    @Nullable
++    @Override
 +    public final ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
 +        return ((ServerLevel)this).chunkSource.getChunkAtIfLoadedImmediately(x, z);
 +    }
@@ -364,18 +347,19 @@
 +    //  reduces need to do isLoaded before getType
 +    public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos blockposition) {
 +        return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null;
-+    }
-+
-+    @Override
-     public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-+        // Paper end
-         ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create);
+     }
  
-         if (ichunkaccess == null && create) {
-@@ -207,6 +445,22 @@
+     @Nullable
+     @Override
+     public ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
++        // Paper end
+         ChunkAccess chunk = this.getChunkSource().getChunk(x, z, chunkStatus, requireChunk);
+         if (chunk == null && requireChunk) {
+             throw new IllegalStateException("Should always be able to create a chunk!");
+@@ -210,6 +_,22 @@
  
      @Override
-     public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
+     public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) {
 +        // CraftBukkit start - tree generation
 +        if (this.captureTreeGeneration) {
 +            // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
@@ -395,12 +379,11 @@
          if (this.isOutsideBuildHeight(pos)) {
              return false;
          } else if (!this.isClientSide && this.isDebug()) {
-@@ -214,44 +468,125 @@
+@@ -217,11 +_,28 @@
          } else {
-             LevelChunk chunk = this.getChunkAt(pos);
+             LevelChunk chunkAt = this.getChunkAt(pos);
              Block block = state.getBlock();
--            BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0);
- 
+-            BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0);
 +            // CraftBukkit start - capture blockstates
 +            boolean captured = false;
 +            if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) {
@@ -411,9 +394,9 @@
 +            }
 +            // CraftBukkit end
 +
-+            BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
++            BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
 +
-             if (iblockdata1 == null) {
+             if (blockState == null) {
 +                // CraftBukkit start - remove blockstate if failed (or the same)
 +                if (this.captureBlockStates && captured) {
 +                    this.capturedBlockStates.remove(pos);
@@ -421,69 +404,35 @@
 +                // CraftBukkit end
                  return false;
              } else {
-                 BlockState iblockdata2 = this.getBlockState(pos);
- 
--                if (iblockdata2 == state) {
+                 BlockState blockState1 = this.getBlockState(pos);
 +                /*
-+                if (iblockdata2 == iblockdata) {
-                     if (iblockdata1 != iblockdata2) {
--                        this.setBlocksDirty(pos, iblockdata1, iblockdata2);
-+                        this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
-                     }
+                 if (blockState1 == state) {
+                     if (blockState != blockState1) {
+                         this.setBlocksDirty(pos, blockState, blockState1);
+@@ -249,12 +_,76 @@
  
--                    if ((flags & 2) != 0 && (!this.isClientSide || (flags & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
--                        this.sendBlockUpdated(pos, iblockdata1, state, flags);
-+                    if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) {
-+                        this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
-                     }
- 
--                    if ((flags & 1) != 0) {
--                        this.blockUpdated(pos, iblockdata1.getBlock());
--                        if (!this.isClientSide && state.hasAnalogOutputSignal()) {
--                            this.updateNeighbourForOutputSignal(pos, block);
-+                    if ((i & 1) != 0) {
-+                        this.blockUpdated(blockposition, iblockdata1.getBlock());
-+                        if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
-+                            this.updateNeighbourForOutputSignal(blockposition, block);
-                         }
-                     }
- 
--                    if ((flags & 16) == 0 && maxUpdateDepth > 0) {
--                        int k = flags & -34;
-+                    if ((i & 16) == 0 && j > 0) {
-+                        int k = i & -34;
- 
--                        iblockdata1.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
--                        state.updateNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
--                        state.updateIndirectNeighbourShapes(this, pos, k, maxUpdateDepth - 1);
-+                        iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
-+                        iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
-+                        iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
-                     }
- 
--                    this.onBlockStateChange(pos, iblockdata1, iblockdata2);
-+                    this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
+                     this.onBlockStateChange(pos, blockState, blockState1);
                  }
 +                */
- 
++
 +                // CraftBukkit start
 +                if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
 +                    // Modularize client and physic updates
 +                    // Spigot start
 +                    try {
-+                        this.notifyAndUpdatePhysics(pos, chunk, iblockdata1, state, iblockdata2, flags, maxUpdateDepth);
++                        this.notifyAndUpdatePhysics(pos, chunkAt, blockState, state, blockState1, flags, recursionLeft);
 +                    } catch (StackOverflowError ex) {
 +                        Level.lastPhysicsProblem = new BlockPos(pos);
 +                    }
 +                    // Spigot end
 +                }
 +                // CraftBukkit end
-+
+ 
                  return true;
-+            }
-+        }
-+    }
-+
+             }
+         }
+     }
+ 
 +    // CraftBukkit start - Split off from above in order to directly send client and physic updates
 +    public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, BlockState actualBlock, int i, int j) {
 +        BlockState iblockdata = newBlock;
@@ -520,60 +469,59 @@
 +                }
 +                // CraftBukkit end
 +                if (!cancelledUpdates) { // Paper - Fix block place logic
-+                iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
-+                iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
++                    iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
++                    iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
 +                } // Paper - Fix block place logic
-             }
++            }
 +
 +            // CraftBukkit start - SPIGOT-5710
 +            if (!this.preventPoiUpdated) {
 +                this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
 +            }
 +            // CraftBukkit end
-         }
-     }
++        }
++    }
 +    // CraftBukkit end
++
+     public void onBlockStateChange(BlockPos pos, BlockState blockState, BlockState newState) {
+     }
  
-     public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {}
- 
-@@ -270,15 +605,33 @@
+@@ -271,13 +_,31 @@
              return false;
          } else {
-             FluidState fluid = this.getFluidState(pos);
+             FluidState fluidState = this.getFluidState(pos);
+-            if (!(blockState.getBlock() instanceof BaseFireBlock)) {
+-                this.levelEvent(2001, pos, Block.getId(blockState));
 +            // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar
 +            // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent,
 +            // it doesn't imply destruction of a block that plays a sound effect / drops an item.
 +            boolean playEffect = true;
-+            BlockState effectType = iblockdata;
-+            int xp = iblockdata.getBlock().getExpDrop(iblockdata, (ServerLevel) this, pos, ItemStack.EMPTY, true);
++            BlockState effectType = blockState;
++            int xp = blockState.getBlock().getExpDrop(blockState, (ServerLevel) this, pos, ItemStack.EMPTY, true);
 +            if (com.destroystokyo.paper.event.block.BlockDestroyEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+                com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluid.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, drop);
++                com.destroystokyo.paper.event.block.BlockDestroyEvent event = new com.destroystokyo.paper.event.block.BlockDestroyEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), fluidState.createLegacyBlock().createCraftBlockData(), effectType.createCraftBlockData(), xp, dropBlock);
 +                if (!event.callEvent()) {
 +                    return false;
 +                }
 +                effectType = ((CraftBlockData) event.getEffectBlock()).getState();
 +                playEffect = event.playEffect();
-+                drop = event.willDrop();
++                dropBlock = event.willDrop();
 +                xp = event.getExpToDrop();
 +            }
 +            // Paper end - BlockDestroyEvent
- 
--            if (!(iblockdata.getBlock() instanceof BaseFireBlock)) {
--                this.levelEvent(2001, pos, Block.getId(iblockdata));
-+            if (playEffect && !(effectType.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent
++            if (playEffect && !(blockState.getBlock() instanceof BaseFireBlock)) { // Paper - BlockDestroyEvent
 +                this.levelEvent(2001, pos, Block.getId(effectType)); // Paper - BlockDestroyEvent
              }
  
-             if (drop) {
-                 BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
- 
--                Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY);
-+                Block.dropResources(iblockdata, this, pos, tileentity, breakingEntity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
-+                iblockdata.getBlock().popExperience((ServerLevel) this, pos, xp, breakingEntity); // Paper - Properly handle xp dropping; custom amount
+             if (dropBlock) {
+                 BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+-                Block.dropResources(blockState, this, pos, blockEntity, entity, ItemStack.EMPTY);
++                Block.dropResources(blockState, this, pos, blockEntity, entity, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
++                blockState.getBlock().popExperience((ServerLevel) this, pos, xp, entity); // Paper - Properly handle xp dropping; custom amount
              }
  
-             boolean flag1 = this.setBlock(pos, fluid.createLegacyBlock(), 3, maxUpdateDepth);
-@@ -340,10 +693,18 @@
+             boolean flag = this.setBlock(pos, fluidState.createLegacyBlock(), 3, recursionLeft);
+@@ -344,10 +_,18 @@
  
      @Override
      public BlockState getBlockState(BlockPos pos) {
@@ -590,121 +538,112 @@
          } else {
 -            LevelChunk chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
 +            ChunkAccess chunk = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.FULL, true); // Paper - manually inline to reduce hops and avoid unnecessary null check to reduce total byte code size, this should never return null and if it does we will see it the next line but the real stack trace will matter in the chunk engine
- 
              return chunk.getBlockState(pos);
          }
-@@ -446,34 +807,53 @@
+     }
+@@ -454,32 +_,54 @@
              this.pendingBlockEntityTickers.clear();
          }
  
 -        Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
 +        // Spigot start
 +        // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
-         boolean flag = this.tickRateManager().runsNormally();
+         boolean runsNormally = this.tickRateManager().runsNormally();
  
 -        while (iterator.hasNext()) {
--            TickingBlockEntity tickingblockentity = (TickingBlockEntity) iterator.next();
+-            TickingBlockEntity tickingBlockEntity = iterator.next();
 +        int tilesThisCycle = 0;
 +        var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
 +        toRemove.add(null); // Paper - Fix MC-117075
 +        for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
 +            this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0;
-+            TickingBlockEntity tickingblockentity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition);
++            TickingBlockEntity tickingBlockEntity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition);
 +            // Spigot end
- 
-             if (tickingblockentity.isRemoved()) {
+             if (tickingBlockEntity.isRemoved()) {
 -                iterator.remove();
 +                // Spigot start
-+                tilesThisCycle--;
-+                toRemove.add(tickingblockentity); // Paper - Fix MC-117075; use removeAll
++                    tilesThisCycle--;
++                    toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
 +                // Spigot end
-             } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
-                 tickingblockentity.tick();
+             } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
+                 tickingBlockEntity.tick();
              }
          }
 +        this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
  
          this.tickingBlockEntities = false;
-         gameprofilerfiller.pop();
+         profilerFiller.pop();
 +        this.spigotConfig.currentPrimedTnt = 0; // Spigot
      }
  
-     public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
+     public <T extends Entity> void guardEntityTick(Consumer<T> consumerEntity, T entity) {
          try {
-             tickConsumer.accept(entity);
-         } catch (Throwable throwable) {
--            CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking entity");
--            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being ticked");
--
--            entity.fillCrashReportCategory(crashreportsystemdetails);
--            throw new ReportedException(crashreport);
+             consumerEntity.accept(entity);
+         } catch (Throwable var6) {
+-            CrashReport crashReport = CrashReport.forThrowable(var6, "Ticking entity");
+-            CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being ticked");
+-            entity.fillCrashReportCategory(crashReportCategory);
+-            throw new ReportedException(crashReport);
 +            // Paper start - Prevent block entity and entity crashes
 +            final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
-+            MinecraftServer.LOGGER.error(msg, throwable);
++            MinecraftServer.LOGGER.error(msg, var6);
 +            getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
 +            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
 +            // Paper end - Prevent block entity and entity crashes
          }
-+    }
+     }
++
 +    // Paper start - Option to prevent armor stands from doing entity lookups
 +    @Override
 +    public boolean noCollision(@Nullable Entity entity, AABB box) {
-+        if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false;
++        if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups)
++            return false;
 +        return LevelAccessor.super.noCollision(entity, box);
-     }
++    }
 +    // Paper end - Option to prevent armor stands from doing entity lookups
  
      public boolean shouldTickDeath(Entity entity) {
          return true;
-@@ -510,13 +890,32 @@
+@@ -599,6 +_,19 @@
      @Nullable
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
--        return this.isOutsideBuildHeight(pos) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE));
 +        // CraftBukkit start
 +        return this.getBlockEntity(pos, true);
-     }
- 
++    }
++
 +    @Nullable
-+    public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
++    public BlockEntity getBlockEntity(BlockPos pos, boolean validate) {
 +        // Paper start - Perf: Optimize capturedTileEntities lookup
 +        net.minecraft.world.level.block.entity.BlockEntity blockEntity;
-+        if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) {
++        if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) {
 +            return blockEntity;
 +        }
 +        // Paper end - Perf: Optimize capturedTileEntities lookup
 +        // CraftBukkit end
-+        return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE));
-+    }
-+
+         if (this.isOutsideBuildHeight(pos)) {
+             return null;
+         } else {
+@@ -611,6 +_,12 @@
      public void setBlockEntity(BlockEntity blockEntity) {
-         BlockPos blockposition = blockEntity.getBlockPos();
- 
-         if (!this.isOutsideBuildHeight(blockposition)) {
+         BlockPos blockPos = blockEntity.getBlockPos();
+         if (!this.isOutsideBuildHeight(blockPos)) {
 +            // CraftBukkit start
 +            if (this.captureBlockStates) {
-+                this.capturedTileEntities.put(blockposition.immutable(), blockEntity);
++                this.capturedTileEntities.put(blockPos.immutable(), blockEntity);
 +                return;
 +            }
 +            // CraftBukkit end
-             this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity);
+             this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity);
          }
      }
-@@ -643,7 +1042,7 @@
- 
-                 for (int k = 0; k < j; ++k) {
-                     EnderDragonPart entitycomplexpart = aentitycomplexpart[k];
--                    T t0 = (Entity) filter.tryCast(entitycomplexpart);
-+                    T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error
- 
-                     if (t0 != null && predicate.test(t0)) {
-                         result.add(t0);
-@@ -912,7 +1311,7 @@
- 
-     public static enum ExplosionInteraction implements StringRepresentable {
- 
--        NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger");
-+        NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger"), STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY
+@@ -987,7 +_,8 @@
+         BLOCK("block"),
+         MOB("mob"),
+         TNT("tnt"),
+-        TRIGGER("trigger");
++        TRIGGER("trigger"),
++        STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY
  
          public static final Codec<Level.ExplosionInteraction> CODEC = StringRepresentable.fromEnum(Level.ExplosionInteraction::values);
          private final String id;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch
new file mode 100644
index 0000000000..d80e4a75be
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/LevelAccessor.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/level/LevelAccessor.java
++++ b/net/minecraft/world/level/LevelAccessor.java
+@@ -101,4 +_,6 @@
+     default void gameEvent(ResourceKey<GameEvent> gameEvent, BlockPos pos, GameEvent.Context context) {
+         this.gameEvent(this.registryAccess().lookupOrThrow(Registries.GAME_EVENT).getOrThrow(gameEvent), pos, context);
+     }
++
++    net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/LevelReader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch
similarity index 81%
rename from paper-server/patches/unapplied/net/minecraft/world/level/LevelReader.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch
index 4d6ea6f76d..c740a23a7f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/LevelReader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/LevelReader.java.patch
@@ -1,8 +1,8 @@
 --- a/net/minecraft/world/level/LevelReader.java
 +++ b/net/minecraft/world/level/LevelReader.java
-@@ -26,6 +26,9 @@
+@@ -26,6 +_,9 @@
      @Nullable
-     ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
+     ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk);
  
 +    @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading)
 +    @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);}
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/LevelWriter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch
similarity index 95%
rename from paper-server/patches/unapplied/net/minecraft/world/level/LevelWriter.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch
index 3008822ea1..a092d0dad8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/LevelWriter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/LevelWriter.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/LevelWriter.java
 +++ b/net/minecraft/world/level/LevelWriter.java
-@@ -28,4 +28,10 @@
+@@ -27,4 +_,10 @@
      default boolean addFreshEntity(Entity entity) {
          return false;
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
new file mode 100644
index 0000000000..f58012a2a3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -0,0 +1,234 @@
+--- a/net/minecraft/world/level/NaturalSpawner.java
++++ b/net/minecraft/world/level/NaturalSpawner.java
+@@ -49,6 +_,13 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+ 
++// CraftBukkit start
++import net.minecraft.world.level.storage.LevelData;
++import org.bukkit.craftbukkit.util.CraftSpawnCategory;
++import org.bukkit.entity.SpawnCategory;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
++
+ public final class NaturalSpawner {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     private static final int MIN_SPAWN_DISTANCE = 24;
+@@ -72,6 +_,13 @@
+             if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) {
+                 MobCategory category = entity.getType().getCategory();
+                 if (category != MobCategory.MISC) {
++                    // Paper start - Only count natural spawns
++                    if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
++                        !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ||
++                            entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
++                        continue;
++                    }
++                    // Paper end - Only count natural spawns
+                     BlockPos blockPos = entity.blockPosition();
+                     chunkGetter.query(ChunkPos.asLong(blockPos), chunk -> {
+                         MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
+@@ -96,17 +_,36 @@
+         return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
+     }
+ 
++    // CraftBukkit start - add server
+     public static List<MobCategory> getFilteredSpawningCategories(
+-        NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives
++        NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives, ServerLevel worldserver
+     ) {
++        LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
++        // CraftBukkit end
+         List<MobCategory> list = new ArrayList<>(SPAWNING_CATEGORIES.length);
+-
+-        for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
+-            if ((spawnFriendlies || !mobCategory.isFriendly())
+-                && (spawnEnemies || mobCategory.isFriendly())
+-                && (spawnPassives || !mobCategory.isPersistent())
+-                && spawnState.canSpawnForCategoryGlobal(mobCategory)) {
+-                list.add(mobCategory);
++        MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES;
++        int i = aenumcreaturetype.length;
++
++        for (int j = 0; j < i; ++j) {
++            MobCategory enumcreaturetype = SPAWNING_CATEGORIES[j];
++            // CraftBukkit start - Use per-world spawn limits
++            boolean spawnThisTick = true;
++            int limit = enumcreaturetype.getMaxInstancesPerChunk();
++            SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
++            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
++                spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
++                limit = worldserver.getWorld().getSpawnLimit(spawnCategory);
++            }
++
++            if (!spawnThisTick || limit == 0) {
++                continue;
++            }
++
++            if ((spawnFriendlies || !enumcreaturetype.isFriendly())
++                && (spawnEnemies || enumcreaturetype.isFriendly())
++                && (spawnPassives || !enumcreaturetype.isPersistent())
++                && spawnState.canSpawnForCategoryGlobal(enumcreaturetype)) {
++                list.add(enumcreaturetype);
+             }
+         }
+ 
+@@ -126,6 +_,16 @@
+         profilerFiller.pop();
+     }
+ 
++    // Paper start - Add mobcaps commands
++    public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) {
++        final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category));
++        if (categoryLimit < 1) {
++            return categoryLimit;
++        }
++        return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++    }
++    // Paper end - Add mobcaps commands
++
+     public static void spawnCategoryForChunk(
+         MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
+     ) {
+@@ -151,8 +_,8 @@
+         StructureManager structureManager = level.structureManager();
+         ChunkGenerator generator = level.getChunkSource().getGenerator();
+         int y = pos.getY();
+-        BlockState blockState = chunk.getBlockState(pos);
+-        if (!blockState.isRedstoneConductor(chunk, pos)) {
++        BlockState blockState = chunk.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
++        if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
+             BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+             int i = 0;
+ 
+@@ -174,7 +_,7 @@
+                     Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, false);
+                     if (nearestPlayer != null) {
+                         double d2 = nearestPlayer.distanceToSqr(d, y, d1);
+-                        if (isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) {
++                        if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) {  // Paper - don't load chunks for mob spawn
+                             if (spawnerData == null) {
+                                 Optional<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAt(
+                                     level, structureManager, generator, category, level.random, mutableBlockPos
+@@ -187,8 +_,13 @@
+                                 ceil = spawnerData.minCount + level.random.nextInt(1 + spawnerData.maxCount - spawnerData.minCount);
+                             }
+ 
+-                            if (isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2)
+-                                && filter.test(spawnerData.type, mutableBlockPos, chunk)) {
++                            // Paper start - PreCreatureSpawnEvent
++                            PreSpawnStatus doSpawning = isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2);
++                            if (doSpawning == PreSpawnStatus.ABORT) {
++                                return;
++                            }
++                            if (doSpawning == PreSpawnStatus.SUCCESS && filter.test(spawnerData.type, mutableBlockPos, chunk)) {
++                                // Paper end - PreCreatureSpawnEvent
+                                 Mob mobForSpawn = getMobForSpawn(level, spawnerData.type);
+                                 if (mobForSpawn == null) {
+                                     return;
+@@ -199,10 +_,15 @@
+                                     spawnGroupData = mobForSpawn.finalizeSpawn(
+                                         level, level.getCurrentDifficultyAt(mobForSpawn.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData
+                                     );
+-                                    i++;
+-                                    i3++;
+-                                    level.addFreshEntityWithPassengers(mobForSpawn);
+-                                    callback.run(mobForSpawn, chunk);
++                                    // CraftBukkit start
++                                    // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs.
++                                    level.addFreshEntityWithPassengers(mobForSpawn, (mobForSpawn instanceof net.minecraft.world.entity.animal.Ocelot && !((org.bukkit.entity.Ageable) mobForSpawn.getBukkitEntity()).isAdult()) ? SpawnReason.OCELOT_BABY : SpawnReason.NATURAL);
++                                    if (!mobForSpawn.isRemoved()) {
++                                        ++i;
++                                        ++i3;
++                                        callback.run(mobForSpawn, chunk);
++                                    }
++                                    // CraftBukkit end
+                                     if (i >= mobForSpawn.getMaxSpawnClusterSize()) {
+                                         return;
+                                     }
+@@ -225,7 +_,15 @@
+             && (Objects.equals(new ChunkPos(pos), chunk.getPos()) || level.isNaturalSpawningAllowed(pos));
+     }
+ 
+-    private static boolean isValidSpawnPostitionForType(
++    // Paper start - PreCreatureSpawnEvent
++    private enum PreSpawnStatus {
++        FAIL,
++        SUCCESS,
++        CANCELLED,
++        ABORT
++    }
++    private static PreSpawnStatus isValidSpawnPostitionForType(
++    // Paper end - PreCreatureSpawnEvent
+         ServerLevel level,
+         MobCategory category,
+         StructureManager structureManager,
+@@ -235,16 +_,20 @@
+         double distance
+     ) {
+         EntityType<?> entityType = data.type;
+-        return entityType.getCategory() != MobCategory.MISC
+-            && (
+-                entityType.canSpawnFarFromPlayer()
+-                    || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance())
+-            )
+-            && entityType.canSummon()
+-            && canSpawnMobAt(level, structureManager, generator, category, data, pos)
+-            && SpawnPlacements.isSpawnPositionOk(entityType, level, pos)
+-            && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
+-            && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
++
++        // Paper start - PreCreatureSpawnEvent
++        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
++            io.papermc.paper.util.MCUtil.toLocation(level, pos),
++            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), SpawnReason.NATURAL
++        );
++        if (!event.callEvent()) {
++            if (event.shouldAbortSpawn()) {
++                return PreSpawnStatus.ABORT;
++            }
++            return PreSpawnStatus.CANCELLED;
++        }
++        // Paper end - PreCreatureSpawnEvent
++        return entityType.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entityType.canSpawnFarFromPlayer() && distance > (double) (entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entityType.canSummon() && NaturalSpawner.canSpawnMobAt(level, structureManager, generator, category, data, pos) ? (!SpawnPlacements.isSpawnPositionOk(entityType, level, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random) ? PreSpawnStatus.FAIL : level.noCollision(entityType.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent
+     }
+ 
+     @Nullable
+@@ -258,6 +_,7 @@
+             LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(entityType));
+         } catch (Exception var4) {
+             LOGGER.warn("Failed to create mob", (Throwable)var4);
++            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var4); // Paper - ServerExceptionEvent
+         }
+ 
+         return null;
+@@ -364,6 +_,7 @@
+                                     entity = spawnerData.type.create(levelAccessor.getLevel(), EntitySpawnReason.NATURAL);
+                                 } catch (Exception var27) {
+                                     LOGGER.warn("Failed to create mob", (Throwable)var27);
++                                    com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var27); // Paper - ServerExceptionEvent
+                                     continue;
+                                 }
+ 
+@@ -381,7 +_,7 @@
+                                         EntitySpawnReason.CHUNK_GENERATION,
+                                         spawnGroupData
+                                     );
+-                                    levelAccessor.addFreshEntityWithPassengers(mob);
++                                    levelAccessor.addFreshEntityWithPassengers(mob, SpawnReason.CHUNK_GEN); // CraftBukkit
+                                     flag = true;
+                                 }
+                             }
+@@ -501,8 +_,10 @@
+             return this.unmodifiableMobCategoryCounts;
+         }
+ 
+-        boolean canSpawnForCategoryGlobal(MobCategory category) {
+-            int i = category.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++        // CraftBukkit start
++        boolean canSpawnForCategoryGlobal(MobCategory category, int limit) {
++            int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++            // CraftBukkit end
+             return this.mobCategoryCounts.getInt(category) < i;
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/PathNavigationRegion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch
similarity index 71%
rename from paper-server/patches/unapplied/net/minecraft/world/level/PathNavigationRegion.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch
index 085fb1983b..2305a1f778 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/PathNavigationRegion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/PathNavigationRegion.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/PathNavigationRegion.java
 +++ b/net/minecraft/world/level/PathNavigationRegion.java
-@@ -8,6 +8,7 @@
+@@ -8,6 +_,7 @@
  import net.minecraft.core.Holder;
  import net.minecraft.core.SectionPos;
  import net.minecraft.core.registries.Registries;
@@ -8,19 +8,19 @@
  import net.minecraft.world.entity.Entity;
  import net.minecraft.world.level.biome.Biome;
  import net.minecraft.world.level.biome.Biomes;
-@@ -66,7 +67,7 @@
-     private ChunkAccess getChunk(int chunkX, int chunkZ) {
-         int i = chunkX - this.centerX;
-         int j = chunkZ - this.centerZ;
--        if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) {
-+        if (i >= 0 && i < this.chunks.length && j >= 0 && j < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below
-             ChunkAccess chunkAccess = this.chunks[i][j];
-             return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(chunkX, chunkZ), this.plains.get()));
+@@ -66,13 +_,37 @@
+     private ChunkAccess getChunk(int x, int z) {
+         int i = x - this.centerX;
+         int i1 = z - this.centerZ;
+-        if (i >= 0 && i < this.chunks.length && i1 >= 0 && i1 < this.chunks[i].length) {
++        if (i >= 0 && i < this.chunks.length && i1 >= 0 && i1 < this.chunks[i].length) { // Paper - if this changes, update getChunkIfLoaded below
+             ChunkAccess chunkAccess = this.chunks[i][i1];
+             return (ChunkAccess)(chunkAccess != null ? chunkAccess : new EmptyLevelChunk(this.level, new ChunkPos(x, z), this.plains.get()));
          } else {
-@@ -74,7 +75,31 @@
+             return new EmptyLevelChunk(this.level, new ChunkPos(x, z), this.plains.get());
          }
      }
- 
++
 +    // Paper start - if loaded util
 +    private @Nullable ChunkAccess getChunkIfLoaded(int x, int z) {
 +        // Based on getChunk(int, int)
@@ -32,7 +32,7 @@
 +        }
 +        return null;
 +    }
-     @Override
++    @Override
 +    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
 +        ChunkAccess chunk = getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
 +        return chunk == null ? null : chunk.getFluidState(blockposition);
@@ -44,8 +44,6 @@
 +        return chunk == null ? null : chunk.getBlockState(blockposition);
 +    }
 +    // Paper end
-+
-+    @Override
+ 
+     @Override
      public WorldBorder getWorldBorder() {
-         return this.level.getWorldBorder();
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/ServerExplosion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/level/ServerExplosion.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
index e0f12942e5..e16fda169d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/ServerExplosion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
@@ -1,23 +1,12 @@
 --- a/net/minecraft/world/level/ServerExplosion.java
 +++ b/net/minecraft/world/level/ServerExplosion.java
-@@ -22,18 +22,27 @@
- import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.boss.EnderDragonPart;
-+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.item.PrimedTnt;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.block.BaseFireBlock;
- import net.minecraft.world.level.block.Block;
--import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.material.FluidState;
- import net.minecraft.world.phys.AABB;
+@@ -33,6 +_,18 @@
  import net.minecraft.world.phys.HitResult;
  import net.minecraft.world.phys.Vec3;
+ 
++// CraftBukkit start
++import net.minecraft.world.entity.boss.EnderDragonPart;
++import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
 +import net.minecraft.world.level.block.Blocks;
 +import net.minecraft.world.level.block.state.BlockState;
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
@@ -26,77 +15,82 @@
 +import org.bukkit.Location;
 +import org.bukkit.event.block.BlockExplodeEvent;
 +// CraftBukkit end
- 
++
  public class ServerExplosion implements Explosion {
- 
-@@ -50,16 +59,22 @@
+     private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator();
+     private static final int MAX_DROPS_PER_COMBINED_STACK = 16;
+@@ -47,6 +_,11 @@
      private final DamageSource damageSource;
      private final ExplosionDamageCalculator damageCalculator;
-     private final Map<Player, Vec3> hitPlayers = new HashMap();
+     private final Map<Player, Vec3> hitPlayers = new HashMap<>();
 +    // CraftBukkit - add field
 +    public boolean wasCanceled = false;
 +    public float yield;
 +    // CraftBukkit end
 +    public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
  
-     public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
-         this.level = world;
-         this.source = entity;
--        this.radius = power;
-+        this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values
-         this.center = pos;
-         this.fire = createFire;
-         this.blockInteraction = destructionType;
-         this.damageSource = damageSource == null ? world.damageSources().explosion(this) : damageSource;
-         this.damageCalculator = behavior == null ? this.makeDamageCalculator(entity) : behavior;
+     public ServerExplosion(
+         ServerLevel level,
+@@ -60,12 +_,13 @@
+     ) {
+         this.level = level;
+         this.source = source;
+-        this.radius = radius;
++        this.radius = (float) Math.max(radius, 0.0); // CraftBukkit - clamp bad values
+         this.center = center;
+         this.fire = fire;
+         this.blockInteraction = blockInteraction;
+         this.damageSource = damageSource == null ? level.damageSources().explosion(this) : damageSource;
+         this.damageCalculator = damageCalculator == null ? this.makeDamageCalculator(source) : damageCalculator;
 +        this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
      }
  
      private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
-@@ -135,7 +150,8 @@
+@@ -139,7 +_,8 @@
                          for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
-                             BlockPos blockposition = BlockPos.containing(d4, d5, d6);
-                             BlockState iblockdata = this.level.getBlockState(blockposition);
--                            FluidState fluid = this.level.getFluidState(blockposition);
-+                            if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
-+                            FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
- 
-                             if (!this.level.isInWorldBounds(blockposition)) {
+                             BlockPos blockPos = BlockPos.containing(d3, d4, d5);
+                             BlockState blockState = this.level.getBlockState(blockPos);
+-                            FluidState fluidState = this.level.getFluidState(blockPos);
++                            if (!blockState.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
++                            FluidState fluidState = blockState.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
+                             if (!this.level.isInWorldBounds(blockPos)) {
                                  break;
-@@ -149,6 +165,15 @@
+                             }
+@@ -152,6 +_,15 @@
  
-                             if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
-                                 set.add(blockposition);
+                             if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, f)) {
+                                 set.add(blockPos);
 +                                // Paper start - prevent headless pistons from forming
-+                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
-+                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition);
++                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.getBlock() == Blocks.MOVING_PISTON) {
++                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockPos);
 +                                    if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
-+                                        net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
-+                                        set.add(blockposition.relative(direction.getOpposite()));
++                                        net.minecraft.core.Direction direction = blockState.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
++                                        set.add(blockPos.relative(direction.getOpposite()));
 +                                    }
 +                                }
 +                                // Paper end - prevent headless pistons from forming
                              }
  
-                             d4 += d0 * 0.30000001192092896D;
-@@ -171,7 +196,7 @@
-         int l = Mth.floor(this.center.y + (double) f + 1.0D);
-         int i1 = Mth.floor(this.center.z - (double) f - 1.0D);
-         int j1 = Mth.floor(this.center.z + (double) f + 1.0D);
--        List<Entity> list = this.level.getEntities(this.source, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1));
-+        List<Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB((double) i, (double) k, (double) i1, (double) j, (double) l, (double) j1), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
-         Iterator iterator = list.iterator();
- 
-         while (iterator.hasNext()) {
-@@ -192,10 +217,38 @@
-                         d3 /= d4;
-                         boolean flag = this.damageCalculator.shouldDamageEntity(this, entity);
-                         float f1 = this.damageCalculator.getKnockbackMultiplier(entity);
--                        float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity);
-+                        float f2 = !flag && f1 == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions
- 
-                         if (flag) {
--                            entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
+                             d3 += d * 0.3F;
+@@ -174,8 +_,8 @@
+         int floor3 = Mth.floor(this.center.y + f + 1.0);
+         int floor4 = Mth.floor(this.center.z - f - 1.0);
+         int floor5 = Mth.floor(this.center.z + f + 1.0);
+-
+-        for (Entity entity : this.level.getEntities(this.source, new AABB(floor, floor2, floor4, floor1, floor3, floor5))) {
++        List <Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB(floor, floor2, floor4, floor1, floor3, floor5), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
++        for (Entity entity : list) { // Paper - used in loop
+             if (!entity.ignoreExplosion(this)) {
+                 double d = Math.sqrt(entity.distanceToSqr(this.center)) / f;
+                 if (d <= 1.0) {
+@@ -189,15 +_,43 @@
+                         d3 /= squareRoot;
+                         boolean shouldDamageEntity = this.damageCalculator.shouldDamageEntity(this, entity);
+                         float knockbackMultiplier = this.damageCalculator.getKnockbackMultiplier(entity);
+-                        float f1 = !shouldDamageEntity && knockbackMultiplier == 0.0F ? 0.0F : getSeenPercent(this.center, entity);
++                        float f1 = !shouldDamageEntity && knockbackMultiplier == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions
+                         if (shouldDamageEntity) {
+-                            entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
 +                            // CraftBukkit start
 +
 +                            // Special case ender dragon only give knockback if no damage is cancelled
@@ -115,11 +109,11 @@
 +                                for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
 +                                    // Calculate damage separately for each EntityComplexPart
 +                                    if (list.contains(entityComplexPart)) {
-+                                        entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
++                                        entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
 +                                    }
 +                                }
 +                            } else {
-+                                entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2));
++                                entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
 +                            }
 +
 +                            if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
@@ -128,48 +122,45 @@
 +                            // CraftBukkit end
                          }
  
-                         double d5 = (1.0D - d0) * (double) f2 * (double) f1;
-@@ -204,7 +257,7 @@
-                         if (entity instanceof LivingEntity) {
-                             LivingEntity entityliving = (LivingEntity) entity;
- 
--                            d6 = d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
-+                            d6 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d5 * (1.0D - entityliving.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper
+                         double d4 = (1.0 - d) * f1 * knockbackMultiplier;
+                         double d5;
+                         if (entity instanceof LivingEntity livingEntity) {
+-                            d5 = d4 * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
++                            d5 = entity instanceof Player && this.level.paperConfig().environment.disableExplosionKnockback ? 0 : d4 * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE)); // Paper
                          } else {
-                             d6 = d5;
+                             d5 = d4;
                          }
-@@ -214,11 +267,19 @@
-                         d3 *= d6;
-                         Vec3 vec3d = new Vec3(d1, d2, d3);
- 
+@@ -206,10 +_,18 @@
+                         d2 *= d5;
+                         d3 *= d5;
+                         Vec3 vec3 = new Vec3(d1, d2, d3);
 +                        // CraftBukkit start - Call EntityKnockbackEvent
 +                        if (entity instanceof LivingEntity) {
-+                           // Paper start - knockback events
-+                           io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d6, vec3d);
-+                            vec3d = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback());
-+                           // Paper end - knockback events
++                            // Paper start - knockback events
++                            io.papermc.paper.event.entity.EntityKnockbackEvent event = CraftEventFactory.callEntityKnockbackEvent((org.bukkit.craftbukkit.entity.CraftLivingEntity) entity.getBukkitEntity(), this.source, this.damageSource.getEntity() != null ? this.damageSource.getEntity() : this.source, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.EXPLOSION, d5, vec3);
++                            vec3 = event.isCancelled() ? Vec3.ZERO : org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getKnockback());
++                            // Paper end - knockback events
 +                        }
 +                        // CraftBukkit end
-                         entity.push(vec3d);
+                         entity.push(vec3);
                          if (entity instanceof Player) {
-                             Player entityhuman = (Player) entity;
- 
--                            if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) {
-+                            if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback
-                                 this.hitPlayers.put(entityhuman, vec3d);
+                             Player player = (Player)entity;
+-                            if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying)) {
++                            if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback
+                                 this.hitPlayers.put(player, vec3);
                              }
                          }
-@@ -235,10 +296,62 @@
-         List<ServerExplosion.StackCollector> list1 = new ArrayList();
+@@ -225,7 +_,61 @@
+         List<ServerExplosion.StackCollector> list = new ArrayList<>();
+         Util.shuffle(blocks, this.level.random);
  
-         Util.shuffle(positions, this.level.random);
 +        // CraftBukkit start
 +        org.bukkit.World bworld = this.level.getWorld();
 +        Location location = CraftLocation.toBukkit(this.center, bworld);
 +
 +        List<org.bukkit.block.Block> blockList = new ObjectArrayList<>();
-+        for (int i1 = positions.size() - 1; i1 >= 0; i1--) {
-+            BlockPos cpos = positions.get(i1);
++        for (int i1 = blocks.size() - 1; i1 >= 0; i1--) {
++            BlockPos cpos = blocks.get(i1);
 +            org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ());
 +            if (!bblock.getType().isAir()) {
 +                blockList.add(bblock);
@@ -192,49 +183,47 @@
 +            this.yield = event.getYield();
 +        }
 +
-+        positions.clear();
++        blocks.clear();
 +
 +        for (org.bukkit.block.Block bblock : bukkitBlocks) {
 +            BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ());
-+            positions.add(coords);
++            blocks.add(coords);
 +        }
 +
 +        if (this.wasCanceled) {
 +            return;
 +        }
 +        // CraftBukkit end
-         Iterator iterator = positions.iterator();
- 
-         while (iterator.hasNext()) {
-             BlockPos blockposition = (BlockPos) iterator.next();
++
+         for (BlockPos blockPos : blocks) {
 +            // CraftBukkit start - TNTPrimeEvent
-+            BlockState iblockdata = this.level.getBlockState(blockposition);
++            BlockState iblockdata = this.level.getBlockState(blockPos);
 +            Block block = iblockdata.getBlock();
 +            if (block instanceof net.minecraft.world.level.block.TntBlock) {
 +                Entity sourceEntity = this.source == null ? null : this.source;
 +                BlockPos sourceBlock = sourceEntity == null ? BlockPos.containing(this.center) : null;
-+                if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) {
-+                    this.level.sendBlockUpdated(blockposition, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client
++                if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) {
++                    this.level.sendBlockUpdated(blockPos, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client
 +                    continue;
 +                }
 +            }
 +            // CraftBukkit end
- 
-             this.level.getBlockState(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> {
-                 ServerExplosion.addOrAppendStack(list1, itemstack, blockposition1);
-@@ -262,13 +375,22 @@
-             BlockPos blockposition = (BlockPos) iterator.next();
- 
-             if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockposition).isAir() && this.level.getBlockState(blockposition.below()).isSolidRender()) {
--                this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition));
++
+             this.level
+                 .getBlockState(blockPos)
+                 .onExplosionHit(this.level, blockPos, this, (itemStack, blockPos1) -> addOrAppendStack(list, itemStack, blockPos1));
+@@ -239,12 +_,21 @@
+     private void createFire(List<BlockPos> blocks) {
+         for (BlockPos blockPos : blocks) {
+             if (this.level.random.nextInt(3) == 0 && this.level.getBlockState(blockPos).isAir() && this.level.getBlockState(blockPos.below()).isSolidRender()) {
+-                this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos));
 +                // CraftBukkit start - Ignition by explosion
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockposition, this).isCancelled()) {
-+                    this.level.setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level, blockposition));
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockPos, this).isCancelled()) {
++                    this.level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level, blockPos));
 +                }
 +                // CraftBukkit end
              }
          }
- 
      }
  
      public void explode() {
@@ -243,22 +232,23 @@
 +            return;
 +        }
 +        // CraftBukkit end
-         this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center);
+         this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
          List<BlockPos> list = this.calculateExplodedPositions();
- 
-@@ -288,6 +410,7 @@
+         this.hurtEntities();
+@@ -261,6 +_,7 @@
      }
  
-     private static void addOrAppendStack(List<ServerExplosion.StackCollector> droppedItemsOut, ItemStack item, BlockPos pos) {
-+        if (item.isEmpty()) return; // CraftBukkit - SPIGOT-5425
-         Iterator iterator = droppedItemsOut.iterator();
- 
-         do {
-@@ -372,4 +495,85 @@
- 
+     private static void addOrAppendStack(List<ServerExplosion.StackCollector> stackCollectors, ItemStack stack, BlockPos pos) {
++        if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-5425
+         for (ServerExplosion.StackCollector stackCollector : stackCollectors) {
+             stackCollector.tryMerge(stack);
+             if (stack.isEmpty()) {
+@@ -342,4 +_,86 @@
+             }
          }
      }
 +
++
 +    // Paper start - Optimize explosions
 +    private float getBlockDensity(Vec3 vec3d, Entity entity) {
 +        if (!this.level.paperConfig().environment.optimizeExplosions) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/ServerLevelAccessor.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/level/ServerLevelAccessor.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch
index 2c482ebee0..0d2900e6a1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/ServerLevelAccessor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch
@@ -1,13 +1,14 @@
 --- a/net/minecraft/world/level/ServerLevelAccessor.java
 +++ b/net/minecraft/world/level/ServerLevelAccessor.java
-@@ -8,6 +8,17 @@
+@@ -7,6 +_,17 @@
      ServerLevel getLevel();
  
      default void addFreshEntityWithPassengers(Entity entity) {
 -        entity.getSelfAndPassengers().forEach(this::addFreshEntity);
+-    }
 +        // CraftBukkit start
 +        this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-     }
++    }
 +
 +    default void addFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
 +        entity.getSelfAndPassengers().forEach((e) -> this.addFreshEntity(e, reason));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/StructureManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch
similarity index 67%
rename from paper-server/patches/unapplied/net/minecraft/world/level/StructureManager.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch
index 8a15d2f4f1..2286bb70ae 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/StructureManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/StructureManager.java.patch
@@ -1,20 +1,21 @@
 --- a/net/minecraft/world/level/StructureManager.java
 +++ b/net/minecraft/world/level/StructureManager.java
-@@ -48,7 +48,12 @@
+@@ -48,7 +_,13 @@
      }
  
-     public List<StructureStart> startsForStructure(ChunkPos pos, Predicate<Structure> predicate) {
--        Map<Structure, LongSet> map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
+     public List<StructureStart> startsForStructure(ChunkPos chunkPos, Predicate<Structure> structurePredicate) {
+-        Map<Structure, LongSet> allReferences = this.level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
 +        // Paper start - Fix swamp hut cat generation deadlock
-+        return this.startsForStructure(pos, predicate, null);
++        return this.startsForStructure(chunkPos, structurePredicate, null);
 +    }
-+    public List<StructureStart> startsForStructure(ChunkPos pos, Predicate<Structure> predicate, @Nullable ServerLevelAccessor levelAccessor) {
-+        Map<Structure, LongSet> map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
++
++    public List<StructureStart> startsForStructure(ChunkPos chunkPos, Predicate<Structure> structurePredicate, @Nullable ServerLevelAccessor levelAccessor) {
++        Map<Structure, LongSet> allReferences = (levelAccessor == null ? this.level : levelAccessor).getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
 +        // Paper end - Fix swamp hut cat generation deadlock
          Builder<StructureStart> builder = ImmutableList.builder();
  
-         for (Entry<Structure, LongSet> entry : map.entrySet()) {
-@@ -116,10 +121,20 @@
+         for (Entry<Structure, LongSet> entry : allReferences.entrySet()) {
+@@ -118,10 +_,20 @@
      }
  
      public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate<Holder<Structure>> predicate) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/BaseSpawner.java.patch
deleted file mode 100644
index c836ef12e6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/BaseSpawner.java.patch
+++ /dev/null
@@ -1,167 +0,0 @@
---- a/net/minecraft/world/level/BaseSpawner.java
-+++ b/net/minecraft/world/level/BaseSpawner.java
-@@ -49,15 +49,17 @@
-     public int maxNearbyEntities = 6;
-     public int requiredPlayerRange = 16;
-     public int spawnRange = 4;
-+    private int tickDelay = 0; // Paper - Configurable mob spawner tick rate
- 
-     public BaseSpawner() {}
- 
-     public void setEntityId(EntityType<?> type, @Nullable Level world, RandomSource random, BlockPos pos) {
-         this.getOrCreateNextSpawnData(world, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());
-+        this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282
-     }
- 
-     public boolean isNearPlayer(Level world, BlockPos pos) {
--        return world.hasNearbyAlivePlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange);
-+        return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API
-     }
- 
-     public void clientTick(Level world, BlockPos pos) {
-@@ -82,13 +84,19 @@
-     }
- 
-     public void serverTick(ServerLevel world, BlockPos pos) {
-+        if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick
-+        // Paper start - Configurable mob spawner tick rate
-+        if (spawnDelay > 0 && --tickDelay > 0) return;
-+        tickDelay = world.paperConfig().tickRates.mobSpawner;
-+        if (tickDelay == -1) { return; } // If disabled
-+        // Paper end - Configurable mob spawner tick rate
-         if (this.isNearPlayer(world, pos)) {
--            if (this.spawnDelay == -1) {
-+            if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate
-                 this.delay(world, pos);
-             }
- 
-             if (this.spawnDelay > 0) {
--                --this.spawnDelay;
-+                this.spawnDelay -= tickDelay; // Paper - Configurable mob spawner tick rate
-             } else {
-                 boolean flag = false;
-                 RandomSource randomsource = world.getRandom();
-@@ -125,6 +133,20 @@
-                         } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) {
-                             continue;
-                         }
-+                        // Paper start - PreCreatureSpawnEvent
-+                        com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent event = new com.destroystokyo.paper.event.entity.PreSpawnerSpawnEvent(
-+                            io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2),
-+                            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()),
-+                            io.papermc.paper.util.MCUtil.toLocation(world, pos)
-+                        );
-+                        if (!event.callEvent()) {
-+                            flag = true;
-+                            if (event.shouldAbortSpawn()) {
-+                                break;
-+                            }
-+                            continue;
-+                        }
-+                        // Paper end - PreCreatureSpawnEvent
- 
-                         Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, EntitySpawnReason.SPAWNER, (entity1) -> {
-                             entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot());
-@@ -143,6 +165,7 @@
-                             return;
-                         }
- 
-+                        entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag
-                         entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F);
-                         if (entity instanceof Mob) {
-                             Mob entityinsentient = (Mob) entity;
-@@ -157,13 +180,27 @@
-                                 ((Mob) entity).finalizeSpawn(world, world.getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.SPAWNER, (SpawnGroupData) null);
-                             }
- 
--                            Optional optional1 = mobspawnerdata.getEquipment();
-+                            Optional<net.minecraft.world.entity.EquipmentTable> optional1 = mobspawnerdata.getEquipment(); // CraftBukkit - decompile error
- 
-                             Objects.requireNonNull(entityinsentient);
-                             optional1.ifPresent(entityinsentient::equip);
-+                            // Spigot Start
-+                            if ( entityinsentient.level().spigotConfig.nerfSpawnerMobs )
-+                            {
-+                                entityinsentient.aware = false;
-+                            }
-+                            // Spigot End
-                         }
- 
--                        if (!world.tryAddFreshEntityWithPassengers(entity)) {
-+                        entity.spawnedViaMobSpawner = true; // Paper
-+                        entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason
-+                        flag = true; // Paper
-+                        // CraftBukkit start
-+                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
-+                            continue;
-+                        }
-+                        if (!world.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
-+                            // CraftBukkit end
-                             this.delay(world, pos);
-                             return;
-                         }
-@@ -174,7 +211,7 @@
-                             ((Mob) entity).spawnAnim();
-                         }
- 
--                        flag = true;
-+                        //flag = true; // Paper - moved up above cancellable event
-                     }
-                 }
- 
-@@ -202,7 +239,13 @@
-     }
- 
-     public void load(@Nullable Level world, BlockPos pos, CompoundTag nbt) {
-+        // Paper start - use larger int if set
-+        if (nbt.contains("Paper.Delay")) {
-+            this.spawnDelay = nbt.getInt("Paper.Delay");
-+        } else {
-         this.spawnDelay = nbt.getShort("Delay");
-+        }
-+        // Paper end
-         boolean flag = nbt.contains("SpawnData", 10);
- 
-         if (flag) {
-@@ -225,9 +268,15 @@
-             this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData());
-         }
- 
-+        // Paper start - use ints if set
-+        if (nbt.contains("Paper.MinSpawnDelay", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
-+            this.minSpawnDelay = nbt.getInt("Paper.MinSpawnDelay");
-+            this.maxSpawnDelay = nbt.getInt("Paper.MaxSpawnDelay");
-+            this.spawnCount = nbt.getShort("SpawnCount");
-+        } else // Paper end
-         if (nbt.contains("MinSpawnDelay", 99)) {
--            this.minSpawnDelay = nbt.getShort("MinSpawnDelay");
--            this.maxSpawnDelay = nbt.getShort("MaxSpawnDelay");
-+            this.minSpawnDelay = nbt.getInt("MinSpawnDelay"); // Paper - short -> int
-+            this.maxSpawnDelay = nbt.getInt("MaxSpawnDelay"); // Paper - short -> int
-             this.spawnCount = nbt.getShort("SpawnCount");
-         }
- 
-@@ -244,9 +293,20 @@
-     }
- 
-     public CompoundTag save(CompoundTag nbt) {
--        nbt.putShort("Delay", (short) this.spawnDelay);
--        nbt.putShort("MinSpawnDelay", (short) this.minSpawnDelay);
--        nbt.putShort("MaxSpawnDelay", (short) this.maxSpawnDelay);
-+        // Paper start
-+        if (spawnDelay > Short.MAX_VALUE) {
-+            nbt.putInt("Paper.Delay", this.spawnDelay);
-+        }
-+        nbt.putShort("Delay", (short) Math.min(Short.MAX_VALUE, this.spawnDelay));
-+
-+        if (minSpawnDelay > Short.MAX_VALUE || maxSpawnDelay > Short.MAX_VALUE) {
-+            nbt.putInt("Paper.MinSpawnDelay", this.minSpawnDelay);
-+            nbt.putInt("Paper.MaxSpawnDelay", this.maxSpawnDelay);
-+        }
-+
-+        nbt.putShort("MinSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.minSpawnDelay));
-+        nbt.putShort("MaxSpawnDelay", (short) Math.min(Short.MAX_VALUE, this.maxSpawnDelay));
-+        // Paper end
-         nbt.putShort("SpawnCount", (short) this.spawnCount);
-         nbt.putShort("MaxNearbyEntities", (short) this.maxNearbyEntities);
-         nbt.putShort("RequiredPlayerRange", (short) this.requiredPlayerRange);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/BlockGetter.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/BlockGetter.java.patch
deleted file mode 100644
index b3ea51c1e2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/BlockGetter.java.patch
+++ /dev/null
@@ -1,90 +0,0 @@
---- a/net/minecraft/world/level/BlockGetter.java
-+++ b/net/minecraft/world/level/BlockGetter.java
-@@ -12,6 +12,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
- import net.minecraft.util.Mth;
-+import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.entity.BlockEntity;
- import net.minecraft.world.level.block.entity.BlockEntityType;
- import net.minecraft.world.level.block.state.BlockState;
-@@ -31,11 +32,20 @@
-     default <T extends BlockEntity> Optional<T> getBlockEntity(BlockPos pos, BlockEntityType<T> type) {
-         BlockEntity tileentity = this.getBlockEntity(pos);
- 
--        return tileentity != null && tileentity.getType() == type ? Optional.of(tileentity) : Optional.empty();
-+        return tileentity != null && tileentity.getType() == type ? (Optional<T>) Optional.of(tileentity) : Optional.empty(); // CraftBukkit - decompile error
-     }
- 
-     BlockState getBlockState(BlockPos pos);
-+    // Paper start - if loaded util
-+    @Nullable BlockState getBlockStateIfLoaded(BlockPos blockposition);
- 
-+    default @Nullable Block getBlockIfLoaded(BlockPos blockposition) {
-+        BlockState type = this.getBlockStateIfLoaded(blockposition);
-+        return type == null ? null : type.getBlock();
-+    }
-+    @Nullable FluidState getFluidIfLoaded(BlockPos blockposition);
-+    // Paper end
-+
-     FluidState getFluidState(BlockPos pos);
- 
-     default int getLightEmission(BlockPos pos) {
-@@ -59,10 +69,25 @@
-         });
-     }
- 
--    default BlockHitResult clip(ClipContext context) {
--        return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> {
--            BlockState iblockdata = this.getBlockState(blockposition);
--            FluidState fluid = this.getFluidState(blockposition);
-+    // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
-+    default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
-+        // Paper start - Add predicate for blocks when raytracing
-+        return clip(raytrace1, blockposition, null);
-+    }
-+
-+    default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
-+            // Paper end - Add predicate for blocks when raytracing
-+            // Paper start - Prevent raytrace from loading chunks
-+            BlockState iblockdata = this.getBlockStateIfLoaded(blockposition);
-+            if (iblockdata == null) {
-+                // copied the last function parameter (listed below)
-+                Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo());
-+
-+                return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
-+            }
-+            // Paper end - Prevent raytrace from loading chunks
-+            if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate
-+            FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again
-             Vec3 vec3d = raytrace1.getFrom();
-             Vec3 vec3d1 = raytrace1.getTo();
-             VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition);
-@@ -73,6 +98,18 @@
-             double d1 = movingobjectpositionblock1 == null ? Double.MAX_VALUE : raytrace1.getFrom().distanceToSqr(movingobjectpositionblock1.getLocation());
- 
-             return d0 <= d1 ? movingobjectpositionblock : movingobjectpositionblock1;
-+    }
-+    // CraftBukkit end
-+
-+    default BlockHitResult clip(ClipContext context) {
-+        // Paper start - Add predicate for blocks when raytracing
-+        return clip(context, (java.util.function.Predicate<org.bukkit.block.Block>) null);
-+    }
-+
-+    default BlockHitResult clip(ClipContext context, java.util.function.Predicate<? super org.bukkit.block.Block> canCollide) {
-+        // Paper end - Add predicate for blocks when raytracing
-+        return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> {
-+            return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing
-         }, (raytrace1) -> {
-             Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo());
- 
-@@ -145,7 +182,7 @@
-                 double d13 = d10 * (i1 > 0 ? 1.0D - Mth.frac(d4) : Mth.frac(d4));
-                 double d14 = d11 * (j1 > 0 ? 1.0D - Mth.frac(d5) : Mth.frac(d5));
- 
--                Object object;
-+                T object; // CraftBukkit - decompile error
- 
-                 do {
-                     if (d12 > 1.0D && d13 > 1.0D && d14 > 1.0D) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/ClipContext.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/ClipContext.java.patch
deleted file mode 100644
index 20d8ff1d94..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/ClipContext.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/level/ClipContext.java
-+++ b/net/minecraft/world/level/ClipContext.java
-@@ -22,7 +22,7 @@
-     private final CollisionContext collisionContext;
- 
-     public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) {
--        this(start, end, shapeType, fluidHandling, CollisionContext.of(entity));
-+        this(start, end, shapeType, fluidHandling, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit
-     }
- 
-     public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, CollisionContext shapeContext) {
-@@ -79,7 +79,7 @@
- 
-         private final Predicate<FluidState> canPick;
- 
--        private Fluid(final Predicate predicate) {
-+        private Fluid(final Predicate<FluidState> predicate) { // CraftBukkit - decompile error
-             this.canPick = predicate;
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/GameRules.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/GameRules.java.patch
deleted file mode 100644
index 13a583766e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/GameRules.java.patch
+++ /dev/null
@@ -1,321 +0,0 @@
---- a/net/minecraft/world/level/GameRules.java
-+++ b/net/minecraft/world/level/GameRules.java
-@@ -36,6 +36,14 @@
- 
- public class GameRules {
- 
-+    // Paper start - allow disabling gamerule limits
-+    private static final boolean DISABLE_LIMITS = Boolean.getBoolean("paper.disableGameRuleLimits");
-+
-+    private static int limit(final int limit, final int unlimited) {
-+        return DISABLE_LIMITS ? unlimited : limit;
-+    }
-+    // Paper end - allow disabling gamerule limits
-+
-     public static final int DEFAULT_RANDOM_TICK_SPEED = 3;
-     static final Logger LOGGER = LogUtils.getLogger();
-     private static final Map<GameRules.Key<?>, GameRules.Type<?>> GAME_RULE_TYPES = Maps.newTreeMap(Comparator.comparing((gamerules_gamerulekey) -> {
-@@ -58,7 +66,7 @@
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_SENDCOMMANDFEEDBACK = GameRules.register("sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_REDUCEDDEBUGINFO = GameRules.register("reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> {
-         int i = gamerules_gameruleboolean.get() ? 22 : 23;
--        Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator();
-+        Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world
- 
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-@@ -74,7 +82,7 @@
-     public static final GameRules.Key<GameRules.IntegerValue> RULE_MAX_ENTITY_CRAMMING = GameRules.register("maxEntityCramming", GameRules.Category.MOBS, GameRules.IntegerValue.create(24));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_WEATHER_CYCLE = GameRules.register("doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_LIMITED_CRAFTING = GameRules.register("doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> {
--        Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator();
-+        Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world
- 
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-@@ -90,7 +98,7 @@
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_DISABLE_RAIDS = GameRules.register("disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_DOINSOMNIA = GameRules.register("doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_DO_IMMEDIATE_RESPAWN = GameRules.register("doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> {
--        Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator();
-+        Iterator iterator = minecraftserver.players().iterator(); // CraftBukkit - per-world
- 
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-@@ -120,15 +128,16 @@
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_GLOBAL_SOUND_EVENTS = GameRules.register("globalSoundEvents", GameRules.Category.MISC, GameRules.BooleanValue.create(true));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_DO_VINES_SPREAD = GameRules.register("doVinesSpread", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true));
-     public static final GameRules.Key<GameRules.BooleanValue> RULE_ENDER_PEARLS_VANISH_ON_DEATH = GameRules.register("enderPearlsVanishOnDeath", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true));
--    public static final GameRules.Key<GameRules.IntegerValue> RULE_MINECART_MAX_SPEED = GameRules.register("minecartMaxSpeed", GameRules.Category.MISC, GameRules.IntegerValue.create(8, 1, 1000, FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (minecraftserver, gamerules_gameruleint) -> {
-+    public static final GameRules.Key<GameRules.IntegerValue> RULE_MINECART_MAX_SPEED = GameRules.register("minecartMaxSpeed", GameRules.Category.MISC, GameRules.IntegerValue.create(8, 1, limit(1000, Integer.MAX_VALUE), FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (minecraftserver, gamerules_gameruleint) -> { // Paper - allow disabling gamerule limits
-     }));
--    public static final GameRules.Key<GameRules.IntegerValue> RULE_SPAWN_CHUNK_RADIUS = GameRules.register("spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, 32, FeatureFlagSet.of(), (minecraftserver, gamerules_gameruleint) -> {
--        ServerLevel worldserver = minecraftserver.overworld();
-+    public static final GameRules.Key<GameRules.IntegerValue> RULE_SPAWN_CHUNK_RADIUS = GameRules.register("spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, limit(32, Integer.MAX_VALUE), FeatureFlagSet.of(), (minecraftserver, gamerules_gameruleint) -> { // Paper - allow disabling gamerule limits
-+        ServerLevel worldserver = minecraftserver; // CraftBukkit - per-world
- 
-         worldserver.setDefaultSpawnPos(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle());
-     }));
-     private final Map<GameRules.Key<?>, GameRules.Value<?>> rules;
-     private final FeatureFlagSet enabledFeatures;
-+    private final GameRules.Value<?>[] gameruleArray; // Paper - Perf: Use array for gamerule storage
- 
-     private static <T extends GameRules.Value<T>> GameRules.Key<T> register(String name, GameRules.Category category, GameRules.Type<T> type) {
-         GameRules.Key<T> gamerules_gamerulekey = new GameRules.Key<>(name, category);
-@@ -161,10 +170,21 @@
-     private GameRules(Map<GameRules.Key<?>, GameRules.Value<?>> rules, FeatureFlagSet enabledFeatures) {
-         this.rules = rules;
-         this.enabledFeatures = enabledFeatures;
-+
-+        // Paper start - Perf: Use array for gamerule storage
-+        int arraySize = GameRules.Key.lastGameRuleIndex + 1;
-+        GameRules.Value<?>[] values = new GameRules.Value[arraySize];
-+
-+        for (Entry<GameRules.Key<?>, GameRules.Value<?>> entry : rules.entrySet()) {
-+            values[entry.getKey().gameRuleIndex] = entry.getValue();
-+        }
-+
-+        this.gameruleArray = values;
-+        // Paper end - Perf: Use array for gamerule storage
-     }
- 
-     public <T extends GameRules.Value<T>> T getRule(GameRules.Key<T> key) {
--        T t0 = (GameRules.Value) this.rules.get(key);
-+        T t0 = key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage
- 
-         if (t0 == null) {
-             throw new IllegalArgumentException("Tried to access invalid game rule");
-@@ -184,7 +204,7 @@
- 
-     private void loadFromTag(DynamicLike<?> values) {
-         this.rules.forEach((gamerules_gamerulekey, gamerules_gamerulevalue) -> {
--            DataResult dataresult = values.get(gamerules_gamerulekey.id).asString();
-+            DataResult<String> dataresult = values.get(gamerules_gamerulekey.id).asString(); // CraftBukkit - decompile error
- 
-             Objects.requireNonNull(gamerules_gamerulevalue);
-             dataresult.ifSuccess(gamerules_gamerulevalue::deserialize);
-@@ -205,22 +225,22 @@
- 
-     private <T extends GameRules.Value<T>> void callVisitorCap(GameRules.GameRuleTypeVisitor visitor, GameRules.Key<?> key, GameRules.Type<?> type) {
-         if (type.requiredFeatures.isSubsetOf(this.enabledFeatures)) {
--            visitor.visit(key, type);
--            type.callVisitor(visitor, key);
-+            visitor.visit((GameRules.Key<T>) key, (GameRules.Type<T>) type); // CraftBukkit - decompile error
-+            ((GameRules.Type<T>) type).callVisitor(visitor, (GameRules.Key<T>) key); // CraftBukkit - decompile error
-         }
- 
-     }
- 
--    public void assignFrom(GameRules rules, @Nullable MinecraftServer server) {
--        rules.rules.keySet().forEach((gamerules_gamerulekey) -> {
--            this.assignCap(gamerules_gamerulekey, rules, server);
-+    public void assignFrom(GameRules gamerules, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+        gamerules.rules.keySet().forEach((gamerules_gamerulekey) -> {
-+            this.assignCap(gamerules_gamerulekey, gamerules, minecraftserver);
-         });
-     }
- 
--    private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> key, GameRules rules, @Nullable MinecraftServer server) {
--        T t0 = rules.getRule(key);
-+    private <T extends GameRules.Value<T>> void assignCap(GameRules.Key<T> gamerules_gamerulekey, GameRules gamerules, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+        T t0 = gamerules.getRule(gamerules_gamerulekey);
- 
--        this.getRule(key).setFrom(t0, server);
-+        this.getRule(gamerules_gamerulekey).setFrom(t0, minecraftserver);
-     }
- 
-     public boolean getBoolean(GameRules.Key<GameRules.BooleanValue> rule) {
-@@ -232,6 +252,10 @@
-     }
- 
-     public static final class Key<T extends GameRules.Value<T>> {
-+        // Paper start - Perf: Use array for gamerule storage
-+        public static int lastGameRuleIndex = 0;
-+        public final int gameRuleIndex = lastGameRuleIndex++;
-+        // Paper end - Perf: Use array for gamerule storage
- 
-         final String id;
-         private final GameRules.Category category;
-@@ -285,11 +309,11 @@
- 
-         final Supplier<ArgumentType<?>> argument;
-         private final Function<GameRules.Type<T>, T> constructor;
--        final BiConsumer<MinecraftServer, T> callback;
-+        final BiConsumer<ServerLevel, T> callback; // CraftBukkit - per-world
-         private final GameRules.VisitorCaller<T> visitorCaller;
-         final FeatureFlagSet requiredFeatures;
- 
--        Type(Supplier<ArgumentType<?>> argumentType, Function<GameRules.Type<T>, T> ruleFactory, BiConsumer<MinecraftServer, T> changeCallback, GameRules.VisitorCaller<T> ruleAcceptor, FeatureFlagSet requiredFeatures) {
-+        Type(Supplier<ArgumentType<?>> argumentType, Function<GameRules.Type<T>, T> ruleFactory, BiConsumer<ServerLevel, T> changeCallback, GameRules.VisitorCaller<T> ruleAcceptor, FeatureFlagSet requiredFeatures) { // CraftBukkit - per-world
-             this.argument = argumentType;
-             this.constructor = ruleFactory;
-             this.callback = changeCallback;
-@@ -302,7 +326,7 @@
-         }
- 
-         public T createRule() {
--            return (GameRules.Value) this.constructor.apply(this);
-+            return this.constructor.apply(this); // CraftBukkit - decompile error
-         }
- 
-         public void callVisitor(GameRules.GameRuleTypeVisitor consumer, GameRules.Key<T> key) {
-@@ -322,21 +346,21 @@
-             this.type = type;
-         }
- 
--        protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String name);
-+        protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<T> gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
- 
--        public void setFromArgument(CommandContext<CommandSourceStack> context, String name) {
--            this.updateFromArgument(context, name);
--            this.onChanged(((CommandSourceStack) context.getSource()).getServer());
-+        public void setFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<T> gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent
-+            this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
-+            this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world
-         }
- 
--        public void onChanged(@Nullable MinecraftServer server) {
--            if (server != null) {
--                this.type.callback.accept(server, this.getSelf());
-+        public void onChanged(@Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+            if (minecraftserver != null) {
-+                this.type.callback.accept(minecraftserver, this.getSelf());
-             }
- 
-         }
- 
--        protected abstract void deserialize(String value);
-+        public abstract void deserialize(String value); // PAIL - private->public
- 
-         public abstract String serialize();
- 
-@@ -350,7 +374,7 @@
- 
-         protected abstract T copy();
- 
--        public abstract void setFrom(T rule, @Nullable MinecraftServer server);
-+        public abstract void setFrom(T t0, @Nullable ServerLevel minecraftserver); // CraftBukkit - per-world
-     }
- 
-     public interface GameRuleTypeVisitor {
-@@ -366,7 +390,7 @@
- 
-         private boolean value;
- 
--        static GameRules.Type<GameRules.BooleanValue> create(boolean initialValue, BiConsumer<MinecraftServer, GameRules.BooleanValue> changeCallback) {
-+        static GameRules.Type<GameRules.BooleanValue> create(boolean initialValue, BiConsumer<ServerLevel, GameRules.BooleanValue> changeCallback) { // CraftBukkit - per-world
-             return new GameRules.Type<>(BoolArgumentType::bool, (gamerules_gameruledefinition) -> {
-                 return new GameRules.BooleanValue(gamerules_gameruledefinition, initialValue);
-             }, changeCallback, GameRules.GameRuleTypeVisitor::visitBoolean, FeatureFlagSet.of());
-@@ -383,17 +407,20 @@
-         }
- 
-         @Override
--        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name) {
--            this.value = BoolArgumentType.getBool(context, name);
-+        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<BooleanValue> gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent
-+            io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Boolean>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name)));
-+            if (!event.callEvent()) return;
-+            this.value = Boolean.parseBoolean(event.getValue());
-+            // Paper end - Add WorldGameRuleChangeEvent
-         }
- 
-         public boolean get() {
-             return this.value;
-         }
- 
--        public void set(boolean value, @Nullable MinecraftServer server) {
--            this.value = value;
--            this.onChanged(server);
-+        public void set(boolean flag, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+            this.value = flag;
-+            this.onChanged(minecraftserver);
-         }
- 
-         @Override
-@@ -402,7 +429,7 @@
-         }
- 
-         @Override
--        protected void deserialize(String value) {
-+        public void deserialize(String value) { // PAIL - protected->public
-             this.value = Boolean.parseBoolean(value);
-         }
- 
-@@ -421,9 +448,9 @@
-             return new GameRules.BooleanValue(this.type, this.value);
-         }
- 
--        public void setFrom(GameRules.BooleanValue rule, @Nullable MinecraftServer server) {
--            this.value = rule.value;
--            this.onChanged(server);
-+        public void setFrom(GameRules.BooleanValue gamerules_gameruleboolean, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+            this.value = gamerules_gameruleboolean.value;
-+            this.onChanged(minecraftserver);
-         }
-     }
- 
-@@ -431,13 +458,13 @@
- 
-         private int value;
- 
--        private static GameRules.Type<GameRules.IntegerValue> create(int initialValue, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeCallback) {
-+        private static GameRules.Type<GameRules.IntegerValue> create(int initialValue, BiConsumer<ServerLevel, GameRules.IntegerValue> changeCallback) { // CraftBukkit - per-world
-             return new GameRules.Type<>(IntegerArgumentType::integer, (gamerules_gameruledefinition) -> {
-                 return new GameRules.IntegerValue(gamerules_gameruledefinition, initialValue);
-             }, changeCallback, GameRules.GameRuleTypeVisitor::visitInteger, FeatureFlagSet.of());
-         }
- 
--        static GameRules.Type<GameRules.IntegerValue> create(int initialValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<MinecraftServer, GameRules.IntegerValue> changeCallback) {
-+        static GameRules.Type<GameRules.IntegerValue> create(int initialValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer<ServerLevel, GameRules.IntegerValue> changeCallback) { // CraftBukkit - per-world
-             return new GameRules.Type<>(() -> {
-                 return IntegerArgumentType.integer(min, max);
-             }, (gamerules_gameruledefinition) -> {
-@@ -456,17 +483,20 @@
-         }
- 
-         @Override
--        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name) {
--            this.value = IntegerArgumentType.getInteger(context, name);
-+        protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<IntegerValue> gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent
-+            io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Integer>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name)));
-+            if (!event.callEvent()) return;
-+            this.value = Integer.parseInt(event.getValue());
-+            // Paper end - Add WorldGameRuleChangeEvent
-         }
- 
-         public int get() {
-             return this.value;
-         }
- 
--        public void set(int value, @Nullable MinecraftServer server) {
--            this.value = value;
--            this.onChanged(server);
-+        public void set(int i, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+            this.value = i;
-+            this.onChanged(minecraftserver);
-         }
- 
-         @Override
-@@ -475,7 +505,7 @@
-         }
- 
-         @Override
--        protected void deserialize(String value) {
-+        public void deserialize(String value) { // PAIL - protected->public
-             this.value = IntegerValue.safeParse(value);
-         }
- 
-@@ -517,9 +547,9 @@
-             return new GameRules.IntegerValue(this.type, this.value);
-         }
- 
--        public void setFrom(GameRules.IntegerValue rule, @Nullable MinecraftServer server) {
--            this.value = rule.value;
--            this.onChanged(server);
-+        public void setFrom(GameRules.IntegerValue gamerules_gameruleint, @Nullable ServerLevel minecraftserver) { // CraftBukkit - per-world
-+            this.value = gamerules_gameruleint.value;
-+            this.onChanged(minecraftserver);
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/LevelAccessor.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/LevelAccessor.java.patch
deleted file mode 100644
index 7c0828c3ad..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/LevelAccessor.java.patch
+++ /dev/null
@@ -1,9 +0,0 @@
---- a/net/minecraft/world/level/LevelAccessor.java
-+++ b/net/minecraft/world/level/LevelAccessor.java
-@@ -101,4 +101,6 @@
-     default void gameEvent(ResourceKey<GameEvent> event, BlockPos pos, GameEvent.Context emitter) {
-         this.gameEvent((Holder) this.registryAccess().lookupOrThrow(Registries.GAME_EVENT).getOrThrow(event), pos, emitter);
-     }
-+
-+    net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/NaturalSpawner.java.patch
deleted file mode 100644
index 28fc9a7cb8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/NaturalSpawner.java.patch
+++ /dev/null
@@ -1,212 +0,0 @@
---- a/net/minecraft/world/level/NaturalSpawner.java
-+++ b/net/minecraft/world/level/NaturalSpawner.java
-@@ -47,8 +47,13 @@
- import net.minecraft.world.level.levelgen.structure.Structure;
- import net.minecraft.world.level.levelgen.structure.structures.NetherFortressStructure;
- import net.minecraft.world.level.material.FluidState;
-+import net.minecraft.world.level.storage.LevelData;
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
-+import org.bukkit.craftbukkit.util.CraftSpawnCategory;
-+import org.bukkit.entity.SpawnCategory;
-+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
-+// CraftBukkit end
- 
- public final class NaturalSpawner {
- 
-@@ -82,6 +87,13 @@
-             MobCategory enumcreaturetype = entity.getType().getCategory();
- 
-             if (enumcreaturetype != MobCategory.MISC) {
-+                // Paper start - Only count natural spawns
-+                if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
-+                    !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ||
-+                        entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
-+                    continue;
-+                }
-+                // Paper end - Only count natural spawns
-                 BlockPos blockposition = entity.blockPosition();
- 
-                 chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> {
-@@ -107,15 +119,31 @@
-         return (Biome) chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
-     }
- 
--    public static List<MobCategory> getFilteredSpawningCategories(NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rare) {
-+    // CraftBukkit start - add server
-+    public static List<MobCategory> getFilteredSpawningCategories(NaturalSpawner.SpawnState spawnercreature_d, boolean flag, boolean flag1, boolean flag2, ServerLevel worldserver) {
-+        LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
-+        // CraftBukkit end
-         List<MobCategory> list = new ArrayList(NaturalSpawner.SPAWNING_CATEGORIES.length);
-         MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES;
-         int i = aenumcreaturetype.length;
- 
-         for (int j = 0; j < i; ++j) {
-             MobCategory enumcreaturetype = aenumcreaturetype[j];
-+            // CraftBukkit start - Use per-world spawn limits
-+            boolean spawnThisTick = true;
-+            int limit = enumcreaturetype.getMaxInstancesPerChunk();
-+            SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
-+            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
-+                spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
-+                limit = worldserver.getWorld().getSpawnLimit(spawnCategory);
-+            }
- 
--            if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rare || !enumcreaturetype.isPersistent()) && info.canSpawnForCategoryGlobal(enumcreaturetype)) {
-+            if (!spawnThisTick || limit == 0) {
-+                continue;
-+            }
-+
-+            if ((flag || !enumcreaturetype.isFriendly()) && (flag1 || enumcreaturetype.isFriendly()) && (flag2 || !enumcreaturetype.isPersistent()) && spawnercreature_d.canSpawnForCategoryGlobal(enumcreaturetype, limit)) {
-+                // CraftBukkit end
-                 list.add(enumcreaturetype);
-             }
-         }
-@@ -144,6 +172,16 @@
-         gameprofilerfiller.pop();
-     }
- 
-+    // Paper start - Add mobcaps commands
-+    public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) {
-+        final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category));
-+        if (categoryLimit < 1) {
-+            return categoryLimit;
-+        }
-+        return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
-+    }
-+    // Paper end - Add mobcaps commands
-+
-     public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
-         BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
- 
-@@ -164,9 +202,9 @@
-         StructureManager structuremanager = world.structureManager();
-         ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
-         int i = pos.getY();
--        BlockState iblockdata = chunk.getBlockState(pos);
-+        BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
- 
--        if (!iblockdata.isRedstoneConductor(chunk, pos)) {
-+        if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
-             BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
-             int j = 0;
-             int k = 0;
-@@ -195,7 +233,7 @@
-                             if (entityhuman != null) {
-                                 double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
- 
--                                if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) {
-+                                if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn
-                                     if (biomesettingsmobs_c == null) {
-                                         Optional<MobSpawnSettings.SpawnerData> optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition);
- 
-@@ -207,7 +245,13 @@
-                                         j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
-                                     }
- 
--                                    if (NaturalSpawner.isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
-+                                    // Paper start - PreCreatureSpawnEvent
-+                                    PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
-+                                    if (doSpawning == PreSpawnStatus.ABORT) {
-+                                        return;
-+                                    }
-+                                    if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
-+                                        // Paper end - PreCreatureSpawnEvent
-                                         Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type);
- 
-                                         if (entityinsentient == null) {
-@@ -217,10 +261,15 @@
-                                         entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F);
-                                         if (NaturalSpawner.isValidPositionForMob(world, entityinsentient, d2)) {
-                                             groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.NATURAL, groupdataentity);
--                                            ++j;
--                                            ++k1;
--                                            world.addFreshEntityWithPassengers(entityinsentient);
--                                            runner.run(entityinsentient, chunk);
-+                                            // CraftBukkit start
-+                                            // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs.
-+                                            world.addFreshEntityWithPassengers(entityinsentient, (entityinsentient instanceof net.minecraft.world.entity.animal.Ocelot && !((org.bukkit.entity.Ageable) entityinsentient.getBukkitEntity()).isAdult()) ? SpawnReason.OCELOT_BABY : SpawnReason.NATURAL);
-+                                            if (!entityinsentient.isRemoved()) {
-+                                                ++j;
-+                                                ++k1;
-+                                                runner.run(entityinsentient, chunk);
-+                                            }
-+                                            // CraftBukkit end
-                                             if (j >= entityinsentient.getMaxSpawnClusterSize()) {
-                                                 return;
-                                             }
-@@ -250,10 +299,31 @@
-         return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos));
-     }
- 
--    private static boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) {
-+    // Paper start - PreCreatureSpawnEvent
-+    private enum PreSpawnStatus {
-+        FAIL,
-+        SUCCESS,
-+        CANCELLED,
-+        ABORT
-+    }
-+    private static PreSpawnStatus isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) {
-+        // Paper end - PreCreatureSpawnEvent
-         EntityType<?> entitytypes = spawnEntry.type;
- 
--        return entitytypes.getCategory() == MobCategory.MISC ? false : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? false : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)))) : false));
-+        // Paper start - PreCreatureSpawnEvent
-+        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
-+            io.papermc.paper.util.MCUtil.toLocation(world, pos),
-+            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes), SpawnReason.NATURAL
-+        );
-+        if (!event.callEvent()) {
-+            if (event.shouldAbortSpawn()) {
-+                return PreSpawnStatus.ABORT;
-+            }
-+            return PreSpawnStatus.CANCELLED;
-+        }
-+        // Paper end - PreCreatureSpawnEvent
-+
-+        return entitytypes.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? PreSpawnStatus.FAIL : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent
-     }
- 
-     @Nullable
-@@ -268,6 +338,7 @@
-             NaturalSpawner.LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(type));
-         } catch (Exception exception) {
-             NaturalSpawner.LOGGER.warn("Failed to create mob", exception);
-+            com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent
-         }
- 
-         return null;
-@@ -356,6 +427,7 @@
-                                     entity = biomesettingsmobs_c.type.create(world.getLevel(), EntitySpawnReason.NATURAL);
-                                 } catch (Exception exception) {
-                                     NaturalSpawner.LOGGER.warn("Failed to create mob", exception);
-+                                    com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent
-                                     continue;
-                                 }
- 
-@@ -369,7 +441,7 @@
- 
-                                     if (entityinsentient.checkSpawnRules(world, EntitySpawnReason.CHUNK_GENERATION) && entityinsentient.checkSpawnObstruction(world)) {
-                                         groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupdataentity);
--                                        world.addFreshEntityWithPassengers(entityinsentient);
-+                                        world.addFreshEntityWithPassengers(entityinsentient, SpawnReason.CHUNK_GEN); // CraftBukkit
-                                         flag = true;
-                                     }
-                                 }
-@@ -482,10 +554,12 @@
-             return this.unmodifiableMobCategoryCounts;
-         }
- 
--        boolean canSpawnForCategoryGlobal(MobCategory group) {
--            int i = group.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
-+        // CraftBukkit start
-+        boolean canSpawnForCategoryGlobal(MobCategory enumcreaturetype, int limit) {
-+            int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
-+            // CraftBukkit end
- 
--            return this.mobCategoryCounts.getInt(group) < i;
-+            return this.mobCategoryCounts.getInt(enumcreaturetype) < i;
-         }
- 
-         boolean canSpawnForCategoryLocal(MobCategory group, ChunkPos chunkPos) {

From d3ec6a433b8ec0897abe44867081441cc2bcc4c2 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 15:58:32 -0800
Subject: [PATCH 133/285] net.minecraft.world.level.storage

---
 .../storage/DimensionDataStorage.java.patch   |  20 ++
 .../storage/LevelStorageSource.java.patch     |  86 ++++++++
 .../storage/PlayerDataStorage.java.patch      | 122 +++++++++++
 .../level/storage/PrimaryLevelData.java.patch | 123 +++++++++++
 .../storage/DimensionDataStorage.java.patch   |  20 --
 .../storage/LevelStorageSource.java.patch     | 107 ---------
 .../storage/PlayerDataStorage.java.patch      | 143 ------------
 .../level/storage/PrimaryLevelData.java.patch | 203 ------------------
 8 files changed, 351 insertions(+), 473 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/LevelStorageSource.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/storage/PrimaryLevelData.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
new file mode 100644
index 0000000000..41f17dad54
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/storage/DimensionDataStorage.java
++++ b/net/minecraft/world/level/storage/DimensionDataStorage.java
+@@ -139,7 +_,7 @@
+         } else {
+             int i = Util.maxAllowedExecutorThreads();
+             int size = map.size();
+-            if (size > i) {
++            if (false && size > i) { // Paper - Separate dimension data IO pool; just throw them into the fixed pool queue
+                 this.pendingWriteFuture = this.pendingWriteFuture.thenCompose(object -> {
+                     List<CompletableFuture<?>> list = new ArrayList<>(i);
+                     int i1 = Mth.positiveCeilDiv(size, i);
+@@ -160,7 +_,7 @@
+                         object -> CompletableFuture.allOf(
+                             map.entrySet()
+                                 .stream()
+-                                .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.ioPool()))
++                                .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.DIMENSION_DATA_IO_POOL)) // Paper - Separate dimension data IO pool
+                                 .toArray(CompletableFuture[]::new)
+                         )
+                     );
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
new file mode 100644
index 0000000000..d6448bf4b9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -0,0 +1,86 @@
+--- a/net/minecraft/world/level/storage/LevelStorageSource.java
++++ b/net/minecraft/world/level/storage/LevelStorageSource.java
+@@ -66,7 +_,6 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.LevelSettings;
+ import net.minecraft.world.level.WorldDataConfiguration;
+-import net.minecraft.world.level.dimension.DimensionType;
+ import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.levelgen.WorldDimensions;
+ import net.minecraft.world.level.levelgen.WorldGenSettings;
+@@ -145,6 +_,7 @@
+         PrimaryLevelData primaryLevelData = PrimaryLevelData.parse(
+             dynamic, levelSettings, complete.specialWorldProperty(), worldGenSettings.options(), lifecycle
+         );
++        primaryLevelData.pdc = dynamic.castTyped(NbtOps.INSTANCE).getElement("BukkitValues", null); // CraftBukkit - Add PDC to world
+         return new LevelDataAndDimensions(primaryLevelData, complete);
+     }
+ 
+@@ -340,25 +_,39 @@
+         return this.backupDir;
+     }
+ 
+-    public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String saveName) throws IOException, ContentValidationException {
++    public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String saveName, ResourceKey<LevelStem> dimensionType) throws IOException, ContentValidationException { // CraftBukkit
+         Path levelPath = this.getLevelPath(saveName);
+-        List<ForbiddenSymlinkInfo> list = this.worldDirValidator.validateDirectory(levelPath, true);
++        List<ForbiddenSymlinkInfo> list = Boolean.getBoolean("paper.disableWorldSymlinkValidation") ? List.of() : this.worldDirValidator.validateDirectory(levelPath, true); // Paper - add skipping of symlinks scan
+         if (!list.isEmpty()) {
+             throw new ContentValidationException(levelPath, list);
+         } else {
+-            return new LevelStorageSource.LevelStorageAccess(saveName, levelPath);
++            return new LevelStorageSource.LevelStorageAccess(saveName, levelPath, dimensionType); // CraftBukkit
+         }
+     }
+ 
+-    public LevelStorageSource.LevelStorageAccess createAccess(String saveName) throws IOException {
++    public LevelStorageSource.LevelStorageAccess createAccess(String saveName, ResourceKey<LevelStem> dimensionType) throws IOException { // CraftBukkit
+         Path levelPath = this.getLevelPath(saveName);
+-        return new LevelStorageSource.LevelStorageAccess(saveName, levelPath);
++        return new LevelStorageSource.LevelStorageAccess(saveName, levelPath, dimensionType); // CraftBukkit
+     }
+ 
+     public DirectoryValidator getWorldDirValidator() {
+         return this.worldDirValidator;
+     }
+ 
++    // CraftBukkit start
++    public static Path getStorageFolder(Path path, ResourceKey<LevelStem> dimensionType) {
++        if (dimensionType == LevelStem.OVERWORLD) {
++            return path;
++        } else if (dimensionType == LevelStem.NETHER) {
++            return path.resolve("DIM-1");
++        } else if (dimensionType == LevelStem.END) {
++            return path.resolve("DIM1");
++        } else {
++            return path.resolve("dimensions").resolve(dimensionType.location().getNamespace()).resolve(dimensionType.location().getPath());
++        }
++    }
++    // CraftBukkit end
++
+     public record LevelCandidates(List<LevelStorageSource.LevelDirectory> levels) implements Iterable<LevelStorageSource.LevelDirectory> {
+         public boolean isEmpty() {
+             return this.levels.isEmpty();
+@@ -409,8 +_,12 @@
+         public final LevelStorageSource.LevelDirectory levelDirectory;
+         private final String levelId;
+         private final Map<LevelResource, Path> resources = Maps.newHashMap();
++        // CraftBukkit start
++        public final ResourceKey<LevelStem> dimensionType;
+ 
+-        LevelStorageAccess(final String levelId, final Path levelDir) throws IOException {
++        LevelStorageAccess(final String levelId, final Path levelDir, final ResourceKey<LevelStem> dimensionType) throws IOException {
++            this.dimensionType = dimensionType;
++            // CraftBukkit end
+             this.levelId = levelId;
+             this.levelDirectory = new LevelStorageSource.LevelDirectory(levelDir);
+             this.lock = DirectoryLock.create(levelDir);
+@@ -453,7 +_,7 @@
+         }
+ 
+         public Path getDimensionPath(ResourceKey<Level> dimensionPath) {
+-            return DimensionType.getStorageFolder(dimensionPath, this.levelDirectory.path());
++            return getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
+         }
+ 
+         private void checkLock() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
new file mode 100644
index 0000000000..f531362c07
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
@@ -0,0 +1,122 @@
+--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
+@@ -14,8 +_,10 @@
+ import net.minecraft.nbt.NbtAccounter;
+ import net.minecraft.nbt.NbtIo;
+ import net.minecraft.nbt.NbtUtils;
++import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.datafix.DataFixTypes;
+ import net.minecraft.world.entity.player.Player;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
+ import org.slf4j.Logger;
+ 
+ public class PlayerDataStorage {
+@@ -31,6 +_,7 @@
+     }
+ 
+     public void save(Player player) {
++        if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
+         try {
+             CompoundTag compoundTag = player.saveWithoutId(new CompoundTag());
+             Path path = this.playerDir.toPath();
+@@ -40,30 +_,46 @@
+             Path path3 = path.resolve(player.getStringUUID() + ".dat_old");
+             Util.safeReplaceFile(path2, path1, path3);
+         } catch (Exception var7) {
+-            LOGGER.warn("Failed to save player data for {}", player.getName().getString());
++            LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), var7); // Paper - Print exception
+         }
+     }
+ 
+-    private void backup(Player player, String suffix) {
++    private void backup(String name, String stringUuid, String suffix) { // CraftBukkit
+         Path path = this.playerDir.toPath();
+-        Path path1 = path.resolve(player.getStringUUID() + suffix);
+-        Path path2 = path.resolve(player.getStringUUID() + "_corrupted_" + LocalDateTime.now().format(FORMATTER) + suffix);
++        Path path1 = path.resolve(stringUuid + suffix); // CraftBukkit
++        Path path2 = path.resolve(stringUuid + "_corrupted_" + LocalDateTime.now().format(FORMATTER) + suffix); // CraftBukkit
+         if (Files.isRegularFile(path1)) {
+             try {
+                 Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
+             } catch (Exception var7) {
+-                LOGGER.warn("Failed to copy the player.dat file for {}", player.getName().getString(), var7);
++                LOGGER.warn("Failed to copy the player.dat file for {}", name, var7); // CraftBukkit
+             }
+         }
+     }
+ 
+-    private Optional<CompoundTag> load(Player player, String suffix) {
+-        File file = new File(this.playerDir, player.getStringUUID() + suffix);
++    private Optional<CompoundTag> load(String name, String stringUuid, String suffix) { // CraftBukkit
++        File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
++        // Spigot Start
++        boolean usingWrongFile = false;
++        if (org.bukkit.Bukkit.getOnlineMode() && !file.exists()) { // Paper - Check online mode first
++            file = new File(file, java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString() + suffix);
++            if (file.exists()) {
++                usingWrongFile = true;
++                org.bukkit.Bukkit.getServer().getLogger().warning("Using offline mode UUID file for player " + name + " as it is the only copy we can find.");
++            }
++        }
++        // Spigot End
+         if (file.exists() && file.isFile()) {
+             try {
+-                return Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
++                // Spigot Start
++                Optional<CompoundTag> optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
++                if (usingWrongFile) {
++                    file.renameTo(new File(file.getPath() + ".offline-read"));
++                }
++                return optional;
++                // Spigot End
+             } catch (Exception var5) {
+-                LOGGER.warn("Failed to load player data for {}", player.getName().getString());
++                LOGGER.warn("Failed to load player data for {}", name); // CraftBukkit
+             }
+         }
+ 
+@@ -71,16 +_,40 @@
+     }
+ 
+     public Optional<CompoundTag> load(Player player) {
+-        Optional<CompoundTag> optional = this.load(player, ".dat");
++        // CraftBukkit start
++        return this.load(player.getName().getString(), player.getStringUUID()).map((tag) -> {
++            if (player instanceof ServerPlayer) {
++                CraftPlayer player1 = (CraftPlayer) player.getBukkitEntity();
++                // Only update first played if it is older than the one we have
++                long modified = new File(this.playerDir, player.getStringUUID() + ".dat").lastModified();
++                if (modified < player1.getFirstPlayed()) {
++                    player1.setFirstPlayed(modified);
++                }
++            }
++
++            player.load(tag); // From below
++            return tag;
++        });
++    }
++
++    public Optional<CompoundTag> load(String name, String uuid) {
++        // CraftBukkit end
++        Optional<CompoundTag> optional = this.load(name, uuid, ".dat"); // CraftBukkit
+         if (optional.isEmpty()) {
+-            this.backup(player, ".dat");
++            this.backup(name, uuid, ".dat"); // CraftBukkit
+         }
+ 
+-        return optional.or(() -> this.load(player, ".dat_old")).map(compoundTag -> {
++        return optional.or(() -> this.load(name, uuid, ".dat_old")).map(compoundTag -> { // CraftBukkit
+             int dataVersion = NbtUtils.getDataVersion(compoundTag, -1);
+             compoundTag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundTag, dataVersion);
+-            player.load(compoundTag);
++            // player.load(compoundTag); // CraftBukkit - handled above
+             return compoundTag;
+         });
+     }
++
++    // CraftBukkit start
++    public File getPlayerDir() {
++        return this.playerDir;
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
new file mode 100644
index 0000000000..8cd7249f86
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
@@ -0,0 +1,123 @@
+--- a/net/minecraft/world/level/storage/PrimaryLevelData.java
++++ b/net/minecraft/world/level/storage/PrimaryLevelData.java
+@@ -74,6 +_,21 @@
+     private final Set<String> removedFeatureFlags;
+     private final TimerQueue<MinecraftServer> scheduledEvents;
+ 
++    // CraftBukkit start - Add world and pdc
++    public net.minecraft.core.Registry<net.minecraft.world.level.dimension.LevelStem> customDimensions;
++    private net.minecraft.server.level.ServerLevel world;
++    protected net.minecraft.nbt.Tag pdc;
++
++    public void setWorld(net.minecraft.server.level.ServerLevel world) {
++        if (this.world != null) {
++            return;
++        }
++        this.world = world;
++        world.getWorld().readBukkitValues(this.pdc);
++        this.pdc = null;
++    }
++    // CraftBukkit end
++
+     private PrimaryLevelData(
+         @Nullable CompoundTag loadedPlayerTag,
+         boolean wasModded,
+@@ -237,7 +_,7 @@
+         nbt.put("Version", compoundTag);
+         NbtUtils.addCurrentDataVersion(nbt);
+         DynamicOps<Tag> dynamicOps = registry.createSerializationContext(NbtOps.INSTANCE);
+-        WorldGenSettings.encode(dynamicOps, this.worldOptions, registry)
++        WorldGenSettings.encode(dynamicOps, this.worldOptions, new net.minecraft.world.level.levelgen.WorldDimensions(this.customDimensions != null ? this.customDimensions : registry.lookupOrThrow(net.minecraft.core.registries.Registries.LEVEL_STEM))) // CraftBukkit
+             .resultOrPartial(Util.prefix("WorldGenSettings: ", LOGGER::error))
+             .ifPresent(worldOptionsTag -> nbt.put("WorldGenSettings", worldOptionsTag));
+         nbt.putInt("GameType", this.settings.gameType().getId());
+@@ -281,6 +_,8 @@
+         if (this.wanderingTraderId != null) {
+             nbt.putUUID("WanderingTraderId", this.wanderingTraderId);
+         }
++        nbt.putString("Bukkit.Version", org.bukkit.Bukkit.getName() + "/" + org.bukkit.Bukkit.getVersion() + "/" + org.bukkit.Bukkit.getBukkitVersion()); // CraftBukkit
++        this.world.getWorld().storeBukkitValues(nbt); // CraftBukkit - add pdc
+     }
+ 
+     private static ListTag stringCollectionToTag(Set<String> stringCollection) {
+@@ -358,6 +_,25 @@
+ 
+     @Override
+     public void setThundering(boolean thundering) {
++        // Paper start - Add cause to Weather/ThunderChangeEvents
++        this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN);
++    }
++    public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) {
++        // Paper end - Add cause to Weather/ThunderChangeEvents
++        // CraftBukkit start
++        if (this.thundering == thundering) {
++            return;
++        }
++
++        org.bukkit.World world = org.bukkit.Bukkit.getWorld(this.getLevelName());
++        if (world != null) {
++            org.bukkit.event.weather.ThunderChangeEvent thunder = new org.bukkit.event.weather.ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents
++            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(thunder);
++            if (thunder.isCancelled()) {
++                return;
++            }
++        }
++        // CraftBukkit end
+         this.thundering = thundering;
+     }
+ 
+@@ -378,6 +_,26 @@
+ 
+     @Override
+     public void setRaining(boolean isRaining) {
++        // Paper start - Add cause to Weather/ThunderChangeEvents
++        this.setRaining(isRaining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN);
++    }
++
++    public void setRaining(boolean isRaining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) {
++        // Paper end - Add cause to Weather/ThunderChangeEvents
++        // CraftBukkit start
++        if (this.raining == isRaining) {
++            return;
++        }
++
++        org.bukkit.World world = org.bukkit.Bukkit.getWorld(this.getLevelName());
++        if (world != null) {
++            org.bukkit.event.weather.WeatherChangeEvent weather = new org.bukkit.event.weather.WeatherChangeEvent(world, isRaining, cause); // Paper - Add cause to Weather/ThunderChangeEvents
++            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(weather);
++            if (weather.isCancelled()) {
++                return;
++            }
++        }
++        // CraftBukkit end
+         this.raining = isRaining;
+     }
+ 
+@@ -444,6 +_,12 @@
+     @Override
+     public void setDifficulty(Difficulty difficulty) {
+         this.settings = this.settings.withDifficulty(difficulty);
++        // CraftBukkit start
++        net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket packet = new net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket(this.getDifficulty(), this.isDifficultyLocked());
++        for (net.minecraft.server.level.ServerPlayer player : (java.util.List<net.minecraft.server.level.ServerPlayer>) (java.util.List) this.world.players()) {
++            player.connection.send(packet);
++        }
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -579,6 +_,14 @@
+     public LevelSettings getLevelSettings() {
+         return this.settings.copy();
+     }
++
++    // CraftBukkit start - Check if the name stored in NBT is the correct one
++    public void checkName(String name) {
++        if (!this.settings.levelName.equals(name)) {
++            this.settings.levelName = name;
++        }
++    }
++    // CraftBukkit end
+ 
+     @Deprecated
+     public static enum SpecialWorldProperty {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/DimensionDataStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
deleted file mode 100644
index 0a22aaf58b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/DimensionDataStorage.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/level/storage/DimensionDataStorage.java
-+++ b/net/minecraft/world/level/storage/DimensionDataStorage.java
-@@ -139,7 +139,7 @@
-         } else {
-             int i = Util.maxAllowedExecutorThreads();
-             int j = map.size();
--            if (j > i) {
-+            if (false && j > i) { // Paper - Separate dimension data IO pool; just throw them into the fixed pool queue
-                 this.pendingWriteFuture = this.pendingWriteFuture.thenCompose(object -> {
-                     List<CompletableFuture<?>> list = new ArrayList<>(i);
-                     int k = Mth.positiveCeilDiv(j, i);
-@@ -160,7 +160,7 @@
-                         v -> CompletableFuture.allOf(
-                                 map.entrySet()
-                                     .stream()
--                                    .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.ioPool()))
-+                                    .map(entry -> CompletableFuture.runAsync(() -> tryWrite(entry.getKey(), entry.getValue()), Util.DIMENSION_DATA_IO_POOL)) // Paper - Separate dimension data IO pool
-                                     .toArray(CompletableFuture[]::new)
-                             )
-                     );
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/LevelStorageSource.java.patch
deleted file mode 100644
index e0a1e52b18..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/LevelStorageSource.java.patch
+++ /dev/null
@@ -1,107 +0,0 @@
---- a/net/minecraft/world/level/storage/LevelStorageSource.java
-+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
-@@ -68,7 +68,6 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelSettings;
- import net.minecraft.world.level.WorldDataConfiguration;
--import net.minecraft.world.level.dimension.DimensionType;
- import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.WorldDimensions;
- import net.minecraft.world.level.levelgen.WorldGenSettings;
-@@ -149,7 +148,7 @@
-     }
- 
-     public static WorldDataConfiguration readDataConfig(Dynamic<?> dynamic) {
--        DataResult dataresult = WorldDataConfiguration.CODEC.parse(dynamic);
-+        DataResult<WorldDataConfiguration> dataresult = WorldDataConfiguration.CODEC.parse(dynamic); // CraftBukkit - decompile error
-         Logger logger = LevelStorageSource.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -168,6 +167,7 @@
-         WorldDimensions.Complete worlddimensions_b = generatorsettings.dimensions().bake(dimensionsRegistry);
-         Lifecycle lifecycle = worlddimensions_b.lifecycle().add(registries.allRegistriesLifecycle());
-         PrimaryLevelData worlddataserver = PrimaryLevelData.parse(dynamic1, worldsettings, worlddimensions_b.specialWorldProperty(), generatorsettings.options(), lifecycle);
-+        worlddataserver.pdc = ((Dynamic<Tag>) dynamic1).getElement("BukkitValues", null); // CraftBukkit - Add PDC to world
- 
-         return new LevelDataAndDimensions(worlddataserver, worlddimensions_b);
-     }
-@@ -409,26 +409,40 @@
-         return this.backupDir;
-     }
- 
--    public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String directoryName) throws IOException, ContentValidationException {
--        Path path = this.getLevelPath(directoryName);
--        List<ForbiddenSymlinkInfo> list = this.worldDirValidator.validateDirectory(path, true);
-+    public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String s, ResourceKey<LevelStem> dimensionType) throws IOException, ContentValidationException { // CraftBukkit
-+        Path path = this.getLevelPath(s);
-+        List<ForbiddenSymlinkInfo> list = Boolean.getBoolean("paper.disableWorldSymlinkValidation") ? List.of() : this.worldDirValidator.validateDirectory(path, true); // Paper - add skipping of symlinks scan
- 
-         if (!list.isEmpty()) {
-             throw new ContentValidationException(path, list);
-         } else {
--            return new LevelStorageSource.LevelStorageAccess(directoryName, path);
-+            return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit
-         }
-     }
- 
--    public LevelStorageSource.LevelStorageAccess createAccess(String directoryName) throws IOException {
--        Path path = this.getLevelPath(directoryName);
-+    public LevelStorageSource.LevelStorageAccess createAccess(String s, ResourceKey<LevelStem> dimensionType) throws IOException { // CraftBukkit
-+        Path path = this.getLevelPath(s);
- 
--        return new LevelStorageSource.LevelStorageAccess(directoryName, path);
-+        return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit
-     }
- 
-     public DirectoryValidator getWorldDirValidator() {
-         return this.worldDirValidator;
-+    }
-+
-+    // CraftBukkit start
-+    public static Path getStorageFolder(Path path, ResourceKey<LevelStem> dimensionType) {
-+        if (dimensionType == LevelStem.OVERWORLD) {
-+            return path;
-+        } else if (dimensionType == LevelStem.NETHER) {
-+            return path.resolve("DIM-1");
-+        } else if (dimensionType == LevelStem.END) {
-+            return path.resolve("DIM1");
-+        } else {
-+            return path.resolve("dimensions").resolve(dimensionType.location().getNamespace()).resolve(dimensionType.location().getPath());
-+        }
-     }
-+    // CraftBukkit end
- 
-     public static record LevelCandidates(List<LevelStorageSource.LevelDirectory> levels) implements Iterable<LevelStorageSource.LevelDirectory> {
- 
-@@ -488,8 +502,12 @@
-         public final LevelStorageSource.LevelDirectory levelDirectory;
-         private final String levelId;
-         private final Map<LevelResource, Path> resources = Maps.newHashMap();
-+        // CraftBukkit start
-+        public final ResourceKey<LevelStem> dimensionType;
- 
--        LevelStorageAccess(final String s, final Path path) throws IOException {
-+        LevelStorageAccess(final String s, final Path path, final ResourceKey<LevelStem> dimensionType) throws IOException {
-+            this.dimensionType = dimensionType;
-+            // CraftBukkit end
-             this.levelId = s;
-             this.levelDirectory = new LevelStorageSource.LevelDirectory(path);
-             this.lock = DirectoryLock.create(path);
-@@ -529,7 +547,7 @@
-         }
- 
-         public Path getLevelPath(LevelResource savePath) {
--            Map map = this.resources;
-+            Map<LevelResource, Path> map = this.resources; // CraftBukkit - decompile error
-             LevelStorageSource.LevelDirectory convertable_b = this.levelDirectory;
- 
-             Objects.requireNonNull(this.levelDirectory);
-@@ -537,7 +555,7 @@
-         }
- 
-         public Path getDimensionPath(ResourceKey<Level> key) {
--            return DimensionType.getStorageFolder(key, this.levelDirectory.path());
-+            return LevelStorageSource.getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
-         }
- 
-         private void checkLock() {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
deleted file mode 100644
index 6ed0222e60..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
+++ /dev/null
@@ -1,143 +0,0 @@
---- a/net/minecraft/world/level/storage/PlayerDataStorage.java
-+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
-@@ -15,8 +15,10 @@
- import net.minecraft.nbt.NbtAccounter;
- import net.minecraft.nbt.NbtIo;
- import net.minecraft.nbt.NbtUtils;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.datafix.DataFixTypes;
- import net.minecraft.world.entity.player.Player;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
- import org.slf4j.Logger;
- 
- public class PlayerDataStorage {
-@@ -33,6 +35,7 @@
-     }
- 
-     public void save(Player player) {
-+        if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
-         try {
-             CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag());
-             Path path = this.playerDir.toPath();
-@@ -44,39 +47,60 @@
- 
-             Util.safeReplaceFile(path2, path1, path3);
-         } catch (Exception exception) {
--            PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString());
-+            PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception
-         }
- 
-     }
- 
--    private void backup(Player player, String extension) {
-+    private void backup(String name, String s1, String s) { // name, uuid, extension
-         Path path = this.playerDir.toPath();
--        String s1 = player.getStringUUID();
--        Path path1 = path.resolve(s1 + extension);
-+        // String s1 = entityhuman.getStringUUID(); // CraftBukkit - used above
-+        Path path1 = path.resolve(s1 + s);
- 
--        s1 = player.getStringUUID();
--        Path path2 = path.resolve(s1 + "_corrupted_" + LocalDateTime.now().format(PlayerDataStorage.FORMATTER) + extension);
-+        // s1 = entityhuman.getStringUUID(); // CraftBukkit - used above
-+        Path path2 = path.resolve(s1 + "_corrupted_" + LocalDateTime.now().format(PlayerDataStorage.FORMATTER) + s);
- 
-         if (Files.isRegularFile(path1, new LinkOption[0])) {
-             try {
-                 Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
-             } catch (Exception exception) {
--                PlayerDataStorage.LOGGER.warn("Failed to copy the player.dat file for {}", player.getName().getString(), exception);
-+                PlayerDataStorage.LOGGER.warn("Failed to copy the player.dat file for {}", name, exception); // CraftBukkit
-             }
- 
-         }
-     }
- 
--    private Optional<CompoundTag> load(Player player, String extension) {
-+    // CraftBukkit start
-+    private Optional<CompoundTag> load(String name, String s1, String s) { // name, uuid, extension
-+        // CraftBukkit end
-         File file = this.playerDir;
--        String s1 = player.getStringUUID();
--        File file1 = new File(file, s1 + extension);
-+        // String s1 = entityhuman.getStringUUID(); // CraftBukkit - used above
-+        File file1 = new File(file, s1 + s);
-+        // Spigot Start
-+        boolean usingWrongFile = false;
-+        if ( org.bukkit.Bukkit.getOnlineMode() && !file1.exists() ) // Paper - Check online mode first
-+        {
-+            file1 = new File( file, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + name ).getBytes( java.nio.charset.StandardCharsets.UTF_8 ) ).toString() + s );
-+            if ( file1.exists() )
-+            {
-+                usingWrongFile = true;
-+                org.bukkit.Bukkit.getServer().getLogger().warning( "Using offline mode UUID file for player " + name + " as it is the only copy we can find." );
-+            }
-+        }
-+        // Spigot End
- 
-         if (file1.exists() && file1.isFile()) {
-             try {
--                return Optional.of(NbtIo.readCompressed(file1.toPath(), NbtAccounter.unlimitedHeap()));
-+                // Spigot Start
-+                Optional<CompoundTag> optional = Optional.of(NbtIo.readCompressed(file1.toPath(), NbtAccounter.unlimitedHeap()));
-+                if ( usingWrongFile )
-+                {
-+                    file1.renameTo( new File( file1.getPath() + ".offline-read" ) );
-+                }
-+                return optional;
-+                // Spigot End
-             } catch (Exception exception) {
--                PlayerDataStorage.LOGGER.warn("Failed to load player data for {}", player.getName().getString());
-+                PlayerDataStorage.LOGGER.warn("Failed to load player data for {}", name); // CraftBukkit
-             }
-         }
- 
-@@ -84,20 +108,44 @@
-     }
- 
-     public Optional<CompoundTag> load(Player player) {
--        Optional<CompoundTag> optional = this.load(player, ".dat");
-+        // CraftBukkit start
-+        return this.load(player.getName().getString(), player.getStringUUID()).map((nbttagcompound) -> {
-+            if (player instanceof ServerPlayer) {
-+                CraftPlayer player1 = (CraftPlayer) player.getBukkitEntity();
-+                // Only update first played if it is older than the one we have
-+                long modified = new File(this.playerDir, player.getStringUUID() + ".dat").lastModified();
-+                if (modified < player1.getFirstPlayed()) {
-+                    player1.setFirstPlayed(modified);
-+                }
-+            }
- 
-+            player.load(nbttagcompound); // From below
-+            return nbttagcompound;
-+        });
-+    }
-+
-+    public Optional<CompoundTag> load(String name, String uuid) {
-+        // CraftBukkit end
-+        Optional<CompoundTag> optional = this.load(name, uuid, ".dat"); // CraftBukkit
-+
-         if (optional.isEmpty()) {
--            this.backup(player, ".dat");
-+            this.backup(name, uuid, ".dat"); // CraftBukkit
-         }
- 
-         return optional.or(() -> {
--            return this.load(player, ".dat_old");
-+            return this.load(name, uuid, ".dat_old"); // CraftBukkit
-         }).map((nbttagcompound) -> {
-             int i = NbtUtils.getDataVersion(nbttagcompound, -1);
- 
-             nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i);
--            player.load(nbttagcompound);
-+            // entityhuman.load(nbttagcompound); // CraftBukkit - handled above
-             return nbttagcompound;
-         });
-     }
-+
-+    // CraftBukkit start
-+    public File getPlayerDir() {
-+        return this.playerDir;
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
deleted file mode 100644
index 131a7c275b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
+++ /dev/null
@@ -1,203 +0,0 @@
---- a/net/minecraft/world/level/storage/PrimaryLevelData.java
-+++ b/net/minecraft/world/level/storage/PrimaryLevelData.java
-@@ -20,15 +20,12 @@
- import net.minecraft.SharedConstants;
- import net.minecraft.Util;
- import net.minecraft.core.BlockPos;
-+import net.minecraft.core.Registry;
- import net.minecraft.core.RegistryAccess;
- import net.minecraft.core.UUIDUtil;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.nbt.ListTag;
--import net.minecraft.nbt.NbtOps;
--import net.minecraft.nbt.NbtUtils;
--import net.minecraft.nbt.StringTag;
--import net.minecraft.nbt.Tag;
- import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.world.Difficulty;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.GameType;
-@@ -36,12 +33,26 @@
- import net.minecraft.world.level.LevelSettings;
- import net.minecraft.world.level.WorldDataConfiguration;
- import net.minecraft.world.level.border.WorldBorder;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.dimension.end.EndDragonFight;
--import net.minecraft.world.level.levelgen.WorldGenSettings;
- import net.minecraft.world.level.levelgen.WorldOptions;
- import net.minecraft.world.level.timers.TimerCallbacks;
- import net.minecraft.world.level.timers.TimerQueue;
- import org.slf4j.Logger;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.ListTag;
-+import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.NbtUtils;
-+import net.minecraft.nbt.StringTag;
-+import net.minecraft.nbt.Tag;
-+import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
-+import net.minecraft.world.level.levelgen.WorldDimensions;
-+import net.minecraft.world.level.levelgen.WorldGenSettings;
-+import org.bukkit.Bukkit;
-+import org.bukkit.event.weather.ThunderChangeEvent;
-+import org.bukkit.event.weather.WeatherChangeEvent;
-+// CraftBukkit end
- 
- public class PrimaryLevelData implements ServerLevelData, WorldData {
- 
-@@ -79,7 +90,21 @@
-     private boolean wasModded;
-     private final Set<String> removedFeatureFlags;
-     private final TimerQueue<MinecraftServer> scheduledEvents;
-+    // CraftBukkit start - Add world and pdc
-+    public Registry<LevelStem> customDimensions;
-+    private ServerLevel world;
-+    protected Tag pdc;
- 
-+    public void setWorld(ServerLevel world) {
-+        if (this.world != null) {
-+            return;
-+        }
-+        this.world = world;
-+        world.getWorld().readBukkitValues(this.pdc);
-+        this.pdc = null;
-+    }
-+    // CraftBukkit end
-+
-     private PrimaryLevelData(@Nullable CompoundTag playerData, boolean modded, BlockPos spawnPos, float spawnAngle, long time, long timeOfDay, int version, int clearWeatherTime, int rainTime, boolean raining, int thunderTime, boolean thundering, boolean initialized, boolean difficultyLocked, WorldBorder.Settings worldBorder, int wanderingTraderSpawnDelay, int wanderingTraderSpawnChance, @Nullable UUID wanderingTraderId, Set<String> serverBrands, Set<String> removedFeatures, TimerQueue<MinecraftServer> scheduledEvents, @Nullable CompoundTag customBossEvents, EndDragonFight.Data dragonFight, LevelSettings levelInfo, WorldOptions generatorOptions, PrimaryLevelData.SpecialWorldProperty specialProperty, Lifecycle lifecycle) {
-         this.wasModded = modded;
-         this.spawnPos = spawnPos;
-@@ -116,7 +141,7 @@
- 
-     public static <T> PrimaryLevelData parse(Dynamic<T> dynamic, LevelSettings info, PrimaryLevelData.SpecialWorldProperty specialProperty, WorldOptions generatorOptions, Lifecycle lifecycle) {
-         long i = dynamic.get("Time").asLong(0L);
--        OptionalDynamic optionaldynamic = dynamic.get("Player");
-+        OptionalDynamic<T> optionaldynamic = dynamic.get("Player"); // CraftBukkit - decompile error
-         Codec codec = CompoundTag.CODEC;
- 
-         Objects.requireNonNull(codec);
-@@ -136,7 +161,7 @@
-         WorldBorder.Settings worldborder_c = WorldBorder.Settings.read(dynamic, WorldBorder.DEFAULT_SETTINGS);
-         int k1 = dynamic.get("WanderingTraderSpawnDelay").asInt(0);
-         int l1 = dynamic.get("WanderingTraderSpawnChance").asInt(0);
--        UUID uuid = (UUID) dynamic.get("WanderingTraderId").read(UUIDUtil.CODEC).result().orElse((Object) null);
-+        UUID uuid = (UUID) dynamic.get("WanderingTraderId").read(UUIDUtil.CODEC).result().orElse(null); // CraftBukkit - decompile error
-         Set set = (Set) dynamic.get("ServerBrands").asStream().flatMap((dynamic1) -> {
-             return dynamic1.asString().result().stream();
-         }).collect(Collectors.toCollection(Sets::newLinkedHashSet));
-@@ -145,7 +170,7 @@
-         }).collect(Collectors.toSet());
-         TimerQueue customfunctioncallbacktimerqueue = new TimerQueue<>(TimerCallbacks.SERVER_CALLBACKS, dynamic.get("ScheduledEvents").asStream());
-         CompoundTag nbttagcompound1 = (CompoundTag) dynamic.get("CustomBossEvents").orElseEmptyMap().getValue();
--        DataResult dataresult = dynamic.get("DragonFight").read(EndDragonFight.Data.CODEC);
-+        DataResult<EndDragonFight.Data> dataresult = dynamic.get("DragonFight").read(EndDragonFight.Data.CODEC); // CraftBukkit - decompile error
-         Logger logger = PrimaryLevelData.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -180,7 +205,7 @@
-         levelNbt.put("Version", nbttagcompound2);
-         NbtUtils.addCurrentDataVersion(levelNbt);
-         DynamicOps<Tag> dynamicops = registryManager.createSerializationContext(NbtOps.INSTANCE);
--        DataResult dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, registryManager);
-+        DataResult<Tag> dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, new WorldDimensions(this.customDimensions != null ? this.customDimensions : registryManager.lookupOrThrow(Registries.LEVEL_STEM))); // CraftBukkit
-         Logger logger = PrimaryLevelData.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -230,11 +255,13 @@
-             levelNbt.putUUID("WanderingTraderId", this.wanderingTraderId);
-         }
- 
-+        levelNbt.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit
-+        this.world.getWorld().storeBukkitValues(levelNbt); // CraftBukkit - add pdc
-     }
- 
-     private static ListTag stringCollectionToTag(Set<String> strings) {
-         ListTag nbttaglist = new ListTag();
--        Stream stream = strings.stream().map(StringTag::valueOf);
-+        Stream<StringTag> stream = strings.stream().map(StringTag::valueOf); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(nbttaglist);
-         stream.forEach(nbttaglist::add);
-@@ -310,6 +337,25 @@
- 
-     @Override
-     public void setThundering(boolean thundering) {
-+        // Paper start - Add cause to Weather/ThunderChangeEvents
-+        this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN);
-+    }
-+    public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) {
-+        // Paper end - Add cause to Weather/ThunderChangeEvents
-+        // CraftBukkit start
-+        if (this.thundering == thundering) {
-+            return;
-+        }
-+
-+        org.bukkit.World world = Bukkit.getWorld(this.getLevelName());
-+        if (world != null) {
-+            ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents
-+            Bukkit.getServer().getPluginManager().callEvent(thunder);
-+            if (thunder.isCancelled()) {
-+                return;
-+            }
-+        }
-+        // CraftBukkit end
-         this.thundering = thundering;
-     }
- 
-@@ -330,6 +376,26 @@
- 
-     @Override
-     public void setRaining(boolean raining) {
-+        // Paper start - Add cause to Weather/ThunderChangeEvents
-+        this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN);
-+    }
-+
-+    public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) {
-+        // Paper end - Add cause to Weather/ThunderChangeEvents
-+        // CraftBukkit start
-+        if (this.raining == raining) {
-+            return;
-+        }
-+
-+        org.bukkit.World world = Bukkit.getWorld(this.getLevelName());
-+        if (world != null) {
-+            WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents
-+            Bukkit.getServer().getPluginManager().callEvent(weather);
-+            if (weather.isCancelled()) {
-+                return;
-+            }
-+        }
-+        // CraftBukkit end
-         this.raining = raining;
-     }
- 
-@@ -396,6 +462,12 @@
-     @Override
-     public void setDifficulty(Difficulty difficulty) {
-         this.settings = this.settings.withDifficulty(difficulty);
-+        // CraftBukkit start
-+        ClientboundChangeDifficultyPacket packet = new ClientboundChangeDifficultyPacket(this.getDifficulty(), this.isDifficultyLocked());
-+        for (ServerPlayer player : (java.util.List<ServerPlayer>) (java.util.List) this.world.players()) {
-+            player.connection.send(packet);
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -532,6 +604,14 @@
-         return this.settings.copy();
-     }
- 
-+    // CraftBukkit start - Check if the name stored in NBT is the correct one
-+    public void checkName(String name) {
-+        if (!this.settings.levelName.equals(name)) {
-+            this.settings.levelName = name;
-+        }
-+    }
-+    // CraftBukkit end
-+
-     /** @deprecated */
-     @Deprecated
-     public static enum SpecialWorldProperty {

From 6bb7e658dedf6b2873c997b17c0e5b03498aa23d Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 16:39:48 -0800
Subject: [PATCH 134/285] net.minecraft.world.entity.player

---
 .../world/entity/player/Inventory.java.patch  |  81 +-
 .../world/entity/player/Player.java.patch     | 671 ++++++++++++++++
 .../entity/player/ProfilePublicKey.java.patch |  18 +-
 .../world/entity/player/Player.java.patch     | 735 ------------------
 4 files changed, 714 insertions(+), 791 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/player/Inventory.java.patch (54%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/player/ProfilePublicKey.java.patch (58%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/player/Player.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/player/Inventory.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
similarity index 54%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/player/Inventory.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
index be3c5cccdb..ca6987132b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/player/Inventory.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
@@ -1,30 +1,16 @@
 --- a/net/minecraft/world/entity/player/Inventory.java
 +++ b/net/minecraft/world/entity/player/Inventory.java
-@@ -23,6 +23,12 @@
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class Inventory implements Container, Nameable {
- 
-@@ -38,7 +44,55 @@
-     public int selected;
+@@ -36,6 +_,54 @@
      public final Player player;
      private int timesChanged;
-+
+ 
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
 +    private int maxStack = MAX_STACK;
 +
 +    public List<ItemStack> getContents() {
-+        List<ItemStack> combined = new ArrayList<ItemStack>(this.items.size() + this.armor.size() + this.offhand.size());
-+        for (List<net.minecraft.world.item.ItemStack> sub : this.compartments) {
++        List<ItemStack> combined = new java.util.ArrayList<>(this.items.size() + this.armor.size() + this.offhand.size());
++        for (List<ItemStack> sub : this.compartments) {
 +            combined.addAll(sub);
 +        }
 +
@@ -35,15 +21,15 @@
 +        return this.armor;
 +    }
 +
-+    public void onOpen(CraftHumanEntity who) {
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.add(who);
 +    }
 +
-+    public void onClose(CraftHumanEntity who) {
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
@@ -59,24 +45,28 @@
 +    public void setMaxStackSize(int size) {
 +        this.maxStack = size;
 +    }
- 
++
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        return this.player.getBukkitEntity().getLocation();
 +    }
 +    // CraftBukkit end
 +
      public Inventory(Player player) {
-         this.items = NonNullList.withSize(36, ItemStack.EMPTY);
-         this.armor = NonNullList.withSize(4, ItemStack.EMPTY);
-@@ -56,9 +110,31 @@
-     }
- 
-     private boolean hasRemainingSpaceForItem(ItemStack existingStack, ItemStack stack) {
--        return !existingStack.isEmpty() && ItemStack.isSameItemSameComponents(existingStack, stack) && existingStack.isStackable() && existingStack.getCount() < this.getMaxStackSize(existingStack);
-+        return !existingStack.isEmpty() && existingStack.isStackable() && existingStack.getCount() < this.getMaxStackSize(existingStack) && ItemStack.isSameItemSameComponents(existingStack, stack); // Paper - check if itemstack is stackable first
+         this.player = player;
      }
+@@ -50,10 +_,32 @@
  
+     private boolean hasRemainingSpaceForItem(ItemStack destination, ItemStack origin) {
+         return !destination.isEmpty()
+-            && ItemStack.isSameItemSameComponents(destination, origin)
+             && destination.isStackable()
+-            && destination.getCount() < this.getMaxStackSize(destination);
+-    }
++            && destination.getCount() < this.getMaxStackSize(destination)
++            && ItemStack.isSameItemSameComponents(destination, origin); // Paper - check if itemstack is stackable first
++    }
++
 +    // CraftBukkit start - Watch method above! :D
 +    public int canHold(ItemStack itemstack) {
 +        int remains = itemstack.getCount();
@@ -98,33 +88,30 @@
 +        return itemstack.getCount() - remains;
 +    }
 +    // CraftBukkit end
-+
+ 
      public int getFreeSlot() {
-         for (int i = 0; i < this.items.size(); ++i) {
-             if (((ItemStack) this.items.get(i)).isEmpty()) {
-@@ -69,8 +145,10 @@
+         for (int i = 0; i < this.items.size(); i++) {
+@@ -65,7 +_,10 @@
          return -1;
      }
  
 -    public void addAndPickItem(ItemStack stack) {
--        this.selected = this.getSuitableHotbarSlot();
 +    // Paper start - Add PlayerPickItemEvent
 +    public void addAndPickItem(ItemStack stack, final int targetSlot) {
 +        this.selected = targetSlot;
 +        // Paper end - Add PlayerPickItemEvent
-         if (!((ItemStack) this.items.get(this.selected)).isEmpty()) {
-             int i = this.getFreeSlot();
- 
-@@ -82,8 +160,10 @@
+         this.selected = this.getSuitableHotbarSlot();
+         if (!this.items.get(this.selected).isEmpty()) {
+             int freeSlot = this.getFreeSlot();
+@@ -77,7 +_,10 @@
          this.items.set(this.selected, stack);
      }
  
--    public void pickSlot(int slot) {
--        this.selected = this.getSuitableHotbarSlot();
+-    public void pickSlot(int index) {
 +    // Paper start - Add PlayerPickItemEvent
-+    public void pickSlot(int slot, final int targetSlot) {
++    public void pickSlot(int index, final int targetSlot) {
 +        this.selected = targetSlot;
 +        // Paper end - Add PlayerPickItemEvent
-         ItemStack itemstack = (ItemStack) this.items.get(this.selected);
- 
-         this.items.set(this.selected, (ItemStack) this.items.get(slot));
+         this.selected = this.getSuitableHotbarSlot();
+         ItemStack itemStack = this.items.get(this.selected);
+         this.items.set(this.selected, this.items.get(index));
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
new file mode 100644
index 0000000000..02d43bc4f7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
@@ -0,0 +1,671 @@
+--- a/net/minecraft/world/entity/player/Player.java
++++ b/net/minecraft/world/entity/player/Player.java
+@@ -159,7 +_,7 @@
+     public static final int CLIENT_LOADED_TIMEOUT_TIME = 60;
+     private long timeEntitySatOnShoulder;
+     final Inventory inventory = new Inventory(this);
+-    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer();
++    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(this); // CraftBukkit - add "this" to constructor
+     public final InventoryMenu inventoryMenu;
+     public AbstractContainerMenu containerMenu;
+     protected FoodData foodData = new FoodData();
+@@ -191,13 +_,25 @@
+     private Optional<GlobalPos> lastDeathLocation = Optional.empty();
+     @Nullable
+     public FishingHook fishing;
+-    protected float hurtDir;
++    public float hurtDir; // Paper - protected -> public
+     @Nullable
+     public Vec3 currentImpulseImpactPos;
+     @Nullable
+     public Entity currentExplosionCause;
+     private boolean ignoreFallDamageFromCurrentImpulse;
+     private int currentImpulseContextResetGraceTime;
++    public boolean affectsSpawning = true; // Paper - Affects Spawning API
++    public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
++
++    // CraftBukkit start
++    public boolean fauxSleeping;
++    public int oldLevel = -1;
++
++    @Override
++    public org.bukkit.craftbukkit.entity.CraftHumanEntity getBukkitEntity() {
++        return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity();
++    }
++    // CraftBukkit end
+ 
+     public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) {
+         super(EntityType.PLAYER, level);
+@@ -261,6 +_,13 @@
+ 
+         if (this.isSleeping()) {
+             this.sleepCounter++;
++            // Paper start - Add PlayerDeepSleepEvent
++            if (this.sleepCounter == SLEEP_DURATION) {
++                if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) {
++                    this.sleepCounter = Integer.MIN_VALUE;
++                }
++            }
++            // Paper end - Add PlayerDeepSleepEvent
+             if (this.sleepCounter > 100) {
+                 this.sleepCounter = 100;
+             }
+@@ -278,7 +_,7 @@
+         this.updateIsUnderwater();
+         super.tick();
+         if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) {
+-            this.closeContainer();
++            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
+             this.containerMenu = this.inventoryMenu;
+         }
+ 
+@@ -365,7 +_,7 @@
+     }
+ 
+     private void turtleHelmetTick() {
+-        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
++        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
+     }
+ 
+     private boolean isEquipped(Item item) {
+@@ -512,6 +_,18 @@
+         }
+     }
+ 
++    // Paper start - Inventory close reason; unused code, but to keep signatures aligned
++    public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++        this.closeContainer();
++        this.containerMenu = this.inventoryMenu;
++    }
++    // Paper end - Inventory close reason
++    // Paper start - special close for unloaded inventory
++    public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++        this.containerMenu = this.inventoryMenu;
++    }
++    // Paper end - special close for unloaded inventory
++
+     public void closeContainer() {
+         this.containerMenu = this.inventoryMenu;
+     }
+@@ -523,8 +_,14 @@
+     public void rideTick() {
+         if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) {
+             this.stopRiding();
+-            this.setShiftKeyDown(false);
+-        } else {
++            // CraftBukkit start - SPIGOT-7316: no longer passenger, dismount and return
++            if (!this.isPassenger()) {
++                this.setShiftKeyDown(false);
++                return;
++            }
++        }
++        {
++            // CraftBukkit end
+             super.rideTick();
+             this.oBob = this.bob;
+             this.bob = 0.0F;
+@@ -588,6 +_,7 @@
+         this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft());
+         this.playShoulderEntityAmbientSound(this.getShoulderEntityRight());
+         if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) {
++            if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay
+             this.removeEntitiesOnShoulder();
+         }
+     }
+@@ -717,6 +_,13 @@
+ 
+     @Nullable
+     public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean includeThrowerName) {
++        // CraftBukkit start - SPIGOT-2942: Add boolean to call event
++        return this.drop(droppedItem, dropAround, includeThrowerName, true);
++    }
++
++    @Nullable
++    public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean includeThrowerName, boolean callEvent) {
++        // CraftBukkit end
+         if (!droppedItem.isEmpty() && this.level().isClientSide) {
+             this.swing(InteractionHand.MAIN_HAND);
+         }
+@@ -867,10 +_,10 @@
+             if (this.isDeadOrDying()) {
+                 return false;
+             } else {
+-                this.removeEntitiesOnShoulder();
++                // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down
+                 if (damageSource.scalesWithDifficulty()) {
+                     if (level.getDifficulty() == Difficulty.PEACEFUL) {
+-                        amount = 0.0F;
++                        return false; // CraftBukkit - f = 0.0f -> return false
+                     }
+ 
+                     if (level.getDifficulty() == Difficulty.EASY) {
+@@ -882,7 +_,14 @@
+                     }
+                 }
+ 
+-                return amount != 0.0F && super.hurtServer(level, damageSource, amount);
++                // return amount != 0.0F && super.hurtServer(level, damageSource, amount);
++                // CraftBukkit start - Don't filter out 0 damage
++                boolean damaged = super.hurtServer(level, damageSource, amount);
++                if (damaged) {
++                    this.removeEntitiesOnShoulder();
++                }
++                return damaged;
++                // CraftBukkit end
+             }
+         }
+     }
+@@ -892,7 +_,7 @@
+         super.blockUsingShield(entity);
+         ItemStack itemBlockingWith = this.getItemBlockingWith();
+         if (entity.canDisableShield() && itemBlockingWith != null) {
+-            this.disableShield(itemBlockingWith);
++            this.disableShield(itemBlockingWith, entity); // Paper - Add PlayerShieldDisableEvent
+         }
+     }
+ 
+@@ -902,9 +_,29 @@
+     }
+ 
+     public boolean canHarmPlayer(Player other) {
+-        Team team = this.getTeam();
+-        Team team1 = other.getTeam();
+-        return team == null || !team.isAlliedTo(team1) || team.isAllowFriendlyFire();
++        // CraftBukkit start - Change to check OTHER player's scoreboard team according to API
++        // To summarize this method's logic, it's "Can parameter hurt this"
++        org.bukkit.scoreboard.Team team;
++        if (other instanceof ServerPlayer) {
++            ServerPlayer thatPlayer = (ServerPlayer) other;
++            team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity());
++            if (team == null || team.allowFriendlyFire()) {
++                return true;
++            }
++        } else {
++            // This should never be called, but is implemented anyway
++            org.bukkit.OfflinePlayer thisPlayer = other.level().getCraftServer().getOfflinePlayer(other.getScoreboardName());
++            team = other.level().getCraftServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer);
++            if (team == null || team.allowFriendlyFire()) {
++                return true;
++            }
++        }
++
++        if (this instanceof ServerPlayer) {
++            return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity());
++        }
++        return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName()));
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -943,7 +_,12 @@
+     }
+ 
+     @Override
+-    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
++    // CraftBukkit start
++    protected boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) { // void -> boolean
++        if (true) {
++            return super.actuallyHurt(level, damageSource, amount, event);
++        }
++        // CraftBukkit end
+         if (!this.isInvulnerableTo(level, damageSource)) {
+             amount = this.getDamageAfterArmorAbsorb(damageSource, amount);
+             amount = this.getDamageAfterMagicAbsorb(damageSource, amount);
+@@ -955,7 +_,7 @@
+             }
+ 
+             if (var8 != 0.0F) {
+-                this.causeFoodExhaustion(damageSource.getFoodExhaustion());
++                this.causeFoodExhaustion(damageSource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
+                 this.getCombatTracker().recordDamage(damageSource, var8);
+                 this.setHealth(this.getHealth() - var8);
+                 if (var8 < 3.4028235E37F) {
+@@ -965,6 +_,7 @@
+                 this.gameEvent(GameEvent.ENTITY_DAMAGE);
+             }
+         }
++        return false; // CraftBukkit
+     }
+ 
+     public boolean isTextFilteringEnabled() {
+@@ -1040,13 +_,19 @@
+ 
+     @Override
+     public void removeVehicle() {
+-        super.removeVehicle();
++        // Paper start - Force entity dismount during teleportation
++        this.removeVehicle(false);
++    }
++    @Override
++    public void removeVehicle(boolean suppressCancellation) {
++        super.removeVehicle(suppressCancellation);
++        // Paper end - Force entity dismount during teleportation
+         this.boardingCooldown = 0;
+     }
+ 
+     @Override
+     protected boolean isImmobile() {
+-        return super.isImmobile() || this.isSleeping();
++        return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move...
+     }
+ 
+     @Override
+@@ -1125,8 +_,17 @@
+     }
+ 
+     public void attack(Entity target) {
+-        if (target.isAttackable()) {
+-            if (!target.skipAttackInteraction(this)) {
++        // Paper start - PlayerAttackEntityEvent
++        boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic
++        io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent(
++            (org.bukkit.entity.Player) this.getBukkitEntity(),
++            target.getBukkitEntity(),
++            willAttack
++        );
++
++        if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable.
++            {
++        // Paper end - PlayerAttackEntityEvent
+                 float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
+                 ItemStack weaponItem = this.getWeaponItem();
+                 DamageSource damageSource = Optional.ofNullable(weaponItem.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this));
+@@ -1134,18 +_,25 @@
+                 float attackStrengthScale = this.getAttackStrengthScale(0.5F);
+                 f *= 0.2F + attackStrengthScale * attackStrengthScale * 0.8F;
+                 f1 *= attackStrengthScale;
+-                this.resetAttackStrengthTicker();
++                // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt
+                 if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE)
+-                    && target instanceof Projectile projectile
+-                    && projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
+-                    this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
+-                } else {
++                    && target instanceof Projectile projectile) {
++                        // CraftBukkit start
++                        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(target, damageSource, f1, false)) {
++                            return;
++                        }
++                        if (projectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
++                            this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
++                            return;
++                        }
++                }
++                {
++                    // CraftBukkit end
+                     if (f > 0.0F || f1 > 0.0F) {
+                         boolean flag = attackStrengthScale > 0.9F;
+                         boolean flag1;
+                         if (this.isSprinting() && flag) {
+-                            this.level()
+-                                .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F);
++                            this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
+                             flag1 = true;
+                         } else {
+                             flag1 = false;
+@@ -1161,7 +_,9 @@
+                             && !this.isPassenger()
+                             && target instanceof LivingEntity
+                             && !this.isSprinting();
++                        flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
+                         if (flag2) {
++                            damageSource = damageSource.critical(true); // Paper start - critical damage API
+                             f *= 1.5F;
+                         }
+ 
+@@ -1188,17 +_,23 @@
+                                 if (target instanceof LivingEntity livingEntity1) {
+                                     livingEntity1.knockback(
+                                         f4 * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
++                                        , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK // Paper - knockback events
+                                     );
+                                 } else {
+                                     target.push(
+                                         -Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)) * f4 * 0.5F,
+                                         0.1,
+                                         Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) * f4 * 0.5F
++                                        , this // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
+                                     );
+                                 }
+ 
+                                 this.setDeltaMovement(this.getDeltaMovement().multiply(0.6, 1.0, 0.6));
++                                // Paper start - Configurable sprint interruption on attack
++                                if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) {
+                                 this.setSprinting(false);
++                                }
++                                // Paper end - Configurable sprint interruption on attack
+                             }
+ 
+                             if (flag3) {
+@@ -1212,43 +_,62 @@
+                                         && (!(livingEntity2 instanceof ArmorStand) || !((ArmorStand)livingEntity2).isMarker())
+                                         && this.distanceToSqr(livingEntity2) < 9.0) {
+                                         float f6 = this.getEnchantedDamage(livingEntity2, f5, damageSource) * attackStrengthScale;
++                                        // CraftBukkit start - Only apply knockback if the damage hits
++                                        if (!livingEntity2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep().critical(flag2), f6)) { // Paper - add critical damage API
++                                            continue;
++                                        }
++                                        // CraftBukkit end
+                                         livingEntity2.knockback(
+                                             0.4F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
++                                            , this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK // CraftBukkit // Paper - knockback events
+                                         );
+-                                        livingEntity2.hurt(damageSource, f6);
++                                        // CraftBukkit - moved up
+                                         if (this.level() instanceof ServerLevel serverLevel) {
+                                             EnchantmentHelper.doPostAttackEffects(serverLevel, livingEntity2, damageSource);
+                                         }
+                                     }
+                                 }
+ 
+-                                this.level()
+-                                    .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F);
++                                this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
+                                 this.sweepAttack();
+                             }
+ 
+                             if (target instanceof ServerPlayer && target.hurtMarked) {
++                                // CraftBukkit start - Add Velocity Event
++                                boolean cancelled = false;
++                                org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity();
++                                org.bukkit.util.Vector velocity = org.bukkit.craftbukkit.util.CraftVector.toBukkit(deltaMovement);
++
++                                org.bukkit.event.player.PlayerVelocityEvent event = new org.bukkit.event.player.PlayerVelocityEvent(player, velocity.clone());
++                                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                                if (event.isCancelled()) {
++                                    cancelled = true;
++                                } else if (!velocity.equals(event.getVelocity())) {
++                                    player.setVelocity(event.getVelocity());
++                                }
++
++                                if (!cancelled) {
+                                 ((ServerPlayer)target).connection.send(new ClientboundSetEntityMotionPacket(target));
+                                 target.hurtMarked = false;
+                                 target.setDeltaMovement(deltaMovement);
++                                }
++                                // CraftBukkit end
+                             }
+ 
+                             if (flag2) {
+-                                this.level()
+-                                    .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F);
++                                this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
+                                 this.crit(target);
+                             }
+ 
+                             if (!flag2 && !flag3) {
+                                 if (flag) {
+-                                    this.level()
+-                                        .playSound(
+-                                            null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F
++                                    this.sendSoundEffect(
++                                            this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F // Paper - send while respecting visibility
+                                         );
+                                 } else {
+-                                    this.level()
+-                                        .playSound(
+-                                            null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F
++                                    this.sendSoundEffect(
++                                            this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F // Paper - send while respecting visibility
+                                         );
+                                 }
+                             }
+@@ -1296,10 +_,14 @@
+                                 }
+                             }
+ 
+-                            this.causeFoodExhaustion(0.1F);
++                            this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
+                         } else {
+-                            this.level()
+-                                .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
++                            this.sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
++                            // CraftBukkit start - resync on cancelled event
++                            if (this instanceof ServerPlayer) {
++                                ((ServerPlayer) this).getBukkitEntity().updateInventory();
++                            }
++                            // CraftBukkit end
+                         }
+                     }
+                 }
+@@ -1316,8 +_,21 @@
+         this.attack(target);
+     }
+ 
++    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent
+     public void disableShield(ItemStack stack) {
+-        this.getCooldowns().addCooldown(stack, 100);
++        // Paper start - Add PlayerShieldDisableEvent
++        this.disableShield(stack, null);
++    }
++    public void disableShield(ItemStack stack, @Nullable LivingEntity attacker) {
++        final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null;
++        if (finalAttacker != null) {
++            final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100);
++            if (!shieldDisableEvent.callEvent()) return;
++            this.getCooldowns().addCooldown(stack, shieldDisableEvent.getCooldown());
++        } else {
++            this.getCooldowns().addCooldown(stack, 100);
++        }
++        // Paper end - Add PlayerShieldDisableEvent
+         this.stopUsingItem();
+         this.level().broadcastEntityEvent(this, (byte)30);
+     }
+@@ -1341,7 +_,14 @@
+ 
+     @Override
+     public void remove(Entity.RemovalReason reason) {
+-        super.remove(reason);
++        // CraftBukkit start - add Bukkit remove cause
++        this.remove(reason, null);
++    }
++
++    @Override
++    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        super.remove(entity_removalreason, cause);
++        // CraftBukkit end
+         this.inventoryMenu.removed(this);
+         if (this.containerMenu != null && this.hasContainerOpen()) {
+             this.doCloseContainer();
+@@ -1381,6 +_,12 @@
+     }
+ 
+     public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos) {
++        // CraftBukkit start
++        return this.startSleepInBed(bedPos, false);
++    }
++
++    public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos, boolean force) {
++        // CraftBukkit end
+         this.startSleeping(bedPos);
+         this.sleepCounter = 0;
+         return Either.right(Unit.INSTANCE);
+@@ -1492,7 +_,7 @@
+ 
+     @Override
+     public boolean causeFallDamage(float fallDistance, float multiplier, DamageSource source) {
+-        if (this.abilities.mayfly) {
++        if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage
+             return false;
+         } else {
+             if (fallDistance >= 2.0F) {
+@@ -1532,12 +_,24 @@
+     }
+ 
+     public void startFallFlying() {
+-        this.setSharedFlag(7, true);
++        // CraftBukkit start
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) {
++            this.setSharedFlag(7, true);
++        } else {
++            // SPIGOT-5542: must toggle like below
++            this.setSharedFlag(7, true);
++            this.setSharedFlag(7, false);
++        }
++        // CraftBukkit end
+     }
+ 
+     public void stopFallFlying() {
++        // CraftBukkit start
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) {
+         this.setSharedFlag(7, true);
+         this.setSharedFlag(7, false);
++        }
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -1643,15 +_,35 @@
+     public int getXpNeededForNextLevel() {
+         if (this.experienceLevel >= 30) {
+             return 112 + (this.experienceLevel - 30) * 9;
+-        } else {
++        } else { // Paper - diff on change; calculateTotalExperiencePoints
+             return this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2;
+         }
+     }
+ 
++    // Paper start - send while respecting visibility
++    private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) {
++        fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself
++        if (fromEntity instanceof ServerPlayer serverPlayer) {
++            serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong()));
++        }
++    }
++    // Paper end - send while respecting visibility
++
+     public void causeFoodExhaustion(float exhaustion) {
++        // CraftBukkit start
++        this.causeFoodExhaustion(exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.UNKNOWN);
++    }
++
++    public void causeFoodExhaustion(float exhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason reason) {
++        // CraftBukkit end
+         if (!this.abilities.invulnerable) {
+             if (!this.level().isClientSide) {
+-                this.foodData.addExhaustion(exhaustion);
++                // CraftBukkit start
++                org.bukkit.event.entity.EntityExhaustionEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerExhaustionEvent(this, reason, exhaustion);
++                if (!event.isCancelled()) {
++                    this.foodData.addExhaustion(event.getExhaustion());
++                }
++                // CraftBukkit end
+             }
+         }
+     }
+@@ -1736,13 +_,20 @@
+ 
+     @Override
+     public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
++        // CraftBukkit start
++        this.setItemSlot(slot, stack, false);
++    }
++
++    @Override
++    public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) {
++        // CraftBukkit end
+         this.verifyEquippedItem(stack);
+         if (slot == EquipmentSlot.MAINHAND) {
+-            this.onEquipItem(slot, this.inventory.items.set(this.inventory.selected, stack), stack);
++            this.onEquipItem(slot, this.inventory.items.set(this.inventory.selected, stack), stack, silent); // CraftBukkit
+         } else if (slot == EquipmentSlot.OFFHAND) {
+-            this.onEquipItem(slot, this.inventory.offhand.set(0, stack), stack);
++            this.onEquipItem(slot, this.inventory.offhand.set(0, stack), stack, silent); // CraftBukkit
+         } else if (slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
+-            this.onEquipItem(slot, this.inventory.armor.set(slot.getIndex(), stack), stack);
++            this.onEquipItem(slot, this.inventory.armor.set(slot.getIndex(), stack), stack, silent); // CraftBukkit
+         }
+     }
+ 
+@@ -1783,24 +_,53 @@
+ 
+     public void removeEntitiesOnShoulder() {
+         if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) {
+-            this.respawnEntityOnShoulder(this.getShoulderEntityLeft());
++            // CraftBukkit start
++            if (this.respawnEntityOnShoulder(this.getShoulderEntityLeft())) {
++                this.setShoulderEntityLeft(new CompoundTag());
++            }
++            if (this.respawnEntityOnShoulder(this.getShoulderEntityRight())) {
++                this.setShoulderEntityRight(new CompoundTag());
++            }
++            // CraftBukkit end
++        }
++    }
++
++    // Paper start - release entity api
++    public Entity releaseLeftShoulderEntity() {
++        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft());
++        if (entity != null) {
+             this.setShoulderEntityLeft(new CompoundTag());
+-            this.respawnEntityOnShoulder(this.getShoulderEntityRight());
++        }
++        return entity;
++    }
++
++    public Entity releaseRightShoulderEntity() {
++        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight());
++        if (entity != null) {
+             this.setShoulderEntityRight(new CompoundTag());
+         }
++        return entity;
+     }
++    // Paper end - release entity api
+ 
+-    private void respawnEntityOnShoulder(CompoundTag entityCompound) {
++    private boolean respawnEntityOnShoulder(CompoundTag entityCompound) { // CraftBukkit void->boolean
++    // Paper start - release entity api - return entity - overload
++        return this.respawnEntityOnShoulder0(entityCompound) != null;
++    }
++    @Nullable
++    private Entity respawnEntityOnShoulder0(CompoundTag entityCompound) { // CraftBukkit void->boolean
++    // Paper end - release entity api - return entity - overload
+         if (!this.level().isClientSide && !entityCompound.isEmpty()) {
+-            EntityType.create(entityCompound, this.level(), EntitySpawnReason.LOAD).ifPresent(entity -> {
++            return EntityType.create(entityCompound, this.level(), EntitySpawnReason.LOAD).map((entity) -> { // CraftBukkit
+                 if (entity instanceof TamableAnimal) {
+                     ((TamableAnimal)entity).setOwnerUUID(this.uuid);
+                 }
+ 
+                 entity.setPos(this.getX(), this.getY() + 0.7F, this.getZ());
+-                ((ServerLevel)this.level()).addWithUUID(entity);
+-            });
++                return ((ServerLevel)this.level()).addWithUUID(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY) ? entity : null; // CraftBukkit // Paper start - release entity api - return entity
++            }).orElse(null); // CraftBukkit // Paper end - release entity api - return entity
+         }
++        return null; // Paper - return null
+     }
+ 
+     @Override
+@@ -1988,17 +_,28 @@
+         return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING);
+     }
+ 
++    // Paper start - PlayerReadyArrowEvent
++    protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) {
++        return !(this instanceof ServerPlayer) ||
++                new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent(
++                    ((ServerPlayer) this).getBukkitEntity(),
++                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow),
++                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)
++                ).callEvent();
++    }
++    // Paper end - PlayerReadyArrowEvent
++
+     @Override
+     public ItemStack getProjectile(ItemStack shootable) {
+         if (!(shootable.getItem() instanceof ProjectileWeaponItem)) {
+             return ItemStack.EMPTY;
+         } else {
+-            Predicate<ItemStack> supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getSupportedHeldProjectiles();
++            Predicate<ItemStack> supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getSupportedHeldProjectiles().and(item -> this.tryReadyArrow(shootable, item)); // Paper - PlayerReadyArrowEvent
+             ItemStack heldProjectile = ProjectileWeaponItem.getHeldProjectile(this, supportedHeldProjectiles);
+             if (!heldProjectile.isEmpty()) {
+                 return heldProjectile;
+             } else {
+-                supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getAllSupportedProjectiles();
++                supportedHeldProjectiles = ((ProjectileWeaponItem)shootable.getItem()).getAllSupportedProjectiles().and(item -> this.tryReadyArrow(shootable, item)); // Paper - PlayerReadyArrowEvent
+ 
+                 for (int i = 0; i < this.inventory.getContainerSize(); i++) {
+                     ItemStack item = this.inventory.getItem(i);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/player/ProfilePublicKey.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/player/ProfilePublicKey.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch
index a8a26d91a8..31a265cc65 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/player/ProfilePublicKey.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/ProfilePublicKey.java.patch
@@ -1,27 +1,27 @@
 --- a/net/minecraft/world/entity/player/ProfilePublicKey.java
 +++ b/net/minecraft/world/entity/player/ProfilePublicKey.java
-@@ -24,7 +24,7 @@
+@@ -24,7 +_,7 @@
  
-     public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException {
-         if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) {
+     public static ProfilePublicKey createValidated(SignatureValidator signatureValidator, UUID profileId, ProfilePublicKey.Data data) throws ProfilePublicKey.ValidationException {
+         if (!data.validateSignature(signatureValidator, profileId)) {
 -            throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE);
 +            throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes
          } else {
-             return new ProfilePublicKey(publicKeyData);
+             return new ProfilePublicKey(data);
          }
-@@ -88,8 +88,16 @@
+@@ -88,8 +_,16 @@
      }
  
      public static class ValidationException extends ThrowingComponent {
 +        public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper
 +        @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper
-         public ValidationException(Component messageText) {
+         public ValidationException(Component component) {
 +            // Paper start
-+            this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
++            this(component, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
 +        }
-+        public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) {
++        public ValidationException(Component component, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) {
 +            // Paper end
-             super(messageText);
+             super(component);
 +            this.kickCause = kickCause; // Paper
          }
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/player/Player.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/player/Player.java.patch
deleted file mode 100644
index 3f8a17b363..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/player/Player.java.patch
+++ /dev/null
@@ -1,735 +0,0 @@
---- a/net/minecraft/world/entity/player/Player.java
-+++ b/net/minecraft/world/entity/player/Player.java
-@@ -118,6 +118,15 @@
- import net.minecraft.world.scores.PlayerTeam;
- import net.minecraft.world.scores.Scoreboard;
- import org.slf4j.Logger;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.util.CraftVector;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.entity.EntityDamageEvent;
-+import org.bukkit.event.entity.EntityExhaustionEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerVelocityEvent;
-+// CraftBukkit end
- 
- public abstract class Player extends LivingEntity {
- 
-@@ -139,7 +148,8 @@
-     private static final int CURRENT_IMPULSE_CONTEXT_RESET_GRACE_TIME_TICKS = 40;
-     public static final Vec3 DEFAULT_VEHICLE_ATTACHMENT = new Vec3(0.0D, 0.6D, 0.0D);
-     public static final EntityDimensions STANDING_DIMENSIONS = EntityDimensions.scalable(0.6F, 1.8F).withEyeHeight(1.62F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT));
--    private static final Map<Pose, EntityDimensions> POSES = ImmutableMap.builder().put(Pose.STANDING, Player.STANDING_DIMENSIONS).put(Pose.SLEEPING, Player.SLEEPING_DIMENSIONS).put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.CROUCHING, EntityDimensions.scalable(0.6F, 1.5F).withEyeHeight(1.27F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT))).put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F).withEyeHeight(1.62F)).build();
-+    // CraftBukkit - decompile error
-+    private static final Map<Pose, EntityDimensions> POSES = ImmutableMap.<Pose, EntityDimensions>builder().put(Pose.STANDING, Player.STANDING_DIMENSIONS).put(Pose.SLEEPING, Player.SLEEPING_DIMENSIONS).put(Pose.FALL_FLYING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SWIMMING, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.SPIN_ATTACK, EntityDimensions.scalable(0.6F, 0.6F).withEyeHeight(0.4F)).put(Pose.CROUCHING, EntityDimensions.scalable(0.6F, 1.5F).withEyeHeight(1.27F).withAttachments(EntityAttachments.builder().attach(EntityAttachment.VEHICLE, Player.DEFAULT_VEHICLE_ATTACHMENT))).put(Pose.DYING, EntityDimensions.fixed(0.2F, 0.2F).withEyeHeight(1.62F)).build();
-     private static final EntityDataAccessor<Float> DATA_PLAYER_ABSORPTION_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.FLOAT);
-     private static final EntityDataAccessor<Integer> DATA_SCORE_ID = SynchedEntityData.defineId(Player.class, EntityDataSerializers.INT);
-     public static final EntityDataAccessor<Byte> DATA_PLAYER_MODE_CUSTOMISATION = SynchedEntityData.defineId(Player.class, EntityDataSerializers.BYTE);
-@@ -149,7 +159,7 @@
-     public static final int CLIENT_LOADED_TIMEOUT_TIME = 60;
-     private long timeEntitySatOnShoulder;
-     final Inventory inventory = new Inventory(this);
--    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer();
-+    protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(this); // CraftBukkit - add "this" to constructor
-     public final InventoryMenu inventoryMenu;
-     public AbstractContainerMenu containerMenu;
-     protected FoodData foodData = new FoodData();
-@@ -181,13 +191,25 @@
-     private Optional<GlobalPos> lastDeathLocation;
-     @Nullable
-     public FishingHook fishing;
--    protected float hurtDir;
-+    public float hurtDir; // Paper - protected -> public
-     @Nullable
-     public Vec3 currentImpulseImpactPos;
-     @Nullable
-     public Entity currentExplosionCause;
-     private boolean ignoreFallDamageFromCurrentImpulse;
-     private int currentImpulseContextResetGraceTime;
-+    public boolean affectsSpawning = true; // Paper - Affects Spawning API
-+    public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
-+
-+    // CraftBukkit start
-+    public boolean fauxSleeping;
-+    public int oldLevel = -1;
-+
-+    @Override
-+    public CraftHumanEntity getBukkitEntity() {
-+        return (CraftHumanEntity) super.getBukkitEntity();
-+    }
-+    // CraftBukkit end
- 
-     public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) {
-         super(EntityType.PLAYER, world);
-@@ -244,6 +266,13 @@
- 
-         if (this.isSleeping()) {
-             ++this.sleepCounter;
-+            // Paper start - Add PlayerDeepSleepEvent
-+            if (this.sleepCounter == SLEEP_DURATION) {
-+                if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) {
-+                    this.sleepCounter = Integer.MIN_VALUE;
-+                }
-+            }
-+            // Paper end - Add PlayerDeepSleepEvent
-             if (this.sleepCounter > 100) {
-                 this.sleepCounter = 100;
-             }
-@@ -261,7 +290,7 @@
-         this.updateIsUnderwater();
-         super.tick();
-         if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) {
--            this.closeContainer();
-+            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
-             this.containerMenu = this.inventoryMenu;
-         }
- 
-@@ -353,7 +382,7 @@
-     }
- 
-     private void turtleHelmetTick() {
--        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
-+        this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
-     }
- 
-     private boolean isEquipped(Item item) {
-@@ -511,7 +540,19 @@
-             super.handleEntityEvent(status);
-         }
- 
-+    }
-+
-+    // Paper start - Inventory close reason; unused code, but to keep signatures aligned
-+    public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
-+        closeContainer();
-+        this.containerMenu = this.inventoryMenu;
-+    }
-+    // Paper end - Inventory close reason
-+    // Paper start - special close for unloaded inventory
-+    public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
-+        this.containerMenu = this.inventoryMenu;
-     }
-+    // Paper end - special close for unloaded inventory
- 
-     public void closeContainer() {
-         this.containerMenu = this.inventoryMenu;
-@@ -523,8 +564,14 @@
-     public void rideTick() {
-         if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) {
-             this.stopRiding();
--            this.setShiftKeyDown(false);
--        } else {
-+            // CraftBukkit start - SPIGOT-7316: no longer passenger, dismount and return
-+            if (!this.isPassenger()) {
-+                this.setShiftKeyDown(false);
-+                return;
-+            }
-+        }
-+        {
-+            // CraftBukkit end
-             super.rideTick();
-             this.oBob = this.bob;
-             this.bob = 0.0F;
-@@ -593,6 +640,7 @@
-         this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft());
-         this.playShoulderEntityAmbientSound(this.getShoulderEntityRight());
-         if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) {
-+            if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay
-             this.removeEntitiesOnShoulder();
-         }
- 
-@@ -719,7 +767,14 @@
- 
-     @Nullable
-     public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) {
--        if (!stack.isEmpty() && this.level().isClientSide) {
-+        // CraftBukkit start - SPIGOT-2942: Add boolean to call event
-+        return this.drop(stack, throwRandomly, retainOwnership, true);
-+    }
-+
-+    @Nullable
-+    public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) {
-+        // CraftBukkit end
-+        if (!itemstack.isEmpty() && this.level().isClientSide) {
-             this.swing(InteractionHand.MAIN_HAND);
-         }
- 
-@@ -809,7 +864,7 @@
-         }
- 
-         if (nbt.contains("LastDeathLocation", 10)) {
--            DataResult dataresult = GlobalPos.CODEC.parse(NbtOps.INSTANCE, nbt.get("LastDeathLocation"));
-+            DataResult<GlobalPos> dataresult = GlobalPos.CODEC.parse(NbtOps.INSTANCE, nbt.get("LastDeathLocation")); // CraftBukkit - decompile error
-             Logger logger = Player.LOGGER;
- 
-             Objects.requireNonNull(logger);
-@@ -817,7 +872,7 @@
-         }
- 
-         if (nbt.contains("current_explosion_impact_pos", 9)) {
--            DataResult dataresult1 = Vec3.CODEC.parse(NbtOps.INSTANCE, nbt.get("current_explosion_impact_pos"));
-+            DataResult<Vec3> dataresult1 = Vec3.CODEC.parse(NbtOps.INSTANCE, nbt.get("current_explosion_impact_pos")); // CraftBukkit - decompile error
-             Logger logger1 = Player.LOGGER;
- 
-             Objects.requireNonNull(logger1);
-@@ -854,7 +909,7 @@
-         }
- 
-         this.getLastDeathLocation().flatMap((globalpos) -> {
--            DataResult dataresult = GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, globalpos);
-+            DataResult<Tag> dataresult = GlobalPos.CODEC.encodeStart(NbtOps.INSTANCE, globalpos); // CraftBukkit - decompile error
-             Logger logger = Player.LOGGER;
- 
-             Objects.requireNonNull(logger);
-@@ -886,10 +941,10 @@
-             if (this.isDeadOrDying()) {
-                 return false;
-             } else {
--                this.removeEntitiesOnShoulder();
-+                // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down
-                 if (source.scalesWithDifficulty()) {
-                     if (world.getDifficulty() == Difficulty.PEACEFUL) {
--                        amount = 0.0F;
-+                        return false; // CraftBukkit - f = 0.0f -> return false
-                     }
- 
-                     if (world.getDifficulty() == Difficulty.EASY) {
-@@ -901,7 +956,13 @@
-                     }
-                 }
- 
--                return amount == 0.0F ? false : super.hurtServer(world, source, amount);
-+                // CraftBukkit start - Don't filter out 0 damage
-+                boolean damaged = super.hurtServer(world, source, amount);
-+                if (damaged) {
-+                    this.removeEntitiesOnShoulder();
-+                }
-+                return damaged;
-+                // CraftBukkit end
-             }
-         }
-     }
-@@ -912,7 +973,7 @@
-         ItemStack itemstack = this.getItemBlockingWith();
- 
-         if (attacker.canDisableShield() && itemstack != null) {
--            this.disableShield(itemstack);
-+            this.disableShield(itemstack, attacker); // Paper - Add PlayerShieldDisableEvent
-         }
- 
-     }
-@@ -923,10 +984,29 @@
-     }
- 
-     public boolean canHarmPlayer(Player player) {
--        PlayerTeam scoreboardteam = this.getTeam();
--        PlayerTeam scoreboardteam1 = player.getTeam();
-+        // CraftBukkit start - Change to check OTHER player's scoreboard team according to API
-+        // To summarize this method's logic, it's "Can parameter hurt this"
-+        org.bukkit.scoreboard.Team team;
-+        if (player instanceof ServerPlayer) {
-+            ServerPlayer thatPlayer = (ServerPlayer) player;
-+            team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity());
-+            if (team == null || team.allowFriendlyFire()) {
-+                return true;
-+            }
-+        } else {
-+            // This should never be called, but is implemented anyway
-+            org.bukkit.OfflinePlayer thisPlayer = player.level().getCraftServer().getOfflinePlayer(player.getScoreboardName());
-+            team = player.level().getCraftServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer);
-+            if (team == null || team.allowFriendlyFire()) {
-+                return true;
-+            }
-+        }
- 
--        return scoreboardteam == null ? true : (!scoreboardteam.isAlliedTo(scoreboardteam1) ? true : scoreboardteam.isAllowFriendlyFire());
-+        if (this instanceof ServerPlayer) {
-+            return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity());
-+        }
-+        return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName()));
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -966,32 +1046,38 @@
-         }
-     }
- 
-+    // CraftBukkit start
-     @Override
--    protected void actuallyHurt(ServerLevel world, DamageSource source, float amount) {
--        if (!this.isInvulnerableTo(world, source)) {
--            amount = this.getDamageAfterArmorAbsorb(source, amount);
--            amount = this.getDamageAfterMagicAbsorb(source, amount);
--            float f1 = amount;
-+    protected boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, EntityDamageEvent event) { // void -> boolean
-+        if (true) {
-+            return super.actuallyHurt(worldserver, damagesource, f, event);
-+        }
-+        // CraftBukkit end
-+        if (!this.isInvulnerableTo(worldserver, damagesource)) {
-+            f = this.getDamageAfterArmorAbsorb(damagesource, f);
-+            f = this.getDamageAfterMagicAbsorb(damagesource, f);
-+            float f1 = f;
- 
--            amount = Math.max(amount - this.getAbsorptionAmount(), 0.0F);
--            this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - amount));
--            float f2 = f1 - amount;
-+            f = Math.max(f - this.getAbsorptionAmount(), 0.0F);
-+            this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - f));
-+            float f2 = f1 - f;
- 
-             if (f2 > 0.0F && f2 < 3.4028235E37F) {
-                 this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F));
-             }
- 
--            if (amount != 0.0F) {
--                this.causeFoodExhaustion(source.getFoodExhaustion());
--                this.getCombatTracker().recordDamage(source, amount);
--                this.setHealth(this.getHealth() - amount);
--                if (amount < 3.4028235E37F) {
--                    this.awardStat(Stats.DAMAGE_TAKEN, Math.round(amount * 10.0F));
-+            if (f != 0.0F) {
-+                this.causeFoodExhaustion(damagesource.getFoodExhaustion(), EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
-+                this.getCombatTracker().recordDamage(damagesource, f);
-+                this.setHealth(this.getHealth() - f);
-+                if (f < 3.4028235E37F) {
-+                    this.awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F));
-                 }
- 
-                 this.gameEvent(GameEvent.ENTITY_DAMAGE);
-             }
-         }
-+        return false; // CraftBukkit
-     }
- 
-     public boolean isTextFilteringEnabled() {
-@@ -1061,13 +1147,19 @@
- 
-     @Override
-     public void removeVehicle() {
--        super.removeVehicle();
-+        // Paper start - Force entity dismount during teleportation
-+        this.removeVehicle(false);
-+    }
-+    @Override
-+    public void removeVehicle(boolean suppressCancellation) {
-+        super.removeVehicle(suppressCancellation);
-+        // Paper end - Force entity dismount during teleportation
-         this.boardingCooldown = 0;
-     }
- 
-     @Override
-     protected boolean isImmobile() {
--        return super.isImmobile() || this.isSleeping();
-+        return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move...
-     }
- 
-     @Override
-@@ -1134,8 +1226,17 @@
-     }
- 
-     public void attack(Entity target) {
--        if (target.isAttackable()) {
--            if (!target.skipAttackInteraction(this)) {
-+        // Paper start - PlayerAttackEntityEvent
-+        boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic
-+        io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent(
-+            (org.bukkit.entity.Player) this.getBukkitEntity(),
-+            target.getBukkitEntity(),
-+            willAttack
-+        );
-+
-+        if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable.
-+            {
-+        // Paper end - PlayerAttackEntityEvent
-                 float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE);
-                 ItemStack itemstack = this.getWeaponItem();
-                 DamageSource damagesource = (DamageSource) Optional.ofNullable(itemstack.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this));
-@@ -1144,10 +1245,15 @@
- 
-                 f *= 0.2F + f2 * f2 * 0.8F;
-                 f1 *= f2;
--                this.resetAttackStrengthTicker();
-+                // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt
-                 if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && target instanceof Projectile) {
-                     Projectile iprojectile = (Projectile) target;
- 
-+                    // CraftBukkit start
-+                    if (CraftEventFactory.handleNonLivingEntityDamageEvent(target, damagesource, f1, false)) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     if (iprojectile.deflect(ProjectileDeflection.AIM_DEFLECT, this, this, true)) {
-                         this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource());
-                         return;
-@@ -1159,7 +1265,7 @@
-                     boolean flag1;
- 
-                     if (this.isSprinting() && flag) {
--                        this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F);
-+                        sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-                         flag1 = true;
-                     } else {
-                         flag1 = false;
-@@ -1168,7 +1274,9 @@
-                     f += itemstack.getItem().getAttackDamageBonus(target, f, damagesource);
-                     boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && !this.isSprinting();
- 
-+                    flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
-                     if (flag2) {
-+                        damagesource = damagesource.critical(true); // Paper start - critical damage API
-                         f *= 1.5F;
-                     }
- 
-@@ -1202,13 +1310,17 @@
-                             if (target instanceof LivingEntity) {
-                                 LivingEntity entityliving1 = (LivingEntity) target;
- 
--                                entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)));
-+                                entityliving1.knockback((double) (f5 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // Paper - knockback events
-                             } else {
--                                target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F));
-+                                target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * f5 * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f5 * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
-                             }
- 
-                             this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D));
-+                            // Paper start - Configurable sprint interruption on attack
-+                            if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) {
-                             this.setSprinting(false);
-+                            }
-+                            // Paper end - Configurable sprint interruption on attack
-                         }
- 
-                         LivingEntity entityliving2;
-@@ -1223,8 +1335,13 @@
-                                 if (entityliving2 != this && entityliving2 != target && !this.isAlliedTo((Entity) entityliving2) && (!(entityliving2 instanceof ArmorStand) || !((ArmorStand) entityliving2).isMarker()) && this.distanceToSqr((Entity) entityliving2) < 9.0D) {
-                                     float f7 = this.getEnchantedDamage(entityliving2, f6, damagesource) * f2;
- 
--                                    entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)));
--                                    entityliving2.hurt(damagesource, f7);
-+                                    // CraftBukkit start - Only apply knockback if the damage hits
-+                                    if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep().critical(flag2), f7)) { // Paper - add critical damage API
-+                                        continue;
-+                                    }
-+                                    // CraftBukkit end
-+                                    entityliving2.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.SWEEP_ATTACK); // CraftBukkit // Paper - knockback events
-+                                    // entityliving2.hurt(damagesource, f7); // CraftBukkit - moved up
-                                     Level world = this.level();
- 
-                                     if (world instanceof ServerLevel) {
-@@ -1235,26 +1352,43 @@
-                                 }
-                             }
- 
--                            this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F);
-+                            sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-                             this.sweepAttack();
-                         }
- 
-                         if (target instanceof ServerPlayer && target.hurtMarked) {
-+                            // CraftBukkit start - Add Velocity Event
-+                            boolean cancelled = false;
-+                            org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity();
-+                            org.bukkit.util.Vector velocity = CraftVector.toBukkit(vec3d);
-+
-+                            PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
-+                            this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                            if (event.isCancelled()) {
-+                                cancelled = true;
-+                            } else if (!velocity.equals(event.getVelocity())) {
-+                                player.setVelocity(event.getVelocity());
-+                            }
-+
-+                            if (!cancelled) {
-                             ((ServerPlayer) target).connection.send(new ClientboundSetEntityMotionPacket(target));
-                             target.hurtMarked = false;
-                             target.setDeltaMovement(vec3d);
-+                            }
-+                            // CraftBukkit end
-                         }
- 
-                         if (flag2) {
--                            this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F);
-+                            sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-                             this.crit(target);
-                         }
- 
-                         if (!flag2 && !flag3) {
-                             if (flag) {
--                                this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F);
-+                                sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-                             } else {
--                                this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F);
-+                                sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-                             }
-                         }
- 
-@@ -1308,9 +1442,14 @@
-                             }
-                         }
- 
--                        this.causeFoodExhaustion(0.1F);
-+                        this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
-                     } else {
--                        this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
-+                        sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility
-+                        // CraftBukkit start - resync on cancelled event
-+                        if (this instanceof ServerPlayer) {
-+                            ((ServerPlayer) this).getBukkitEntity().updateInventory();
-+                        }
-+                        // CraftBukkit end
-                     }
-                 }
- 
-@@ -1327,8 +1466,21 @@
-         this.attack(target);
-     }
- 
-+    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent
-     public void disableShield(ItemStack shield) {
--        this.getCooldowns().addCooldown(shield, 100);
-+        // Paper start - Add PlayerShieldDisableEvent
-+        this.disableShield(shield, null);
-+    }
-+    public void disableShield(ItemStack shield, @Nullable LivingEntity attacker) {
-+        final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null;
-+        if (finalAttacker != null) {
-+            final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100);
-+            if (!shieldDisableEvent.callEvent()) return;
-+            this.getCooldowns().addCooldown(shield, shieldDisableEvent.getCooldown());
-+        } else {
-+            this.getCooldowns().addCooldown(shield, 100);
-+        }
-+        // Paper end - Add PlayerShieldDisableEvent
-         this.stopUsingItem();
-         this.level().broadcastEntityEvent(this, (byte) 30);
-     }
-@@ -1351,7 +1503,14 @@
- 
-     @Override
-     public void remove(Entity.RemovalReason reason) {
--        super.remove(reason);
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.remove(reason, null);
-+    }
-+
-+    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
-+        super.remove(entity_removalreason, cause);
-+        // CraftBukkit end
-         this.inventoryMenu.removed(this);
-         if (this.containerMenu != null && this.hasContainerOpen()) {
-             this.doCloseContainer();
-@@ -1391,7 +1550,13 @@
-     }
- 
-     public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos pos) {
--        this.startSleeping(pos);
-+        // CraftBukkit start
-+        return this.startSleepInBed(pos, false);
-+    }
-+
-+    public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockposition, boolean force) {
-+        // CraftBukkit end
-+        this.startSleeping(blockposition);
-         this.sleepCounter = 0;
-         return Either.right(Unit.INSTANCE);
-     }
-@@ -1503,7 +1668,7 @@
- 
-     @Override
-     public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
--        if (this.abilities.mayfly) {
-+        if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage
-             return false;
-         } else {
-             if (fallDistance >= 2.0F) {
-@@ -1545,12 +1710,24 @@
-     }
- 
-     public void startFallFlying() {
--        this.setSharedFlag(7, true);
-+        // CraftBukkit start
-+        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) {
-+            this.setSharedFlag(7, true);
-+        } else {
-+            // SPIGOT-5542: must toggle like below
-+            this.setSharedFlag(7, true);
-+            this.setSharedFlag(7, false);
-+        }
-+        // CraftBukkit end
-     }
- 
-     public void stopFallFlying() {
-+        // CraftBukkit start
-+        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) {
-         this.setSharedFlag(7, true);
-         this.setSharedFlag(7, false);
-+        }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -1662,13 +1839,32 @@
-     }
- 
-     public int getXpNeededForNextLevel() {
--        return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2);
-+        return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints
-+    }
-+    // Paper start - send while respecting visibility
-+    private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) {
-+        fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity itself
-+        if (fromEntity instanceof ServerPlayer serverPlayer) {
-+            serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong()));
-+        }
-     }
-+    // Paper end - send while respecting visibility
- 
-+    // CraftBukkit start
-     public void causeFoodExhaustion(float exhaustion) {
-+        this.causeFoodExhaustion(exhaustion, EntityExhaustionEvent.ExhaustionReason.UNKNOWN);
-+    }
-+
-+    public void causeFoodExhaustion(float f, EntityExhaustionEvent.ExhaustionReason reason) {
-+        // CraftBukkit end
-         if (!this.abilities.invulnerable) {
-             if (!this.level().isClientSide) {
--                this.foodData.addExhaustion(exhaustion);
-+                // CraftBukkit start
-+                EntityExhaustionEvent event = CraftEventFactory.callPlayerExhaustionEvent(this, reason, f);
-+                if (!event.isCancelled()) {
-+                    this.foodData.addExhaustion(event.getExhaustion());
-+                }
-+                // CraftBukkit end
-             }
- 
-         }
-@@ -1748,13 +1944,20 @@
- 
-     @Override
-     public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
--        this.verifyEquippedItem(stack);
--        if (slot == EquipmentSlot.MAINHAND) {
--            this.onEquipItem(slot, (ItemStack) this.inventory.items.set(this.inventory.selected, stack), stack);
--        } else if (slot == EquipmentSlot.OFFHAND) {
--            this.onEquipItem(slot, (ItemStack) this.inventory.offhand.set(0, stack), stack);
--        } else if (slot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
--            this.onEquipItem(slot, (ItemStack) this.inventory.armor.set(slot.getIndex(), stack), stack);
-+        // CraftBukkit start
-+        this.setItemSlot(slot, stack, false);
-+    }
-+
-+    @Override
-+    public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
-+        // CraftBukkit end
-+        this.verifyEquippedItem(itemstack);
-+        if (enumitemslot == EquipmentSlot.MAINHAND) {
-+            this.onEquipItem(enumitemslot, (ItemStack) this.inventory.items.set(this.inventory.selected, itemstack), itemstack, silent); // CraftBukkit
-+        } else if (enumitemslot == EquipmentSlot.OFFHAND) {
-+            this.onEquipItem(enumitemslot, (ItemStack) this.inventory.offhand.set(0, itemstack), itemstack, silent); // CraftBukkit
-+        } else if (enumitemslot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
-+            this.onEquipItem(enumitemslot, (ItemStack) this.inventory.armor.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
-         }
- 
-     }
-@@ -1798,26 +2001,55 @@
- 
-     public void removeEntitiesOnShoulder() {
-         if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) {
--            this.respawnEntityOnShoulder(this.getShoulderEntityLeft());
-+            // CraftBukkit start
-+            if (this.respawnEntityOnShoulder(this.getShoulderEntityLeft())) {
-+                this.setShoulderEntityLeft(new CompoundTag());
-+            }
-+            if (this.respawnEntityOnShoulder(this.getShoulderEntityRight())) {
-+                this.setShoulderEntityRight(new CompoundTag());
-+            }
-+            // CraftBukkit end
-+        }
-+
-+    }
-+
-+    // Paper start - release entity api
-+    public Entity releaseLeftShoulderEntity() {
-+        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft());
-+        if (entity != null) {
-             this.setShoulderEntityLeft(new CompoundTag());
--            this.respawnEntityOnShoulder(this.getShoulderEntityRight());
-+        }
-+        return entity;
-+    }
-+
-+    public Entity releaseRightShoulderEntity() {
-+        Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight());
-+        if (entity != null) {
-             this.setShoulderEntityRight(new CompoundTag());
-         }
-+        return entity;
-+    }
-+    // Paper end - release entity api
- 
-+    private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean
-+    // Paper start - release entity api - return entity - overload
-+        return this.respawnEntityOnShoulder0(nbttagcompound) != null;
-     }
- 
--    private void respawnEntityOnShoulder(CompoundTag entityNbt) {
--        if (!this.level().isClientSide && !entityNbt.isEmpty()) {
--            EntityType.create(entityNbt, this.level(), EntitySpawnReason.LOAD).ifPresent((entity) -> {
-+    private Entity respawnEntityOnShoulder0(CompoundTag nbttagcompound) { // CraftBukkit void->boolean
-+    // Paper end - release entity api - return entity - overload
-+        if (!this.level().isClientSide && !nbttagcompound.isEmpty()) {
-+            return EntityType.create(nbttagcompound, this.level(), EntitySpawnReason.LOAD).map((entity) -> { // CraftBukkit
-                 if (entity instanceof TamableAnimal) {
-                     ((TamableAnimal) entity).setOwnerUUID(this.uuid);
-                 }
- 
-                 entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ());
--                ((ServerLevel) this.level()).addWithUUID(entity);
--            });
-+                return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY) ? entity : null; // CraftBukkit // Paper start - release entity api - return entity
-+            }).orElse(null); // CraftBukkit // Paper end - release entity api - return entity
-         }
- 
-+        return null; // Paper - return null
-     }
- 
-     @Override
-@@ -2003,20 +2235,31 @@
-     @Override
-     public ImmutableList<Pose> getDismountPoses() {
-         return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING);
-+    }
-+
-+    // Paper start - PlayerReadyArrowEvent
-+    protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) {
-+        return !(this instanceof ServerPlayer) ||
-+                new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent(
-+                    ((ServerPlayer) this).getBukkitEntity(),
-+                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow),
-+                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)
-+                ).callEvent();
-     }
-+    // Paper end - PlayerReadyArrowEvent
- 
-     @Override
-     public ItemStack getProjectile(ItemStack stack) {
-         if (!(stack.getItem() instanceof ProjectileWeaponItem)) {
-             return ItemStack.EMPTY;
-         } else {
--            Predicate<ItemStack> predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles();
-+            Predicate<ItemStack> predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent
-             ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate);
- 
-             if (!itemstack1.isEmpty()) {
-                 return itemstack1;
-             } else {
--                predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles();
-+                predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent
- 
-                 for (int i = 0; i < this.inventory.getContainerSize(); ++i) {
-                     ItemStack itemstack2 = this.inventory.getItem(i);

From 11b18775459ed558f57a041f8b277632f8b4f140 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 16:44:37 -0800
Subject: [PATCH 135/285] net.minecraft.world.entity.ai.attributes

---
 .../attributes/AttributeInstance.java.patch   | 27 +++++++++++++
 .../ai/attributes/AttributeMap.java.patch     |  2 +-
 .../ai/attributes/Attributes.java.patch       | 38 +++++++++++++++++++
 .../attributes/AttributeInstance.java.patch   | 27 -------------
 .../ai/attributes/Attributes.java.patch       | 31 ---------------
 5 files changed, 66 insertions(+), 59 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch (96%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/Attributes.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
new file mode 100644
index 0000000000..c315956cf0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
+@@ -153,20 +_,20 @@
+         double baseValue = this.getBaseValue();
+ 
+         for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) {
+-            baseValue += attributeModifier.amount();
++            baseValue += attributeModifier.amount(); // Paper - destroy speed API - diff on change
+         }
+ 
+         double d = baseValue;
+ 
+         for (AttributeModifier attributeModifier1 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) {
+-            d += baseValue * attributeModifier1.amount();
++            d += baseValue * attributeModifier1.amount(); // Paper - destroy speed API - diff on change
+         }
+ 
+         for (AttributeModifier attributeModifier1 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) {
+-            d *= 1.0 + attributeModifier1.amount();
++            d *= 1.0 + attributeModifier1.amount(); // Paper - destroy speed API - diff on change
+         }
+ 
+-        return this.attribute.value().sanitizeValue(d);
++        return this.attribute.value().sanitizeValue(d); // Paper - destroy speed API - diff on change
+     }
+ 
+     private Collection<AttributeModifier> getModifiersOrEmpty(AttributeModifier.Operation operation) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch
index 08b12ec883..f685d112e2 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/AttributeMap.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
 +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
-@@ -162,4 +162,12 @@
+@@ -162,4 +_,12 @@
              }
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
new file mode 100644
index 0000000000..b6fa57bafe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/entity/ai/attributes/Attributes.java
++++ b/net/minecraft/world/entity/ai/attributes/Attributes.java
+@@ -1,3 +_,4 @@
++// mc-dev import
+ package net.minecraft.world.entity.ai.attributes;
+ 
+ import net.minecraft.core.Holder;
+@@ -10,7 +_,7 @@
+     public static final Holder<Attribute> ARMOR_TOUGHNESS = register(
+         "armor_toughness", new RangedAttribute("attribute.name.armor_toughness", 0.0, 0.0, 20.0).setSyncable(true)
+     );
+-    public static final Holder<Attribute> ATTACK_DAMAGE = register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0, 0.0, 2048.0));
++    public static final Holder<Attribute> ATTACK_DAMAGE = register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0, 0.0, org.spigotmc.SpigotConfig.attackDamage)); // Spigot
+     public static final Holder<Attribute> ATTACK_KNOCKBACK = register("attack_knockback", new RangedAttribute("attribute.name.attack_knockback", 0.0, 0.0, 5.0));
+     public static final Holder<Attribute> ATTACK_SPEED = register(
+         "attack_speed", new RangedAttribute("attribute.name.attack_speed", 4.0, 0.0, 1024.0).setSyncable(true)
+@@ -49,10 +_,10 @@
+     );
+     public static final Holder<Attribute> LUCK = register("luck", new RangedAttribute("attribute.name.luck", 0.0, -1024.0, 1024.0).setSyncable(true));
+     public static final Holder<Attribute> MAX_ABSORPTION = register(
+-        "max_absorption", new RangedAttribute("attribute.name.max_absorption", 0.0, 0.0, 2048.0).setSyncable(true)
++        "max_absorption", new RangedAttribute("attribute.name.max_absorption", 0.0, 0.0, org.spigotmc.SpigotConfig.maxAbsorption).setSyncable(true) // Spigot
+     );
+     public static final Holder<Attribute> MAX_HEALTH = register(
+-        "max_health", new RangedAttribute("attribute.name.max_health", 20.0, 1.0, 1024.0).setSyncable(true)
++        "max_health", new RangedAttribute("attribute.name.max_health", 20.0, 1.0, org.spigotmc.SpigotConfig.maxHealth).setSyncable(true) // Spigot
+     );
+     public static final Holder<Attribute> MINING_EFFICIENCY = register(
+         "mining_efficiency", new RangedAttribute("attribute.name.mining_efficiency", 0.0, 0.0, 1024.0).setSyncable(true)
+@@ -61,7 +_,7 @@
+         "movement_efficiency", new RangedAttribute("attribute.name.movement_efficiency", 0.0, 0.0, 1.0).setSyncable(true)
+     );
+     public static final Holder<Attribute> MOVEMENT_SPEED = register(
+-        "movement_speed", new RangedAttribute("attribute.name.movement_speed", 0.7, 0.0, 1024.0).setSyncable(true)
++        "movement_speed", new RangedAttribute("attribute.name.movement_speed", 0.7, 0.0, org.spigotmc.SpigotConfig.movementSpeed).setSyncable(true) // Spigot
+     );
+     public static final Holder<Attribute> OXYGEN_BONUS = register(
+         "oxygen_bonus", new RangedAttribute("attribute.name.oxygen_bonus", 0.0, 0.0, 1024.0).setSyncable(true)
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
deleted file mode 100644
index d6d3313220..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/AttributeInstance.java.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
-+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
-@@ -153,20 +153,20 @@
-         double d = this.getBaseValue();
- 
-         for (AttributeModifier attributeModifier : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_VALUE)) {
--            d += attributeModifier.amount();
-+            d += attributeModifier.amount(); // Paper - destroy speed API - diff on change
-         }
- 
-         double e = d;
- 
-         for (AttributeModifier attributeModifier2 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_BASE)) {
--            e += d * attributeModifier2.amount();
-+            e += d * attributeModifier2.amount(); // Paper - destroy speed API - diff on change
-         }
- 
-         for (AttributeModifier attributeModifier3 : this.getModifiersOrEmpty(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)) {
--            e *= 1.0 + attributeModifier3.amount();
-+            e *= 1.0 + attributeModifier3.amount(); // Paper - destroy speed API - diff on change
-         }
- 
--        return this.attribute.value().sanitizeValue(e);
-+        return attribute.value().sanitizeValue(e); // Paper - destroy speed API - diff on change
-     }
- 
-     private Collection<AttributeModifier> getModifiersOrEmpty(AttributeModifier.Operation operation) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
deleted file mode 100644
index 51565ac321..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/entity/ai/attributes/Attributes.java
-+++ b/net/minecraft/world/entity/ai/attributes/Attributes.java
-@@ -1,3 +1,4 @@
-+// mc-dev import
- package net.minecraft.world.entity.ai.attributes;
- 
- import net.minecraft.core.Holder;
-@@ -9,7 +10,7 @@
- 
-     public static final Holder<Attribute> ARMOR = Attributes.register("armor", (new RangedAttribute("attribute.name.armor", 0.0D, 0.0D, 30.0D)).setSyncable(true));
-     public static final Holder<Attribute> ARMOR_TOUGHNESS = Attributes.register("armor_toughness", (new RangedAttribute("attribute.name.armor_toughness", 0.0D, 0.0D, 20.0D)).setSyncable(true));
--    public static final Holder<Attribute> ATTACK_DAMAGE = Attributes.register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0D, 0.0D, 2048.0D));
-+    public static final Holder<Attribute> ATTACK_DAMAGE = Attributes.register("attack_damage", new RangedAttribute("attribute.name.attack_damage", 2.0D, 0.0D, org.spigotmc.SpigotConfig.attackDamage));
-     public static final Holder<Attribute> ATTACK_KNOCKBACK = Attributes.register("attack_knockback", new RangedAttribute("attribute.name.attack_knockback", 0.0D, 0.0D, 5.0D));
-     public static final Holder<Attribute> ATTACK_SPEED = Attributes.register("attack_speed", (new RangedAttribute("attribute.name.attack_speed", 4.0D, 0.0D, 1024.0D)).setSyncable(true));
-     public static final Holder<Attribute> BLOCK_BREAK_SPEED = Attributes.register("block_break_speed", (new RangedAttribute("attribute.name.block_break_speed", 1.0D, 0.0D, 1024.0D)).setSyncable(true));
-@@ -24,11 +25,11 @@
-     public static final Holder<Attribute> JUMP_STRENGTH = Attributes.register("jump_strength", (new RangedAttribute("attribute.name.jump_strength", 0.41999998688697815D, 0.0D, 32.0D)).setSyncable(true));
-     public static final Holder<Attribute> KNOCKBACK_RESISTANCE = Attributes.register("knockback_resistance", new RangedAttribute("attribute.name.knockback_resistance", 0.0D, 0.0D, 1.0D));
-     public static final Holder<Attribute> LUCK = Attributes.register("luck", (new RangedAttribute("attribute.name.luck", 0.0D, -1024.0D, 1024.0D)).setSyncable(true));
--    public static final Holder<Attribute> MAX_ABSORPTION = Attributes.register("max_absorption", (new RangedAttribute("attribute.name.max_absorption", 0.0D, 0.0D, 2048.0D)).setSyncable(true));
--    public static final Holder<Attribute> MAX_HEALTH = Attributes.register("max_health", (new RangedAttribute("attribute.name.max_health", 20.0D, 1.0D, 1024.0D)).setSyncable(true));
-+    public static final Holder<Attribute> MAX_ABSORPTION = Attributes.register("max_absorption", (new RangedAttribute("attribute.name.max_absorption", 0.0D, 0.0D, org.spigotmc.SpigotConfig.maxAbsorption)).setSyncable(true));
-+    public static final Holder<Attribute> MAX_HEALTH = Attributes.register("max_health", (new RangedAttribute("attribute.name.max_health", 20.0D, 1.0D, org.spigotmc.SpigotConfig.maxHealth)).setSyncable(true));
-     public static final Holder<Attribute> MINING_EFFICIENCY = Attributes.register("mining_efficiency", (new RangedAttribute("attribute.name.mining_efficiency", 0.0D, 0.0D, 1024.0D)).setSyncable(true));
-     public static final Holder<Attribute> MOVEMENT_EFFICIENCY = Attributes.register("movement_efficiency", (new RangedAttribute("attribute.name.movement_efficiency", 0.0D, 0.0D, 1.0D)).setSyncable(true));
--    public static final Holder<Attribute> MOVEMENT_SPEED = Attributes.register("movement_speed", (new RangedAttribute("attribute.name.movement_speed", 0.7D, 0.0D, 1024.0D)).setSyncable(true));
-+    public static final Holder<Attribute> MOVEMENT_SPEED = Attributes.register("movement_speed", (new RangedAttribute("attribute.name.movement_speed", 0.7D, 0.0D, org.spigotmc.SpigotConfig.movementSpeed)).setSyncable(true));
-     public static final Holder<Attribute> OXYGEN_BONUS = Attributes.register("oxygen_bonus", (new RangedAttribute("attribute.name.oxygen_bonus", 0.0D, 0.0D, 1024.0D)).setSyncable(true));
-     public static final Holder<Attribute> SAFE_FALL_DISTANCE = Attributes.register("safe_fall_distance", (new RangedAttribute("attribute.name.safe_fall_distance", 3.0D, -1024.0D, 1024.0D)).setSyncable(true));
-     public static final Holder<Attribute> SCALE = Attributes.register("scale", (new RangedAttribute("attribute.name.scale", 1.0D, 0.0625D, 16.0D)).setSyncable(true).setSentiment(Attribute.Sentiment.NEUTRAL));

From b16f6f533a11f8cd69ecc9f39a82ceb64bac0dbf Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 21:31:05 -0500
Subject: [PATCH 136/285] net/minecraft/world/level/block/a-r

---
 .../block/AbstractCandleBlock.java.patch      |  14 ++
 .../block/AbstractCauldronBlock.java.patch    |  11 +
 .../world/level/block/AnvilBlock.java.patch   |  10 +-
 .../level/block/BambooSaplingBlock.java.patch |  19 ++
 .../level/block/BambooStalkBlock.java.patch   |  87 ++++++++
 .../world/level/block/BarrelBlock.java.patch  |   8 +-
 .../level/block/BaseFireBlock.java.patch      |  50 ++---
 .../block/BasePressurePlateBlock.java.patch   |  45 ++++
 .../level/block/BaseRailBlock.java.patch      |  10 +
 .../world/level/block/BeaconBlock.java.patch  |   8 +-
 .../world/level/block/BedBlock.java.patch     |  73 +++----
 .../world/level/block/BeehiveBlock.java.patch |  60 +++++
 .../world/level/block/BellBlock.java.patch    |  14 ++
 .../level/block/BigDripleafBlock.java.patch   |  88 ++++++++
 .../level/block/BlastFurnaceBlock.java.patch  |   6 +-
 .../world/level/block/Block.java.patch        | 128 +++++------
 .../level/block/BrewingStandBlock.java.patch  |   8 +-
 .../level/block/BubbleColumnBlock.java.patch  |   8 +-
 .../block/BuddingAmethystBlock.java.patch     |  16 +-
 .../world/level/block/BushBlock.java.patch    |  29 +++
 .../world/level/block/ButtonBlock.java.patch  |  62 ++++++
 .../world/level/block/CactusBlock.java.patch  |  12 +
 .../world/level/block/CakeBlock.java.patch    |  43 ++--
 .../level/block/CampfireBlock.java.patch      |  25 +++
 .../block/CartographyTableBlock.java.patch    |  10 +-
 .../level/block/CarvedPumpkinBlock.java.patch |  18 ++
 .../level/block/CauldronBlock.java.patch      |  46 ++++
 .../world/level/block/CaveVines.java.patch    |  27 +++
 .../level/block/CaveVinesBlock.java.patch     |  25 ++-
 .../block/CeilingHangingSignBlock.java.patch  |   6 +-
 .../block/ChangeOverTimeBlock.java.patch      |  11 +
 .../world/level/block/ChestBlock.java.patch   |  79 +++++++
 .../level/block/ChorusFlowerBlock.java.patch  |  24 ++
 .../level/block/ChorusPlantBlock.java.patch   |  30 +--
 .../world/level/block/CocoaBlock.java.patch   |  24 ++
 .../world/level/block/CommandBlock.java.patch |  27 +++
 .../level/block/ComparatorBlock.java.patch    |  21 ++
 .../level/block/ComposterBlock.java.patch     | 148 +++++++++++++
 .../block/ConcretePowderBlock.java.patch      |  66 ++++++
 .../world/level/block/CoralBlock.java.patch   |  12 +-
 .../level/block/CoralFanBlock.java.patch      |  12 +-
 .../level/block/CoralPlantBlock.java.patch    |  12 +-
 .../level/block/CoralWallFanBlock.java.patch  |  12 +-
 .../world/level/block/CrafterBlock.java.patch |  76 +++++++
 .../level/block/CraftingTableBlock.java.patch |  11 +-
 .../world/level/block/CropBlock.java.patch    |  49 +++++
 .../block/DaylightDetectorBlock.java.patch    |  10 +
 .../level/block/DecoratedPotBlock.java.patch  |  14 ++
 .../level/block/DetectorRailBlock.java.patch  |  35 +++
 .../world/level/block/DiodeBlock.java.patch   |  21 ++
 .../level/block/DirtPathBlock.java.patch      |   8 +-
 .../level/block/DispenserBlock.java.patch     |  47 ++++
 .../world/level/block/DoorBlock.java.patch    |  29 +++
 .../block/DoubleBlockCombiner.java.patch      |  12 +-
 .../level/block/DoublePlantBlock.java.patch   |  21 ++
 .../level/block/DragonEggBlock.java.patch     |  21 ++
 .../block/DropExperienceBlock.java.patch      |  13 +-
 .../world/level/block/DropperBlock.java.patch |  59 +++++
 .../level/block/EndGatewayBlock.java.patch    |  30 +++
 .../level/block/EndPortalBlock.java.patch     |  62 ++++++
 .../level/block/EnderChestBlock.java.patch    |  26 +++
 .../level/block/EyeblossomBlock.java.patch    |  10 +-
 .../world/level/block/FarmBlock.java.patch    |  93 ++++++++
 .../level/block/FenceGateBlock.java.patch     |  20 ++
 .../world/level/block/FireBlock.java.patch    | 169 +++++++++++++++
 .../level/block/FlowerPotBlock.java.patch     |  10 +-
 .../level/block/FrogspawnBlock.java.patch     |  31 +++
 .../level/block/FrostedIceBlock.java.patch    |  24 ++
 .../world/level/block/FungusBlock.java.patch  |  21 ++
 .../world/level/block/FurnaceBlock.java.patch |   6 +-
 .../level/block/GrindstoneBlock.java.patch    |  12 +
 .../block/GrowingPlantHeadBlock.java.patch    |  36 +++
 .../world/level/block/HoneyBlock.java.patch   |   6 +-
 .../world/level/block/HopperBlock.java.patch  |  18 +-
 .../level/block/HugeMushroomBlock.java.patch  |  24 +-
 .../world/level/block/IceBlock.java.patch     |  30 +++
 .../level/block/InfestedBlock.java.patch      |  11 +
 .../level/block/LavaCauldronBlock.java.patch  |   6 +-
 .../block/LayeredCauldronBlock.java.patch     | 109 ++++++++++
 .../world/level/block/LeavesBlock.java.patch  |  17 ++
 .../world/level/block/LecternBlock.java.patch |  67 ++++++
 .../world/level/block/LeverBlock.java.patch   |  22 ++
 .../world/level/block/LightBlock.java.patch   |  14 +-
 .../level/block/LightningRodBlock.java.patch  |  21 ++
 .../world/level/block/LiquidBlock.java.patch  |  67 ++++++
 .../world/level/block/LoomBlock.java.patch    |  12 +
 .../world/level/block/MagmaBlock.java.patch   |  11 +
 .../block/MangrovePropaguleBlock.java.patch   |   8 +-
 .../level/block/MultifaceSpreader.java.patch  |  42 ++++
 .../level/block/MushroomBlock.java.patch      |  36 +++
 .../level/block/NetherPortalBlock.java.patch  | 126 +++++++++++
 .../level/block/NetherWartBlock.java.patch    |  14 ++
 .../world/level/block/NoteBlock.java.patch    |  78 +++++++
 .../world/level/block/NyliumBlock.java.patch  |  12 +-
 .../level/block/ObserverBlock.java.patch      |  21 ++
 .../level/block/PitcherCropBlock.java.patch   |  20 ++
 .../block/PointedDripstoneBlock.java.patch    |  68 ++++++
 .../level/block/PowderSnowBlock.java.patch    |  25 +++
 .../level/block/PoweredRailBlock.java.patch   |  16 ++
 .../level/block/PressurePlateBlock.java.patch |  43 ++++
 .../world/level/block/PumpkinBlock.java.patch |  28 ++-
 .../world/level/block/RailState.java.patch    |  36 +--
 .../level/block/RedStoneOreBlock.java.patch   |  89 ++++++++
 .../level/block/RedstoneLampBlock.java.patch  |  26 +++
 .../level/block/RedstoneTorchBlock.java.patch |  69 +++---
 .../level/block/RespawnAnchorBlock.java.patch |  37 ++++
 .../level/block/RootedDirtBlock.java.patch    |   8 +-
 .../block/AbstractCandleBlock.java.patch      |  14 --
 .../block/AbstractCauldronBlock.java.patch    |  11 -
 .../level/block/BambooSaplingBlock.java.patch |  19 --
 .../level/block/BambooStalkBlock.java.patch   |  89 --------
 .../block/BasePressurePlateBlock.java.patch   |  56 -----
 .../level/block/BaseRailBlock.java.patch      |  10 -
 .../world/level/block/BeehiveBlock.java.patch |  79 -------
 .../world/level/block/BellBlock.java.patch    |  14 --
 .../level/block/BigDripleafBlock.java.patch   | 116 ----------
 .../world/level/block/BushBlock.java.patch    |  27 ---
 .../world/level/block/ButtonBlock.java.patch  |  78 -------
 .../world/level/block/CactusBlock.java.patch  |  42 ----
 .../level/block/CampfireBlock.java.patch      |  25 ---
 .../level/block/CarvedPumpkinBlock.java.patch |  29 ---
 .../level/block/CauldronBlock.java.patch      |  56 -----
 .../world/level/block/CaveVines.java.patch    |  42 ----
 .../block/ChangeOverTimeBlock.java.patch      |  11 -
 .../world/level/block/ChestBlock.java.patch   | 118 ----------
 .../level/block/ChorusFlowerBlock.java.patch  |  73 -------
 .../world/level/block/CocoaBlock.java.patch   |  33 ---
 .../world/level/block/CommandBlock.java.patch |  37 ----
 .../level/block/ComparatorBlock.java.patch    |  39 ----
 .../level/block/ComposterBlock.java.patch     | 184 ----------------
 .../block/ConcretePowderBlock.java.patch      |  76 -------
 .../world/level/block/CrafterBlock.java.patch |  89 --------
 .../world/level/block/CropBlock.java.patch    |  59 -----
 .../block/DaylightDetectorBlock.java.patch    |  10 -
 .../level/block/DecoratedPotBlock.java.patch  |  14 --
 .../level/block/DetectorRailBlock.java.patch  |  44 ----
 .../world/level/block/DiodeBlock.java.patch   |  29 ---
 .../level/block/DispenserBlock.java.patch     |  61 ------
 .../world/level/block/DoorBlock.java.patch    |  37 ----
 .../level/block/DoublePlantBlock.java.patch   |  21 --
 .../level/block/DragonEggBlock.java.patch     |  29 ---
 .../world/level/block/DropperBlock.java.patch |  75 -------
 .../level/block/EndGatewayBlock.java.patch    |  34 ---
 .../level/block/EndPortalBlock.java.patch     |  91 --------
 .../level/block/EnderChestBlock.java.patch    |  25 ---
 .../world/level/block/FarmBlock.java.patch    | 112 ----------
 .../level/block/FenceGateBlock.java.patch     |  20 --
 .../world/level/block/FireBlock.java.patch    | 205 ------------------
 .../level/block/FrogspawnBlock.java.patch     |  31 ---
 .../level/block/FrostedIceBlock.java.patch    |  24 --
 .../world/level/block/FungusBlock.java.patch  |  16 --
 .../level/block/GrindstoneBlock.java.patch    |  13 --
 .../block/GrowingPlantHeadBlock.java.patch    |  39 ----
 .../world/level/block/IceBlock.java.patch     |  30 ---
 .../level/block/InfestedBlock.java.patch      |  19 --
 .../block/LayeredCauldronBlock.java.patch     | 127 -----------
 .../world/level/block/LeavesBlock.java.patch  |  25 ---
 .../world/level/block/LecternBlock.java.patch |  69 ------
 .../world/level/block/LeverBlock.java.patch   |  31 ---
 .../level/block/LightningRodBlock.java.patch  |  33 ---
 .../world/level/block/LiquidBlock.java.patch  |  78 -------
 .../world/level/block/LoomBlock.java.patch    |  13 --
 .../world/level/block/MagmaBlock.java.patch   |  11 -
 .../level/block/MultifaceSpreader.java.patch  |  43 ----
 .../level/block/MushroomBlock.java.patch      |  46 ----
 .../level/block/NetherPortalBlock.java.patch  | 163 --------------
 .../level/block/NetherWartBlock.java.patch    |  14 --
 .../world/level/block/NoteBlock.java.patch    |  78 -------
 .../level/block/ObserverBlock.java.patch      |  30 ---
 .../level/block/PitcherCropBlock.java.patch   |  28 ---
 .../block/PointedDripstoneBlock.java.patch    |  96 --------
 .../level/block/PowderSnowBlock.java.patch    |  24 --
 .../level/block/PoweredRailBlock.java.patch   |  24 --
 .../level/block/PressurePlateBlock.java.patch |  61 ------
 .../level/block/RedStoneOreBlock.java.patch   | 102 ---------
 .../level/block/RedstoneLampBlock.java.patch  |  35 ---
 .../level/block/RespawnAnchorBlock.java.patch |  46 ----
 177 files changed, 3259 insertions(+), 3980 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/AnvilBlock.java.patch (62%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BarrelBlock.java.patch (73%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BaseFireBlock.java.patch (56%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BeaconBlock.java.patch (69%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BedBlock.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch (78%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/Block.java.patch (57%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BrewingStandBlock.java.patch (72%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BubbleColumnBlock.java.patch (66%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CakeBlock.java.patch (51%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CartographyTableBlock.java.patch (64%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CaveVinesBlock.java.patch (72%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch (61%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/ChorusPlantBlock.java.patch (56%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CoralBlock.java.patch (58%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CoralFanBlock.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CoralPlantBlock.java.patch (55%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CoralWallFanBlock.java.patch (53%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/CraftingTableBlock.java.patch (62%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/DirtPathBlock.java.patch (61%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/DropExperienceBlock.java.patch (71%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/EyeblossomBlock.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/FlowerPotBlock.java.patch (91%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/FurnaceBlock.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/HoneyBlock.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/HopperBlock.java.patch (61%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/HugeMushroomBlock.java.patch (66%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/LavaCauldronBlock.java.patch (74%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/LightBlock.java.patch (82%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch (56%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/NyliumBlock.java.patch (52%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/PumpkinBlock.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/RailState.java.patch (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/RootedDirtBlock.java.patch (60%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BambooStalkBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BaseRailBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BeehiveBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BellBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BigDripleafBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/BushBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ButtonBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CactusBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CampfireBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CauldronBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVines.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ChestBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CocoaBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CommandBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ComparatorBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ComposterBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CrafterBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/CropBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DetectorRailBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DiodeBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DispenserBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DoorBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DoublePlantBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DragonEggBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/DropperBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/EndGatewayBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/EndPortalBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/EnderChestBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FarmBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FenceGateBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FireBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FrogspawnBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FrostedIceBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/FungusBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/GrindstoneBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/IceBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/InfestedBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LeavesBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LecternBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LeverBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LightningRodBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LiquidBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/LoomBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/MagmaBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/MultifaceSpreader.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/MushroomBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/NetherPortalBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/NetherWartBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/NoteBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ObserverBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/PitcherCropBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/PowderSnowBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/PoweredRailBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/PressurePlateBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
new file mode 100644
index 0000000000..978c35863e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/AbstractCandleBlock.java
++++ b/net/minecraft/world/level/block/AbstractCandleBlock.java
+@@ -44,6 +_,11 @@
+     @Override
+     protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+         if (!level.isClientSide && projectile.isOnFire() && this.canBeLit(state)) {
++           // CraftBukkit start
++           if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, hit.getBlockPos(), projectile).isCancelled()) {
++               return;
++           }
++           // CraftBukkit end
+             setLit(level, state, hit.getBlockPos(), true);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
new file mode 100644
index 0000000000..506f6551ef
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/AbstractCauldronBlock.java
++++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java
+@@ -58,7 +_,7 @@
+         ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult
+     ) {
+         CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem());
+-        return cauldronInteraction.interact(state, level, pos, player, hand, stack);
++        return cauldronInteraction.interact(state, level, pos, player, hand, stack, hitResult.getDirection()); // Paper - pass hit direction
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/AnvilBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/AnvilBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch
index 81462ca698..8701c8ba8c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/AnvilBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/AnvilBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/AnvilBlock.java
 +++ b/net/minecraft/world/level/block/AnvilBlock.java
-@@ -62,8 +62,9 @@
+@@ -62,8 +_,9 @@
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_ANVIL);
 +            } // Paper - Fix InventoryOpenEvent cancellation
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
new file mode 100644
index 0000000000..df4b06991a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/level/block/BambooSaplingBlock.java
++++ b/net/minecraft/world/level/block/BambooSaplingBlock.java
+@@ -43,7 +_,7 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (random.nextInt(3) == 0 && level.isEmptyBlock(pos.above()) && level.getRawBrightness(pos.above(), 0) >= 9) {
++        if (random.nextFloat() < (level.spigotConfig.bambooModifier / (100.0f * 3)) && level.isEmptyBlock(pos.above()) && level.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
+             this.growBamboo(level, pos);
+         }
+     }
+@@ -99,6 +_,6 @@
+     }
+ 
+     protected void growBamboo(Level level, BlockPos state) {
+-        level.setBlock(state.above(), Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, state, state.above(), Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); // CraftBukkit - BlockSpreadEvent
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
new file mode 100644
index 0000000000..8560405814
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/level/block/BambooStalkBlock.java
++++ b/net/minecraft/world/level/block/BambooStalkBlock.java
+@@ -130,9 +_,9 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (state.getValue(STAGE) == 0) {
+-            if (random.nextInt(3) == 0 && level.isEmptyBlock(pos.above()) && level.getRawBrightness(pos.above(), 0) >= 9) {
++            if (random.nextFloat() < (level.spigotConfig.bambooModifier / (100.0f * 3)) && level.isEmptyBlock(pos.above()) && level.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
+                 int i = this.getHeightBelowUpToMax(level, pos) + 1;
+-                if (i < 16) {
++                if (i < level.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height
+                     this.growBamboo(state, level, pos, random, i);
+                 }
+             }
+@@ -168,7 +_,7 @@
+     public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
+         int heightAboveUpToMax = this.getHeightAboveUpToMax(level, pos);
+         int heightBelowUpToMax = this.getHeightBelowUpToMax(level, pos);
+-        return heightAboveUpToMax + heightBelowUpToMax + 1 < 16 && level.getBlockState(pos.above(heightAboveUpToMax)).getValue(STAGE) != 1;
++        return heightAboveUpToMax + heightBelowUpToMax + 1 < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(heightAboveUpToMax)).getValue(STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height
+     }
+ 
+     @Override
+@@ -186,7 +_,7 @@
+         for (int i2 = 0; i2 < i1; i2++) {
+             BlockPos blockPos = pos.above(heightAboveUpToMax);
+             BlockState blockState = level.getBlockState(blockPos);
+-            if (i >= 16 || blockState.getValue(STAGE) == 1 || !level.isEmptyBlock(blockPos.above())) {
++            if (i >= level.paperConfig().maxGrowthHeight.bamboo.max || !blockState.is(Blocks.BAMBOO) || blockState.getValue(BambooStalkBlock.STAGE) == 1 || !level.isEmptyBlock(blockPos.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height
+                 return;
+             }
+ 
+@@ -206,29 +_,38 @@
+         BlockPos blockPos = pos.below(2);
+         BlockState blockState1 = level.getBlockState(blockPos);
+         BambooLeaves bambooLeaves = BambooLeaves.NONE;
++        boolean shouldUpdateOthers = false; // CraftBukkit
+         if (age >= 1) {
+             if (!blockState.is(Blocks.BAMBOO) || blockState.getValue(LEAVES) == BambooLeaves.NONE) {
+                 bambooLeaves = BambooLeaves.SMALL;
+             } else if (blockState.is(Blocks.BAMBOO) && blockState.getValue(LEAVES) != BambooLeaves.NONE) {
+                 bambooLeaves = BambooLeaves.LARGE;
+                 if (blockState1.is(Blocks.BAMBOO)) {
+-                    level.setBlock(pos.below(), blockState.setValue(LEAVES, BambooLeaves.SMALL), 3);
+-                    level.setBlock(blockPos, blockState1.setValue(LEAVES, BambooLeaves.NONE), 3);
++                    // CraftBukkit start - moved down
++                    // world.setBlock(blockposition.below(), (IBlockData) iblockdata1.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.SMALL), 3);
++                    // world.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.NONE), 3);
++                    shouldUpdateOthers = true;
++                    // CraftBukkit end
+                 }
+             }
+         }
+ 
+         int i = state.getValue(AGE) != 1 && !blockState1.is(Blocks.BAMBOO) ? 0 : 1;
+-        int i1 = (age < 11 || !(random.nextFloat() < 0.25F)) && age != 15 ? 0 : 1;
+-        level.setBlock(
+-            pos.above(), this.defaultBlockState().setValue(AGE, Integer.valueOf(i)).setValue(LEAVES, bambooLeaves).setValue(STAGE, Integer.valueOf(i1)), 3
+-        );
++        int i1 = (age < level.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && age != (level.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height
++        // CraftBukkit start
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.above(), this.defaultBlockState().setValue(net.minecraft.world.level.block.BambooStalkBlock.AGE, i).setValue(BambooStalkBlock.LEAVES, bambooLeaves).setValue(net.minecraft.world.level.block.BambooStalkBlock.STAGE, i1), 3)) {
++            if (shouldUpdateOthers) {
++                level.setBlock(pos.below(), blockState.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
++                level.setBlock(blockPos, blockState1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3);
++            }
++        }
++        // CraftBukkit end
+     }
+ 
+     protected int getHeightAboveUpToMax(BlockGetter level, BlockPos pos) {
+         int i = 0;
+ 
+-        while (i < 16 && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) {
++        while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height
+             i++;
+         }
+ 
+@@ -238,7 +_,7 @@
+     protected int getHeightBelowUpToMax(BlockGetter level, BlockPos pos) {
+         int i = 0;
+ 
+-        while (i < 16 && level.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO)) {
++        while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height
+             i++;
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BarrelBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch
similarity index 73%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BarrelBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch
index 349f801388..23b0b9718c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BarrelBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BarrelBlock.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/block/BarrelBlock.java
 +++ b/net/minecraft/world/level/block/BarrelBlock.java
-@@ -41,8 +41,7 @@
+@@ -41,8 +_,7 @@
  
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
--        if (world instanceof ServerLevel serverLevel && world.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity) {
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (level instanceof ServerLevel serverLevel && level.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity) {
 -            player.openMenu(barrelBlockEntity);
-+        if (world instanceof ServerLevel serverLevel && world.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity && player.openMenu(barrelBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (level instanceof ServerLevel serverLevel && level.getBlockEntity(pos) instanceof BarrelBlockEntity barrelBlockEntity && player.openMenu(barrelBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.OPEN_BARREL);
              PiglinAi.angerNearbyPiglins(serverLevel, player, true);
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BaseFireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BaseFireBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
index aeae088ca7..aa29a091f9 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BaseFireBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/BaseFireBlock.java
 +++ b/net/minecraft/world/level/block/BaseFireBlock.java
-@@ -12,6 +12,7 @@
+@@ -12,6 +_,7 @@
  import net.minecraft.world.entity.Entity;
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.item.context.BlockPlaceContext;
@@ -8,22 +8,23 @@
  import net.minecraft.world.level.BlockGetter;
  import net.minecraft.world.level.Level;
  import net.minecraft.world.level.block.state.BlockBehaviour;
-@@ -127,6 +128,7 @@
+@@ -128,6 +_,8 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
          if (!entity.fireImmune()) {
              if (entity.getRemainingFireTicks() < 0) {
                  entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1);
-@@ -137,7 +139,18 @@
+@@ -137,7 +_,18 @@
              }
  
              if (entity.getRemainingFireTicks() >= 0) {
 -                entity.igniteForSeconds(8.0F);
 +                // CraftBukkit start
-+                org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), entity.getBukkitEntity(), 8.0F);
-+                world.getCraftServer().getPluginManager().callEvent(event);
++                org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), entity.getBukkitEntity(), 8.0F);
++                level.getCraftServer().getPluginManager().callEvent(event);
 +
 +                if (!event.isCancelled()) {
 +                    entity.igniteForSeconds(event.getDuration(), false);
@@ -36,41 +37,36 @@
              }
          }
  
-@@ -146,26 +159,26 @@
+@@ -146,24 +_,24 @@
      }
  
      @Override
--    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
--        if (!oldState.is(state.getBlock())) {
-+    protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) { // CraftBukkit - context
-+        if (!iblockdata1.is(iblockdata.getBlock())) {
-             if (BaseFireBlock.inPortalDimension(world)) {
--                Optional<PortalShape> optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X);
-+                Optional<PortalShape> optional = PortalShape.findEmptyPortalShape(world, blockposition, Direction.Axis.X);
- 
+-    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
++    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving, UseOnContext context) { // CraftBukkit - context
+         if (!oldState.is(state.getBlock())) {
+             if (inPortalDimension(level)) {
+                 Optional<PortalShape> optional = PortalShape.findEmptyPortalShape(level, pos, Direction.Axis.X);
                  if (optional.isPresent()) {
--                    ((PortalShape) optional.get()).createPortalBlocks(world);
-+                    ((PortalShape) optional.get()).createPortalBlocks(world, (context == null) ? null : context.getPlayer()); // CraftBukkit - player
+-                    optional.get().createPortalBlocks(level);
++                    optional.get().createPortalBlocks(level, (context == null) ? null : context.getPlayer()); // CraftBukkit - player
                      return;
                  }
              }
  
--            if (!state.canSurvive(world, pos)) {
--                world.removeBlock(pos, false);
-+            if (!iblockdata.canSurvive(world, blockposition)) {
-+                this.fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke
+             if (!state.canSurvive(level, pos)) {
+-                level.removeBlock(pos, false);
++                this.fireExtinguished(level, pos); // CraftBukkit - fuel block broke
              }
- 
          }
      }
  
-     private static boolean inPortalDimension(Level world) {
--        return world.dimension() == Level.OVERWORLD || world.dimension() == Level.NETHER;
-+        return world.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD || world.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit - getTypeKey()
+     private static boolean inPortalDimension(Level level) {
+-        return level.dimension() == Level.OVERWORLD || level.dimension() == Level.NETHER;
++        return level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD || level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit - getTypeKey()
      }
  
      @Override
-@@ -213,4 +226,12 @@
+@@ -208,4 +_,12 @@
              }
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
new file mode 100644
index 0000000000..2cada56fbe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
++++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
+@@ -81,6 +_,7 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide) {
+             int signalForState = this.getSignalForState(state);
+             if (signalForState == 0) {
+@@ -93,6 +_,19 @@
+         int signalStrength = this.getSignalStrength(level, pos);
+         boolean flag = currentSignal > 0;
+         boolean flag1 = signalStrength > 0;
++        
++        // CraftBukkit start - Interact Pressure Plate
++        org.bukkit.World bworld = level.getWorld();
++        org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++
++        if (flag != flag1) {
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), currentSignal, signalStrength);
++            manager.callEvent(eventRedstone);
++
++            flag1 = eventRedstone.getNewCurrent() > 0;
++            signalStrength = eventRedstone.getNewCurrent();
++        }
++        // CraftBukkit end
+         if (currentSignal != signalStrength) {
+             BlockState blockState = this.setSignalForState(state, signalStrength);
+             level.setBlock(pos, blockState, 2);
+@@ -145,7 +_,13 @@
+     }
+ 
+     protected static int getEntityCount(Level level, AABB box, Class<? extends Entity> entityClass) {
+-        return level.getEntitiesOfClass(entityClass, box, EntitySelector.NO_SPECTATORS.and(entity -> !entity.isIgnoringBlockTriggers())).size();
++        // CraftBukkit start
++        return BasePressurePlateBlock.getEntities(level, box, entityClass).size();
++    }
++
++    protected static <T extends Entity> java.util.List<T> getEntities(Level level, AABB box, Class<T> entityClass) {
++        return level.getEntitiesOfClass(entityClass, box, EntitySelector.NO_SPECTATORS.and(entity -> !entity.isIgnoringBlockTriggers()));
++        // CraftBukkit end
+     }
+ 
+     protected abstract int getSignalStrength(Level level, BlockPos pos);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch
new file mode 100644
index 0000000000..d8e5440b15
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BaseRailBlock.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/block/BaseRailBlock.java
++++ b/net/minecraft/world/level/block/BaseRailBlock.java
+@@ -71,6 +_,7 @@
+         state = this.updateDir(level, pos, state, true);
+         if (this.isStraight) {
+             level.neighborChanged(state, pos, this, null, movedByPiston);
++            state = level.getBlockState(pos); // Paper - Fix some rails connecting improperly
+         }
+ 
+         return state;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BeaconBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch
similarity index 69%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BeaconBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch
index b23b55da23..3a2da5d64c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BeaconBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BeaconBlock.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/block/BeaconBlock.java
 +++ b/net/minecraft/world/level/block/BeaconBlock.java
-@@ -46,8 +46,7 @@
+@@ -46,8 +_,7 @@
  
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
--        if (!world.isClientSide && world.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity) {
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide && level.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity) {
 -            player.openMenu(beaconBlockEntity);
-+        if (!world.isClientSide && world.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity && player.openMenu(beaconBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (!level.isClientSide && level.getBlockEntity(pos) instanceof BeaconBlockEntity beaconBlockEntity && player.openMenu(beaconBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_BEACON);
          }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BedBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
index 09946b2d9f..dd115140b2 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BedBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
@@ -1,21 +1,20 @@
 --- a/net/minecraft/world/level/block/BedBlock.java
 +++ b/net/minecraft/world/level/block/BedBlock.java
-@@ -95,7 +95,8 @@
+@@ -92,7 +_,7 @@
                  }
              }
  
--            if (!BedBlock.canSetSpawn(world)) {
-+            // CraftBukkit - moved world and biome check into EntityHuman
-+            if (false && !BedBlock.canSetSpawn(world)) {
-                 world.removeBlock(pos, false);
-                 BlockPos blockposition1 = pos.relative(((Direction) state.getValue(BedBlock.FACING)).getOpposite());
- 
-@@ -108,25 +109,65 @@
-                 world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK);
+-            if (!canSetSpawn(level)) {
++            if (false && !canSetSpawn(level)) { // CraftBukkit - moved world and biome check into EntityHuman
+                 level.removeBlock(pos, false);
+                 BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
+                 if (level.getBlockState(blockPos).is(this)) {
+@@ -103,22 +_,63 @@
+                 level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK);
                  return InteractionResult.SUCCESS_SERVER;
-             } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) {
-+                if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first
-                 if (!this.kickVillagerOutOfBed(world, pos)) {
+             } else if (state.getValue(OCCUPIED)) {
++                if (!BedBlock.canSetSpawn(level)) return this.explodeBed(state, level, pos); // Paper - check explode first
+                 if (!this.kickVillagerOutOfBed(level, pos)) {
                      player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true);
                  }
  
@@ -25,10 +24,10 @@
 +                BlockState finaliblockdata = state;
 +                BlockPos finalblockposition = pos;
 +                // CraftBukkit end
-                 player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> {
+                 player.startSleepInBed(pos).ifLeft(bedSleepingProblem -> {
 +                    // Paper start - PlayerBedFailEnterEvent
-+                    if (entityhuman_enumbedresult != null) {
-+                        io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage()));
++                    if (bedSleepingProblem != null) {
++                        io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[bedSleepingProblem.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(level, finalblockposition), !level.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(bedSleepingProblem.getMessage()));
 +                        if (!event.callEvent()) {
 +                            return;
 +                        }
@@ -38,19 +37,18 @@
 +                        this.explodeBed(finaliblockdata, world, finalblockposition);
 +                    } else
 +                    // CraftBukkit end
-                     if (entityhuman_enumbedresult.getMessage() != null) {
--                        player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true);
+                     if (bedSleepingProblem.getMessage() != null) {
+                         player.displayClientMessage(bedSleepingProblem.getMessage(), true);
 +                        final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent
 +                        if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent
                      }
 +                    } // Paper - PlayerBedFailEnterEvent
- 
                  });
                  return InteractionResult.SUCCESS_SERVER;
-+            }
-+        }
-+    }
-+
+             }
+         }
+     }
+ 
 +    // CraftBukkit start
 +    private InteractionResult explodeBed(BlockState iblockdata, Level world, BlockPos blockposition) {
 +        {
@@ -65,28 +63,25 @@
 +
 +                Vec3 vec3d = blockposition.getCenter();
 +
-+                world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state
++                world.explode(null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state
 +                return InteractionResult.SUCCESS;
-             }
-         }
-     }
++             }
++         }
++     }
 +    // CraftBukkit end
- 
-     public static boolean canSetSpawn(Level world) {
--        return world.dimensionType().bedWorks();
-+        return world.dimensionType().bedWorks(); // Paper - actually check if the bed works
++
+     public static boolean canSetSpawn(Level level) {
+         return level.dimensionType().bedWorks();
      }
- 
-     private boolean kickVillagerOutOfBed(Level world, BlockPos pos) {
-@@ -320,6 +361,11 @@
-             BlockPos blockposition1 = pos.relative((Direction) state.getValue(BedBlock.FACING));
- 
-             world.setBlock(blockposition1, (BlockState) state.setValue(BedBlock.PART, BedPart.HEAD), 3);
+@@ -311,6 +_,11 @@
+         if (!level.isClientSide) {
+             BlockPos blockPos = pos.relative(state.getValue(FACING));
+             level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), 3);
 +            // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states
-+            if (world.captureBlockStates) {
++            if (level.captureBlockStates) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.blockUpdated(pos, Blocks.AIR);
-             state.updateNeighbourShapes(world, pos, 3);
+             level.blockUpdated(pos, Blocks.AIR);
+             state.updateNeighbourShapes(level, pos, 3);
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch
new file mode 100644
index 0000000000..221e36c665
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BeehiveBlock.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/level/block/BeehiveBlock.java
++++ b/net/minecraft/world/level/block/BeehiveBlock.java
+@@ -91,8 +_,8 @@
+     }
+ 
+     @Override
+-    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
+-        super.playerDestroy(level, player, pos, state, te, stack);
++    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
++        super.playerDestroy(level, player, pos, state, te, stack, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
+         if (!level.isClientSide && te instanceof BeehiveBlockEntity beehiveBlockEntity) {
+             if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_BEE_SPAWNS_WHEN_MINING)) {
+                 beehiveBlockEntity.emptyAllLivingFromHive(player, state, BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY);
+@@ -100,7 +_,7 @@
+                 this.angerNearbyBees(level, pos);
+             }
+ 
+-            CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer)player, state, stack, beehiveBlockEntity.getOccupantCount());
++            // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer)player, state, stack, beehiveBlockEntity.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped
+         }
+     }
+ 
+@@ -122,14 +_,14 @@
+             for (Bee bee : entitiesOfClass) {
+                 if (bee.getTarget() == null) {
+                     Player player = Util.getRandom(entitiesOfClass1, level.random);
+-                    bee.setTarget(player);
++                    bee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+                 }
+             }
+         }
+     }
+ 
+     public static void dropHoneycomb(Level level, BlockPos pos) {
+-        popResource(level, pos, new ItemStack(Items.HONEYCOMB, 3));
++        popResource(level, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - Add PlayerShearBlockEvent; conflict on change, item needs to be set below
+     }
+ 
+     @Override
+@@ -141,8 +_,19 @@
+         if (honeyLevelValue >= 5) {
+             Item item = stack.getItem();
+             if (stack.is(Items.SHEARS)) {
++                // Paper start - Add PlayerShearBlockEvent
++                io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>());
++                event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3)));
++                if (!event.callEvent()) {
++                    return InteractionResult.PASS;
++                }
++                // Paper end
+                 level.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.BLOCKS, 1.0F, 1.0F);
+-                dropHoneycomb(level, pos);
++                // Paper start - Add PlayerShearBlockEvent
++                for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) {
++                    popResource(level, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop));
++                }
++                // Paper end - Add PlayerShearBlockEvent
+                 stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
+                 flag = true;
+                 level.gameEvent(player, GameEvent.SHEAR, pos);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch
new file mode 100644
index 0000000000..67deb9015b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BellBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/BellBlock.java
++++ b/net/minecraft/world/level/block/BellBlock.java
+@@ -142,6 +_,11 @@
+                 direction = level.getBlockState(pos).getValue(FACING);
+             }
+ 
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBellRingEvent(level, pos, direction, entity)) {
++                return false;
++            }
++            // CraftBukkit end
+             ((BellBlockEntity)blockEntity).onHit(direction);
+             level.playSound(null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F);
+             level.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
new file mode 100644
index 0000000000..8f4e70e47f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
@@ -0,0 +1,88 @@
+--- a/net/minecraft/world/level/block/BigDripleafBlock.java
++++ b/net/minecraft/world/level/block/BigDripleafBlock.java
+@@ -136,7 +_,7 @@
+ 
+     @Override
+     protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+-        this.setTiltAndScheduleTick(state, level, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++        this.setTiltAndScheduleTick(state, level, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, projectile); // CraftBukkit
+     }
+ 
+     @Override
+@@ -199,9 +_,23 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide) {
+             if (state.getValue(TILT) == Tilt.NONE && canEntityTilt(pos, entity) && !level.hasNeighborSignal(pos)) {
+-                this.setTiltAndScheduleTick(state, level, pos, Tilt.UNSTABLE, null);
++                // CraftBukkit start - tilt dripleaf
++                org.bukkit.event.Cancellable cancellable;
++                if (entity instanceof net.minecraft.world.entity.player.Player) {
++                    cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((net.minecraft.world.entity.player.Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++                } else {
++                    cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                    level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++                }
++
++                if (cancellable.isCancelled()) {
++                    return;
++                }
++                this.setTiltAndScheduleTick(state, level, pos, Tilt.UNSTABLE, null, entity);
++                // CraftBukkit end
+             }
+         }
+     }
+@@ -213,9 +_,9 @@
+         } else {
+             Tilt tilt = state.getValue(TILT);
+             if (tilt == Tilt.UNSTABLE) {
+-                this.setTiltAndScheduleTick(state, level, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++                this.setTiltAndScheduleTick(state, level, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
+             } else if (tilt == Tilt.PARTIAL) {
+-                this.setTiltAndScheduleTick(state, level, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++                this.setTiltAndScheduleTick(state, level, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
+             } else if (tilt == Tilt.FULL) {
+                 resetTilt(state, level, pos);
+             }
+@@ -238,8 +_,9 @@
+         return entity.onGround() && entity.position().y > pos.getY() + 0.6875F;
+     }
+ 
+-    private void setTiltAndScheduleTick(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound) {
+-        setTilt(state, level, pos, tilt);
++    private void setTiltAndScheduleTick(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound, @Nullable Entity entity) {
++        if (!BigDripleafBlock.setTilt(state, level, pos, tilt, entity)) return;
++        // CraftBukkit end
+         if (sound != null) {
+             playTiltSound(level, pos, sound);
+         }
+@@ -251,18 +_,25 @@
+     }
+ 
+     private static void resetTilt(BlockState state, Level level, BlockPos pos) {
+-        setTilt(state, level, pos, Tilt.NONE);
++        setTilt(state, level, pos, Tilt.NONE, null); // CraftBukkit
+         if (state.getValue(TILT) != Tilt.NONE) {
+             playTiltSound(level, pos, SoundEvents.BIG_DRIPLEAF_TILT_UP);
+         }
+     }
+ 
+-    private static void setTilt(BlockState state, Level level, BlockPos pos, Tilt tilt) {
++    private static boolean setTilt(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable Entity entity) {  // CraftBukkit
++        if (entity != null) {
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.setValue(BigDripleafBlock.TILT, tilt))) {
++                return false;
++            }
++        }
++        // CraftBukkit end
+         Tilt tilt1 = state.getValue(TILT);
+         level.setBlock(pos, state.setValue(TILT, tilt), 2);
+         if (tilt.causesVibration() && tilt != tilt1) {
+             level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos);
+         }
++        return true; // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch
similarity index 78%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch
index 3317c55a37..11ef775f5e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BlastFurnaceBlock.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/level/block/BlastFurnaceBlock.java
 +++ b/net/minecraft/world/level/block/BlastFurnaceBlock.java
-@@ -45,8 +45,7 @@
+@@ -45,8 +_,7 @@
      @Override
-     protected void openContainer(Level world, BlockPos pos, Player player) {
-         BlockEntity blockEntity = world.getBlockEntity(pos);
+     protected void openContainer(Level level, BlockPos pos, Player player) {
+         BlockEntity blockEntity = level.getBlockEntity(pos);
 -        if (blockEntity instanceof BlastFurnaceBlockEntity) {
 -            player.openMenu((MenuProvider)blockEntity);
 +        if (blockEntity instanceof BlastFurnaceBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
similarity index 57%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/Block.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
index c1060afc42..06e6874b95 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/Block.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/Block.java
 +++ b/net/minecraft/world/level/block/Block.java
-@@ -88,6 +88,21 @@
+@@ -90,6 +_,21 @@
      public static final int UPDATE_LIMIT = 512;
      protected final StateDefinition<Block, BlockState> stateDefinition;
      private BlockState defaultBlockState;
@@ -22,8 +22,8 @@
      @Nullable
      private Item item;
      private static final int CACHE_SIZE = 256;
-@@ -295,12 +310,38 @@
- 
+@@ -272,6 +_,27 @@
+         return state.getDrops(builder);
      }
  
 +    // Paper start - Add BlockBreakBlockEvent
@@ -47,91 +47,92 @@
 +    }
 +    // Paper end - Add BlockBreakBlockEvent
 +
-     public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
+     public static void dropResources(BlockState state, Level level, BlockPos pos) {
+         if (level instanceof ServerLevel) {
+             getDrops(state, (ServerLevel)level, pos, null).forEach(itemStack -> popResource(level, pos, itemStack));
+@@ -287,9 +_,14 @@
+     }
+ 
+     public static void dropResources(BlockState state, Level level, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
 +    // Paper start - Properly handle xp dropping
-+        dropResources(state, world, pos, blockEntity, entity, tool, true);
++        dropResources(state, level, pos, blockEntity, entity, tool, true);
 +    }
-+    public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
++    public static void dropResources(BlockState state, Level level, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
 +    // Paper end - Properly handle xp dropping
-         if (world instanceof ServerLevel) {
-             Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> {
-                 Block.popResource(world, pos, itemstack1);
-             });
--            state.spawnAfterBreak((ServerLevel) world, pos, tool, true);
-+            state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping
+         if (level instanceof ServerLevel) {
+             getDrops(state, (ServerLevel)level, pos, blockEntity, entity, tool).forEach(itemStack -> popResource(level, pos, itemStack));
+-            state.spawnAfterBreak((ServerLevel)level, pos, tool, true);
++            state.spawnAfterBreak((ServerLevel) level, pos, tool, dropExperience); // Paper - Properly handle xp dropping
          }
- 
-     }
-@@ -340,7 +381,13 @@
-                 ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get();
- 
-                 entityitem.setDefaultPickUpDelay();
--                world.addFreshEntity(entityitem);
-+                // CraftBukkit start
-+                if (world.captureDrops != null) {
-+                    world.captureDrops.add(entityitem);
-+                } else {
-+                    world.addFreshEntity(entityitem);
-+                }
-+                // CraftBukkit end
-                 return;
-             }
-         }
-@@ -348,8 +395,13 @@
      }
  
-     public void popExperience(ServerLevel world, BlockPos pos, int size) {
+@@ -320,13 +_,24 @@
+         if (level instanceof ServerLevel serverLevel && !stack.isEmpty() && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
+             ItemEntity itemEntity = itemEntitySupplier.get();
+             itemEntity.setDefaultPickUpDelay();
+-            level.addFreshEntity(itemEntity);
++            // CraftBukkit start
++            if (level.captureDrops != null) {
++                level.captureDrops.add(itemEntity);
++            } else {
++                level.addFreshEntity(itemEntity);
++            }
++            // CraftBukkit end
+         }
+     }
+ 
+     public void popExperience(ServerLevel level, BlockPos pos, int amount) {
 +        // Paper start - add entity parameter
-+        popExperience(world, pos, size, null);
++        popExperience(level, pos, amount, null);
 +    }
-+    public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) {
-+        // Paper end - add entity parameter
-         if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
--            ExperienceOrb.award(world, Vec3.atCenterOf(pos), size);
-+            ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper
++    public void popExperience(ServerLevel level, BlockPos pos, int amount, net.minecraft.world.entity.Entity entity) {
++        // Paper end - add entity paramete
+         if (level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
+-            ExperienceOrb.award(level, Vec3.atCenterOf(pos), amount);
++            ExperienceOrb.award(level, Vec3.atCenterOf(pos), amount, entity); // Paper
          }
- 
      }
-@@ -367,10 +419,18 @@
+ 
+@@ -345,10 +_,18 @@
          return this.defaultBlockState();
      }
  
 +    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix drops not preventing stats/food exhaustion
-     public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
-+        // Paper start - fix drops not preventing stats/food exhaustion
-+        this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true);
+     public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
++    // Paper start - fix drops not preventing stats/food exhaustion
++        this.playerDestroy(level, player, pos, state, blockEntity, tool, true, true);
 +    }
-+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) {
-+        // Paper end - fix drops not preventing stats/food exhaustion
++    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) {
++    // Paper end - fix drops not preventing stats/food exhaustion
          player.awardStat(Stats.BLOCK_MINED.get(this));
 -        player.causeFoodExhaustion(0.005F);
--        Block.dropResources(state, world, pos, blockEntity, player, tool);
+-        dropResources(state, level, pos, blockEntity, player, tool);
 +        player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
 +        if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion
-+        Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping
++        Block.dropResources(state, level, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping
 +        } // Paper - fix drops not preventing stats/food exhaustion
      }
  
-     public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {}
-@@ -490,15 +550,35 @@
+     public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
+@@ -469,12 +_,33 @@
          return this.builtInRegistryHolder;
      }
  
--    protected void tryDropExperience(ServerLevel world, BlockPos pos, ItemStack tool, IntProvider experience) {
--        int i = EnchantmentHelper.processBlockExperience(world, tool, experience.sample(world.getRandom()));
-+    // CraftBukkit start
-+    protected int tryDropExperience(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, IntProvider intprovider) {
-+        int i = EnchantmentHelper.processBlockExperience(worldserver, itemstack, intprovider.sample(worldserver.getRandom()));
- 
+-    protected void tryDropExperience(ServerLevel level, BlockPos pos, ItemStack heldItem, IntProvider amount) {
++    protected int tryDropExperience(ServerLevel level, BlockPos pos, ItemStack heldItem, IntProvider amount) { // CraftBukkit
+         int i = EnchantmentHelper.processBlockExperience(level, heldItem, amount.sample(level.getRandom()));
          if (i > 0) {
--            this.popExperience(world, pos, i);
-+            // this.popExperience(worldserver, blockposition, i);
+-            this.popExperience(level, pos, i);
+-        }
+-    }
++            // CraftBukkit start
++            //this.popExperience(level, pos, i);
 +            return i;
-         }
- 
-+        return 0;
-     }
- 
++            // Craftbukkit end
++        }
++        return 0; // CraftBukkit
++    }
++    // CraftBukkit start
 +    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
 +        return 0;
 +    }
@@ -148,7 +149,6 @@
 +        return value;
 +    }
 +    // Spigot end
-+
-     private static record ShapePairKey(VoxelShape first, VoxelShape second) {
  
-         public boolean equals(Object object) {
+     record ShapePairKey(VoxelShape first, VoxelShape second) {
+         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BrewingStandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch
similarity index 72%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BrewingStandBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch
index ad19f46261..e2bbf75753 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BrewingStandBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BrewingStandBlock.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/block/BrewingStandBlock.java
 +++ b/net/minecraft/world/level/block/BrewingStandBlock.java
-@@ -68,8 +68,7 @@
+@@ -68,8 +_,7 @@
  
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
--        if (!world.isClientSide && world.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity) {
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide && level.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity) {
 -            player.openMenu(brewingStandBlockEntity);
-+        if (!world.isClientSide && world.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity && player.openMenu(brewingStandBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (!level.isClientSide && level.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStandBlockEntity && player.openMenu(brewingStandBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_BREWINGSTAND);
          }
  
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BubbleColumnBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch
similarity index 66%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BubbleColumnBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch
index aa97c1238d..1ca54afa47 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BubbleColumnBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BubbleColumnBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/BubbleColumnBlock.java
 +++ b/net/minecraft/world/level/block/BubbleColumnBlock.java
-@@ -48,6 +48,7 @@
+@@ -48,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         BlockState blockState = world.getBlockState(pos.above());
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         BlockState blockState = level.getBlockState(pos.above());
          if (blockState.isAir()) {
              entity.onAboveBubbleCol(state.getValue(DRAG_DOWN));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
index ee6f708bf9..e6ad3e837b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
@@ -1,17 +1,17 @@
 --- a/net/minecraft/world/level/block/BuddingAmethystBlock.java
 +++ b/net/minecraft/world/level/block/BuddingAmethystBlock.java
-@@ -45,7 +45,13 @@
-             if (block != null) {
-                 BlockState iblockdata2 = (BlockState) ((BlockState) block.defaultBlockState().setValue(AmethystClusterBlock.FACING, enumdirection)).setValue(AmethystClusterBlock.WATERLOGGED, iblockdata1.getFluidState().getType() == Fluids.WATER);
- 
--                world.setBlockAndUpdate(blockposition1, iblockdata2);
+@@ -44,7 +_,13 @@
+                 BlockState blockState1 = block.defaultBlockState()
+                     .setValue(AmethystClusterBlock.FACING, direction)
+                     .setValue(AmethystClusterBlock.WATERLOGGED, Boolean.valueOf(blockState.getFluidState().getType() == Fluids.WATER));
+-                level.setBlockAndUpdate(blockPos, blockState1);
 +                // Paper start - Have Amethyst throw both spread and grow events
 +                if (block == Blocks.SMALL_AMETHYST_BUD) {
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, iblockdata2); // CraftBukkit
++                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState1); // CraftBukkit
 +                } else {
-+                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, blockposition1, iblockdata2);
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, blockState1);
 +                }
 +                // Paper end - Have Amethyst throw both spread and grow events
              }
- 
          }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch
new file mode 100644
index 0000000000..709e307ca3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BushBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/BushBlock.java
++++ b/net/minecraft/world/level/block/BushBlock.java
+@@ -6,6 +_,7 @@
+ import net.minecraft.tags.BlockTags;
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.world.level.BlockGetter;
++import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.LevelReader;
+ import net.minecraft.world.level.ScheduledTickAccess;
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+@@ -35,9 +_,15 @@
+         BlockState neighborState,
+         RandomSource random
+     ) {
+-        return !state.canSurvive(level, pos)
+-            ? Blocks.AIR.defaultBlockState()
+-            : super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
++       // CraftBukkit start
++       if (!state.canSurvive(level, pos)) {
++           // Suppress during worldgen
++           if (!(level instanceof net.minecraft.server.level.ServerLevel world1 && world1.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world1, pos).isCancelled()) { // Paper
++               return Blocks.AIR.defaultBlockState();
++           }
++       }
++       return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
++       // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch
new file mode 100644
index 0000000000..956c25b452
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ButtonBlock.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/ButtonBlock.java
++++ b/net/minecraft/world/level/block/ButtonBlock.java
+@@ -114,6 +_,19 @@
+         if (state.getValue(POWERED)) {
+             return InteractionResult.CONSUME;
+         } else {
++            // CraftBukkit start
++            boolean powered = state.getValue(ButtonBlock.POWERED);
++            org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            int old = (powered) ? 15 : 0;
++            int current = (!powered) ? 15 : 0;
++
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(block, old, current);
++            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++            if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++                return InteractionResult.SUCCESS;
++            }
++            // CraftBukkit end
+             this.press(state, level, pos, player);
+             return InteractionResult.SUCCESS;
+         }
+@@ -179,6 +_,7 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide && this.type.canButtonBeActivatedByArrows() && !state.getValue(POWERED)) {
+             this.checkPressed(state, level, pos);
+         }
+@@ -190,7 +_,31 @@
+             : null;
+         boolean flag = abstractArrow != null;
+         boolean poweredValue = state.getValue(POWERED);
++        // CraftBukkit start - Call interact event when arrows turn on wooden buttons
++        if (poweredValue != flag && flag) {
++            org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(abstractArrow.getBukkitEntity(), block);
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            if (event.isCancelled()) {
++                return;
++            }
++        }
++        // CraftBukkit end
+         if (flag != poweredValue) {
++            // CraftBukkit start
++            boolean powered = poweredValue;
++            org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            int old = (powered) ? 15 : 0;
++            int current = (!powered) ? 15 : 0;
++
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(block, old, current);
++            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++            if ((flag && eventRedstone.getNewCurrent() <= 0) || (!flag && eventRedstone.getNewCurrent() > 0)) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(flag)), 3);
+             this.updateNeighbours(state, level, pos);
+             this.playSound(null, level, pos, flag);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
new file mode 100644
index 0000000000..acde2470f8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/CactusBlock.java
++++ b/net/minecraft/world/level/block/CactusBlock.java
+@@ -113,7 +_,8 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+-        entity.hurt(level.damageSources().cactus(), 1.0F);
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
++        entity.hurt(level.damageSources().cactus().directBlock(level, pos), 1.0F); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CakeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CakeBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch
index 346de8857c..d564317a09 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CakeBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CakeBlock.java.patch
@@ -1,25 +1,25 @@
 --- a/net/minecraft/world/level/block/CakeBlock.java
 +++ b/net/minecraft/world/level/block/CakeBlock.java
-@@ -66,6 +66,12 @@
-             if (block instanceof CandleBlock) {
-                 CandleBlock candleblock = (CandleBlock) block;
- 
-+                // Paper start - call change block event
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(candleblock))) {
-+                    player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease
-+                    return InteractionResult.TRY_WITH_EMPTY_HAND;
-+                }
-+                // Paper end - call change block event
-                 stack.consume(1, player);
-                 world.playSound((Player) null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F);
-                 world.setBlockAndUpdate(pos, CandleCakeBlock.byCandle(candleblock));
-@@ -97,10 +103,29 @@
+@@ -67,6 +_,12 @@
+     ) {
+         Item item = stack.getItem();
+         if (stack.is(ItemTags.CANDLES) && state.getValue(BITES) == 0 && Block.byItem(item) instanceof CandleBlock candleBlock) {
++            // Paper start - call change block event
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(candleBlock))) {
++                player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease
++                return InteractionResult.TRY_WITH_EMPTY_HAND;
++            }
++            // Paper end - call change block event
+             stack.consume(1, player);
+             level.playSound(null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0F, 1.0F);
+             level.setBlockAndUpdate(pos, CandleCakeBlock.byCandle(candleBlock));
+@@ -97,9 +_,28 @@
          if (!player.canEat(false)) {
              return InteractionResult.PASS;
          } else {
 +            // Paper start - call change block event
-+            int i = state.getValue(CakeBlock.BITES);
-+            final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock();
++            int bitesValue = state.getValue(CakeBlock.BITES);
++            final BlockState newState = bitesValue < MAX_BITES ? state.setValue(CakeBlock.BITES, bitesValue + 1) : level.getFluidState(pos).createLegacyBlock();
 +            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) {
 +                ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate();
 +                return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles
@@ -27,11 +27,11 @@
 +            // Paper end - call change block event
              player.awardStat(Stats.EAT_CAKE_SLICE);
 -            player.getFoodData().eat(2, 0.1F);
--            int i = (Integer) state.getValue(CakeBlock.BITES);
+-            int bitesValue = state.getValue(BITES);
 +            // CraftBukkit start
 +            // entityhuman.getFoodData().eat(2, 0.1F);
 +            int oldFoodLevel = player.getFoodData().foodLevel;
- 
++
 +            org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel);
 +
 +            if (!event.isCancelled()) {
@@ -41,7 +41,6 @@
 +            ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate();
 +            // CraftBukkit end
 +            // Paper - move up
-+
-             world.gameEvent((Entity) player, (Holder) GameEvent.EAT, pos);
-             if (i < 6) {
-                 world.setBlock(pos, (BlockState) state.setValue(CakeBlock.BITES, i + 1), 3);
+             level.gameEvent(player, GameEvent.EAT, pos);
+             if (bitesValue < 6) {
+                 level.setBlock(pos, state.setValue(BITES, Integer.valueOf(bitesValue + 1)), 3);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch
new file mode 100644
index 0000000000..0663b2c497
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CampfireBlock.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/CampfireBlock.java
++++ b/net/minecraft/world/level/block/CampfireBlock.java
+@@ -112,8 +_,9 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (state.getValue(LIT) && entity instanceof LivingEntity) {
+-            entity.hurt(level.damageSources().campfire(), this.fireDamage);
++            entity.hurt(level.damageSources().campfire().directBlock(level, pos), (float) this.fireDamage); // CraftBukkit
+         }
+ 
+         super.entityInside(state, level, pos, entity);
+@@ -242,6 +_,11 @@
+             && projectile.mayInteract(serverLevel, blockPos)
+             && !state.getValue(LIT)
+             && !state.getValue(WATERLOGGED)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, blockPos, projectile).isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(blockPos, state.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)), 11);
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CartographyTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CartographyTableBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch
index 237e4da1be..2aa0c9dd39 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CartographyTableBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CartographyTableBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/CartographyTableBlock.java
 +++ b/net/minecraft/world/level/block/CartographyTableBlock.java
-@@ -32,8 +32,9 @@
+@@ -32,8 +_,9 @@
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_CARTOGRAPHY_TABLE);
 +            } // Paper - Fix InventoryOpenEvent cancellation
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
new file mode 100644
index 0000000000..4343b42a31
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+@@ -79,9 +_,13 @@
+     }
+ 
+     private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) {
+-        clearPatternBlocks(level, patternMatch);
++        // clearPatternBlocks(level, patternMatch); // CraftBukkit - moved down
+         golem.moveTo(pos.getX() + 0.5, pos.getY() + 0.05, pos.getZ() + 0.5, 0.0F, 0.0F);
+-        level.addFreshEntity(golem);
++        if (!level.addFreshEntity(golem, (golem.getType() == EntityType.SNOW_GOLEM) ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_IRONGOLEM)) {
++            return;
++        }
++        clearPatternBlocks(level, patternMatch); // CraftBukkit - from above
++        // CraftBukkit end
+ 
+         for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, golem.getBoundingBox().inflate(5.0))) {
+             CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, golem);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
new file mode 100644
index 0000000000..e420669453
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/block/CauldronBlock.java
++++ b/net/minecraft/world/level/block/CauldronBlock.java
+@@ -40,9 +_,19 @@
+     public void handlePrecipitation(BlockState state, Level level, BlockPos pos, Biome.Precipitation precipitation) {
+         if (shouldHandlePrecipitation(level, precipitation)) {
+             if (precipitation == Biome.Precipitation.RAIN) {
++                // Paper start - Call CauldronLevelChangeEvent
++                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++                    return;
++                }
++                // Paper end - Call CauldronLevelChangeEvent
+                 level.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState());
+                 level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos);
+             } else if (precipitation == Biome.Precipitation.SNOW) {
++                // Paper start - Call CauldronLevelChangeEvent
++                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++                    return;
++                }
++                // Paper end - Call CauldronLevelChangeEvent
+                 level.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState());
+                 level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos);
+             }
+@@ -58,13 +_,19 @@
+     protected void receiveStalactiteDrip(BlockState state, Level level, BlockPos pos, Fluid fluid) {
+         if (fluid == Fluids.WATER) {
+             BlockState blockState = Blocks.WATER_CAULDRON.defaultBlockState();
+-            level.setBlockAndUpdate(pos, blockState);
+-            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
++            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++                return;
++            }
++            // Paper end - Call CauldronLevelChangeEvent
+             level.levelEvent(1047, pos, 0);
+         } else if (fluid == Fluids.LAVA) {
+             BlockState blockState = Blocks.LAVA_CAULDRON.defaultBlockState();
+-            level.setBlockAndUpdate(pos, blockState);
+-            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
++            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++                return;
++            }
++            // Paper end - Call CauldronLevelChangeEvent
+             level.levelEvent(1046, pos, 0);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch
new file mode 100644
index 0000000000..6355a6f4b2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVines.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/block/CaveVines.java
++++ b/net/minecraft/world/level/block/CaveVines.java
+@@ -23,7 +_,23 @@
+ 
+     static InteractionResult use(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
+         if (state.getValue(BERRIES)) {
+-            Block.popResource(level, pos, new ItemStack(Items.GLOW_BERRIES, 1));
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, (BlockState) state.setValue(CaveVines.BERRIES, false))) {
++                return InteractionResult.SUCCESS;
++            }
++
++            if (entity instanceof net.minecraft.world.entity.player.Player) {
++                org.bukkit.event.player.PlayerHarvestBlockEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerHarvestBlockEvent(level, pos, (net.minecraft.world.entity.player.Player) entity, net.minecraft.world.InteractionHand.MAIN_HAND, java.util.Collections.singletonList(new ItemStack(Items.GLOW_BERRIES, 1)));
++                if (event.isCancelled()) {
++                    return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
++                }
++                for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
++                    Block.popResource(level, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemStack));
++                }
++            } else {
++                Block.popResource(level, pos, new ItemStack(Items.GLOW_BERRIES, 1));
++            }
++            // CraftBukkit end
+             float f = Mth.randomBetween(level.random, 0.8F, 1.2F);
+             level.playSound(null, pos, SoundEvents.CAVE_VINES_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, f);
+             BlockState blockState = state.setValue(BERRIES, Boolean.valueOf(false));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVinesBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
similarity index 72%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVinesBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
index 511200af88..7f52226493 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVinesBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
@@ -1,22 +1,23 @@
 --- a/net/minecraft/world/level/block/CaveVinesBlock.java
 +++ b/net/minecraft/world/level/block/CaveVinesBlock.java
-@@ -50,9 +50,18 @@
-         return to.setValue(BERRIES, from.getValue(BERRIES));
+@@ -52,7 +_,7 @@
+ 
+     @Override
+     protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
+-        return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F));
++        return this.getGrowIntoState(state, random, null); // Paper - Fix Spigot growth modifiers
      }
  
-+    // Paper start - Fix Spigot growth modifiers
      @Override
+@@ -85,4 +_,11 @@
+     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+         level.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2);
+     }
++    // Paper start - Fix Spigot growth modifiers
++    @Override
 +    protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
 +        final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F);
 +        return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value);
 +    }
 +    // Paper end - Fix Spigot growth modifiers
-+
-+    @Override
-     protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
--        return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F));
-+        // Paper start - Fix Spigot growth modifiers
-+        return this.getGrowIntoState(state, random, null);
-     }
- 
-     @Override
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
index b85ba5b3dc..64f4e71297 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/CeilingHangingSignBlock.java
 +++ b/net/minecraft/world/level/block/CeilingHangingSignBlock.java
-@@ -159,6 +159,6 @@
+@@ -184,6 +_,6 @@
      @Nullable
      @Override
-     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockState state, BlockEntityType<T> type) {
--        return createTickerHelper(type, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
+     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
+-        return createTickerHelper(blockEntityType, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
 +        return null; // Craftbukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
new file mode 100644
index 0000000000..6601a243af
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
++++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
+@@ -16,7 +_,7 @@
+     default void changeOverTime(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         float f = 0.05688889F;
+         if (random.nextFloat() < 0.05688889F) {
+-            this.getNextState(state, level, pos, random).ifPresent(blockState -> level.setBlockAndUpdate(pos, blockState));
++            this.getNextState(state, level, pos, random).ifPresent(blockState -> org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, blockState)); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
new file mode 100644
index 0000000000..5200deab3e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/level/block/ChestBlock.java
++++ b/net/minecraft/world/level/block/ChestBlock.java
+@@ -120,6 +_,38 @@
+         }
+     };
+ 
++    // CraftBukkit start
++    public static class DoubleInventory implements MenuProvider {
++
++        private final ChestBlockEntity tileentitychest;
++        private final ChestBlockEntity tileentitychest1;
++        public final CompoundContainer inventorylargechest;
++
++        public DoubleInventory(ChestBlockEntity tileentitychest, ChestBlockEntity tileentitychest1, CompoundContainer inventorylargechest) {
++            this.tileentitychest = tileentitychest;
++            this.tileentitychest1 = tileentitychest1;
++            this.inventorylargechest = inventorylargechest;
++        }
++
++        @Nullable
++        @Override
++        public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
++            if (this.tileentitychest.canOpen(player) && this.tileentitychest1.canOpen(player)) {
++                this.tileentitychest.unpackLootTable(playerInventory.player);
++                this.tileentitychest1.unpackLootTable(playerInventory.player);
++                return ChestMenu.sixRows(syncId, playerInventory, this.inventorylargechest);
++            } else {
++                return null;
++            }
++        }
++
++        @Override
++        public Component getDisplayName() {
++            return (Component) (this.tileentitychest.hasCustomName() ? this.tileentitychest.getDisplayName() : (this.tileentitychest1.hasCustomName() ? this.tileentitychest1.getDisplayName() : Component.translatable("container.chestDouble")));
++        }
++    };
++    // CraftBukkit end
++
+     @Override
+     public MapCodec<? extends ChestBlock> codec() {
+         return CODEC;
+@@ -245,8 +_,7 @@
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (level instanceof ServerLevel serverLevel) {
+             MenuProvider menuProvider = this.getMenuProvider(state, level, pos);
+-            if (menuProvider != null) {
+-                player.openMenu(menuProvider);
++            if (menuProvider != null && player.openMenu(menuProvider).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+                 player.awardStat(this.getOpenChestStat());
+                 PiglinAi.angerNearbyPiglins(serverLevel, player, true);
+             }
+@@ -285,7 +_,14 @@
+     @Nullable
+     @Override
+     protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
+-        return this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null);
++    // CraftBukkit start
++        return this.getMenuProvider(state, level, pos, false);
++    }
++
++    @Nullable
++    public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos, boolean ignoreObstructions) {
++        return this.combine(state, level, pos, ignoreObstructions).apply(MENU_PROVIDER_COMBINER).orElse(null);
++    // CraftBukkit end
+     }
+ 
+     public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity lid) {
+@@ -328,6 +_,11 @@
+     }
+ 
+     private static boolean isCatSittingOnChest(LevelAccessor level, BlockPos pos) {
++        // Paper start - Option to disable chest cat detection
++        if (level.getMinecraftWorld().paperConfig().entities.behavior.disableChestCatDetection) {
++            return false;
++        }
++        // Paper end - Option to disable chest cat detection
+         List<Cat> entitiesOfClass = level.getEntitiesOfClass(
+             Cat.class, new AABB(pos.getX(), pos.getY() + 1, pos.getZ(), pos.getX() + 1, pos.getY() + 2, pos.getZ() + 1)
+         );
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
new file mode 100644
index 0000000000..7909effb3a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/block/ChorusFlowerBlock.java
++++ b/net/minecraft/world/level/block/ChorusFlowerBlock.java
+@@ -96,8 +_,10 @@
+                 }
+ 
+                 if (flag && allNeighborsEmpty(level, blockPos, null) && level.isEmptyBlock(pos.above(2))) {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, ageValue), 2)) { // CraftBukkit - add event
+                     level.setBlock(pos, ChorusPlantBlock.getStateWithConnections(level, pos, this.plant.defaultBlockState()), 2);
+                     this.placeGrownFlower(level, blockPos, ageValue);
++                    } // CraftBukkit
+                 } else if (ageValue < 4) {
+                     int i = random.nextInt(4);
+                     if (flag1) {
+@@ -112,8 +_,10 @@
+                         if (level.isEmptyBlock(blockPos1)
+                             && level.isEmptyBlock(blockPos1.below())
+                             && allNeighborsEmpty(level, blockPos1, randomDirection.getOpposite())) {
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos1, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, ageValue + 1), 2)) { // CraftBukkit - add event
+                             this.placeGrownFlower(level, blockPos1, ageValue + 1);
+                             flag2 = true;
++                            } // CraftBukkit
+                         }
+                     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusPlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusPlantBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch
index 80c24699ea..f73493ba87 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusPlantBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusPlantBlock.java.patch
@@ -1,34 +1,34 @@
 --- a/net/minecraft/world/level/block/ChorusPlantBlock.java
 +++ b/net/minecraft/world/level/block/ChorusPlantBlock.java
-@@ -38,6 +38,7 @@
+@@ -38,6 +_,7 @@
  
      @Override
-     public BlockState getStateForPlacement(BlockPlaceContext ctx) {
+     public BlockState getStateForPlacement(BlockPlaceContext context) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates
-         return getStateWithConnections(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState());
+         return getStateWithConnections(context.getLevel(), context.getClickedPos(), this.defaultBlockState());
      }
  
-@@ -68,6 +69,7 @@
+@@ -68,6 +_,7 @@
          BlockState neighborState,
          RandomSource random
      ) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return state; // Paper - add option to disable block updates
-         if (!state.canSurvive(world, pos)) {
-             tickView.scheduleTick(pos, this, 1);
-             return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-@@ -79,6 +81,7 @@
+         if (!state.canSurvive(level, pos)) {
+             scheduledTickAccess.scheduleTick(pos, this, 1);
+             return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
+@@ -81,6 +_,7 @@
  
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return; // Paper - add option to disable block updates
-         if (!state.canSurvive(world, pos)) {
-             world.destroyBlock(pos, true);
+         if (!state.canSurvive(level, pos)) {
+             level.destroyBlock(pos, true);
          }
-@@ -86,6 +89,7 @@
+@@ -88,6 +_,7 @@
  
      @Override
-     protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
+     protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableChorusPlantUpdates) return true; // Paper - add option to disable block updates
-         BlockState blockState = world.getBlockState(pos.below());
-         boolean bl = !world.getBlockState(pos.above()).isAir() && !blockState.isAir();
+         BlockState blockState = level.getBlockState(pos.below());
+         boolean flag = !level.getBlockState(pos.above()).isAir() && !blockState.isAir();
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch
new file mode 100644
index 0000000000..1b46405a40
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CocoaBlock.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/block/CocoaBlock.java
++++ b/net/minecraft/world/level/block/CocoaBlock.java
+@@ -64,10 +_,10 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (level.random.nextInt(5) == 0) {
++        if (level.random.nextFloat() < (level.spigotConfig.cocoaModifier / (100.0f * 5))) { // Spigot - SPIGOT-7159: Better modifier resolution
+             int ageValue = state.getValue(AGE);
+             if (ageValue < 2) {
+-                level.setBlock(pos, state.setValue(AGE, Integer.valueOf(ageValue + 1)), 2);
++                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(CocoaBlock.AGE, ageValue + 1), 2); // CraftBukkkit
+             }
+         }
+     }
+@@ -141,7 +_,7 @@
+ 
+     @Override
+     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+-        level.setBlock(pos, state.setValue(AGE, Integer.valueOf(state.getValue(AGE) + 1)), 2);
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(CocoaBlock.AGE, state.getValue(CocoaBlock.AGE) + 1), 2); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
new file mode 100644
index 0000000000..f285e643af
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/block/CommandBlock.java
++++ b/net/minecraft/world/level/block/CommandBlock.java
+@@ -70,6 +_,15 @@
+ 
+     private void setPoweredAndUpdate(Level level, BlockPos pos, CommandBlockEntity blockEntity, boolean powered) {
+         boolean isPowered = blockEntity.isPowered();
++        // CraftBukkit start
++        org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++        int old = isPowered ? 15 : 0;
++        int current = powered ? 15 : 0;
++
++        org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, old, current);
++        level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++        powered = eventRedstone.getNewCurrent() > 0;
++        // CraftBukkit end
+         if (powered != isPowered) {
+             blockEntity.setPowered(powered);
+             if (powered) {
+@@ -126,7 +_,7 @@
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         BlockEntity blockEntity = level.getBlockEntity(pos);
+-        if (blockEntity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) {
++        if (blockEntity instanceof CommandBlockEntity  && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
+             player.openCommandBlock((CommandBlockEntity)blockEntity);
+             return InteractionResult.SUCCESS;
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch
new file mode 100644
index 0000000000..713361e2bd
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ComparatorBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/ComparatorBlock.java
++++ b/net/minecraft/world/level/block/ComparatorBlock.java
+@@ -170,8 +_,18 @@
+             boolean shouldTurnOn = this.shouldTurnOn(level, pos, state);
+             boolean poweredValue = state.getValue(POWERED);
+             if (poweredValue && !shouldTurnOn) {
++                // CraftBukkit start
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(false)), 2);
+             } else if (!poweredValue && shouldTurnOn) {
++                // CraftBukkit start
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch
new file mode 100644
index 0000000000..2f9e73d103
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ComposterBlock.java.patch
@@ -0,0 +1,148 @@
+--- a/net/minecraft/world/level/block/ComposterBlock.java
++++ b/net/minecraft/world/level/block/ComposterBlock.java
+@@ -243,6 +_,11 @@
+         if (levelValue < 8 && COMPOSTABLES.containsKey(stack.getItem())) {
+             if (levelValue < 7 && !level.isClientSide) {
+                 BlockState blockState = addItem(player, state, level, pos, stack);
++                // Paper start - handle cancelled events
++                if (blockState == null) {
++                    return InteractionResult.PASS;
++                }
++                // Paper end
+                 level.levelEvent(1500, pos, state != blockState ? 1 : 0);
+                 player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+                 stack.consume(1, player);
+@@ -268,7 +_,19 @@
+     public static BlockState insertItem(Entity entity, BlockState state, ServerLevel level, ItemStack stack, BlockPos pos) {
+         int levelValue = state.getValue(LEVEL);
+         if (levelValue < 7 && COMPOSTABLES.containsKey(stack.getItem())) {
+-            BlockState blockState = addItem(entity, state, level, pos, stack);
++            // CraftBukkit start
++            double rand = level.getRandom().nextDouble();
++            BlockState blockState = null; // Paper
++            if (false && (state == blockState || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, blockState))) { // Paper - move event call into addItem
++                return state;
++            }
++            blockState = ComposterBlock.addItem(entity, state, level, pos, stack, rand);
++            // Paper start - handle cancelled events
++            if (blockState == null) {
++                return state;
++            }
++            // Paper end
++            // CraftBukkit end
+             stack.shrink(1);
+             return blockState;
+         } else {
+@@ -277,6 +_,14 @@
+     }
+ 
+     public static BlockState extractProduce(Entity entity, BlockState state, Level level, BlockPos pos) {
++        // CraftBukkit start
++        if (entity != null && !(entity instanceof Player)) {
++            BlockState iblockdata1 = ComposterBlock.empty(entity, state, org.bukkit.craftbukkit.util.DummyGeneratorAccess.INSTANCE, pos);
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, iblockdata1)) {
++                return state;
++            }
++        }
++        // CraftBukkit end
+         if (!level.isClientSide) {
+             Vec3 vec3 = Vec3.atLowerCornerWithOffset(pos, 0.5, 1.01, 0.5).offsetRandom(level.random, 0.7F);
+             ItemEntity itemEntity = new ItemEntity(level, vec3.x(), vec3.y(), vec3.z(), new ItemStack(Items.BONE_MEAL));
+@@ -296,14 +_,39 @@
+         return blockState;
+     }
+ 
++    @Nullable // Paper
+     static BlockState addItem(@Nullable Entity entity, BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack) {
++        // CraftBukkit start
++        return ComposterBlock.addItem(entity, state, level, pos, stack, level.getRandom().nextDouble());
++    }
++    @Nullable // Paper - make it nullable
++    static BlockState addItem(@Nullable Entity entity, BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack, double rand) {
+         int levelValue = state.getValue(LEVEL);
+         float _float = COMPOSTABLES.getFloat(stack.getItem());
+-        if ((levelValue != 0 || !(_float > 0.0F)) && !(level.getRandom().nextDouble() < _float)) {
++        // Paper start - Add CompostItemEvent and EntityCompostItemEvent
++        boolean willRaiseLevel = !((levelValue != 0 || _float <= 0.0F) && rand >= (double) _float);
++        final io.papermc.paper.event.block.CompostItemEvent event;
++        if (entity == null) {
++            event = new io.papermc.paper.event.block.CompostItemEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), stack.getBukkitStack(), willRaiseLevel);
++        } else {
++            event = new io.papermc.paper.event.entity.EntityCompostItemEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), stack.getBukkitStack(), willRaiseLevel);
++        }
++        if (!event.callEvent()) { // check for cancellation of entity event (non entity event can't be cancelled cause of hoppers)
++            return null;
++        }
++        willRaiseLevel = event.willRaiseLevel();
++
++        if (!willRaiseLevel) {
++            // Paper end - Add CompostItemEvent and EntityCompostItemEvent
+             return state;
+         } else {
+             int i = levelValue + 1;
+             BlockState blockState = state.setValue(LEVEL, Integer.valueOf(i));
++            // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events
++            if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, blockState)) {
++                return null;
++            }
++            // Paper end
+             level.setBlock(pos, blockState, 3);
+             level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, blockState));
+             if (i == 7) {
+@@ -348,13 +_,14 @@
+         if (levelValue == 8) {
+             return new ComposterBlock.OutputContainer(state, level, pos, new ItemStack(Items.BONE_MEAL));
+         } else {
+-            return (WorldlyContainer)(levelValue < 7 ? new ComposterBlock.InputContainer(state, level, pos) : new ComposterBlock.EmptyContainer());
++            return (WorldlyContainer)(levelValue < 7 ? new ComposterBlock.InputContainer(state, level, pos) : new ComposterBlock.EmptyContainer(level, pos)); // CraftBukkit - empty generatoraccess, blockposition
+         }
+     }
+ 
+     public static class EmptyContainer extends SimpleContainer implements WorldlyContainer {
+-        public EmptyContainer() {
++        public EmptyContainer(LevelAccessor generatoraccess, BlockPos blockposition) { // CraftBukkit
+             super(0);
++            this.bukkitOwner = new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(generatoraccess, blockposition, this); // CraftBukkit
+         }
+ 
+         @Override
+@@ -381,9 +_,11 @@
+ 
+         public InputContainer(BlockState state, LevelAccessor level, BlockPos pos) {
+             super(1);
++            this.bukkitOwner = new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(level, pos, this); // CraftBukkit
+             this.state = state;
+             this.level = level;
+             this.pos = pos;
++            this.bukkitOwner = new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(level, pos, this); // CraftBukkit
+         }
+ 
+         @Override
+@@ -412,6 +_,11 @@
+             if (!item.isEmpty()) {
+                 this.changed = true;
+                 BlockState blockState = ComposterBlock.addItem(null, this.state, this.level, this.pos, item);
++                // Paper start - Add CompostItemEvent and EntityCompostItemEvent
++                if (blockState == null) {
++                    return;
++                }
++                // Paper end - Add CompostItemEvent and EntityCompostItemEvent
+                 this.level.levelEvent(1500, this.pos, blockState != this.state ? 1 : 0);
+                 this.removeItemNoUpdate(0);
+             }
+@@ -453,8 +_,15 @@
+ 
+         @Override
+         public void setChanged() {
++            // CraftBukkit start - allow putting items back (eg cancelled InventoryMoveItemEvent)
++            if (this.isEmpty()) {
+             ComposterBlock.empty(null, this.state, this.level, this.pos);
+             this.changed = true;
++            } else {
++                this.level.setBlock(this.pos, this.state, 3);
++                this.changed = false;
++            }
++            // CraftBukkit end
+         }
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
new file mode 100644
index 0000000000..09d88b80ed
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
@@ -0,0 +1,66 @@
+--- a/net/minecraft/world/level/block/ConcretePowderBlock.java
++++ b/net/minecraft/world/level/block/ConcretePowderBlock.java
+@@ -38,7 +_,7 @@
+     @Override
+     public void onLand(Level level, BlockPos pos, BlockState state, BlockState replaceableState, FallingBlockEntity fallingBlock) {
+         if (shouldSolidify(level, pos, replaceableState)) {
+-            level.setBlock(pos, this.concrete.defaultBlockState(), 3);
++            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, this.concrete.defaultBlockState(), 3); // CraftBukkit
+         }
+     }
+ 
+@@ -47,7 +_,24 @@
+         BlockGetter level = context.getLevel();
+         BlockPos clickedPos = context.getClickedPos();
+         BlockState blockState = level.getBlockState(clickedPos);
+-        return shouldSolidify(level, clickedPos, blockState) ? this.concrete.defaultBlockState() : super.getStateForPlacement(context);
++        // CraftBukkit start
++        if (!ConcretePowderBlock.shouldSolidify(level, clickedPos, blockState)) {
++            return super.getStateForPlacement(context);
++        }
++
++        // TODO: An event factory call for methods like this
++        org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState((net.minecraft.world.level.LevelAccessor) level, clickedPos);
++        craftBlockState.setData(this.concrete.defaultBlockState());
++
++        org.bukkit.event.block.BlockFormEvent event = new org.bukkit.event.block.BlockFormEvent(craftBlockState.getBlock(), craftBlockState);
++        level.getServer().server.getPluginManager().callEvent(event);
++
++        if (!event.isCancelled()) {
++            return craftBlockState.getHandle();
++        }
++
++        return super.getStateForPlacement(context);
++        // CraftBukkit end
+     }
+ 
+     private static boolean shouldSolidify(BlockGetter level, BlockPos pos, BlockState state) {
+@@ -88,9 +_,25 @@
+         BlockState neighborState,
+         RandomSource random
+     ) {
+-        return touchesLiquid(level, pos)
+-            ? this.concrete.defaultBlockState()
+-            : super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
++        // CraftBukkit start
++        if (ConcretePowderBlock.touchesLiquid(level, pos)) {
++            // Suppress during worldgen
++            if (!(level instanceof Level world1)) {
++                return this.concrete.defaultBlockState();
++            }
++            org.bukkit.craftbukkit.block.CraftBlockState blockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world1, pos);
++            blockState.setData(this.concrete.defaultBlockState());
++
++            org.bukkit.event.block.BlockFormEvent event = new org.bukkit.event.block.BlockFormEvent(blockState.getBlock(), blockState);
++            world1.getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled()) {
++                return blockState.getHandle();
++            }
++        }
++
++        return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
++        // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CoralBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch
index 389b3b96ec..87d67142fb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/CoralBlock.java
 +++ b/net/minecraft/world/level/block/CoralBlock.java
-@@ -40,6 +40,11 @@
+@@ -37,6 +_,11 @@
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (!this.scanForWater(world, pos)) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (!this.scanForWater(level, pos)) {
 +            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState()).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState()).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, this.deadBlock.defaultBlockState(), 2);
+             level.setBlock(pos, this.deadBlock.defaultBlockState(), 2);
          }
- 
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralFanBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CoralFanBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch
index 295f4f0049..ff375bb06b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralFanBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralFanBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/CoralFanBlock.java
 +++ b/net/minecraft/world/level/block/CoralFanBlock.java
-@@ -41,6 +41,11 @@
+@@ -38,6 +_,11 @@
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (!scanForWater(state, world, pos)) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (!scanForWater(state, level, pos)) {
 +            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false)).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false)).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false), 2);
+             level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)), 2);
          }
- 
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralPlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CoralPlantBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch
index bccf3cd4a0..c768b4c634 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralPlantBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralPlantBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/CoralPlantBlock.java
 +++ b/net/minecraft/world/level/block/CoralPlantBlock.java
-@@ -46,6 +46,11 @@
+@@ -43,6 +_,11 @@
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (!scanForWater(state, world, pos)) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (!scanForWater(state, level, pos)) {
 +            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false)).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false)).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false), 2);
+             level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)), 2);
          }
- 
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralWallFanBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
similarity index 53%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
index 172e22d8b2..7ac0a5182f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/CoralWallFanBlock.java
 +++ b/net/minecraft/world/level/block/CoralWallFanBlock.java
-@@ -41,6 +41,11 @@
+@@ -38,6 +_,11 @@
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (!scanForWater(state, world, pos)) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (!scanForWater(state, level, pos)) {
 +            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false).setValue(CoralWallFanBlock.FACING, state.getValue(CoralWallFanBlock.FACING))).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false).setValue(CoralWallFanBlock.FACING, state.getValue(CoralWallFanBlock.FACING))).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, (BlockState) ((BlockState) this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false)).setValue(CoralWallFanBlock.FACING, (Direction) state.getValue(CoralWallFanBlock.FACING)), 2);
+             level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)).setValue(FACING, state.getValue(FACING)), 2);
          }
- 
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch
new file mode 100644
index 0000000000..b592d3f859
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CrafterBlock.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/level/block/CrafterBlock.java
++++ b/net/minecraft/world/level/block/CrafterBlock.java
+@@ -11,6 +_,7 @@
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.RandomSource;
++import net.minecraft.world.CompoundContainer;
+ import net.minecraft.world.Container;
+ import net.minecraft.world.Containers;
+ import net.minecraft.world.InteractionResult;
+@@ -159,6 +_,13 @@
+             } else {
+                 RecipeHolder<CraftingRecipe> recipeHolder = potentialResults.get();
+                 ItemStack itemStack = recipeHolder.value().assemble(var11, level.registryAccess());
++                // CraftBukkit start
++                org.bukkit.event.block.CrafterCraftEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callCrafterCraftEvent(pos, level, crafterBlockEntity, itemStack, recipeHolder);
++                if (event.isCancelled()) {
++                    return;
++                }
++                itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getResult());
++                // CraftBukkit end
+                 if (itemStack.isEmpty()) {
+                     level.levelEvent(1050, pos, 0);
+                 } else {
+@@ -193,7 +_,25 @@
+         Container containerAt = HopperBlockEntity.getContainerAt(level, pos.relative(direction));
+         ItemStack itemStack = stack.copy();
+         if (containerAt != null && (containerAt instanceof CrafterBlockEntity || stack.getCount() > containerAt.getMaxStackSize(stack))) {
++            // CraftBukkit start - InventoryMoveItemEvent
++            org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++
++            org.bukkit.inventory.Inventory destinationInventory;
++            // Have to special case large chests as they work oddly
++            if (containerAt instanceof CompoundContainer) {
++                destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) containerAt);
++            } else {
++                destinationInventory = containerAt.getOwner().getInventory();
++            }
++
++            org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(crafter.getOwner().getInventory(), oitemstack, destinationInventory, true);
++            level.getCraftServer().getPluginManager().callEvent(event);
++            itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
+             while (!itemStack.isEmpty()) {
++                if (event.isCancelled()) {
++                    break;
++                }
++                // CraftBukkit end
+                 ItemStack itemStack1 = itemStack.copyWithCount(1);
+                 ItemStack itemStack2 = HopperBlockEntity.addItem(crafter, containerAt, itemStack1, direction.getOpposite());
+                 if (!itemStack2.isEmpty()) {
+@@ -203,7 +_,25 @@
+                 itemStack.shrink(1);
+             }
+         } else if (containerAt != null) {
++            // CraftBukkit start - InventoryMoveItemEvent
++            org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
++
++            org.bukkit.inventory.Inventory destinationInventory;
++            // Have to special case large chests as they work oddly
++            if (containerAt instanceof CompoundContainer) {
++                destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) containerAt);
++            } else {
++                destinationInventory = containerAt.getOwner().getInventory();
++            }
++
++            org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(crafter.getOwner().getInventory(), oitemstack, destinationInventory, true);
++            level.getCraftServer().getPluginManager().callEvent(event);
++            itemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
+             while (!itemStack.isEmpty()) {
++                if (event.isCancelled()) {
++                    break;
++                }
++                // CraftBukkit end
+                 int count = itemStack.getCount();
+                 itemStack = HopperBlockEntity.addItem(crafter, containerAt, itemStack, direction.getOpposite());
+                 if (count == itemStack.getCount()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CraftingTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
similarity index 62%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/CraftingTableBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
index e611ea83b5..3285cee056 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CraftingTableBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
@@ -1,11 +1,10 @@
 --- a/net/minecraft/world/level/block/CraftingTableBlock.java
 +++ b/net/minecraft/world/level/block/CraftingTableBlock.java
-@@ -31,8 +31,9 @@
-     @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+@@ -32,7 +_,9 @@
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+             player.openMenu(state.getMenuProvider(level, pos));
++            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_CRAFTING_TABLE);
 +            } // Paper - Fix InventoryOpenEvent cancellation
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
new file mode 100644
index 0000000000..2e9c06c89a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/level/block/CropBlock.java
++++ b/net/minecraft/world/level/block/CropBlock.java
+@@ -88,8 +_,25 @@
+             int age = this.getAge(state);
+             if (age < this.getMaxAge()) {
+                 float growthSpeed = getGrowthSpeed(this, level, pos);
+-                if (random.nextInt((int)(25.0F / growthSpeed) + 1) == 0) {
+-                    level.setBlock(pos, this.getStateForAge(age + 1), 2);
++                // Spigot start
++                int modifier;
++                if (this == Blocks.BEETROOTS) {
++                    modifier = level.spigotConfig.beetrootModifier;
++                } else if (this == Blocks.CARROTS) {
++                    modifier = level.spigotConfig.carrotModifier;
++                } else if (this == Blocks.POTATOES) {
++                    modifier = level.spigotConfig.potatoModifier;
++                // Paper start - Fix Spigot growth modifiers
++                } else if (this == Blocks.TORCHFLOWER_CROP) {
++                    modifier = level.spigotConfig.torchFlowerModifier;
++                // Paper end - Fix Spigot growth modifiers
++                } else {
++                    modifier = level.spigotConfig.wheatModifier;
++                }
++
++                if (random.nextFloat() < (modifier / (100.0f * (Math.floor((25.0F / growthSpeed) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution
++                    // Spigot end
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(age + 1), 2); // CraftBukkit
+                 }
+             }
+         }
+@@ -102,7 +_,7 @@
+             i = maxAge;
+         }
+ 
+-        level.setBlock(pos, this.getStateForAge(i), 2);
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(i), 2); // CraftBukkit
+     }
+ 
+     protected int getBonemealAgeIncrease(Level level) {
+@@ -164,7 +_,8 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+-        if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
++        if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
+             serverLevel.destroyBlock(pos, true, entity);
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
new file mode 100644
index 0000000000..eee537faaa
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/block/DaylightDetectorBlock.java
++++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java
+@@ -70,6 +_,7 @@
+ 
+         i = Mth.clamp(i, 0, 15);
+         if (state.getValue(POWER) != i) {
++            i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, state.getValue(DaylightDetectorBlock.POWER), i).getNewCurrent(); // CraftBukkit - Call BlockRedstoneEvent
+             level.setBlock(pos, state.setValue(POWER, Integer.valueOf(i)), 3);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
new file mode 100644
index 0000000000..492d3d7e78
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/DecoratedPotBlock.java
++++ b/net/minecraft/world/level/block/DecoratedPotBlock.java
+@@ -237,6 +_,11 @@
+     protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+         BlockPos blockPos = hit.getBlockPos();
+         if (level instanceof ServerLevel serverLevel && projectile.mayInteract(serverLevel, blockPos) && projectile.mayBreak(serverLevel)) {
++            // CraftBukkit start - call EntityChangeBlockEvent
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockPos, this.getFluidState(state).createLegacyBlock())) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(blockPos, state.setValue(CRACKED, Boolean.valueOf(true)), 4);
+             level.destroyBlock(blockPos, true, projectile);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
new file mode 100644
index 0000000000..3dcaa1506e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/level/block/DetectorRailBlock.java
++++ b/net/minecraft/world/level/block/DetectorRailBlock.java
+@@ -54,6 +_,7 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide) {
+             if (!state.getValue(POWERED)) {
+                 this.checkPressed(level, pos, state);
+@@ -84,6 +_,7 @@
+ 
+     private void checkPressed(Level level, BlockPos pos, BlockState state) {
+         if (this.canSurvive(state, level, pos)) {
++            if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly
+             boolean poweredValue = state.getValue(POWERED);
+             boolean flag = false;
+             List<AbstractMinecart> interactingMinecartOfType = this.getInteractingMinecartOfType(level, pos, AbstractMinecart.class, entity -> true);
+@@ -91,6 +_,16 @@
+                 flag = true;
+             }
+ 
++            // CraftBukkit start
++            if (poweredValue != flag) {
++                org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++                org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(block, flag ? 15 : 0, flag ? 15 : 0);
++                level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++                flag = eventRedstone.getNewCurrent() > 0;
++            }
++            // CraftBukkit end
+             if (flag && !poweredValue) {
+                 BlockState blockState = state.setValue(POWERED, Boolean.valueOf(true));
+                 level.setBlock(pos, blockState, 3);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch
new file mode 100644
index 0000000000..bde27499c8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DiodeBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/DiodeBlock.java
++++ b/net/minecraft/world/level/block/DiodeBlock.java
+@@ -56,8 +_,18 @@
+             boolean poweredValue = state.getValue(POWERED);
+             boolean shouldTurnOn = this.shouldTurnOn(level, pos, state);
+             if (poweredValue && !shouldTurnOn) {
++                // CraftBukkit start
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(false)), 2);
+             } else if (!poweredValue) {
++                // CraftBukkit start
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
+                 if (!shouldTurnOn) {
+                     level.scheduleTick(pos, this, this.getDelay(state), TickPriority.VERY_HIGH);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DirtPathBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/DirtPathBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch
index b394c4adfa..bc73d1c956 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DirtPathBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DirtPathBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/DirtPathBlock.java
 +++ b/net/minecraft/world/level/block/DirtPathBlock.java
-@@ -51,6 +51,11 @@
+@@ -60,6 +_,11 @@
  
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
 +        // CraftBukkit start - do not fade if the block is valid here
-+        if (state.canSurvive(world, pos)) {
++        if (state.canSurvive(level, pos)) {
 +            return;
 +        }
 +        // CraftBukkit end
-         FarmBlock.turnToDirt((Entity) null, state, world, pos);
+         FarmBlock.turnToDirt(null, state, level, pos);
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
new file mode 100644
index 0000000000..1660450641
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/DispenserBlock.java
++++ b/net/minecraft/world/level/block/DispenserBlock.java
+@@ -50,6 +_,7 @@
+     private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior();
+     public static final Map<Item, DispenseItemBehavior> DISPENSER_REGISTRY = new IdentityHashMap<>();
+     private static final int TRIGGER_DURATION = 4;
++    public static boolean eventFired = false; // CraftBukkit
+ 
+     @Override
+     public MapCodec<? extends DispenserBlock> codec() {
+@@ -71,8 +_,7 @@
+ 
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide && level.getBlockEntity(pos) instanceof DispenserBlockEntity dispenserBlockEntity) {
+-            player.openMenu(dispenserBlockEntity);
++        if (!level.isClientSide && level.getBlockEntity(pos) instanceof DispenserBlockEntity dispenserBlockEntity && player.openMenu(dispenserBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+             player.awardStat(dispenserBlockEntity instanceof DropperBlockEntity ? Stats.INSPECT_DROPPER : Stats.INSPECT_DISPENSER);
+         }
+ 
+@@ -87,18 +_,26 @@
+             BlockSource blockSource = new BlockSource(level, pos, state, dispenserBlockEntity);
+             int randomSlot = dispenserBlockEntity.getRandomSlot(level.random);
+             if (randomSlot < 0) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(level, pos)) { // Paper - Add BlockFailedDispenseEvent
+                 level.levelEvent(1001, pos, 0);
+                 level.gameEvent(GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(dispenserBlockEntity.getBlockState()));
++                } // Paper - Add BlockFailedDispenseEvent
+             } else {
+                 ItemStack item = dispenserBlockEntity.getItem(randomSlot);
+                 DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item);
+                 if (dispenseMethod != DispenseItemBehavior.NOOP) {
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent
+                     dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item));
+                 }
+             }
+         }
+     }
+ 
++    // Paper start - Fix NPE with equippable and items without behavior
++    public static DispenseItemBehavior getDispenseBehavior(BlockSource pointer, ItemStack stack) {
++        return ((DispenserBlock) pointer.state().getBlock()).getDispenseMethod(pointer.level(), stack);
++    }
++    // Paper end - Fix NPE with equippable and items without behavior
+     protected DispenseItemBehavior getDispenseMethod(Level level, ItemStack item) {
+         if (!item.isItemEnabled(level.enabledFeatures())) {
+             return DEFAULT_BEHAVIOR;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
new file mode 100644
index 0000000000..87099f1713
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/DoorBlock.java
++++ b/net/minecraft/world/level/block/DoorBlock.java
+@@ -229,9 +_,23 @@
+ 
+     @Override
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
+-        boolean flag = level.hasNeighborSignal(pos)
+-            || level.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
+-        if (!this.defaultBlockState().is(neighborBlock) && flag != state.getValue(POWERED)) {
++        // CraftBukkit start
++        BlockPos otherHalf = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
++        org.bukkit.World bworld = level.getWorld();
++        org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++        org.bukkit.block.Block blockTop = bworld.getBlockAt(otherHalf.getX(), otherHalf.getY(), otherHalf.getZ());
++
++        int power = bukkitBlock.getBlockPower();
++        int powerTop = blockTop.getBlockPower();
++        if (powerTop > power) power = powerTop;
++        int oldPower = (Boolean) state.getValue(DoorBlock.POWERED) ? 15 : 0;
++
++        if (oldPower == 0 ^ power == 0) {
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, power);
++            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++            boolean flag = eventRedstone.getNewCurrent() > 0;
++        // CraftBukkit end
+             if (flag != state.getValue(OPEN)) {
+                 this.playSound(null, level, pos, flag);
+                 level.gameEvent(null, flag ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch
index 657fe3fb50..3416e7d85d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoubleBlockCombiner.java.patch
@@ -1,16 +1,16 @@
 --- a/net/minecraft/world/level/block/DoubleBlockCombiner.java
 +++ b/net/minecraft/world/level/block/DoubleBlockCombiner.java
-@@ -34,7 +34,12 @@
+@@ -34,7 +_,12 @@
                  return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity);
              } else {
-                 BlockPos blockPos = pos.relative(directionMapper.apply(state));
--                BlockState blockState = world.getBlockState(blockPos);
+                 BlockPos blockPos = pos.relative(directionGetter.apply(state));
+-                BlockState blockState = level.getBlockState(blockPos);
 +                // Paper start - Don't load Chunks from Hoppers and other things
-+                BlockState blockState = world.getBlockStateIfLoaded(blockPos);
++                BlockState blockState = level.getBlockStateIfLoaded(blockPos);
 +                if (blockState == null) {
 +                    return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity);
 +                }
 +                // Paper end - Don't load Chunks from Hoppers and other things
                  if (blockState.is(state.getBlock())) {
-                     DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState);
-                     if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE
+                     DoubleBlockCombiner.BlockType blockType1 = doubleBlockTypeGetter.apply(blockState);
+                     if (blockType1 != DoubleBlockCombiner.BlockType.SINGLE
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch
new file mode 100644
index 0000000000..f57b086e6a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/DoublePlantBlock.java
++++ b/net/minecraft/world/level/block/DoublePlantBlock.java
+@@ -112,11 +_,16 @@
+     }
+ 
+     @Override
+-    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
+-        super.playerDestroy(level, player, pos, Blocks.AIR.defaultBlockState(), te, stack);
++    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
++        super.playerDestroy(level, player, pos, Blocks.AIR.defaultBlockState(), te, stack, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion;
+     }
+ 
+     protected static void preventDropFromBottomPart(Level level, BlockPos pos, BlockState state, Player player) {
++        // CraftBukkit start
++        if (((net.minecraft.server.level.ServerLevel)level).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, pos).isCancelled()) { // Paper
++            return;
++        }
++        // CraftBukkit end
+         DoubleBlockHalf doubleBlockHalf = state.getValue(HALF);
+         if (doubleBlockHalf == DoubleBlockHalf.UPPER) {
+             BlockPos blockPos = pos.below();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
new file mode 100644
index 0000000000..4d35f93eec
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/DragonEggBlock.java
++++ b/net/minecraft/world/level/block/DragonEggBlock.java
+@@ -55,6 +_,18 @@
+                 level.random.nextInt(16) - level.random.nextInt(16)
+             );
+             if (level.getBlockState(blockPos).isAir() && worldBorder.isWithinBounds(blockPos)) {
++                // CraftBukkit start
++                org.bukkit.block.Block from = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++                org.bukkit.block.Block to = level.getWorld().getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ());
++                org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(from, to);
++                org.bukkit.Bukkit.getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    return;
++                }
++
++                blockPos = new BlockPos(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ());
++                // CraftBukkit end
+                 if (level.isClientSide) {
+                     for (int i1 = 0; i1 < 128; i1++) {
+                         double randomDouble = level.random.nextDouble();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DropExperienceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch
similarity index 71%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/DropExperienceBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch
index 4d0a4875d4..1487606ce5 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DropExperienceBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DropExperienceBlock.java.patch
@@ -1,11 +1,12 @@
 --- a/net/minecraft/world/level/block/DropExperienceBlock.java
 +++ b/net/minecraft/world/level/block/DropExperienceBlock.java
-@@ -31,9 +31,16 @@
+@@ -31,8 +_,16 @@
      @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
 -        if (dropExperience) {
--            this.tryDropExperience(world, pos, tool, this.xpRange);
+-            this.tryDropExperience(level, pos, stack, this.xpRange);
+-        }
 +        // CraftBukkit start - Delegate to getExpDrop
 +    }
 +
@@ -13,8 +14,8 @@
 +    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
 +        if (flag) {
 +            return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange);
-         }
- 
++         }
++
 +        return 0;
 +        // CraftBukkit end
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch
new file mode 100644
index 0000000000..047ee9babb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DropperBlock.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/level/block/DropperBlock.java
++++ b/net/minecraft/world/level/block/DropperBlock.java
+@@ -8,6 +_,7 @@
+ import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
+ import net.minecraft.core.dispenser.DispenseItemBehavior;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.world.CompoundContainer;
+ import net.minecraft.world.Container;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
+@@ -23,7 +_,7 @@
+ public class DropperBlock extends DispenserBlock {
+     private static final Logger LOGGER = LogUtils.getLogger();
+     public static final MapCodec<DropperBlock> CODEC = simpleCodec(DropperBlock::new);
+-    private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior();
++    private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior(true); // CraftBukkit
+ 
+     @Override
+     public MapCodec<DropperBlock> codec() {
+@@ -53,6 +_,7 @@
+             BlockSource blockSource = new BlockSource(level, pos, state, dispenserBlockEntity);
+             int randomSlot = dispenserBlockEntity.getRandomSlot(level.random);
+             if (randomSlot < 0) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(level, pos)) // Paper - Add BlockFailedDispenseEvent
+                 level.levelEvent(1001, pos, 0);
+             } else {
+                 ItemStack item = dispenserBlockEntity.getItem(randomSlot);
+@@ -61,10 +_,29 @@
+                     Container containerAt = HopperBlockEntity.getContainerAt(level, pos.relative(direction));
+                     ItemStack itemStack;
+                     if (containerAt == null) {
++                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent
+                         itemStack = DISPENSE_BEHAVIOUR.dispense(blockSource, item);
+                     } else {
+-                        itemStack = HopperBlockEntity.addItem(dispenserBlockEntity, containerAt, item.copyWithCount(1), direction.getOpposite());
+-                        if (itemStack.isEmpty()) {
++                        // CraftBukkit start - Fire event when pushing items into other inventories
++                        org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1));
++
++                        org.bukkit.inventory.Inventory destinationInventory;
++                        // Have to special case large chests as they work oddly
++                        if (containerAt instanceof CompoundContainer) {
++                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) containerAt);
++                        } else {
++                            destinationInventory = containerAt.getOwner().getInventory();
++                        }
++
++                        org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(containerAt.getOwner().getInventory(), oitemstack, destinationInventory, true);
++                        level.getCraftServer().getPluginManager().callEvent(event);
++                        if (event.isCancelled()) {
++                            return;
++                        }
++                        itemStack = HopperBlockEntity.addItem(dispenserBlockEntity, containerAt, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), direction.getOpposite());
++                        if (event.getItem().equals(oitemstack) && itemStack.isEmpty()) {
++                            // CraftBukkit end
++
+                             itemStack = item.copy();
+                             itemStack.shrink(1);
+                         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch
new file mode 100644
index 0000000000..2b564a8c31
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/block/EndGatewayBlock.java
++++ b/net/minecraft/world/level/block/EndGatewayBlock.java
+@@ -89,10 +_,15 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (entity.canUsePortal(false)
+             && !level.isClientSide
+             && level.getBlockEntity(pos) instanceof TheEndGatewayBlockEntity theEndGatewayBlockEntity
+             && !theEndGatewayBlockEntity.isCoolingDown()) {
++            // Paper start - call EntityPortalEnterEvent
++            org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type
++            if (!event.callEvent()) return;
++            // Paper end - call EntityPortalEnterEvent
+             entity.setAsInsidePortal(this, pos);
+             TheEndGatewayBlockEntity.triggerCooldown(level, pos, state, theEndGatewayBlockEntity);
+         }
+@@ -107,9 +_,9 @@
+                 return null;
+             } else {
+                 return entity instanceof ThrownEnderpearl
+-                    ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET)
++                    ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY) // CraftBukkit
+                     : new TeleportTransition(
+-                        level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET
++                        level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY // CraftBukkit
+                     );
+             }
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch
new file mode 100644
index 0000000000..436949f5f8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/EndPortalBlock.java
++++ b/net/minecraft/world/level/block/EndPortalBlock.java
+@@ -56,8 +_,15 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (entity.canUsePortal(false)) {
++            // CraftBukkit start - Entity in portal
++            org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type
++            level.getCraftServer().getPluginManager().callEvent(event);
++            if (event.isCancelled()) return; // Paper - make cancellable
++            // CraftBukkit end
+             if (!level.isClientSide && level.dimension() == Level.END && entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) {
++                if (level.paperConfig().misc.disableEndCredits) {serverPlayer.seenCredits = true; return;} // Paper - Option to disable end credits
+                 serverPlayer.showEndCredits();
+             } else {
+                 entity.setAsInsidePortal(this, pos);
+@@ -67,7 +_,7 @@
+ 
+     @Override
+     public TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos) {
+-        ResourceKey<Level> resourceKey = level.dimension() == Level.END ? Level.OVERWORLD : Level.END;
++        ResourceKey<Level> resourceKey = level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends
+         ServerLevel level1 = level.getServer().getLevel(resourceKey);
+         if (level1 == null) {
+             return null;
+@@ -78,7 +_,7 @@
+             float f;
+             Set<Relative> set;
+             if (flag) {
+-                EndPlatformFeature.createEndPlatform(level1, BlockPos.containing(bottomCenter).below(), true);
++                EndPlatformFeature.createEndPlatform(level1, BlockPos.containing(bottomCenter).below(), true, entity); // CraftBukkit
+                 f = Direction.WEST.toYRot();
+                 set = Relative.union(Relative.DELTA, Set.of(Relative.X_ROT));
+                 if (entity instanceof ServerPlayer) {
+@@ -88,15 +_,21 @@
+                 f = 0.0F;
+                 set = Relative.union(Relative.DELTA, Relative.ROTATION);
+                 if (entity instanceof ServerPlayer serverPlayer) {
+-                    return serverPlayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING);
++                    return serverPlayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL); // CraftBukkit
+                 }
+ 
+                 bottomCenter = entity.adjustSpawnLocation(level1, blockPos).getBottomCenter();
+             }
+ 
+-            return new TeleportTransition(
+-                level1, bottomCenter, Vec3.ZERO, f, 0.0F, set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET)
+-            );
++            // CraftBukkit start
++            org.bukkit.craftbukkit.event.CraftPortalEvent event = entity.callPortalEvent(entity, org.bukkit.craftbukkit.util.CraftLocation.toBukkit(bottomCenter, level1.getWorld(), f, entity.getXRot()), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL, 0, 0);
++            if (event == null) {
++                return null;
++            }
++            org.bukkit.Location to = event.getTo();
++
++            return new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(to), entity.getDeltaMovement(), to.getYaw(), to.getPitch(), set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL);
++            // CraftBukkit end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
new file mode 100644
index 0000000000..cc9bee614b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/block/EnderChestBlock.java
++++ b/net/minecraft/world/level/block/EnderChestBlock.java
+@@ -78,16 +_,15 @@
+         PlayerEnderChestContainer enderChestInventory = player.getEnderChestInventory();
+         if (enderChestInventory != null && level.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) {
+             BlockPos blockPos = pos.above();
+-            if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) {
++            if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) {  // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic
+                 return InteractionResult.SUCCESS;
+             } else {
+-                if (level instanceof ServerLevel serverLevel) {
+-                    enderChestInventory.setActiveChest(enderChestBlockEntity);
+-                    player.openMenu(
+-                        new SimpleMenuProvider(
+-                            (containerId, playerInventory, player1) -> ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE
+-                        )
+-                    );
++                // Paper start - Fix InventoryOpenEvent cancellation - moved up;
++                enderChestInventory.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations
++                if (level instanceof ServerLevel serverLevel && player.openMenu(
++                    new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, enderChestInventory), CONTAINER_TITLE)
++                ).isPresent()) {
++                // Paper end - Fix InventoryOpenEvent cancellation - moved up;
+                     player.awardStat(Stats.OPEN_ENDERCHEST);
+                     PiglinAi.angerNearbyPiglins(serverLevel, player, true);
+                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/EyeblossomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/EyeblossomBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch
index 0f677874ee..a7765e31cc 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/EyeblossomBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/EyeblossomBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/EyeblossomBlock.java
 +++ b/net/minecraft/world/level/block/EyeblossomBlock.java
-@@ -100,6 +100,7 @@
+@@ -99,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide()
-             && world.getDifficulty() != Difficulty.PEACEFUL
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide()
+             && level.getDifficulty() != Difficulty.PEACEFUL
              && entity instanceof Bee bee
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch
new file mode 100644
index 0000000000..fb670445d2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch
@@ -0,0 +1,93 @@
+--- a/net/minecraft/world/level/block/FarmBlock.java
++++ b/net/minecraft/world/level/block/FarmBlock.java
+@@ -95,31 +_,56 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         int moistureValue = state.getValue(MOISTURE);
++        if (moistureValue > 0 && level.paperConfig().tickRates.wetFarmland != 1 && (level.paperConfig().tickRates.wetFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks
++        if (moistureValue == 0 && level.paperConfig().tickRates.dryFarmland != 1 && (level.paperConfig().tickRates.dryFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks
+         if (!isNearWater(level, pos) && !level.isRainingAt(pos.above())) {
+             if (moistureValue > 0) {
+-                level.setBlock(pos, state.setValue(MOISTURE, Integer.valueOf(moistureValue - 1)), 2);
++                org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, state.setValue(FarmBlock.MOISTURE, moistureValue - 1), 2); // CraftBukkit
+             } else if (!shouldMaintainFarmland(level, pos)) {
+                 turnToDirt(null, state, level, pos);
+             }
+         } else if (moistureValue < 7) {
+-            level.setBlock(pos, state.setValue(MOISTURE, Integer.valueOf(7)), 2);
++            org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, state.setValue(FarmBlock.MOISTURE, 7), 2); // CraftBukkit
+         }
+     }
+ 
+     @Override
+     public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
++        super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
+         if (level instanceof ServerLevel serverLevel
+             && level.random.nextFloat() < fallDistance - 0.5F
+             && entity instanceof LivingEntity
+             && (entity instanceof Player || serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))
+             && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
++                // CraftBukkit start - Interact soil
++                org.bukkit.event.Cancellable cancellable;
++                if (entity instanceof Player) {
++                    cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++                } else {
++                    cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                    level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++                }
++
++                if (cancellable.isCancelled()) {
++                    return;
++                }
++
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
++                    return;
++                }
++                // CraftBukkit end
+             turnToDirt(entity, state, level, pos);
+         }
+ 
+-        super.fallOn(level, state, pos, entity, fallDistance);
++        // super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved up
+     }
+ 
+     public static void turnToDirt(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
++        // CraftBukkit start
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
++            return;
++        }
++        // CraftBukkit end
+         BlockState blockState = pushEntitiesUp(state, Blocks.DIRT.defaultBlockState(), level, pos);
+         level.setBlockAndUpdate(pos, blockState);
+         level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, blockState));
+@@ -130,13 +_,27 @@
+     }
+ 
+     private static boolean isNearWater(LevelReader level, BlockPos pos) {
+-        for (BlockPos blockPos : BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4))) {
+-            if (level.getFluidState(blockPos).is(FluidTags.WATER)) {
+-                return true;
++        // Paper start - Perf: remove abstract block iteration
++        int xOff = pos.getX();
++        int yOff = pos.getY();
++        int zOff = pos.getZ();
++        for (int dz = -4; dz <= 4; ++dz) {
++            int z = dz + zOff;
++            for (int dx = -4; dx <= 4; ++dx) {
++                int x = xOff + dx;
++                for (int dy = 0; dy <= 1; ++dy) {
++                    int y = dy + yOff;
++                    net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)level.getChunk(x >> 4, z >> 4);
++                    net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState();
++                    if (fluid.is(FluidTags.WATER)) {
++                        return true;
++                    }
++                }
+             }
+         }
+ 
+         return false;
++        // Paper end - Perf: remove abstract block iteration
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch
new file mode 100644
index 0000000000..718a99aac2
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FenceGateBlock.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/block/FenceGateBlock.java
++++ b/net/minecraft/world/level/block/FenceGateBlock.java
+@@ -213,6 +_,17 @@
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
+         if (!level.isClientSide) {
+             boolean hasNeighborSignal = level.hasNeighborSignal(pos);
++            // CraftBukkit start
++            boolean oldPowered = state.getValue(FenceGateBlock.POWERED);
++            if (oldPowered != hasNeighborSignal) {
++                int newPower = hasNeighborSignal ? 15 : 0;
++                int oldPower = oldPowered ? 15 : 0;
++                org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, newPower);
++                level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++                hasNeighborSignal = eventRedstone.getNewCurrent() > 0;
++            }
++            // CraftBukkit end
+             if (state.getValue(POWERED) != hasNeighborSignal) {
+                 level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(hasNeighborSignal)).setValue(OPEN, Boolean.valueOf(hasNeighborSignal)), 2);
+                 if (state.getValue(OPEN) != hasNeighborSignal) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
new file mode 100644
index 0000000000..ac0a574cbf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
@@ -0,0 +1,169 @@
+--- a/net/minecraft/world/level/block/FireBlock.java
++++ b/net/minecraft/world/level/block/FireBlock.java
+@@ -14,6 +_,7 @@
+ import net.minecraft.tags.BiomeTags;
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.world.item.context.BlockPlaceContext;
++import net.minecraft.world.item.context.UseOnContext;
+ import net.minecraft.world.level.BlockGetter;
+ import net.minecraft.world.level.GameRules;
+ import net.minecraft.world.level.Level;
+@@ -122,7 +_,25 @@
+         BlockState neighborState,
+         RandomSource random
+     ) {
+-        return this.canSurvive(state, level, pos) ? this.getStateWithAge(level, pos, state.getValue(AGE)) : Blocks.AIR.defaultBlockState();
++        // CraftBukkit start
++        if (!(level instanceof ServerLevel)) return this.canSurvive(state, level, pos) ? (BlockState) this.getStateWithAge(level, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation
++        if (!this.canSurvive(state, level, pos)) {
++            // Suppress during worldgen
++            if (!(level instanceof Level world1)) {
++                return Blocks.AIR.defaultBlockState();
++            }
++            org.bukkit.craftbukkit.block.CraftBlockState blockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world1, pos);
++            blockState.setData(Blocks.AIR.defaultBlockState());
++
++            org.bukkit.event.block.BlockFadeEvent event = new org.bukkit.event.block.BlockFadeEvent(blockState.getBlock(), blockState);
++            world1.getCraftServer().getPluginManager().callEvent(event);
++
++            if (!event.isCancelled()) {
++                return blockState.getHandle();
++            }
++        }
++        return this.getStateWithAge(level, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation"
++        // CraftBukkit end
+     }
+ 
+     @Override
+@@ -162,10 +_,10 @@
+ 
+     @Override
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        level.scheduleTick(pos, this, getFireTickDelay(level.random));
++        level.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(level)); // Paper - Add fire-tick-delay option
+         if (level.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
+             if (!state.canSurvive(level, pos)) {
+-                level.removeBlock(pos, false);
++                this.fireExtinguished(level, pos); // CraftBukkit - invalid place location
+             }
+ 
+             BlockState blockState = level.getBlockState(pos.below());
+@@ -184,26 +_,28 @@
+                     if (!this.isValidFireLocation(level, pos)) {
+                         BlockPos blockPos = pos.below();
+                         if (!level.getBlockState(blockPos).isFaceSturdy(level, blockPos, Direction.UP) || ageValue > 3) {
+-                            level.removeBlock(pos, false);
++                            this.fireExtinguished(level, pos); // CraftBukkit - extinguished by rain
+                         }
+ 
+                         return;
+                     }
+ 
+                     if (ageValue == 15 && random.nextInt(4) == 0 && !this.canBurn(level.getBlockState(pos.below()))) {
+-                        level.removeBlock(pos, false);
++                        this.fireExtinguished(level, pos); // CraftBukkit
+                         return;
+                     }
+                 }
+ 
+                 boolean isIncreasedFireBurnout = level.getBiome(pos).is(BiomeTags.INCREASED_FIRE_BURNOUT);
+                 int i = isIncreasedFireBurnout ? -50 : 0;
+-                this.checkBurnOut(level, pos.east(), 300 + i, random, ageValue);
+-                this.checkBurnOut(level, pos.west(), 300 + i, random, ageValue);
+-                this.checkBurnOut(level, pos.below(), 250 + i, random, ageValue);
+-                this.checkBurnOut(level, pos.above(), 250 + i, random, ageValue);
+-                this.checkBurnOut(level, pos.north(), 300 + i, random, ageValue);
+-                this.checkBurnOut(level, pos.south(), 300 + i, random, ageValue);
++                // CraftBukkit start - add source blockposition to burn calls
++                this.checkBurnOut(level, pos.east(), 300 + i, random, ageValue, pos);
++                this.checkBurnOut(level, pos.west(), 300 + i, random, ageValue, pos);
++                this.checkBurnOut(level, pos.below(), 250 + i, random, ageValue, pos);
++                this.checkBurnOut(level, pos.above(), 250 + i, random, ageValue, pos);
++                this.checkBurnOut(level, pos.north(), 300 + i, random, ageValue, pos);
++                this.checkBurnOut(level, pos.south(), 300 + i, random, ageValue, pos);
++                // CraftBukkit end
+                 BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+ 
+                 for (int i1 = -1; i1 <= 1; i1++) {
+@@ -225,7 +_,15 @@
+ 
+                                     if (i5 > 0 && random.nextInt(i4) <= i5 && (!level.isRaining() || !this.isNearRain(level, mutableBlockPos))) {
+                                         int min1 = Math.min(15, ageValue + random.nextInt(5) / 4);
+-                                        level.setBlock(mutableBlockPos, this.getStateWithAge(level, mutableBlockPos, min1), 3);
++                                        // CraftBukkit start - Call to stop spread of fire
++                                        if (level.getBlockState(mutableBlockPos).getBlock() != Blocks.FIRE) {
++                                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, mutableBlockPos, pos).isCancelled()) {
++                                                continue;
++                                            }
++
++                                            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, mutableBlockPos, this.getStateWithAge(level, mutableBlockPos, min1), 3); // CraftBukkit
++                                        }
++                                        // CraftBukkit end
+                                     }
+                                 }
+                             }
+@@ -256,19 +_,42 @@
+             : this.igniteOdds.getInt(state.getBlock());
+     }
+ 
+-    private void checkBurnOut(Level level, BlockPos pos, int chance, RandomSource random, int age) {
++    private void checkBurnOut(Level level, BlockPos pos, int chance, RandomSource random, int age, BlockPos sourceposition) { // CraftBukkit add sourceposition
+         int burnOdds = this.getBurnOdds(level.getBlockState(pos));
+         if (random.nextInt(chance) < burnOdds) {
+             BlockState blockState = level.getBlockState(pos);
++
++            // CraftBukkit start
++            org.bukkit.block.Block theBlock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            org.bukkit.block.Block sourceBlock = level.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ());
++
++            org.bukkit.event.block.BlockBurnEvent event = new org.bukkit.event.block.BlockBurnEvent(theBlock, sourceBlock);
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            if (event.isCancelled()) {
++                return;
++            }
++
++            if (blockState.getBlock() instanceof TntBlock && !org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) {
++                return;
++            }
++            // CraftBukkit end
+             if (random.nextInt(age + 10) < 5 && !level.isRainingAt(pos)) {
+                 int min = Math.min(age + random.nextInt(5) / 4, 15);
+                 level.setBlock(pos, this.getStateWithAge(level, pos, min), 3);
+             } else {
+-                level.removeBlock(pos, false);
++                if(blockState.getBlock() != Blocks.TNT) level.removeBlock(pos, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down
+             }
+ 
+             Block block = blockState.getBlock();
+             if (block instanceof TntBlock) {
++                // Paper start - TNTPrimeEvent
++                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) {
++                    return;
++                }
++                level.removeBlock(pos, false);
++                // Paper end - TNTPrimeEvent
+                 TntBlock.explode(level, pos);
+             }
+         }
+@@ -310,13 +_,14 @@
+     }
+ 
+     @Override
+-    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+-        super.onPlace(state, level, pos, oldState, isMoving);
+-        level.scheduleTick(pos, this, getFireTickDelay(level.random));
++    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving, UseOnContext context) {
++        super.onPlace(state, level, pos, oldState, isMoving, context);
++        // CraftBukkit end
++        level.scheduleTick(pos, this, FireBlock.getFireTickDelay(level)); // Paper - Add fire-tick-delay option
+     }
+ 
+-    private static int getFireTickDelay(RandomSource random) {
+-        return 30 + random.nextInt(10);
++    private static int getFireTickDelay(Level world) { // Paper - Add fire-tick-delay option
++        return world.paperConfig().environment.fireTickDelay + world.random.nextInt(10); // Paper - Add fire-tick-delay option
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FlowerPotBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
similarity index 91%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/FlowerPotBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
index a2ee2e43ae..bf55cfd303 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FlowerPotBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/FlowerPotBlock.java
 +++ b/net/minecraft/world/level/block/FlowerPotBlock.java
-@@ -63,6 +63,18 @@
+@@ -67,6 +_,18 @@
          } else if (!this.isEmpty()) {
              return InteractionResult.CONSUME;
          } else {
 +            // Paper start - Add PlayerFlowerPotManipulateEvent
-+            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
 +            org.bukkit.inventory.ItemStack placedStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack);
 +
 +            io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, placedStack, true);
@@ -16,10 +16,10 @@
 +                return InteractionResult.CONSUME;
 +            }
 +            // Paper end - Add PlayerFlowerPotManipulateEvent
-             world.setBlock(pos, blockState, 3);
-             world.gameEvent(player, GameEvent.BLOCK_CHANGE, pos);
+             level.setBlock(pos, blockState, 3);
+             level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos);
              player.awardStat(Stats.POT_FLOWER);
-@@ -77,6 +89,18 @@
+@@ -81,6 +_,18 @@
              return InteractionResult.CONSUME;
          } else {
              ItemStack itemStack = new ItemStack(this.potted);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch
new file mode 100644
index 0000000000..961a62ea3e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FrogspawnBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/FrogspawnBlock.java
++++ b/net/minecraft/world/level/block/FrogspawnBlock.java
+@@ -89,6 +_,7 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (entity.getType().equals(EntityType.FALLING_BLOCK)) {
+             this.destroyBlock(level, pos);
+         }
+@@ -101,6 +_,11 @@
+     }
+ 
+     private void hatchFrogspawn(ServerLevel level, BlockPos pos, RandomSource random) {
++        // Paper start - Call BlockFadeEvent
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
++            return;
++        }
++        // Paper end - Call BlockFadeEven
+         this.destroyBlock(level, pos);
+         level.playSound(null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F);
+         this.spawnTadpoles(level, pos, random);
+@@ -121,7 +_,7 @@
+                 int randomInt1 = random.nextInt(1, 361);
+                 tadpole.moveTo(d, pos.getY() - 0.5, d1, randomInt1, 0.0F);
+                 tadpole.setPersistenceRequired();
+-                level.addFreshEntity(tadpole);
++                level.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason
+             }
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch
new file mode 100644
index 0000000000..0f1075049a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FrostedIceBlock.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/block/FrostedIceBlock.java
++++ b/net/minecraft/world/level/block/FrostedIceBlock.java
+@@ -42,6 +_,7 @@
+ 
+     @Override
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
++        if (!level.paperConfig().environment.frostedIce.enabled) return; // Paper - Frosted ice options
+         if ((random.nextInt(3) == 0 || this.fewerNeigboursThan(level, pos, 4))
+             && level.getMaxLocalRawBrightness(pos) > 11 - state.getValue(AGE) - state.getLightBlock()
+             && this.slightlyMelt(state, level, pos)) {
+@@ -51,11 +_,11 @@
+                 mutableBlockPos.setWithOffset(pos, direction);
+                 BlockState blockState = level.getBlockState(mutableBlockPos);
+                 if (blockState.is(this) && !this.slightlyMelt(blockState, level, mutableBlockPos)) {
+-                    level.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, 20, 40));
++                    level.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, level.paperConfig().environment.frostedIce.delay.min, level.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options
+                 }
+             }
+         } else {
+-            level.scheduleTick(pos, this, Mth.nextInt(random, 20, 40));
++            level.scheduleTick(pos, this, Mth.nextInt(random, level.paperConfig().environment.frostedIce.delay.min, level.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch
new file mode 100644
index 0000000000..1e6860b8a1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/FungusBlock.java
++++ b/net/minecraft/world/level/block/FungusBlock.java
+@@ -72,6 +_,17 @@
+ 
+     @Override
+     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+-        this.getFeature(level).ifPresent(holder -> holder.value().place(level, level.getChunkSource().getGenerator(), random, pos));
++        this.getFeature(level)
++            // CraftBukkit start
++            .map((value) -> {
++                if (this == Blocks.WARPED_FUNGUS) {
++                    SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS;
++                } else if (this == Blocks.CRIMSON_FUNGUS) {
++                    SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS;
++                }
++                return value;
++            })
++            .ifPresent(holder -> holder.value().place(level, level.getChunkSource().getGenerator(), random, pos));
++        // CraftBukkit end
+     }
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FurnaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/FurnaceBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch
index ffebc4893d..c383a4b8f2 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FurnaceBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FurnaceBlock.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/level/block/FurnaceBlock.java
 +++ b/net/minecraft/world/level/block/FurnaceBlock.java
-@@ -45,8 +45,7 @@
+@@ -45,8 +_,7 @@
      @Override
-     protected void openContainer(Level world, BlockPos pos, Player player) {
-         BlockEntity blockEntity = world.getBlockEntity(pos);
+     protected void openContainer(Level level, BlockPos pos, Player player) {
+         BlockEntity blockEntity = level.getBlockEntity(pos);
 -        if (blockEntity instanceof FurnaceBlockEntity) {
 -            player.openMenu((MenuProvider)blockEntity);
 +        if (blockEntity instanceof FurnaceBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch
new file mode 100644
index 0000000000..f242c126d6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/GrindstoneBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/GrindstoneBlock.java
++++ b/net/minecraft/world/level/block/GrindstoneBlock.java
+@@ -151,8 +_,7 @@
+ 
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++        if (!level.isClientSide && player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+             player.awardStat(Stats.INTERACT_WITH_GRINDSTONE);
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
new file mode 100644
index 0000000000..ddbe3daa0a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+@@ -44,14 +_,31 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (state.getValue(AGE) < 25 && random.nextDouble() < this.growPerTickProbability) {
++        // Spigot start
++        int modifier;
++        if (this == Blocks.KELP) {
++            modifier = level.spigotConfig.kelpModifier;
++        } else if (this == Blocks.TWISTING_VINES) {
++            modifier = level.spigotConfig.twistingVinesModifier;
++        } else if (this == Blocks.WEEPING_VINES) {
++            modifier = level.spigotConfig.weepingVinesModifier;
++        } else {
++            modifier = level.spigotConfig.caveVinesModifier;
++        }
++             if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
++        // Spigot end
+             BlockPos blockPos = pos.relative(this.growthDirection);
+             if (this.canGrowInto(level.getBlockState(blockPos))) {
+-                level.setBlockAndUpdate(blockPos, this.getGrowIntoState(state, level.random));
++                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, this.getGrowIntoState(state, level.random, level)); // CraftBukkit // Paper - Fix Spigot growth modifiers
+             }
+         }
+     }
+ 
++    // Paper start - Fix Spigot growth modifiers
++    protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
++        return this.getGrowIntoState(state, random);
++    }
++    // Paper end - Fix Spigot growth modifiers
+     protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
+         return state.cycle(AGE);
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/HoneyBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/HoneyBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch
index 4609ae3eee..36a0794c0b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/HoneyBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/HoneyBlock.java
 +++ b/net/minecraft/world/level/block/HoneyBlock.java
-@@ -60,6 +60,7 @@
+@@ -60,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
          if (this.isSlidingDown(pos, entity)) {
              this.maybeDoSlideAchievement(entity, pos);
              this.doSlideMovement(entity);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/HopperBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch
similarity index 61%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/HopperBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch
index 3e829e33d0..b33d6bde71 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/HopperBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/HopperBlock.java.patch
@@ -1,20 +1,20 @@
 --- a/net/minecraft/world/level/block/HopperBlock.java
 +++ b/net/minecraft/world/level/block/HopperBlock.java
-@@ -125,8 +125,7 @@
+@@ -125,8 +_,7 @@
  
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
--        if (!world.isClientSide && world.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity) {
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide && level.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity) {
 -            player.openMenu(hopperBlockEntity);
-+        if (!world.isClientSide && world.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity && player.openMenu(hopperBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (!level.isClientSide && level.getBlockEntity(pos) instanceof HopperBlockEntity hopperBlockEntity && player.openMenu(hopperBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INSPECT_HOPPER);
          }
  
-@@ -178,6 +177,7 @@
+@@ -178,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         BlockEntity blockEntity = world.getBlockEntity(pos);
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         BlockEntity blockEntity = level.getBlockEntity(pos);
          if (blockEntity instanceof HopperBlockEntity) {
-             HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity);
+             HopperBlockEntity.entityInside(level, pos, state, entity, (HopperBlockEntity)blockEntity);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/HugeMushroomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch
similarity index 66%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/HugeMushroomBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch
index fb60ae21d7..9a4c3e64c9 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/HugeMushroomBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/HugeMushroomBlock.java.patch
@@ -1,30 +1,30 @@
 --- a/net/minecraft/world/level/block/HugeMushroomBlock.java
 +++ b/net/minecraft/world/level/block/HugeMushroomBlock.java
-@@ -45,6 +45,7 @@
+@@ -45,6 +_,7 @@
  
      @Override
-     public BlockState getStateForPlacement(BlockPlaceContext ctx) {
+     public BlockState getStateForPlacement(BlockPlaceContext context) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return this.defaultBlockState(); // Paper - add option to disable block updates
-         BlockGetter blockGetter = ctx.getLevel();
-         BlockPos blockPos = ctx.getClickedPos();
+         BlockGetter level = context.getLevel();
+         BlockPos clickedPos = context.getClickedPos();
          return this.defaultBlockState()
-@@ -67,6 +68,7 @@
+@@ -67,6 +_,7 @@
          BlockState neighborState,
          RandomSource random
      ) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates
          return neighborState.is(this)
              ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(false))
-             : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-@@ -74,6 +76,7 @@
+             : super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
+@@ -74,6 +_,7 @@
  
      @Override
-     protected BlockState rotate(BlockState state, Rotation rotation) {
+     protected BlockState rotate(BlockState state, Rotation rot) {
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableMushroomBlockUpdates) return state; // Paper - add option to disable block updates
-         return state.setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH))
-             .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH))
-             .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST))
-@@ -84,6 +87,7 @@
+         return state.setValue(PROPERTY_BY_DIRECTION.get(rot.rotate(Direction.NORTH)), state.getValue(NORTH))
+             .setValue(PROPERTY_BY_DIRECTION.get(rot.rotate(Direction.SOUTH)), state.getValue(SOUTH))
+             .setValue(PROPERTY_BY_DIRECTION.get(rot.rotate(Direction.EAST)), state.getValue(EAST))
+@@ -84,6 +_,7 @@
  
      @Override
      protected BlockState mirror(BlockState state, Mirror mirror) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch
new file mode 100644
index 0000000000..b89beb8bf6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/IceBlock.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/block/IceBlock.java
++++ b/net/minecraft/world/level/block/IceBlock.java
+@@ -32,8 +_,13 @@
+     }
+ 
+     @Override
+-    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
+-        super.playerDestroy(level, player, pos, state, te, stack);
++    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
++        super.playerDestroy(level, player, pos, state, te, stack, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
++        // Paper start - Improve Block#breakNaturally API
++        this.afterDestroy(level, pos, stack);
++    }
++    public void afterDestroy(Level level, BlockPos pos, ItemStack stack) {
++        // Paper end - Improve Block#breakNaturally API
+         if (!EnchantmentHelper.hasTag(stack, EnchantmentTags.PREVENTS_ICE_MELTING)) {
+             if (level.dimensionType().ultraWarm()) {
+                 level.removeBlock(pos, false);
+@@ -55,6 +_,11 @@
+     }
+ 
+     protected void melt(BlockState state, Level level, BlockPos pos) {
++        // CraftBukkit start
++        if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, level.dimensionType().ultraWarm() ? Blocks.AIR.defaultBlockState() : Blocks.WATER.defaultBlockState()).isCancelled()) {
++            return;
++        }
++        // CraftBukkit end
+         if (level.dimensionType().ultraWarm()) {
+             level.removeBlock(pos, false);
+         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch
new file mode 100644
index 0000000000..23815e6bfd
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/InfestedBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/InfestedBlock.java
++++ b/net/minecraft/world/level/block/InfestedBlock.java
+@@ -52,7 +_,7 @@
+         Silverfish silverfish = EntityType.SILVERFISH.create(level, EntitySpawnReason.TRIGGERED);
+         if (silverfish != null) {
+             silverfish.moveTo(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0.0F, 0.0F);
+-            level.addFreshEntity(silverfish);
++            level.addFreshEntity(silverfish, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason
+             silverfish.spawnAnim();
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LavaCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch
similarity index 74%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/LavaCauldronBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch
index b8db542f5e..4c96395fdf 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LavaCauldronBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LavaCauldronBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/LavaCauldronBlock.java
 +++ b/net/minecraft/world/level/block/LavaCauldronBlock.java
-@@ -32,6 +32,7 @@
+@@ -32,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
          if (this.isEntityInsideContent(state, pos, entity)) {
              entity.lavaHurt();
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
new file mode 100644
index 0000000000..3bd7696353
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
@@ -0,0 +1,109 @@
+--- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -61,35 +_,79 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (level instanceof ServerLevel serverLevel && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) {
+-            entity.clearFire();
+-            if (entity.mayInteract(serverLevel, pos)) {
+-                this.handleEntityOnFireInside(state, level, pos);
++            // CraftBukkit start - moved down
++            // entity.clearFire();
++            if ((entity instanceof net.minecraft.world.entity.player.Player || serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(serverLevel, pos)) { // Paper - Fixes MC-248588
++                if (this.handleEntityOnFireInsideWithEvent(state, level, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities
++                    entity.clearFire();
++                }
++            // CraftBukkit end
+             }
+         }
+     }
+ 
+-    private void handleEntityOnFireInside(BlockState state, Level level, BlockPos pos) {
++    // CraftBukkit start
++    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent
++    private boolean handleEntityOnFireInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+         if (this.precipitationType == Biome.Precipitation.SNOW) {
+-            lowerFillLevel(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), level, pos);
++            return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), level, pos, entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); // CraftBukkit
+         } else {
+-            lowerFillLevel(state, level, pos);
++            return LayeredCauldronBlock.lowerFillLevel(state, level, pos, entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); // CraftBukkit
+         }
+     }
+ 
+     public static void lowerFillLevel(BlockState state, Level level, BlockPos pos) {
+-        int i = state.getValue(LEVEL) - 1;
+-        BlockState blockState = i == 0 ? Blocks.CAULDRON.defaultBlockState() : state.setValue(LEVEL, Integer.valueOf(i));
+-        level.setBlockAndUpdate(pos, blockState);
+-        level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
+-    }
++        // CraftBukkit start
++        LayeredCauldronBlock.lowerFillLevel(state, level, pos, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.UNKNOWN);
++    }
++    public static boolean lowerFillLevel(BlockState state, Level level, BlockPos BlockPos, Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) {
++        int i = (Integer) state.getValue(LayeredCauldronBlock.LEVEL) - 1;
++        BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, i);
++
++        return LayeredCauldronBlock.changeLevel(state, level, BlockPos, iblockdata1, entity, reason);
++     }
++    // Paper start - fix powdered snow cauldron extinguishing entities
++    protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) {
++        if (this.precipitationType == Biome.Precipitation.SNOW) {
++            return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
++        } else {
++            return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
++        }
++    }
++    // Paper end - fix powdered snow cauldron extinguishing entities
++
++    // CraftBukkit start
++    // Paper start - Call CauldronLevelChangeEvent
++    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
++        return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true);
++    }
++
++    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
++    // Paper end - Call CauldronLevelChangeEvent
++        org.bukkit.craftbukkit.block.CraftBlockState newState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition);
++        newState.setData(newBlock);
++
++        org.bukkit.event.block.CauldronLevelChangeEvent event = new org.bukkit.event.block.CauldronLevelChangeEvent(
++                world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()),
++                (entity == null) ? null : entity.getBukkitEntity(), reason, newState
++        );
++        world.getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return false;
++        }
++        newState.update(true);
++        if (sendGameEvent) world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent
++        return true;
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public void handlePrecipitation(BlockState state, Level level, BlockPos pos, Biome.Precipitation precipitation) {
+         if (CauldronBlock.shouldHandlePrecipitation(level, precipitation) && state.getValue(LEVEL) != 3 && precipitation == this.precipitationType) {
+             BlockState blockState = state.cycle(LEVEL);
+-            level.setBlockAndUpdate(pos, blockState);
+-            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++            LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
+         }
+     }
+ 
+@@ -107,8 +_,11 @@
+     protected void receiveStalactiteDrip(BlockState state, Level level, BlockPos pos, Fluid fluid) {
+         if (!this.isFull(state)) {
+             BlockState blockState = state.setValue(LEVEL, Integer.valueOf(state.getValue(LEVEL) + 1));
+-            level.setBlockAndUpdate(pos, blockState);
+-            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++            // CraftBukkit start
++            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) {
++                return;
++            }
++            // CraftBukkit end
+             level.levelEvent(1047, pos, 0);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch
new file mode 100644
index 0000000000..b186e88130
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LeavesBlock.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/level/block/LeavesBlock.java
++++ b/net/minecraft/world/level/block/LeavesBlock.java
+@@ -63,6 +_,14 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (this.decaying(state)) {
++            // CraftBukkit start
++            org.bukkit.event.block.LeavesDecayEvent event = new org.bukkit.event.block.LeavesDecayEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            if (event.isCancelled() || level.getBlockState(pos).getBlock() != this) {
++                return;
++            }
++            // CraftBukkit end
+             dropResources(state, level, pos);
+             level.removeBlock(pos, false);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch
new file mode 100644
index 0000000000..fffc21a6fe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LecternBlock.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/level/block/LecternBlock.java
++++ b/net/minecraft/world/level/block/LecternBlock.java
+@@ -169,7 +_,24 @@
+ 
+     private static void placeBook(@Nullable LivingEntity entity, Level level, BlockPos pos, BlockState state, ItemStack stack) {
+         if (level.getBlockEntity(pos) instanceof LecternBlockEntity lecternBlockEntity) {
+-            lecternBlockEntity.setBook(stack.consumeAndReturn(1, entity));
++            // Paper start - Add PlayerInsertLecternBookEvent
++            ItemStack eventSourcedBookStack = null;
++            if (entity instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) {
++                final io.papermc.paper.event.player.PlayerInsertLecternBookEvent event = new io.papermc.paper.event.player.PlayerInsertLecternBookEvent(
++                    serverPlayer.getBukkitEntity(),
++                    org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
++                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack.copyWithCount(1))
++                );
++                if (!event.callEvent()) return;
++                eventSourcedBookStack = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getBook());
++            }
++            if (eventSourcedBookStack == null) {
++                eventSourcedBookStack = stack.consumeAndReturn(1, entity);
++            } else {
++                stack.consume(1, entity);
++            }
++            lecternBlockEntity.setBook(eventSourcedBookStack);
++            // Paper end - Add PlayerInsertLecternBookEvent
+             resetBookState(entity, level, pos, state, true);
+             level.playSound(null, pos, SoundEvents.BOOK_PUT, SoundSource.BLOCKS, 1.0F, 1.0F);
+         }
+@@ -189,6 +_,16 @@
+     }
+ 
+     private static void changePowered(Level level, BlockPos pos, BlockState state, boolean powered) {
++        // Paper start - Call BlockRedstoneEvent properly
++        final int currentRedstoneLevel = state.getValue(LecternBlock.POWERED) ? 15 : 0, targetRedstoneLevel = powered ? 15 : 0;
++        if (currentRedstoneLevel != targetRedstoneLevel) {
++            final org.bukkit.event.block.BlockRedstoneEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, currentRedstoneLevel, targetRedstoneLevel);
++
++            if (event.getNewCurrent() != targetRedstoneLevel) {
++                return;
++            }
++        }
++        // Paper end - Call BlockRedstoneEvent properly
+         level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(powered)), 3);
+         updateBelow(level, pos, state);
+     }
+@@ -218,9 +_,10 @@
+     }
+ 
+     private void popBook(BlockState state, Level level, BlockPos pos) {
+-        if (level.getBlockEntity(pos) instanceof LecternBlockEntity lecternBlockEntity) {
++        if (level.getBlockEntity(pos, false) instanceof LecternBlockEntity lecternBlockEntity) { // CraftBukkit - don't validate, type may be changed already
+             Direction direction = state.getValue(FACING);
+             ItemStack itemStack = lecternBlockEntity.getBook().copy();
++            if (itemStack.isEmpty()) return; // CraftBukkit - SPIGOT-5500
+             float f = 0.25F * direction.getStepX();
+             float f1 = 0.25F * direction.getStepZ();
+             ItemEntity itemEntity = new ItemEntity(level, pos.getX() + 0.5 + f, pos.getY() + 1, pos.getZ() + 0.5 + f1, itemStack);
+@@ -296,8 +_,7 @@
+ 
+     private void openScreen(Level level, BlockPos pos, Player player) {
+         BlockEntity blockEntity = level.getBlockEntity(pos);
+-        if (blockEntity instanceof LecternBlockEntity) {
+-            player.openMenu((LecternBlockEntity)blockEntity);
++        if (blockEntity instanceof LecternBlockEntity lecternBlockEntity && player.openMenu(lecternBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+             player.awardStat(Stats.INTERACT_WITH_LECTERN);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch
new file mode 100644
index 0000000000..98f131c9db
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LeverBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/LeverBlock.java
++++ b/net/minecraft/world/level/block/LeverBlock.java
+@@ -100,6 +_,19 @@
+                 makeParticle(blockState, level, pos, 1.0F);
+             }
+         } else {
++            // CraftBukkit start - Interact Lever
++            boolean powered = state.getValue(LeverBlock.POWERED); // Old powered state
++            org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            int old = (powered) ? 15 : 0;
++            int current = (!powered) ? 15 : 0;
++
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(block, old, current);
++            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++            if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++                return InteractionResult.SUCCESS;
++            }
++            // CraftBukkit end
+             this.pull(state, level, pos, null);
+         }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LightBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch
similarity index 82%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/LightBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch
index 4e34991e01..c9fda343b0 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LightBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LightBlock.java.patch
@@ -1,18 +1,16 @@
 --- a/net/minecraft/world/level/block/LightBlock.java
 +++ b/net/minecraft/world/level/block/LightBlock.java
-@@ -50,7 +50,15 @@
+@@ -49,6 +_,13 @@
+     protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
          builder.add(LEVEL, WATERLOGGED);
      }
- 
 +    // Paper start - prevent unintended light block manipulation
-     @Override
++    @Override
 +    protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) {
 +        if (player.getItemInHand(hand).getItem() != Items.LIGHT || (world instanceof final net.minecraft.server.level.ServerLevel serverLevel && !player.mayInteract(serverLevel, pos)) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return net.minecraft.world.InteractionResult.PASS; } // Paper - Prevent unintended light block manipulation
 +        return super.useItemOn(stack, state, world, pos, player, hand, hit);
 +    }
 +    // Paper end - prevent unintended light block manipulation
-+
-+    @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide && player.canUseGameMasterBlocks()) {
-             world.setBlock(pos, state.cycle(LEVEL), 2);
+ 
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch
new file mode 100644
index 0000000000..7480d6343d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/LightningRodBlock.java
++++ b/net/minecraft/world/level/block/LightningRodBlock.java
+@@ -84,6 +_,18 @@
+     }
+ 
+     public void onLightningStrike(BlockState state, Level level, BlockPos pos) {
++        // CraftBukkit start
++        boolean powered = state.getValue(LightningRodBlock.POWERED);
++        int old = (powered) ? 15 : 0;
++        int current = (!powered) ? 15 : 0;
++
++        org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), old, current);
++        level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++        if (eventRedstone.getNewCurrent() <= 0) {
++            return;
++        }
++        // CraftBukkit end
+         level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 3);
+         this.updateNeighbours(state, level, pos);
+         level.scheduleTick(pos, this, 8);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
new file mode 100644
index 0000000000..3589e09e7d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/level/block/LiquidBlock.java
++++ b/net/minecraft/world/level/block/LiquidBlock.java
+@@ -135,9 +_,28 @@
+     @Override
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+         if (this.shouldSpreadLiquid(level, pos, state)) {
+-            level.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level));
+-        }
+-    }
++            level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
++        }
++    }
++    // Paper start - Configurable speed for water flowing over lava
++    public int getFlowSpeed(Level world, BlockPos blockposition) {
++        if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) {
++            if (
++                isLava(world, blockposition.north(1)) ||
++                isLava(world, blockposition.south(1)) ||
++                isLava(world, blockposition.west(1)) ||
++                isLava(world, blockposition.east(1))
++            ) {
++                return world.paperConfig().environment.waterOverLavaFlowSpeed;
++            }
++        }
++        return this.fluid.getTickDelay(world);
++    }
++    private static boolean isLava(Level world, BlockPos blockPos) {
++        final FluidState fluidState = world.getFluidIfLoaded(blockPos);
++        return fluidState != null && fluidState.is(FluidTags.LAVA);
++    }
++    // Paper end - Configurable speed for water flowing over lava
+ 
+     @Override
+     protected BlockState updateShape(
+@@ -160,7 +_,7 @@
+     @Override
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
+         if (this.shouldSpreadLiquid(level, pos, state)) {
+-            level.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(level));
++            level.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(level, pos)); // Paper - Configurable speed for water flowing over lava
+         }
+     }
+ 
+@@ -173,13 +_,21 @@
+                 if (level.getFluidState(blockPos).is(FluidTags.WATER)) {
+                     Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
+                     level.setBlockAndUpdate(pos, block.defaultBlockState());
+-                    this.fizz(level, pos);
++                    // CraftBukkit start
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) {
++                        this.fizz(level, pos);
++                    }
++                    // CraftBukkit end
+                     return false;
+                 }
+ 
+                 if (isSoulSoil && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) {
+                     level.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState());
+-                    this.fizz(level, pos);
++                    // CraftBukkit start
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, Blocks.BASALT.defaultBlockState())) {
++                        this.fizz(level, pos);
++                    }
++                    // CraftBukkit end
+                     return false;
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch
new file mode 100644
index 0000000000..04d34f0a8c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LoomBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/LoomBlock.java
++++ b/net/minecraft/world/level/block/LoomBlock.java
+@@ -32,8 +_,7 @@
+ 
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+-        if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++        if (!level.isClientSide && player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+             player.awardStat(Stats.INTERACT_WITH_LOOM);
+         }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch
new file mode 100644
index 0000000000..ae7d0a7fd6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/MagmaBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/MagmaBlock.java
++++ b/net/minecraft/world/level/block/MagmaBlock.java
+@@ -29,7 +_,7 @@
+     @Override
+     public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+         if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) {
+-            entity.hurt(level.damageSources().hotFloor(), 1.0F);
++            entity.hurt(level.damageSources().hotFloor().directBlock(level, pos), 1.0F); // CraftBukkit
+         }
+ 
+         super.stepOn(level, pos, state, entity);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch
index 8a2e3114dc..0ebbe0b613 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/MangrovePropaguleBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/MangrovePropaguleBlock.java
 +++ b/net/minecraft/world/level/block/MangrovePropaguleBlock.java
-@@ -123,7 +123,7 @@
+@@ -123,7 +_,7 @@
      @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
          if (!isHanging(state)) {
 -            if (random.nextInt(7) == 0) {
-+            if (random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers
-                 this.advanceTree(world, pos, state, random);
++            if (random.nextFloat() < (level.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers
+                 this.advanceTree(level, pos, state, random);
              }
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch
new file mode 100644
index 0000000000..20085ae07e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/MultifaceSpreader.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/level/block/MultifaceSpreader.java
++++ b/net/minecraft/world/level/block/MultifaceSpreader.java
+@@ -154,14 +_,14 @@
+                     level.getChunk(pos.pos()).markPosForPostprocessing(pos.pos());
+                 }
+ 
+-                return level.setBlock(pos.pos(), stateForPlacement, 2);
++                return org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos.source(), pos.pos(), stateForPlacement, 2); // CraftBukkit
+             } else {
+                 return false;
+             }
+         }
+     }
+ 
+-    public record SpreadPos(BlockPos pos, Direction face) {
++    public record SpreadPos(BlockPos pos, Direction face, BlockPos source) { // CraftBukkit
+     }
+ 
+     @FunctionalInterface
+@@ -173,19 +_,19 @@
+         SAME_POSITION {
+             @Override
+             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+-                return new MultifaceSpreader.SpreadPos(pos, face);
++                return new MultifaceSpreader.SpreadPos(pos, face, pos); // CraftBukkit
+             }
+         },
+         SAME_PLANE {
+             @Override
+             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+-                return new MultifaceSpreader.SpreadPos(pos.relative(face), spreadDirection);
++                return new MultifaceSpreader.SpreadPos(pos.relative(face), spreadDirection, pos); // CraftBukkit
+             }
+         },
+         WRAP_AROUND {
+             @Override
+             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+-                return new MultifaceSpreader.SpreadPos(pos.relative(face).relative(spreadDirection), face.getOpposite());
++                return new MultifaceSpreader.SpreadPos(pos.relative(face).relative(spreadDirection), face.getOpposite(), pos); // CraftBukkit
+             }
+         };
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch
new file mode 100644
index 0000000000..188da77aec
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/block/MushroomBlock.java
++++ b/net/minecraft/world/level/block/MushroomBlock.java
+@@ -47,7 +_,7 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (random.nextInt(25) == 0) {
++        if (random.nextFloat() < (level.spigotConfig.mushroomModifier / (100.0f * 25))) { // Spigot - SPIGOT-7159: Better modifier resolution
+             int i = 5;
+             int i1 = 4;
+ 
+@@ -60,6 +_,7 @@
+             }
+ 
+             BlockPos blockPos1 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1);
++            final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event
+ 
+             for (int i2 = 0; i2 < 4; i2++) {
+                 if (level.isEmptyBlock(blockPos1) && state.canSurvive(level, blockPos1)) {
+@@ -70,7 +_,7 @@
+             }
+ 
+             if (level.isEmptyBlock(blockPos1) && state.canSurvive(level, blockPos1)) {
+-                level.setBlock(blockPos1, state, 2);
++                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, sourcePos, blockPos1, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event
+             }
+         }
+     }
+@@ -93,6 +_,7 @@
+             return false;
+         } else {
+             level.removeBlock(pos, false);
++            SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit
+             if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) {
+                 return true;
+             } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
new file mode 100644
index 0000000000..d6707c3b66
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
@@ -0,0 +1,126 @@
+--- a/net/minecraft/world/level/block/NetherPortalBlock.java
++++ b/net/minecraft/world/level/block/NetherPortalBlock.java
+@@ -70,7 +_,7 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (level.dimensionType().natural()
++        if (level.spigotConfig.enableZombiePigmenPortalSpawns && level.dimensionType().natural() // Spigot
+             && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
+             && random.nextInt(2000) < level.getDifficulty().getId()) {
+             while (level.getBlockState(pos).is(this)) {
+@@ -78,9 +_,13 @@
+             }
+ 
+             if (level.getBlockState(pos).isValidSpawn(level, pos, EntityType.ZOMBIFIED_PIGLIN)) {
+-                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EntitySpawnReason.STRUCTURE);
++                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EntitySpawnReason.STRUCTURE, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL); // CraftBukkit - set spawn reason to NETHER_PORTAL
+                 if (entity != null) {
+                     entity.setPortalCooldown();
++                    // Paper start - Add option to nerf pigmen from nether portals
++                    entity.fromNetherPortal = true;
++                    if (level.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false;
++                    // Paper end - Add option to nerf pigmen from nether portals
+                     Entity vehicle = entity.getVehicle();
+                     if (vehicle != null) {
+                         vehicle.setPortalCooldown();
+@@ -111,7 +_,13 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (entity.canUsePortal(false)) {
++            // CraftBukkit start - Entity in portal
++            org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type
++            level.getCraftServer().getPluginManager().callEvent(event);
++            if (event.isCancelled()) return; // Paper - make cancellable
++            // CraftBukkit end
+             entity.setAsInsidePortal(this, pos);
+         }
+     }
+@@ -134,22 +_,46 @@
+     @Nullable
+     @Override
+     public TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos) {
+-        ResourceKey<Level> resourceKey = level.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
++        // CraftBukkit start
++        ResourceKey<Level> resourceKey = level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER;
+         ServerLevel level1 = level.getServer().getLevel(resourceKey);
++        // Paper start - Add EntityPortalReadyEvent
++        io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), level1 == null ? null : level1.getWorld(), org.bukkit.PortalType.NETHER);
++        if (!portalReadyEvent.callEvent()) {
++            entity.portalProcess = null;
++            return null;
++        }
++        level1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle();
++        // Paper end - Add EntityPortalReadyEvent
+         if (level1 == null) {
+             return null;
+         } else {
+-            boolean flag = level1.dimension() == Level.NETHER;
++            boolean flag = level1.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit
+             WorldBorder worldBorder = level1.getWorldBorder();
+             double teleportationScale = DimensionType.getTeleportationScale(level.dimensionType(), level1.dimensionType());
+             BlockPos blockPos = worldBorder.clampToBounds(entity.getX() * teleportationScale, entity.getY(), entity.getZ() * teleportationScale);
++            // Paper start - Configurable portal search radius
++            int portalSearchRadius = level1.paperConfig().environment.portalSearchRadius;
++            if (entity.level().paperConfig().environment.portalSearchVanillaDimensionScaling && flag) { // flag = is going to nether
++                portalSearchRadius = (int) (portalSearchRadius / level1.dimensionType().coordinateScale());
++            }
++            // Paper end - Configurable portal search radius
++            // CraftBukkit start
++            org.bukkit.craftbukkit.event.CraftPortalEvent event = entity.callPortalEvent(entity, org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level1.getWorld()), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, level1.paperConfig().environment.portalCreateRadius); // Paper - use custom portal search radius
++            if (event == null) {
++                return null;
++            }
++            level1 = ((org.bukkit.craftbukkit.CraftWorld) event.getTo().getWorld()).getHandle();
++            worldBorder = level1.getWorldBorder();
++            blockPos = worldBorder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ());
++            // CraftBukkit end
+             return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder);
+         }
+     }
+ 
+     @Nullable
+-    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder) {
+-        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, isNether, worldBorder);
++    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit
++        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // Craftbukkit
+         BlockUtil.FoundRectangle largestRectangleAround;
+         TeleportTransition.PostTeleportTransition postTeleportTransition;
+         if (optional.isPresent()) {
+@@ -164,17 +_,22 @@
+                 blockPos1 -> level.getBlockState(blockPos1) == blockState
+             );
+             postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(entity1 -> entity1.placePortalTicket(blockPos));
+-        } else {
++        } else if (canCreatePortal) { // CraftBukkit
+             Direction.Axis axis = entity.level().getBlockState(pos).getOptionalValue(AXIS).orElse(Direction.Axis.X);
+-            Optional<BlockUtil.FoundRectangle> optional1 = level.getPortalForcer().createPortal(exitPos, axis);
++            Optional<BlockUtil.FoundRectangle> optional1 = level.getPortalForcer().createPortal(exitPos, axis, entity, createRadius); // CraftBukkit
+             if (optional1.isEmpty()) {
+-                LOGGER.error("Unable to create a portal, likely target out of worldborder");
++                // LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit
+                 return null;
+             }
+ 
+             largestRectangleAround = optional1.get();
+             postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET);
+         }
++        // CraftBukkit start
++        else {
++            return null;
++        }
++        // CraftBukkit end
+ 
+         return getDimensionTransitionFromExit(entity, pos, largestRectangleAround, level, postTeleportTransition);
+     }
+@@ -220,7 +_,7 @@
+         boolean flag = axis1 == Direction.Axis.X;
+         Vec3 vec3 = new Vec3(blockPos.getX() + (flag ? d2 : d4), blockPos.getY() + d3, blockPos.getZ() + (flag ? d4 : d2));
+         Vec3 vec31 = PortalShape.findCollisionFreePosition(vec3, level, entity, dimensions);
+-        return new TeleportTransition(level, vec31, Vec3.ZERO, i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postTeleportTransition);
++        return new TeleportTransition(level, vec31, Vec3.ZERO, i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch
new file mode 100644
index 0000000000..d55e5647cf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherWartBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/NetherWartBlock.java
++++ b/net/minecraft/world/level/block/NetherWartBlock.java
+@@ -55,9 +_,9 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         int ageValue = state.getValue(AGE);
+-        if (ageValue < 3 && random.nextInt(10) == 0) {
++        if (ageValue < 3 && random.nextFloat() < (level.spigotConfig.wartModifier / (100.0f * 10))) { // Spigot - SPIGOT-7159: Better modifier resolution
+             state = state.setValue(AGE, Integer.valueOf(ageValue + 1));
+-            level.setBlock(pos, state, 2);
++            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state, 2); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch
new file mode 100644
index 0000000000..0ff72778c3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NoteBlock.java.patch
@@ -0,0 +1,78 @@
+--- a/net/minecraft/world/level/block/NoteBlock.java
++++ b/net/minecraft/world/level/block/NoteBlock.java
+@@ -70,6 +_,7 @@
+ 
+     @Override
+     public BlockState getStateForPlacement(BlockPlaceContext context) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return this.defaultBlockState(); // Paper - place without considering instrument
+         return this.setInstrument(context.getLevel(), context.getClickedPos(), this.defaultBlockState());
+     }
+ 
+@@ -84,6 +_,7 @@
+         BlockState neighborState,
+         RandomSource random
+     ) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return state; // Paper - prevent noteblock instrument from updating
+         boolean flag = direction.getAxis() == Direction.Axis.Y;
+         return flag
+             ? this.setInstrument(level, pos, state)
+@@ -92,10 +_,12 @@
+ 
+     @Override
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return; // Paper - prevent noteblock powered-state from updating
+         boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+         if (hasNeighborSignal != state.getValue(POWERED)) {
+             if (hasNeighborSignal) {
+                 this.playNote(null, state, level, pos);
++                state = level.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event
+             }
+ 
+             level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(hasNeighborSignal)), 3);
+@@ -104,6 +_,13 @@
+ 
+     private void playNote(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
+         if (state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) {
++            // CraftBukkit start
++            // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
++            // if (event.isCancelled()) {
++            //     return;
++            // }
++            // CraftBukkit end
++            // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event?
+             level.blockEvent(pos, this, 0, 0);
+             level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, pos);
+         }
+@@ -121,7 +_,7 @@
+     @Override
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+-            state = state.cycle(NOTE);
++            if (!io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) state = state.cycle(NoteBlock.NOTE); // Paper - prevent noteblock note from updating
+             level.setBlock(pos, state, 3);
+             this.playNote(player, state, level, pos);
+             player.awardStat(Stats.TUNE_NOTEBLOCK);
+@@ -145,9 +_,13 @@
+     @Override
+     protected boolean triggerEvent(BlockState state, Level level, BlockPos pos, int id, int param) {
+         NoteBlockInstrument noteBlockInstrument = state.getValue(INSTRUMENT);
++        // Paper start - move NotePlayEvent call to fix instrument/note changes
++        org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(level, pos, noteBlockInstrument, state.getValue(NOTE));
++        if (event.isCancelled()) return false;
++        // Paper end - move NotePlayEvent call to fix instrument/note changes
+         float pitchFromNote;
+         if (noteBlockInstrument.isTunable()) {
+-            int noteValue = state.getValue(NOTE);
++            int noteValue = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes
+             pitchFromNote = getPitchFromNote(noteValue);
+             level.addParticle(ParticleTypes.NOTE, pos.getX() + 0.5, pos.getY() + 1.2, pos.getZ() + 0.5, noteValue / 24.0, 0.0, 0.0);
+         } else {
+@@ -163,7 +_,7 @@
+ 
+             holder = Holder.direct(SoundEvent.createVariableRangeEvent(customSoundId));
+         } else {
+-            holder = noteBlockInstrument.getSoundEvent();
++            holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes
+         }
+ 
+         level.playSeededSound(
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/NyliumBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/NyliumBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch
index cc4aae4d48..c447936cca 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/NyliumBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NyliumBlock.java.patch
@@ -1,14 +1,14 @@
 --- a/net/minecraft/world/level/block/NyliumBlock.java
 +++ b/net/minecraft/world/level/block/NyliumBlock.java
-@@ -41,6 +41,11 @@
+@@ -39,6 +_,11 @@
      @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (!NyliumBlock.canBeNylium(state, world, pos)) {
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (!canBeNylium(state, level, pos)) {
 +            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.NETHERRACK.defaultBlockState()).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.NETHERRACK.defaultBlockState()).isCancelled()) {
 +                return;
 +            }
 +            // CraftBukkit end
-             world.setBlockAndUpdate(pos, Blocks.NETHERRACK.defaultBlockState());
+             level.setBlockAndUpdate(pos, Blocks.NETHERRACK.defaultBlockState());
          }
- 
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch
new file mode 100644
index 0000000000..12eb553307
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ObserverBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/ObserverBlock.java
++++ b/net/minecraft/world/level/block/ObserverBlock.java
+@@ -50,8 +_,18 @@
+     @Override
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (state.getValue(POWERED)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(false)), 2);
+         } else {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
+             level.scheduleTick(pos, this, 2);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
new file mode 100644
index 0000000000..174bccf67d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/block/PitcherCropBlock.java
++++ b/net/minecraft/world/level/block/PitcherCropBlock.java
+@@ -131,7 +_,7 @@
+     @Override
+     public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         float growthSpeed = CropBlock.getGrowthSpeed(this, level, pos);
+-        boolean flag = random.nextInt((int)(25.0F / growthSpeed) + 1) == 0;
++        boolean flag = random.nextFloat() < (level.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / growthSpeed) + 1))); // Paper - Fix Spigot growth modifiers
+         if (flag) {
+             this.grow(level, state, pos, 1);
+         }
+@@ -141,7 +_,7 @@
+         int min = Math.min(state.getValue(AGE) + ageIncrement, 4);
+         if (this.canGrow(level, pos, state, min)) {
+             BlockState blockState = state.setValue(AGE, Integer.valueOf(min));
+-            level.setBlock(pos, blockState, 2);
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, 2)) return; // Paper
+             if (isDouble(min)) {
+                 level.setBlock(pos.above(), blockState.setValue(HALF, DoubleBlockHalf.UPPER), 3);
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
new file mode 100644
index 0000000000..b998179c3b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java
++++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java
+@@ -147,6 +_,11 @@
+                 && projectile.mayBreak(serverLevel)
+                 && projectile instanceof ThrownTrident
+                 && projectile.getDeltaMovement().length() > 0.6) {
++                // CraftBukkit start
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockPos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                    return;
++                }
++                // CraftBukkit end
+                 level.destroyBlock(blockPos, true);
+             }
+         }
+@@ -155,7 +_,7 @@
+     @Override
+     public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
+         if (state.getValue(TIP_DIRECTION) == Direction.UP && state.getValue(THICKNESS) == DripstoneThickness.TIP) {
+-            entity.causeFallDamage(fallDistance + 2.0F, 2.0F, level.damageSources().stalagmite());
++            entity.causeFallDamage(fallDistance + 2.0F, 2.0F, level.damageSources().stalagmite().directBlock(level, pos)); // CraftBukkit
+         } else {
+             super.fallOn(level, state, pos, entity, fallDistance);
+         }
+@@ -213,10 +_,12 @@
+                         if (blockPos != null) {
+                             if (fluidAboveStalactite.get().sourceState.is(Blocks.MUD) && fluid == Fluids.WATER) {
+                                 BlockState blockState = Blocks.CLAY.defaultBlockState();
++                                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, fluidAboveStalactite.get().pos, blockState)) { // Paper - Call BlockFormEvent
+                                 level.setBlockAndUpdate(fluidAboveStalactite.get().pos, blockState);
+                                 Block.pushEntitiesUp(fluidAboveStalactite.get().sourceState, blockState, level, fluidAboveStalactite.get().pos);
+                                 level.gameEvent(GameEvent.BLOCK_CHANGE, fluidAboveStalactite.get().pos, GameEvent.Context.of(blockState));
+                                 level.levelEvent(1504, blockPos, 0);
++                                } // Paper - Call BlockFormEvent
+                             } else {
+                                 BlockPos blockPos1 = findFillableCauldronBelowStalactiteTip(level, blockPos, fluid);
+                                 if (blockPos1 != null) {
+@@ -380,17 +_,17 @@
+         if (isUnmergedTipWithDirection(blockState, direction.getOpposite())) {
+             createMergedTips(blockState, server, blockPos);
+         } else if (blockState.isAir() || blockState.is(Blocks.WATER)) {
+-            createDripstone(server, blockPos, direction, DripstoneThickness.TIP);
++            createDripstone(server, blockPos, direction, DripstoneThickness.TIP, pos); // CraftBukkit;
+         }
+     }
+ 
+-    private static void createDripstone(LevelAccessor level, BlockPos pos, Direction direction, DripstoneThickness thickness) {
++    private static void createDripstone(LevelAccessor level, BlockPos pos, Direction direction, DripstoneThickness thickness, BlockPos source) { // CraftBukkit
+         BlockState blockState = Blocks.POINTED_DRIPSTONE
+             .defaultBlockState()
+             .setValue(TIP_DIRECTION, direction)
+             .setValue(THICKNESS, thickness)
+             .setValue(WATERLOGGED, Boolean.valueOf(level.getFluidState(pos).getType() == Fluids.WATER));
+-        level.setBlock(pos, blockState, 3);
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, pos, blockState, 3); // CraftBukkit
+     }
+ 
+     private static void createMergedTips(BlockState state, LevelAccessor level, BlockPos pos) {
+@@ -404,8 +_,8 @@
+             blockPos = pos.below();
+         }
+ 
+-        createDripstone(level, blockPos1, Direction.DOWN, DripstoneThickness.TIP_MERGE);
+-        createDripstone(level, blockPos, Direction.UP, DripstoneThickness.TIP_MERGE);
++        createDripstone(level, blockPos1, Direction.DOWN, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
++        createDripstone(level, blockPos, Direction.UP, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
+     }
+ 
+     public static void spawnDripParticle(Level level, BlockPos pos, BlockState state) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch
new file mode 100644
index 0000000000..1254939fc6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PowderSnowBlock.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/PowderSnowBlock.java
++++ b/net/minecraft/world/level/block/PowderSnowBlock.java
+@@ -58,6 +_,7 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!(entity instanceof LivingEntity) || entity.getInBlockState().is(this)) {
+             entity.makeStuckInBlock(state, new Vec3(0.9F, 1.5, 0.9F));
+             if (level.isClientSide) {
+@@ -80,8 +_,13 @@
+         entity.setIsInPowderSnow(true);
+         if (level instanceof ServerLevel serverLevel) {
+             if (entity.isOnFire()
+-                && (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player)
++                // CraftBukkit - move down
+                 && entity.mayInteract(serverLevel, pos)) {
++                // CraftBukkit start
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.destroyBlock(pos, false);
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch
new file mode 100644
index 0000000000..04777f56ac
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PoweredRailBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/PoweredRailBlock.java
++++ b/net/minecraft/world/level/block/PoweredRailBlock.java
+@@ -133,6 +_,13 @@
+             || this.findPoweredRailSignal(level, pos, state, true, 0)
+             || this.findPoweredRailSignal(level, pos, state, false, 0);
+         if (flag != poweredValue) {
++            // CraftBukkit start
++            int power = flag ? 15 : 0;
++            int newPower = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, power, 15 - power).getNewCurrent();
++            if (newPower == power) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(flag)), 3);
+             level.updateNeighborsAt(pos.below(), this);
+             if (state.getValue(SHAPE).isSlope()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch
new file mode 100644
index 0000000000..235e99adff
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PressurePlateBlock.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/PressurePlateBlock.java
++++ b/net/minecraft/world/level/block/PressurePlateBlock.java
+@@ -5,6 +_,7 @@
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.LivingEntity;
++import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+@@ -46,7 +_,31 @@
+             case EVERYTHING -> Entity.class;
+             case MOBS -> LivingEntity.class;
+         };
+-        return getEntityCount(level, TOUCH_AABB.move(pos), clazz) > 0 ? 15 : 0;
++        // CraftBukkit start - Call interact event when turning on a pressure plate
++        for (Entity entity : getEntities(level, PressurePlateBlock.TOUCH_AABB.move(pos), clazz)) {
++            if (this.getSignalForState(level.getBlockState(pos)) == 0) {
++                org.bukkit.World bworld = level.getWorld();
++                org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++                org.bukkit.event.Cancellable cancellable;
++
++                if (entity instanceof Player) {
++                    cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++                } else {
++                    cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                    manager.callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++                }
++
++                // We only want to block turning the plate on if all events are cancelled
++                if (cancellable.isCancelled()) {
++                    continue;
++                }
++            }
++
++            return 15;
++        }
++
++        return 0;
++        // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/PumpkinBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
index d3d384ea46..20e12e9b1d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PumpkinBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
@@ -1,7 +1,7 @@
 --- a/net/minecraft/world/level/block/PumpkinBlock.java
 +++ b/net/minecraft/world/level/block/PumpkinBlock.java
-@@ -38,16 +38,24 @@
-         } else if (world.isClientSide) {
+@@ -40,21 +_,30 @@
+         } else if (level.isClientSide) {
              return InteractionResult.SUCCESS;
          } else {
 +            // Paper start - Add PlayerShearBlockEvent
@@ -11,26 +11,24 @@
 +                return InteractionResult.PASS;
 +            }
 +            // Paper end - Add PlayerShearBlockEvent
-             Direction direction = hit.getDirection();
-             Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction;
-             world.playSound(null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F);
-             world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11);
+             Direction direction = hitResult.getDirection();
+             Direction direction1 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction;
+             level.playSound(null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F);
+             level.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction1), 11);
 +            for (org.bukkit.inventory.ItemStack item : event.getDrops()) { // Paper - Add PlayerShearBlockEvent
              ItemEntity itemEntity = new ItemEntity(
-                 world,
-                 (double)pos.getX() + 0.5 + (double)direction2.getStepX() * 0.65,
-                 (double)pos.getY() + 0.1,
-                 (double)pos.getZ() + 0.5 + (double)direction2.getStepZ() * 0.65,
+                 level,
+                 pos.getX() + 0.5 + direction1.getStepX() * 0.65,
+                 pos.getY() + 0.1,
+                 pos.getZ() + 0.5 + direction1.getStepZ() * 0.65,
 -                new ItemStack(Items.PUMPKIN_SEEDS, 4)
 +                org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item) // Paper - Add PlayerShearBlockEvent
              );
              itemEntity.setDeltaMovement(
-                 0.05 * (double)direction2.getStepX() + world.random.nextDouble() * 0.02,
-@@ -55,6 +63,7 @@
-                 0.05 * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02
+                 0.05 * direction1.getStepX() + level.random.nextDouble() * 0.02, 0.05, 0.05 * direction1.getStepZ() + level.random.nextDouble() * 0.02
              );
-             world.addFreshEntity(itemEntity);
+             level.addFreshEntity(itemEntity);
 +            } // Paper - Add PlayerShearBlockEvent
              stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
-             world.gameEvent(player, GameEvent.SHEAR, pos);
+             level.gameEvent(player, GameEvent.SHEAR, pos);
              player.awardStat(Stats.ITEM_USED.get(Items.SHEARS));
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RailState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch
similarity index 59%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/RailState.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch
index b79bfb5efd..1a9dbf420d 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RailState.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RailState.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/RailState.java
 +++ b/net/minecraft/world/level/block/RailState.java
-@@ -17,6 +17,12 @@
+@@ -17,6 +_,12 @@
      private final boolean isStraight;
      private final List<BlockPos> connections = Lists.newArrayList();
  
@@ -10,24 +10,24 @@
 +    }
 +    // Paper end - Fix some rails connecting improperly
 +
-     public RailState(Level world, BlockPos pos, BlockState state) {
-         this.level = world;
+     public RailState(Level level, BlockPos pos, BlockState state) {
+         this.level = level;
          this.pos = pos;
-@@ -141,6 +147,11 @@
+@@ -141,6 +_,11 @@
      }
  
-     private void connectTo(RailState placementHelper) {
+     private void connectTo(RailState state) {
 +        // Paper start - Fix some rails connecting improperly
-+        if (!this.isValid() || !placementHelper.isValid()) {
++        if (!this.isValid() || !state.isValid()) {
 +            return;
 +        }
 +        // Paper end - Fix some rails connecting improperly
-         this.connections.add(placementHelper.pos);
+         this.connections.add(state.pos);
          BlockPos blockPos = this.pos.north();
-         BlockPos blockPos2 = this.pos.south();
-@@ -331,10 +342,15 @@
-         this.state = this.state.setValue(this.block.getShapeProperty(), railShape2);
-         if (forceUpdate || this.level.getBlockState(this.pos) != this.state) {
+         BlockPos blockPos1 = this.pos.south();
+@@ -331,10 +_,15 @@
+         this.state = this.state.setValue(this.block.getShapeProperty(), railShape);
+         if (alwaysPlace || this.level.getBlockState(this.pos) != this.state) {
              this.level.setBlock(this.pos, this.state, 3);
 +            // Paper start - Fix some rails connecting improperly
 +            if (!this.isValid()) {
@@ -36,13 +36,13 @@
 +            // Paper end - Fix some rails connecting improperly
  
              for (int i = 0; i < this.connections.size(); i++) {
-                 RailState railState = this.getRail(this.connections.get(i));
--                if (railState != null) {
-+                if (railState != null && railState.isValid()) { // Paper - Fix some rails connecting improperly
-                     railState.removeSoftConnections();
-                     if (railState.canConnectTo(this)) {
-                         railState.connectTo(this);
-@@ -347,6 +363,6 @@
+                 RailState rail = this.getRail(this.connections.get(i));
+-                if (rail != null) {
++                if (rail != null && rail.isValid()) { // Paper - Fix some rails connecting improperly
+                     rail.removeSoftConnections();
+                     if (rail.canConnectTo(this)) {
+                         rail.connectTo(this);
+@@ -347,6 +_,6 @@
      }
  
      public BlockState getState() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
new file mode 100644
index 0000000000..6f93bcddd4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
@@ -0,0 +1,89 @@
+--- a/net/minecraft/world/level/block/RedStoneOreBlock.java
++++ b/net/minecraft/world/level/block/RedStoneOreBlock.java
+@@ -37,14 +_,27 @@
+ 
+     @Override
+     protected void attack(BlockState state, Level level, BlockPos pos, Player player) {
+-        interact(state, level, pos);
++        interact(state, level, pos, player); // CraftBukkit - add entityhuman
+         super.attack(state, level, pos, player);
+     }
+ 
+     @Override
+     public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+         if (!entity.isSteppingCarefully()) {
+-            interact(state, level, pos);
++            // CraftBukkit start
++            if (entity instanceof Player) {
++                org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++                if (!event.isCancelled()) {
++                    RedStoneOreBlock.interact(level.getBlockState(pos), level, pos, entity); // add entity
++                }
++            } else {
++                org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                level.getCraftServer().getPluginManager().callEvent(event);
++                if (!event.isCancelled()) {
++                    RedStoneOreBlock.interact(level.getBlockState(pos), level, pos, entity); // add entity
++                }
++            }
++            // CraftBukkit end
+         }
+ 
+         super.stepOn(level, pos, state, entity);
+@@ -57,7 +_,7 @@
+         if (level.isClientSide) {
+             spawnParticles(level, pos);
+         } else {
+-            interact(state, level, pos);
++            interact(state, level, pos, player); // CraftBukkit - add entityhuman
+         }
+ 
+         return (InteractionResult)(stack.getItem() instanceof BlockItem && new BlockPlaceContext(player, hand, stack, hitResult).canPlace()
+@@ -65,9 +_,14 @@
+             : InteractionResult.SUCCESS);
+     }
+ 
+-    private static void interact(BlockState state, Level level, BlockPos pos) {
++    private static void interact(BlockState state, Level level, BlockPos pos, Entity entity) { // CraftBukkit - add Entity
+         spawnParticles(level, pos);
+         if (!state.getValue(LIT)) {
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.setValue(RedStoneOreBlock.LIT, true))) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(true)), 3);
+         }
+     }
+@@ -80,6 +_,11 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (state.getValue(LIT)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, state.setValue(RedStoneOreBlock.LIT, false)).isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(false)), 3);
+         }
+     }
+@@ -87,9 +_,17 @@
+     @Override
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+-        if (dropExperience) {
+-            this.tryDropExperience(level, pos, stack, UniformInt.of(1, 5));
++        // CraftBukkit start - Delegated to getExpDrop
++    }
++
++    @Override
++    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++        if (flag) {
++            return this.tryDropExperience(worldserver, blockposition, itemstack, UniformInt.of(1, 5));
+         }
++
++        return 0;
++        // CraftBukkit end
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
new file mode 100644
index 0000000000..32b4488b6d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/block/RedstoneLampBlock.java
++++ b/net/minecraft/world/level/block/RedstoneLampBlock.java
+@@ -41,6 +_,11 @@
+                 if (litValue) {
+                     level.scheduleTick(pos, this, 4);
+                 } else {
++                    // CraftBukkit start
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++                        return;
++                    }
++                    // CraftBukkit end
+                     level.setBlock(pos, state.cycle(LIT), 2);
+                 }
+             }
+@@ -50,6 +_,11 @@
+     @Override
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (state.getValue(LIT) && !level.hasNeighborSignal(pos)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlock(pos, state.cycle(LIT), 2);
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
index 69d1dcb6e7..d8efa215bb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
@@ -1,47 +1,42 @@
 --- a/net/minecraft/world/level/block/RedstoneTorchBlock.java
 +++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java
-@@ -22,11 +22,13 @@
- import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils;
- import net.minecraft.world.level.redstone.Orientation;
- 
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
-+
+@@ -24,7 +_,7 @@
  public class RedstoneTorchBlock extends BaseTorchBlock {
- 
      public static final MapCodec<RedstoneTorchBlock> CODEC = simpleCodec(RedstoneTorchBlock::new);
      public static final BooleanProperty LIT = BlockStateProperties.LIT;
--    private static final Map<BlockGetter, List<RedstoneTorchBlock.Toggle>> RECENT_TOGGLES = new WeakHashMap();
+-    private static final Map<BlockGetter, List<RedstoneTorchBlock.Toggle>> RECENT_TOGGLES = new WeakHashMap<>();
 +    // Paper - Faster redstone torch rapid clock removal; Move the mapped list to World
      public static final int RECENT_TOGGLE_TIMER = 60;
      public static final int MAX_RECENT_TOGGLES = 8;
      public static final int RESTART_DELAY = 160;
-@@ -79,14 +81,34 @@
+@@ -72,14 +_,34 @@
      @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         boolean flag = this.hasNeighborSignal(world, pos, state);
--        List<RedstoneTorchBlock.Toggle> list = (List) RedstoneTorchBlock.RECENT_TOGGLES.get(world);
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         boolean hasNeighborSignal = this.hasNeighborSignal(level, pos, state);
+-        List<RedstoneTorchBlock.Toggle> list = RECENT_TOGGLES.get(level);
 -
--        while (list != null && !list.isEmpty() && world.getGameTime() - ((RedstoneTorchBlock.Toggle) list.get(0)).when > 60L) {
+-        while (list != null && !list.isEmpty() && level.getGameTime() - list.get(0).when > 60L) {
 -            list.remove(0);
 +        // Paper start - Faster redstone torch rapid clock removal
-+        java.util.ArrayDeque<RedstoneTorchBlock.Toggle> redstoneUpdateInfos = world.redstoneUpdateInfos;
++        java.util.ArrayDeque<RedstoneTorchBlock.Toggle> redstoneUpdateInfos = level.redstoneUpdateInfos;
 +        if (redstoneUpdateInfos != null) {
 +            RedstoneTorchBlock.Toggle curr;
-+            while ((curr = redstoneUpdateInfos.peek()) != null && world.getGameTime() - curr.when > 60L) {
++            while ((curr = redstoneUpdateInfos.peek()) != null && level.getGameTime() - curr.when > 60L) {
 +                redstoneUpdateInfos.poll();
 +            }
          }
+-
 +        // Paper end - Faster redstone torch rapid clock removal
- 
++
 +        // CraftBukkit start
-+        org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager();
-+        org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++        org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++        org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
 +        int oldCurrent = ((Boolean) state.getValue(RedstoneTorchBlock.LIT)).booleanValue() ? 15 : 0;
 +
-+        BlockRedstoneEvent event = new BlockRedstoneEvent(block, oldCurrent, oldCurrent);
++        org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(block, oldCurrent, oldCurrent);
 +        // CraftBukkit end
-         if ((Boolean) state.getValue(RedstoneTorchBlock.LIT)) {
-             if (flag) {
+         if (state.getValue(LIT)) {
+             if (hasNeighborSignal) {
 +                // CraftBukkit start
 +                if (oldCurrent != 0) {
 +                    event.setNewCurrent(0);
@@ -51,13 +46,13 @@
 +                    }
 +                }
 +                // CraftBukkit end
-                 world.setBlock(pos, (BlockState) state.setValue(RedstoneTorchBlock.LIT, false), 3);
-                 if (RedstoneTorchBlock.isToggledTooFrequently(world, pos, true)) {
-                     world.levelEvent(1502, pos, 0);
-@@ -94,6 +116,15 @@
+                 level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(false)), 3);
+                 if (isToggledTooFrequently(level, pos, true)) {
+                     level.levelEvent(1502, pos, 0);
+@@ -87,6 +_,15 @@
                  }
              }
-         } else if (!flag && !RedstoneTorchBlock.isToggledTooFrequently(world, pos, false)) {
+         } else if (!hasNeighborSignal && !isToggledTooFrequently(level, pos, false)) {
 +            // CraftBukkit start
 +            if (oldCurrent != 15) {
 +                event.setNewCurrent(15);
@@ -67,22 +62,20 @@
 +                }
 +            }
 +            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(RedstoneTorchBlock.LIT, true), 3);
+             level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(true)), 3);
          }
- 
-@@ -134,9 +165,12 @@
+     }
+@@ -124,7 +_,12 @@
      }
  
-     private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) {
--        List<RedstoneTorchBlock.Toggle> list = (List) RedstoneTorchBlock.RECENT_TOGGLES.computeIfAbsent(world, (iblockaccess) -> {
--            return Lists.newArrayList();
--        });
+     private static boolean isToggledTooFrequently(Level level, BlockPos pos, boolean logToggle) {
+-        List<RedstoneTorchBlock.Toggle> list = RECENT_TOGGLES.computeIfAbsent(level, toggles -> Lists.newArrayList());
 +        // Paper start - Faster redstone torch rapid clock removal
-+        java.util.ArrayDeque<RedstoneTorchBlock.Toggle> list = world.redstoneUpdateInfos;
++        java.util.ArrayDeque<RedstoneTorchBlock.Toggle> list = level.redstoneUpdateInfos;
 +        if (list == null) {
-+            list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>();
++            list = level.redstoneUpdateInfos = new java.util.ArrayDeque<>();
 +        }
 +        // Paper end - Faster redstone torch rapid clock removal
- 
-         if (addNew) {
-             list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), world.getGameTime()));
+         if (logToggle) {
+             list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getGameTime()));
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
new file mode 100644
index 0000000000..17ec642eda
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
++++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
+@@ -102,11 +_,16 @@
+             if (!level.isClientSide) {
+                 ServerPlayer serverPlayer = (ServerPlayer)player;
+                 if (serverPlayer.getRespawnDimension() != level.dimension() || !pos.equals(serverPlayer.getRespawnPosition())) {
+-                    serverPlayer.setRespawnPosition(level.dimension(), pos, 0.0F, false, true);
++                    if (serverPlayer.setRespawnPosition(level.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent
+                     level.playSound(
+                         null, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F
+                     );
+                     return InteractionResult.SUCCESS_SERVER;
++                    // Paper start - Add PlayerSetSpawnEvent
++                    } else {
++                        return InteractionResult.FAIL;
++                    }
++                    // Paper end - Add PlayerSetSpawnEvent
+                 }
+             }
+ 
+@@ -140,6 +_,7 @@
+     }
+ 
+     private void explode(BlockState state, Level level, final BlockPos pos2) {
++        org.bukkit.block.BlockState blockState = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos2).getState(); // CraftBukkit - capture BlockState before remove block
+         level.removeBlock(pos2, false);
+         boolean flag = Direction.Plane.HORIZONTAL.stream().map(pos2::relative).anyMatch(pos -> isWaterThatWouldFlow(pos, level));
+         final boolean flag1 = flag || level.getFluidState(pos2.above()).is(FluidTags.WATER);
+@@ -153,7 +_,7 @@
+         };
+         Vec3 center = pos2.getCenter();
+         level.explode(
+-            null, level.damageSources().badRespawnPointExplosion(center), explosionDamageCalculator, center, 5.0F, true, Level.ExplosionInteraction.BLOCK
++            null, level.damageSources().badRespawnPointExplosion(center, blockState), explosionDamageCalculator, center, 5.0F, true, Level.ExplosionInteraction.BLOCK // CraftBukkit - add state
+         );
+     }
+ 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RootedDirtBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/RootedDirtBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch
index de8d7301e4..6a3b2066a8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RootedDirtBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/RootedDirtBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/RootedDirtBlock.java
 +++ b/net/minecraft/world/level/block/RootedDirtBlock.java
-@@ -34,7 +34,7 @@
+@@ -33,7 +_,7 @@
  
      @Override
-     public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
--        world.setBlockAndUpdate(pos.below(), Blocks.HANGING_ROOTS.defaultBlockState());
-+        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState()); // CraftBukkit
+     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+-        level.setBlockAndUpdate(pos.below(), Blocks.HANGING_ROOTS.defaultBlockState());
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState()); // CraftBukkit
      }
  
      @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
deleted file mode 100644
index 2c42f4cb51..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/level/block/AbstractCandleBlock.java
-+++ b/net/minecraft/world/level/block/AbstractCandleBlock.java
-@@ -47,6 +47,11 @@
-     @Override
-     protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {
-         if (!world.isClientSide && projectile.isOnFire() && this.canBeLit(state)) {
-+            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, hit.getBlockPos(), projectile).isCancelled()) {
-+                return;
-+            }
-+            // CraftBukkit end
-             AbstractCandleBlock.setLit(world, state, hit.getBlockPos(), true);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
deleted file mode 100644
index e2c8cb7f03..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/AbstractCauldronBlock.java
-+++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java
-@@ -56,7 +56,7 @@
-     @Override
-     protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
-         CauldronInteraction cauldronInteraction = this.interactions.map().get(stack.getItem());
--        return cauldronInteraction.interact(state, world, pos, player, hand, stack);
-+        return cauldronInteraction.interact(state, world, pos, player, hand, stack, hit.getDirection()); // Paper - pass hit direction
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooSaplingBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
deleted file mode 100644
index 88344a70cd..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/world/level/block/BambooSaplingBlock.java
-+++ b/net/minecraft/world/level/block/BambooSaplingBlock.java
-@@ -45,7 +45,7 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if (random.nextInt(3) == 0 && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) {
-+        if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
-             this.growBamboo(world, pos);
-         }
- 
-@@ -87,6 +87,6 @@
-     }
- 
-     protected void growBamboo(Level world, BlockPos pos) {
--        world.setBlock(pos.above(), (BlockState) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
-+        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); // CraftBukkit - BlockSpreadEvent
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooStalkBlock.java.patch
deleted file mode 100644
index eb12b201ec..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BambooStalkBlock.java.patch
+++ /dev/null
@@ -1,89 +0,0 @@
---- a/net/minecraft/world/level/block/BambooStalkBlock.java
-+++ b/net/minecraft/world/level/block/BambooStalkBlock.java
-@@ -134,10 +134,10 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if ((Integer) state.getValue(BambooStalkBlock.STAGE) == 0) {
--            if (random.nextInt(3) == 0 && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) {
-+            if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
-                 int i = this.getHeightBelowUpToMax(world, pos) + 1;
- 
--                if (i < 16) {
-+                if (i < world.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height
-                     this.growBamboo(state, world, pos, random, i);
-                 }
-             }
-@@ -164,7 +164,7 @@
-         int i = this.getHeightAboveUpToMax(world, pos);
-         int j = this.getHeightBelowUpToMax(world, pos);
- 
--        return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1;
-+        return i + j + 1 < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height
-     }
- 
-     @Override
-@@ -183,7 +183,7 @@
-             BlockPos blockposition1 = pos.above(i);
-             BlockState iblockdata1 = world.getBlockState(blockposition1);
- 
--            if (k >= 16 || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) {
-+            if (k >= world.paperConfig().maxGrowthHeight.bamboo.max || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height
-                 return;
-             }
- 
-@@ -204,14 +204,18 @@
-         BlockPos blockposition1 = pos.below(2);
-         BlockState iblockdata2 = world.getBlockState(blockposition1);
-         BambooLeaves blockpropertybamboosize = BambooLeaves.NONE;
-+        boolean shouldUpdateOthers = false; // CraftBukkit
- 
-         if (height >= 1) {
-             if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
-                 if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
-                     blockpropertybamboosize = BambooLeaves.LARGE;
-                     if (iblockdata2.is(Blocks.BAMBOO)) {
--                        world.setBlock(pos.below(), (BlockState) iblockdata1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
--                        world.setBlock(blockposition1, (BlockState) iblockdata2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3);
-+                        // CraftBukkit start - moved down
-+                        // world.setBlock(blockposition.below(), (IBlockData) iblockdata1.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.SMALL), 3);
-+                        // world.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.NONE), 3);
-+                        shouldUpdateOthers = true;
-+                        // CraftBukkit end
-                     }
-                 }
-             } else {
-@@ -220,15 +224,22 @@
-         }
- 
-         int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1;
--        int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1;
-+        int k = (height < world.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && height != (world.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height
- 
--        world.setBlock(pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3);
-+        // CraftBukkit start
-+        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) {
-+            if (shouldUpdateOthers) {
-+                world.setBlock(pos.below(), (BlockState) iblockdata1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
-+                world.setBlock(blockposition1, (BlockState) iblockdata2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3);
-+            }
-+        }
-+        // CraftBukkit end
-     }
- 
-     protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) {
-         int i;
- 
--        for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) {
-+        for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height
-             ;
-         }
- 
-@@ -238,7 +249,7 @@
-     protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) {
-         int i;
- 
--        for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) {
-+        for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height
-             ;
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
deleted file mode 100644
index 65470ccca0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
-+++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
-@@ -22,6 +22,7 @@
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
- 
- public abstract class BasePressurePlateBlock extends Block {
- 
-@@ -76,6 +77,7 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide) {
-             int i = this.getSignalForState(state);
- 
-@@ -91,6 +93,19 @@
-         boolean flag = output > 0;
-         boolean flag1 = j > 0;
- 
-+        // CraftBukkit start - Interact Pressure Plate
-+        org.bukkit.World bworld = world.getWorld();
-+        org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager();
-+
-+        if (flag != flag1) {
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), output, j);
-+            manager.callEvent(eventRedstone);
-+
-+            flag1 = eventRedstone.getNewCurrent() > 0;
-+            j = eventRedstone.getNewCurrent();
-+        }
-+        // CraftBukkit end
-+
-         if (output != j) {
-             BlockState iblockdata1 = this.setSignalForState(state, j);
- 
-@@ -145,9 +160,15 @@
-     }
- 
-     protected static int getEntityCount(Level world, AABB box, Class<? extends Entity> entityClass) {
--        return world.getEntitiesOfClass(entityClass, box, EntitySelector.NO_SPECTATORS.and((entity) -> {
-+        // CraftBukkit start
-+        return BasePressurePlateBlock.getEntities(world, box, entityClass).size();
-+    }
-+
-+    protected static <T extends Entity> java.util.List<T> getEntities(Level world, AABB axisalignedbb, Class<T> oclass) {
-+        // CraftBukkit end
-+        return world.getEntitiesOfClass(oclass, axisalignedbb, EntitySelector.NO_SPECTATORS.and((entity) -> {
-             return !entity.isIgnoringBlockTriggers();
--        })).size();
-+        })); // CraftBukkit
-     }
- 
-     protected abstract int getSignalStrength(Level world, BlockPos pos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BaseRailBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BaseRailBlock.java.patch
deleted file mode 100644
index 9e6c76abd9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BaseRailBlock.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/level/block/BaseRailBlock.java
-+++ b/net/minecraft/world/level/block/BaseRailBlock.java
-@@ -71,6 +71,7 @@
-         state = this.updateDir(world, pos, state, true);
-         if (this.isStraight) {
-             world.neighborChanged(state, pos, this, null, notify);
-+            state = world.getBlockState(pos); // Paper - Fix some rails connecting improperly
-         }
- 
-         return state;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BeehiveBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BeehiveBlock.java.patch
deleted file mode 100644
index 73f015dbe6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BeehiveBlock.java.patch
+++ /dev/null
@@ -1,79 +0,0 @@
---- a/net/minecraft/world/level/block/BeehiveBlock.java
-+++ b/net/minecraft/world/level/block/BeehiveBlock.java
-@@ -94,8 +94,8 @@
-     }
- 
-     @Override
--    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
--        super.playerDestroy(world, player, pos, state, blockEntity, tool);
-+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
-+        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
-         if (!world.isClientSide && blockEntity instanceof BeehiveBlockEntity tileentitybeehive) {
-             if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_BEE_SPAWNS_WHEN_MINING)) {
-                 tileentitybeehive.emptyAllLivingFromHive(player, state, BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY);
-@@ -103,7 +103,7 @@
-                 this.angerNearbyBees(world, pos);
-             }
- 
--            CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount());
-+            // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped
-         }
- 
-     }
-@@ -133,7 +133,7 @@
-                 if (entitybee.getTarget() == null) {
-                     Player entityhuman = (Player) Util.getRandom(list1, world.random);
- 
--                    entitybee.setTarget(entityhuman);
-+                    entitybee.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
-                 }
-             }
-         }
-@@ -141,7 +141,7 @@
-     }
- 
-     public static void dropHoneycomb(Level world, BlockPos pos) {
--        popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3));
-+        popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - Add PlayerShearBlockEvent; conflict on change, item needs to be set below
-     }
- 
-     @Override
-@@ -153,8 +153,19 @@
-             Item item = stack.getItem();
- 
-             if (stack.is(Items.SHEARS)) {
-+                // Paper start - Add PlayerShearBlockEvent
-+                io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>());
-+                event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.HONEYCOMB, 3)));
-+                if (!event.callEvent()) {
-+                    return InteractionResult.PASS;
-+                }
-+                // Paper end
-                 world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.BLOCKS, 1.0F, 1.0F);
--                BeehiveBlock.dropHoneycomb(world, pos);
-+                // Paper start - Add PlayerShearBlockEvent
-+                for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) {
-+                    popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop));
-+                }
-+                // Paper end - Add PlayerShearBlockEvent
-                 stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
-                 flag = true;
-                 world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos);
-@@ -297,7 +308,7 @@
-                         ItemStack itemstack = new ItemStack(this);
- 
-                         itemstack.applyComponents(tileentitybeehive.collectComponents());
--                        itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, (Comparable) i));
-+                        itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, i)); // CraftBukkit - decompile error
-                         ItemEntity entityitem = new ItemEntity(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack);
- 
-                         entityitem.setDefaultPickUpDelay();
-@@ -332,7 +343,7 @@
-         ItemStack itemstack = super.getCloneItemStack(world, pos, state, includeData);
- 
-         if (includeData) {
--            itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, (Comparable) ((Integer) state.getValue(BeehiveBlock.HONEY_LEVEL))));
-+            itemstack.set(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(BeehiveBlock.HONEY_LEVEL, ((Integer) state.getValue(BeehiveBlock.HONEY_LEVEL)))); // CraftBukkit - decompile error
-         }
- 
-         return itemstack;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BellBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BellBlock.java.patch
deleted file mode 100644
index d677bbedab..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BellBlock.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/level/block/BellBlock.java
-+++ b/net/minecraft/world/level/block/BellBlock.java
-@@ -148,6 +148,11 @@
-             if (direction == null) {
-                 direction = (Direction) world.getBlockState(pos).getValue(BellBlock.FACING);
-             }
-+            // CraftBukkit start
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBellRingEvent(world, pos, direction, entity)) {
-+                return false;
-+            }
-+            // CraftBukkit end
- 
-             ((BellBlockEntity) tileentity).onHit(direction);
-             world.playSound((Player) null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BigDripleafBlock.java.patch
deleted file mode 100644
index c0eab8e717..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BigDripleafBlock.java.patch
+++ /dev/null
@@ -1,116 +0,0 @@
---- a/net/minecraft/world/level/block/BigDripleafBlock.java
-+++ b/net/minecraft/world/level/block/BigDripleafBlock.java
-@@ -44,6 +44,10 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityInteractEvent;
-+// CraftBukkit end
- 
- public class BigDripleafBlock extends HorizontalDirectionalBlock implements BonemealableBlock, SimpleWaterloggedBlock {
- 
-@@ -119,7 +123,7 @@
- 
-     @Override
-     protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {
--        this.setTiltAndScheduleTick(state, world, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
-+        this.setTiltAndScheduleTick(state, world, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, projectile); // CraftBukkit
-     }
- 
-     @Override
-@@ -176,9 +180,23 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide) {
-             if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) {
--                this.setTiltAndScheduleTick(state, world, pos, Tilt.UNSTABLE, (SoundEvent) null);
-+                // CraftBukkit start - tilt dripleaf
-+                org.bukkit.event.Cancellable cancellable;
-+                if (entity instanceof Player) {
-+                    cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                } else {
-+                    cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                    world.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
-+                }
-+
-+                if (cancellable.isCancelled()) {
-+                    return;
-+                }
-+                this.setTiltAndScheduleTick(state, world, pos, Tilt.UNSTABLE, (SoundEvent) null, entity);
-+                // CraftBukkit end
-             }
- 
-         }
-@@ -192,9 +210,9 @@
-             Tilt tilt = (Tilt) state.getValue(BigDripleafBlock.TILT);
- 
-             if (tilt == Tilt.UNSTABLE) {
--                this.setTiltAndScheduleTick(state, world, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
-+                this.setTiltAndScheduleTick(state, world, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
-             } else if (tilt == Tilt.PARTIAL) {
--                this.setTiltAndScheduleTick(state, world, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
-+                this.setTiltAndScheduleTick(state, world, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
-             } else if (tilt == Tilt.FULL) {
-                 BigDripleafBlock.resetTilt(state, world, pos);
-             }
-@@ -220,36 +238,46 @@
-         return entity.onGround() && entity.position().y > (double) ((float) pos.getY() + 0.6875F);
-     }
- 
--    private void setTiltAndScheduleTick(BlockState state, Level world, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound) {
--        BigDripleafBlock.setTilt(state, world, pos, tilt);
--        if (sound != null) {
--            BigDripleafBlock.playTiltSound(world, pos, sound);
-+    // CraftBukkit start
-+    private void setTiltAndScheduleTick(BlockState iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable SoundEvent soundeffect, @Nullable Entity entity) {
-+        if (!BigDripleafBlock.setTilt(iblockdata, world, blockposition, tilt, entity)) return;
-+        // CraftBukkit end
-+        if (soundeffect != null) {
-+            BigDripleafBlock.playTiltSound(world, blockposition, soundeffect);
-         }
- 
-         int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
- 
-         if (i != -1) {
--            world.scheduleTick(pos, (Block) this, i);
-+            world.scheduleTick(blockposition, (Block) this, i);
-         }
- 
-     }
- 
-     private static void resetTilt(BlockState state, Level world, BlockPos pos) {
--        BigDripleafBlock.setTilt(state, world, pos, Tilt.NONE);
-+        BigDripleafBlock.setTilt(state, world, pos, Tilt.NONE, null); // CraftBukkit
-         if (state.getValue(BigDripleafBlock.TILT) != Tilt.NONE) {
-             BigDripleafBlock.playTiltSound(world, pos, SoundEvents.BIG_DRIPLEAF_TILT_UP);
-         }
- 
-     }
- 
--    private static void setTilt(BlockState state, Level world, BlockPos pos, Tilt tilt) {
--        Tilt tilt1 = (Tilt) state.getValue(BigDripleafBlock.TILT);
-+    // CraftBukkit start
-+    private static boolean setTilt(BlockState iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable Entity entity) {
-+        if (entity != null) {
-+            if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(BigDripleafBlock.TILT, tilt))) {
-+                return false;
-+            }
-+        }
-+        // CraftBukkit end
-+        Tilt tilt1 = (Tilt) iblockdata.getValue(BigDripleafBlock.TILT);
- 
--        world.setBlock(pos, (BlockState) state.setValue(BigDripleafBlock.TILT, tilt), 2);
-+        world.setBlock(blockposition, (BlockState) iblockdata.setValue(BigDripleafBlock.TILT, tilt), 2);
-         if (tilt.causesVibration() && tilt != tilt1) {
--            world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos);
-+            world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, blockposition);
-         }
- 
-+        return true; // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/BushBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/BushBlock.java.patch
deleted file mode 100644
index d8fb08b681..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/BushBlock.java.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- a/net/minecraft/world/level/block/BushBlock.java
-+++ b/net/minecraft/world/level/block/BushBlock.java
-@@ -6,6 +6,7 @@
- import net.minecraft.tags.BlockTags;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.level.BlockGetter;
-+import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelReader;
- import net.minecraft.world.level.ScheduledTickAccess;
- import net.minecraft.world.level.block.state.BlockBehaviour;
-@@ -27,7 +28,15 @@
- 
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
--        return !state.canSurvive(world, pos) ? Blocks.AIR.defaultBlockState() : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-+        // CraftBukkit start
-+        if (!state.canSurvive(world, pos)) {
-+            // Suppress during worldgen
-+            if (!(world instanceof net.minecraft.server.level.ServerLevel world1 && world1.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world1, pos).isCancelled()) { // Paper
-+                return Blocks.AIR.defaultBlockState();
-+            }
-+        }
-+        return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ButtonBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ButtonBlock.java.patch
deleted file mode 100644
index 8311b29036..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ButtonBlock.java.patch
+++ /dev/null
@@ -1,78 +0,0 @@
---- a/net/minecraft/world/level/block/ButtonBlock.java
-+++ b/net/minecraft/world/level/block/ButtonBlock.java
-@@ -34,6 +34,10 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+import org.bukkit.event.entity.EntityInteractEvent;
-+// CraftBukkit end
- 
- public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock {
- 
-@@ -126,6 +130,19 @@
-         if ((Boolean) state.getValue(ButtonBlock.POWERED)) {
-             return InteractionResult.CONSUME;
-         } else {
-+            // CraftBukkit start
-+            boolean powered = ((Boolean) state.getValue(ButtonBlock.POWERED));
-+            org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            int old = (powered) ? 15 : 0;
-+            int current = (!powered) ? 15 : 0;
-+
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
-+            world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+            if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
-+                return InteractionResult.SUCCESS;
-+            }
-+            // CraftBukkit end
-             this.press(state, world, pos, player);
-             return InteractionResult.SUCCESS;
-         }
-@@ -191,17 +208,43 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) {
-             this.checkPressed(state, world, pos);
-         }
-     }
- 
-     protected void checkPressed(BlockState state, Level world, BlockPos pos) {
--        AbstractArrow entityarrow = this.type.canButtonBeActivatedByArrows() ? (AbstractArrow) world.getEntitiesOfClass(AbstractArrow.class, state.getShape(world, pos).bounds().move(pos)).stream().findFirst().orElse((Object) null) : null;
-+        AbstractArrow entityarrow = this.type.canButtonBeActivatedByArrows() ? (AbstractArrow) world.getEntitiesOfClass(AbstractArrow.class, state.getShape(world, pos).bounds().move(pos)).stream().findFirst().orElse(null) : null; // CraftBukkit - decompile error
-         boolean flag = entityarrow != null;
-         boolean flag1 = (Boolean) state.getValue(ButtonBlock.POWERED);
- 
-+        // CraftBukkit start - Call interact event when arrows turn on wooden buttons
-+        if (flag1 != flag && flag) {
-+            org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            EntityInteractEvent event = new EntityInteractEvent(entityarrow.getBukkitEntity(), block);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+        }
-+        // CraftBukkit end
-+
-         if (flag != flag1) {
-+            // CraftBukkit start
-+            boolean powered = flag1;
-+            org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            int old = (powered) ? 15 : 0;
-+            int current = (!powered) ? 15 : 0;
-+
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
-+            world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+            if ((flag && eventRedstone.getNewCurrent() <= 0) || (!flag && eventRedstone.getNewCurrent() > 0)) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(ButtonBlock.POWERED, flag), 3);
-             this.updateNeighbours(state, world, pos);
-             this.playSound((Player) null, world, pos, flag);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CactusBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CactusBlock.java.patch
deleted file mode 100644
index c7084f8b41..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CactusBlock.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/level/block/CactusBlock.java
-+++ b/net/minecraft/world/level/block/CactusBlock.java
-@@ -22,6 +22,7 @@
- import net.minecraft.world.level.redstone.Orientation;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class CactusBlock extends Block {
- 
-@@ -61,16 +62,17 @@
-                 ;
-             }
- 
--            if (i < 3) {
-+            if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height
-                 int j = (Integer) state.getValue(CactusBlock.AGE);
- 
--                if (j == 15) {
--                    world.setBlockAndUpdate(blockposition1, this.defaultBlockState());
-+                int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution
-+                if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
-+                    CraftEventFactory.handleBlockGrowEvent(world, blockposition1, this.defaultBlockState()); // CraftBukkit
-                     BlockState iblockdata1 = (BlockState) state.setValue(CactusBlock.AGE, 0);
- 
-                     world.setBlock(pos, iblockdata1, 4);
-                     world.neighborChanged(iblockdata1, blockposition1, this, (Orientation) null, false);
--                } else {
-+                } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
-                     world.setBlock(pos, (BlockState) state.setValue(CactusBlock.AGE, j + 1), 4);
-                 }
- 
-@@ -120,7 +122,8 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
--        entity.hurt(world.damageSources().cactus(), 1.0F);
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-+        entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CampfireBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CampfireBlock.java.patch
deleted file mode 100644
index 133764e05b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CampfireBlock.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/level/block/CampfireBlock.java
-+++ b/net/minecraft/world/level/block/CampfireBlock.java
-@@ -112,8 +112,9 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity) {
--            entity.hurt(world.damageSources().campfire(), (float) this.fireDamage);
-+            entity.hurt(world.damageSources().campfire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit
-         }
- 
-         super.entityInside(state, world, pos, entity);
-@@ -219,6 +220,11 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition) && !(Boolean) state.getValue(CampfireBlock.LIT) && !(Boolean) state.getValue(CampfireBlock.WATERLOGGED)) {
-+                // CraftBukkit start
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, projectile).isCancelled()) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(blockposition, (BlockState) state.setValue(BlockStateProperties.LIT, true), 11);
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
deleted file mode 100644
index 8b9fd76990..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
-+++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
-@@ -24,6 +24,9 @@
- import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder;
- import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
- import net.minecraft.world.level.block.state.properties.EnumProperty;
-+// CraftBukkit start
-+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
-+// CraftBukkit end
- 
- public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
- 
-@@ -87,9 +90,14 @@
-     }
- 
-     private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) {
--        CarvedPumpkinBlock.clearPatternBlocks(world, patternResult);
-+        // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down
-         entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F);
--        world.addFreshEntity(entity);
-+        // CraftBukkit start
-+        if (!world.addFreshEntity(entity, (entity.getType() == EntityType.SNOW_GOLEM) ? SpawnReason.BUILD_SNOWMAN : SpawnReason.BUILD_IRONGOLEM)) {
-+            return;
-+        }
-+        CarvedPumpkinBlock.clearPatternBlocks(world, patternResult); // CraftBukkit - from above
-+        // CraftBukkit end
-         Iterator iterator = world.getEntitiesOfClass(ServerPlayer.class, entity.getBoundingBox().inflate(5.0D)).iterator();
- 
-         while (iterator.hasNext()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CauldronBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CauldronBlock.java.patch
deleted file mode 100644
index 0d9def53a1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CauldronBlock.java.patch
+++ /dev/null
@@ -1,56 +0,0 @@
---- a/net/minecraft/world/level/block/CauldronBlock.java
-+++ b/net/minecraft/world/level/block/CauldronBlock.java
-@@ -12,6 +12,9 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.material.Fluid;
- import net.minecraft.world.level.material.Fluids;
-+// CraftBukkit start
-+import org.bukkit.event.block.CauldronLevelChangeEvent;
-+// CraftBukkit end
- 
- public class CauldronBlock extends AbstractCauldronBlock {
- 
-@@ -41,9 +44,19 @@
-     public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) {
-         if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) {
-             if (precipitation == Biome.Precipitation.RAIN) {
-+                // Paper start - Call CauldronLevelChangeEvent
-+                if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
-+                    return;
-+                }
-+                // Paper end - Call CauldronLevelChangeEvent
-                 world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState());
-                 world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos);
-             } else if (precipitation == Biome.Precipitation.SNOW) {
-+                // Paper start - Call CauldronLevelChangeEvent
-+                if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
-+                    return;
-+                }
-+                // Paper end - Call CauldronLevelChangeEvent
-                 world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState());
-                 world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos);
-             }
-@@ -62,13 +75,19 @@
- 
-         if (fluid == Fluids.WATER) {
-             iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState();
--            world.setBlockAndUpdate(pos, iblockdata1);
--            world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-+            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
-+            if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
-+                return;
-+            }
-+            // Paper end - Call CauldronLevelChangeEvent
-             world.levelEvent(1047, pos, 0);
-         } else if (fluid == Fluids.LAVA) {
-             iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState();
--            world.setBlockAndUpdate(pos, iblockdata1);
--            world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-+            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
-+            if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
-+                return;
-+            }
-+            // Paper end - Call CauldronLevelChangeEvent
-             world.levelEvent(1046, pos, 0);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVines.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVines.java.patch
deleted file mode 100644
index b340d9c9cb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CaveVines.java.patch
+++ /dev/null
@@ -1,42 +0,0 @@
---- a/net/minecraft/world/level/block/CaveVines.java
-+++ b/net/minecraft/world/level/block/CaveVines.java
-@@ -19,6 +19,13 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.shapes.VoxelShape;
- 
-+// CraftBukkit start
-+import java.util.Collections;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.player.PlayerHarvestBlockEvent;
-+// CraftBukkit end
-+
- public interface CaveVines {
- 
-     VoxelShape SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D);
-@@ -26,7 +33,24 @@
- 
-     static InteractionResult use(@Nullable Entity picker, BlockState state, Level world, BlockPos pos) {
-         if ((Boolean) state.getValue(CaveVines.BERRIES)) {
--            Block.popResource(world, pos, new ItemStack(Items.GLOW_BERRIES, 1));
-+            // CraftBukkit start
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(picker, pos, (BlockState) state.setValue(CaveVines.BERRIES, false))) {
-+                return InteractionResult.SUCCESS;
-+            }
-+
-+            if (picker instanceof Player) {
-+                PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(world, pos, (Player) picker, net.minecraft.world.InteractionHand.MAIN_HAND, Collections.singletonList(new ItemStack(Items.GLOW_BERRIES, 1)));
-+                if (event.isCancelled()) {
-+                    return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
-+                }
-+                for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
-+                    Block.popResource(world, pos, CraftItemStack.asNMSCopy(itemStack));
-+                }
-+            } else {
-+                Block.popResource(world, pos, new ItemStack(Items.GLOW_BERRIES, 1));
-+            }
-+            // CraftBukkit end
-+
-             float f = Mth.randomBetween(world.random, 0.8F, 1.2F);
- 
-             world.playSound((Player) null, pos, SoundEvents.CAVE_VINES_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, f);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
deleted file mode 100644
index 94b24017e5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
-+++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
-@@ -20,7 +20,7 @@
- 
-         if (random.nextFloat() < 0.05688889F) {
-             this.getNextState(state, world, pos, random).ifPresent((iblockdata1) -> {
--                world.setBlockAndUpdate(pos, iblockdata1);
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, iblockdata1); // CraftBukkit
-             });
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChestBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ChestBlock.java.patch
deleted file mode 100644
index 4b025cb2e4..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChestBlock.java.patch
+++ /dev/null
@@ -1,118 +0,0 @@
---- a/net/minecraft/world/level/block/ChestBlock.java
-+++ b/net/minecraft/world/level/block/ChestBlock.java
-@@ -91,24 +91,7 @@
-         public Optional<MenuProvider> acceptDouble(final ChestBlockEntity first, final ChestBlockEntity second) {
-             final CompoundContainer inventorylargechest = new CompoundContainer(first, second);
- 
--            return Optional.of(new MenuProvider(this) {
--                @Nullable
--                @Override
--                public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
--                    if (first.canOpen(player) && second.canOpen(player)) {
--                        first.unpackLootTable(playerInventory.player);
--                        second.unpackLootTable(playerInventory.player);
--                        return ChestMenu.sixRows(syncId, playerInventory, inventorylargechest);
--                    } else {
--                        return null;
--                    }
--                }
--
--                @Override
--                public Component getDisplayName() {
--                    return (Component) (first.hasCustomName() ? first.getDisplayName() : (second.hasCustomName() ? second.getDisplayName() : Component.translatable("container.chestDouble")));
--                }
--            });
-+            return Optional.of(new DoubleInventory(first, second, inventorylargechest)); // CraftBukkit // CraftBukkit - decompile error
-         }
- 
-         public Optional<MenuProvider> acceptSingle(ChestBlockEntity single) {
-@@ -118,8 +101,40 @@
-         @Override
-         public Optional<MenuProvider> acceptNone() {
-             return Optional.empty();
-+        }
-+    };
-+
-+    // CraftBukkit start
-+    public static class DoubleInventory implements MenuProvider {
-+
-+        private final ChestBlockEntity tileentitychest;
-+        private final ChestBlockEntity tileentitychest1;
-+        public final CompoundContainer inventorylargechest;
-+
-+        public DoubleInventory(ChestBlockEntity tileentitychest, ChestBlockEntity tileentitychest1, CompoundContainer inventorylargechest) {
-+            this.tileentitychest = tileentitychest;
-+            this.tileentitychest1 = tileentitychest1;
-+            this.inventorylargechest = inventorylargechest;
-+        }
-+
-+        @Nullable
-+        @Override
-+        public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
-+            if (this.tileentitychest.canOpen(player) && this.tileentitychest1.canOpen(player)) {
-+                this.tileentitychest.unpackLootTable(playerInventory.player);
-+                this.tileentitychest1.unpackLootTable(playerInventory.player);
-+                return ChestMenu.sixRows(syncId, playerInventory, this.inventorylargechest);
-+            } else {
-+                return null;
-+            }
-         }
-+
-+        @Override
-+        public Component getDisplayName() {
-+            return (Component) (this.tileentitychest.hasCustomName() ? this.tileentitychest.getDisplayName() : (this.tileentitychest1.hasCustomName() ? this.tileentitychest1.getDisplayName() : Component.translatable("container.chestDouble")));
-+        }
-     };
-+    // CraftBukkit end
- 
-     @Override
-     public MapCodec<? extends ChestBlock> codec() {
-@@ -232,8 +247,7 @@
-         if (world instanceof ServerLevel worldserver) {
-             MenuProvider itileinventory = this.getMenuProvider(state, world, pos);
- 
--            if (itileinventory != null) {
--                player.openMenu(itileinventory);
-+            if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
-                 player.awardStat(this.getOpenChestStat());
-                 PiglinAi.angerNearbyPiglins(worldserver, player, true);
-             }
-@@ -257,7 +271,7 @@
- 
-     @Override
-     public DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> combine(BlockState state, Level world, BlockPos pos, boolean ignoreBlocked) {
--        BiPredicate bipredicate;
-+        BiPredicate<LevelAccessor, BlockPos> bipredicate; // CraftBukkit - decompile error
- 
-         if (ignoreBlocked) {
-             bipredicate = (generatoraccess, blockposition1) -> {
-@@ -273,9 +287,16 @@
-     @Nullable
-     @Override
-     public MenuProvider getMenuProvider(BlockState state, Level world, BlockPos pos) {
--        return (MenuProvider) ((Optional) this.combine(state, world, pos, false).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null);
-+        // CraftBukkit start
-+        return this.getMenuProvider(state, world, pos, false);
-     }
- 
-+    @Nullable
-+    public MenuProvider getMenuProvider(BlockState iblockdata, Level world, BlockPos blockposition, boolean ignoreObstructions) {
-+        return (MenuProvider) ((Optional) this.combine(iblockdata, world, blockposition, ignoreObstructions).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null);
-+        // CraftBukkit end
-+    }
-+
-     public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity progress) {
-         return new DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction>() {
-             public Float2FloatFunction acceptDouble(ChestBlockEntity first, ChestBlockEntity second) {
-@@ -321,6 +342,11 @@
-     }
- 
-     private static boolean isCatSittingOnChest(LevelAccessor world, BlockPos pos) {
-+        // Paper start - Option to disable chest cat detection
-+        if (world.getMinecraftWorld().paperConfig().entities.behavior.disableChestCatDetection) {
-+            return false;
-+        }
-+        // Paper end - Option to disable chest cat detection
-         List<Cat> list = world.getEntitiesOfClass(Cat.class, new AABB((double) pos.getX(), (double) (pos.getY() + 1), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 2), (double) (pos.getZ() + 1)));
- 
-         if (!list.isEmpty()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
deleted file mode 100644
index 88160b2d20..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
+++ /dev/null
@@ -1,73 +0,0 @@
---- a/net/minecraft/world/level/block/ChorusFlowerBlock.java
-+++ b/net/minecraft/world/level/block/ChorusFlowerBlock.java
-@@ -23,6 +23,8 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.shapes.VoxelShape;
- 
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
-+
- public class ChorusFlowerBlock extends Block {
- 
-     public static final MapCodec<ChorusFlowerBlock> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
-@@ -103,8 +105,12 @@
-                 }
- 
-                 if (flag && ChorusFlowerBlock.allNeighborsEmpty(world, blockposition1, (Direction) null) && world.isEmptyBlock(pos.above(2))) {
--                    world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2);
--                    this.placeGrownFlower(world, blockposition1, i);
-+                    // CraftBukkit start - add event
-+                    if (CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i)), 2)) {
-+                        world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2);
-+                        this.placeGrownFlower(world, blockposition1, i);
-+                    }
-+                    // CraftBukkit end
-                 } else if (i < 4) {
-                     j = random.nextInt(4);
-                     if (flag1) {
-@@ -118,18 +124,30 @@
-                         BlockPos blockposition2 = pos.relative(enumdirection);
- 
-                         if (world.isEmptyBlock(blockposition2) && world.isEmptyBlock(blockposition2.below()) && ChorusFlowerBlock.allNeighborsEmpty(world, blockposition2, enumdirection.getOpposite())) {
--                            this.placeGrownFlower(world, blockposition2, i + 1);
--                            flag2 = true;
-+                            // CraftBukkit start - add event
-+                            if (CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i + 1)), 2)) {
-+                                this.placeGrownFlower(world, blockposition2, i + 1);
-+                                flag2 = true;
-+                            }
-+                            // CraftBukkit end
-                         }
-                     }
- 
-                     if (flag2) {
-                         world.setBlock(pos, ChorusPlantBlock.getStateWithConnections(world, pos, this.plant.defaultBlockState()), 2);
-                     } else {
--                        this.placeDeadFlower(world, pos);
-+                        // CraftBukkit start - add event
-+                        if (CraftEventFactory.handleBlockGrowEvent(world, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
-+                            this.placeDeadFlower(world, pos);
-+                        }
-+                        // CraftBukkit end
-                     }
-                 } else {
--                    this.placeDeadFlower(world, pos);
-+                    // CraftBukkit start - add event
-+                    if (CraftEventFactory.handleBlockGrowEvent(world, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
-+                        this.placeDeadFlower(world, pos);
-+                    }
-+                    // CraftBukkit end
-                 }
- 
-             }
-@@ -267,6 +285,11 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) {
-+                // CraftBukkit
-+                if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.destroyBlock(blockposition, true, projectile);
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CocoaBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CocoaBlock.java.patch
deleted file mode 100644
index 963cb2ef7b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CocoaBlock.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/world/level/block/CocoaBlock.java
-+++ b/net/minecraft/world/level/block/CocoaBlock.java
-@@ -20,6 +20,7 @@
- import net.minecraft.world.level.pathfinder.PathComputationType;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class CocoaBlock extends HorizontalDirectionalBlock implements BonemealableBlock {
- 
-@@ -57,11 +58,11 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if (world.random.nextInt(5) == 0) {
-+        if (world.random.nextFloat() < (world.spigotConfig.cocoaModifier / (100.0f * 5))) { // Spigot - SPIGOT-7159: Better modifier resolution
-             int i = (Integer) state.getValue(CocoaBlock.AGE);
- 
-             if (i < 2) {
--                world.setBlock(pos, (BlockState) state.setValue(CocoaBlock.AGE, i + 1), 2);
-+                CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(CocoaBlock.AGE, i + 1), 2); // CraftBukkkit
-             }
-         }
- 
-@@ -131,7 +132,7 @@
- 
-     @Override
-     public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
--        world.setBlock(pos, (BlockState) state.setValue(CocoaBlock.AGE, (Integer) state.getValue(CocoaBlock.AGE) + 1), 2);
-+        CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(CocoaBlock.AGE, (Integer) state.getValue(CocoaBlock.AGE) + 1), 2); // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CommandBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CommandBlock.java.patch
deleted file mode 100644
index 6c6c7fbbd8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CommandBlock.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/world/level/block/CommandBlock.java
-+++ b/net/minecraft/world/level/block/CommandBlock.java
-@@ -31,6 +31,8 @@
- import net.minecraft.world.phys.BlockHitResult;
- import org.slf4j.Logger;
- 
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
-+
- public class CommandBlock extends BaseEntityBlock implements GameMasterBlock {
- 
-     public static final MapCodec<CommandBlock> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
-@@ -78,7 +80,16 @@
- 
-     private void setPoweredAndUpdate(Level world, BlockPos pos, CommandBlockEntity blockEntity, boolean powered) {
-         boolean flag1 = blockEntity.isPowered();
-+        // CraftBukkit start
-+        org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+        int old = flag1 ? 15 : 0;
-+        int current = powered ? 15 : 0;
- 
-+        BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, old, current);
-+        world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+        powered = eventRedstone.getNewCurrent() > 0;
-+        // CraftBukkit end
-+
-         if (powered != flag1) {
-             blockEntity.setPowered(powered);
-             if (powered) {
-@@ -141,7 +152,7 @@
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         BlockEntity tileentity = world.getBlockEntity(pos);
- 
--        if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) {
-+        if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
-             player.openCommandBlock((CommandBlockEntity) tileentity);
-             return InteractionResult.SUCCESS;
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ComparatorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ComparatorBlock.java.patch
deleted file mode 100644
index a59662f5c7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ComparatorBlock.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/level/block/ComparatorBlock.java
-+++ b/net/minecraft/world/level/block/ComparatorBlock.java
-@@ -27,6 +27,7 @@
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.ticks.TickPriority;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class ComparatorBlock extends DiodeBlock implements EntityBlock {
- 
-@@ -110,7 +111,8 @@
- 
-     @Nullable
-     private ItemFrame getItemFrame(Level world, Direction facing, BlockPos pos) {
--        List<ItemFrame> list = world.getEntitiesOfClass(ItemFrame.class, new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1)), (entityitemframe) -> {
-+        // CraftBukkit - decompile error
-+        List<ItemFrame> list = world.getEntitiesOfClass(ItemFrame.class, new AABB((double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), (double) (pos.getX() + 1), (double) (pos.getY() + 1), (double) (pos.getZ() + 1)), (java.util.function.Predicate<ItemFrame>) (entityitemframe) -> {
-             return entityitemframe != null && entityitemframe.getDirection() == facing;
-         });
- 
-@@ -163,8 +165,18 @@
-             boolean flag1 = (Boolean) state.getValue(ComparatorBlock.POWERED);
- 
-             if (flag1 && !flag) {
-+                // CraftBukkit start
-+                if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(pos, (BlockState) state.setValue(ComparatorBlock.POWERED, false), 2);
-             } else if (!flag1 && flag) {
-+                // CraftBukkit start
-+                if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(pos, (BlockState) state.setValue(ComparatorBlock.POWERED, true), 2);
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ComposterBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ComposterBlock.java.patch
deleted file mode 100644
index 919fb7f0cb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ComposterBlock.java.patch
+++ /dev/null
@@ -1,184 +0,0 @@
---- a/net/minecraft/world/level/block/ComposterBlock.java
-+++ b/net/minecraft/world/level/block/ComposterBlock.java
-@@ -41,6 +41,10 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder;
-+import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
-+// CraftBukkit end
- 
- public class ComposterBlock extends Block implements WorldlyContainerHolder {
- 
-@@ -241,6 +245,11 @@
-         if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) {
-             if (i < 7 && !world.isClientSide) {
-                 BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack);
-+                // Paper start - handle cancelled events
-+                if (iblockdata1 == null) {
-+                    return InteractionResult.PASS;
-+                }
-+                // Paper end
- 
-                 world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0);
-                 player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
-@@ -269,7 +278,19 @@
-         int i = (Integer) state.getValue(ComposterBlock.LEVEL);
- 
-         if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) {
--            BlockState iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack);
-+            // CraftBukkit start
-+            double rand = world.getRandom().nextDouble();
-+            BlockState iblockdata1 = null; // Paper
-+            if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem
-+                return state;
-+            }
-+            iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand);
-+            // Paper start - handle cancelled events
-+            if (iblockdata1 == null) {
-+                return state;
-+            }
-+            // Paper end
-+            // CraftBukkit end
- 
-             stack.shrink(1);
-             return iblockdata1;
-@@ -279,6 +300,14 @@
-     }
- 
-     public static BlockState extractProduce(Entity user, BlockState state, Level world, BlockPos pos) {
-+        // CraftBukkit start
-+        if (user != null && !(user instanceof Player)) {
-+            BlockState iblockdata1 = ComposterBlock.empty(user, state, DummyGeneratorAccess.INSTANCE, pos);
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) {
-+                return state;
-+            }
-+        }
-+        // CraftBukkit end
-         if (!world.isClientSide) {
-             Vec3 vec3d = Vec3.atLowerCornerWithOffset(pos, 0.5D, 1.01D, 0.5D).offsetRandom(world.random, 0.7F);
-             ItemEntity entityitem = new ItemEntity(world, vec3d.x(), vec3d.y(), vec3d.z(), new ItemStack(Items.BONE_MEAL));
-@@ -301,20 +330,47 @@
-         return iblockdata1;
-     }
- 
-+    @Nullable // Paper
-     static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) {
--        int i = (Integer) state.getValue(ComposterBlock.LEVEL);
--        float f = ComposterBlock.COMPOSTABLES.getFloat(stack.getItem());
-+        // CraftBukkit start
-+        return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble());
-+    }
- 
--        if ((i != 0 || f <= 0.0F) && world.getRandom().nextDouble() >= (double) f) {
--            return state;
--        } else {
--            int j = i + 1;
--            BlockState iblockdata1 = (BlockState) state.setValue(ComposterBlock.LEVEL, j);
-+    @Nullable // Paper - make it nullable
-+    static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) {
-+        // CraftBukkit end
-+        int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL);
-+        float f = ComposterBlock.COMPOSTABLES.getFloat(itemstack.getItem());
- 
--            world.setBlock(pos, iblockdata1, 3);
--            world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(user, iblockdata1));
-+        // Paper start - Add CompostItemEvent and EntityCompostItemEvent
-+        boolean willRaiseLevel = !((i != 0 || f <= 0.0F) && rand >= (double) f);
-+        final io.papermc.paper.event.block.CompostItemEvent event;
-+        if (entity == null) {
-+            event = new io.papermc.paper.event.block.CompostItemEvent(org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel);
-+        } else {
-+            event = new io.papermc.paper.event.entity.EntityCompostItemEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(generatoraccess, blockposition), itemstack.getBukkitStack(), willRaiseLevel);
-+        }
-+        if (!event.callEvent()) { // check for cancellation of entity event (non entity event can't be cancelled cause of hoppers)
-+            return null;
-+        }
-+        willRaiseLevel = event.willRaiseLevel();
-+
-+        if (!willRaiseLevel) {
-+            // Paper end - Add CompostItemEvent and EntityCompostItemEvent
-+            return iblockdata;
-+        } else {
-+            int j = i + 1;
-+            BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j);
-+            // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events
-+            if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) {
-+                return null;
-+            }
-+            // Paper end
-+
-+            generatoraccess.setBlock(blockposition, iblockdata1, 3);
-+            generatoraccess.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1));
-             if (j == 7) {
--                world.scheduleTick(pos, state.getBlock(), 20);
-+                generatoraccess.scheduleTick(blockposition, iblockdata.getBlock(), 20);
-             }
- 
-             return iblockdata1;
-@@ -354,7 +410,8 @@
-     public WorldlyContainer getContainer(BlockState state, LevelAccessor world, BlockPos pos) {
-         int i = (Integer) state.getValue(ComposterBlock.LEVEL);
- 
--        return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(state, world, pos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(state, world, pos) : new ComposterBlock.EmptyContainer()));
-+        // CraftBukkit - empty generatoraccess, blockposition
-+        return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(state, world, pos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(state, world, pos) : new ComposterBlock.EmptyContainer(world, pos)));
-     }
- 
-     public static class OutputContainer extends SimpleContainer implements WorldlyContainer {
-@@ -369,6 +426,7 @@
-             this.state = state;
-             this.level = world;
-             this.pos = pos;
-+            this.bukkitOwner = new CraftBlockInventoryHolder(world, pos, this); // CraftBukkit
-         }
- 
-         @Override
-@@ -393,8 +451,15 @@
- 
-         @Override
-         public void setChanged() {
-+            // CraftBukkit start - allow putting items back (eg cancelled InventoryMoveItemEvent)
-+            if (this.isEmpty()) {
-             ComposterBlock.empty((Entity) null, this.state, this.level, this.pos);
-             this.changed = true;
-+            } else {
-+                this.level.setBlock(this.pos, this.state, 3);
-+                this.changed = false;
-+            }
-+            // CraftBukkit end
-         }
-     }
- 
-@@ -407,6 +472,7 @@
- 
-         public InputContainer(BlockState state, LevelAccessor world, BlockPos pos) {
-             super(1);
-+            this.bukkitOwner = new CraftBlockInventoryHolder(world, pos, this); // CraftBukkit
-             this.state = state;
-             this.level = world;
-             this.pos = pos;
-@@ -439,6 +505,11 @@
-             if (!itemstack.isEmpty()) {
-                 this.changed = true;
-                 BlockState iblockdata = ComposterBlock.addItem((Entity) null, this.state, this.level, this.pos, itemstack);
-+                // Paper start - Add CompostItemEvent and EntityCompostItemEvent
-+                if (iblockdata == null) {
-+                    return;
-+                }
-+                // Paper end - Add CompostItemEvent and EntityCompostItemEvent
- 
-                 this.level.levelEvent(1500, this.pos, iblockdata != this.state ? 1 : 0);
-                 this.removeItemNoUpdate(0);
-@@ -449,8 +520,9 @@
- 
-     public static class EmptyContainer extends SimpleContainer implements WorldlyContainer {
- 
--        public EmptyContainer() {
-+        public EmptyContainer(LevelAccessor generatoraccess, BlockPos blockposition) { // CraftBukkit
-             super(0);
-+            this.bukkitOwner = new CraftBlockInventoryHolder(generatoraccess, blockposition, this); // CraftBukkit
-         }
- 
-         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
deleted file mode 100644
index 5778abe978..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
+++ /dev/null
@@ -1,76 +0,0 @@
---- a/net/minecraft/world/level/block/ConcretePowderBlock.java
-+++ b/net/minecraft/world/level/block/ConcretePowderBlock.java
-@@ -15,6 +15,11 @@
- import net.minecraft.world.level.ScheduledTickAccess;
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlockState;
-+import org.bukkit.craftbukkit.block.CraftBlockStates;
-+import org.bukkit.event.block.BlockFormEvent;
-+// CraftBukkit end
- 
- public class ConcretePowderBlock extends FallingBlock {
- 
-@@ -38,7 +43,7 @@
-     @Override
-     public void onLand(Level world, BlockPos pos, BlockState fallingBlockState, BlockState currentStateInPos, FallingBlockEntity fallingBlockEntity) {
-         if (ConcretePowderBlock.shouldSolidify(world, pos, currentStateInPos)) {
--            world.setBlock(pos, this.concrete.defaultBlockState(), 3);
-+            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, this.concrete.defaultBlockState(), 3); // CraftBukkit
-         }
- 
-     }
-@@ -49,7 +54,24 @@
-         BlockPos blockposition = ctx.getClickedPos();
-         BlockState iblockdata = world.getBlockState(blockposition);
- 
--        return ConcretePowderBlock.shouldSolidify(world, blockposition, iblockdata) ? this.concrete.defaultBlockState() : super.getStateForPlacement(ctx);
-+        // CraftBukkit start
-+        if (!ConcretePowderBlock.shouldSolidify(world, blockposition, iblockdata)) {
-+            return super.getStateForPlacement(ctx);
-+        }
-+
-+        // TODO: An event factory call for methods like this
-+        CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockposition);
-+        blockState.setData(this.concrete.defaultBlockState());
-+
-+        BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState);
-+        world.getServer().server.getPluginManager().callEvent(event);
-+
-+        if (!event.isCancelled()) {
-+            return blockState.getHandle();
-+        }
-+
-+        return super.getStateForPlacement(ctx);
-+        // CraftBukkit end
-     }
- 
-     private static boolean shouldSolidify(BlockGetter world, BlockPos pos, BlockState state) {
-@@ -85,7 +107,25 @@
- 
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
--        return ConcretePowderBlock.touchesLiquid(world, pos) ? this.concrete.defaultBlockState() : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-+        // CraftBukkit start
-+        if (ConcretePowderBlock.touchesLiquid(world, pos)) {
-+            // Suppress during worldgen
-+            if (!(world instanceof Level world1)) {
-+                return this.concrete.defaultBlockState();
-+            }
-+            CraftBlockState blockState = CraftBlockStates.getBlockState(world1, pos);
-+            blockState.setData(this.concrete.defaultBlockState());
-+
-+            BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState);
-+            world1.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled()) {
-+                return blockState.getHandle();
-+            }
-+        }
-+
-+        return super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CrafterBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CrafterBlock.java.patch
deleted file mode 100644
index b3f1ac312a..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CrafterBlock.java.patch
+++ /dev/null
@@ -1,89 +0,0 @@
---- a/net/minecraft/world/level/block/CrafterBlock.java
-+++ b/net/minecraft/world/level/block/CrafterBlock.java
-@@ -12,6 +12,7 @@
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.RandomSource;
-+import net.minecraft.world.CompoundContainer;
- import net.minecraft.world.Container;
- import net.minecraft.world.Containers;
- import net.minecraft.world.InteractionResult;
-@@ -39,6 +40,12 @@
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.Vec3;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.block.CrafterCraftEvent;
-+import org.bukkit.event.inventory.InventoryMoveItemEvent;
-+import org.bukkit.inventory.Inventory;
-+// CraftBukkit end
- 
- public class CrafterBlock extends BaseEntityBlock {
- 
-@@ -189,6 +196,13 @@
-                 RecipeHolder<CraftingRecipe> recipeholder = (RecipeHolder) optional.get();
-                 ItemStack itemstack = ((CraftingRecipe) recipeholder.value()).assemble(craftinginput, world.registryAccess());
- 
-+                // CraftBukkit start
-+                CrafterCraftEvent event = CraftEventFactory.callCrafterCraftEvent(pos, world, crafterblockentity, itemstack, recipeholder);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+                itemstack = CraftItemStack.asNMSCopy(event.getResult());
-+                // CraftBukkit end
-                 if (itemstack.isEmpty()) {
-                     world.levelEvent(1050, pos, 0);
-                 } else {
-@@ -227,7 +241,25 @@
-         ItemStack itemstack1 = stack.copy();
- 
-         if (iinventory != null && (iinventory instanceof CrafterBlockEntity || stack.getCount() > iinventory.getMaxStackSize(stack))) {
-+            // CraftBukkit start - InventoryMoveItemEvent
-+            CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack1);
-+
-+            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 {
-+                destinationInventory = iinventory.getOwner().getInventory();
-+            }
-+
-+            InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+            itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
-             while (!itemstack1.isEmpty()) {
-+                if (event.isCancelled()) {
-+                    break;
-+                }
-+                // CraftBukkit end
-                 ItemStack itemstack2 = itemstack1.copyWithCount(1);
-                 ItemStack itemstack3 = HopperBlockEntity.addItem(blockEntity, iinventory, itemstack2, enumdirection.getOpposite());
- 
-@@ -238,7 +270,25 @@
-                 itemstack1.shrink(1);
-             }
-         } else if (iinventory != null) {
-+            // CraftBukkit start - InventoryMoveItemEvent
-+            CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack1);
-+
-+            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 {
-+                destinationInventory = iinventory.getOwner().getInventory();
-+            }
-+
-+            InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+            itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
-             while (!itemstack1.isEmpty()) {
-+                if (event.isCancelled()) {
-+                    break;
-+                }
-+                // CraftBukkit end
-                 int i = itemstack1.getCount();
- 
-                 itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, itemstack1, enumdirection.getOpposite());
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/CropBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/CropBlock.java.patch
deleted file mode 100644
index 4883d93884..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/CropBlock.java.patch
+++ /dev/null
@@ -1,59 +0,0 @@
---- a/net/minecraft/world/level/block/CropBlock.java
-+++ b/net/minecraft/world/level/block/CropBlock.java
-@@ -21,6 +21,7 @@
- import net.minecraft.world.level.block.state.properties.IntegerProperty;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class CropBlock extends BushBlock implements BonemealableBlock {
- 
-@@ -82,9 +83,26 @@
-             if (i < this.getMaxAge()) {
-                 float f = CropBlock.getGrowthSpeed(this, world, pos);
- 
--                if (random.nextInt((int) (25.0F / f) + 1) == 0) {
--                    world.setBlock(pos, this.getStateForAge(i + 1), 2);
-+                // Spigot start
-+                int modifier;
-+                if (this == Blocks.BEETROOTS) {
-+                    modifier = world.spigotConfig.beetrootModifier;
-+                } else if (this == Blocks.CARROTS) {
-+                    modifier = world.spigotConfig.carrotModifier;
-+                } else if (this == Blocks.POTATOES) {
-+                    modifier = world.spigotConfig.potatoModifier;
-+                // Paper start - Fix Spigot growth modifiers
-+                } else if (this == Blocks.TORCHFLOWER_CROP) {
-+                    modifier = world.spigotConfig.torchFlowerModifier;
-+                // Paper end - Fix Spigot growth modifiers
-+                } else {
-+                    modifier = world.spigotConfig.wheatModifier;
-                 }
-+
-+                if (random.nextFloat() < (modifier / (100.0f * (Math.floor((25.0F / f) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution
-+                    // Spigot end
-+                    CraftEventFactory.handleBlockGrowEvent(world, pos, this.getStateForAge(i + 1), 2); // CraftBukkit
-+                }
-             }
-         }
- 
-@@ -98,7 +116,7 @@
-             i = j;
-         }
- 
--        world.setBlock(pos, this.getStateForAge(i), 2);
-+        CraftEventFactory.handleBlockGrowEvent(world, pos, this.getStateForAge(i), 2); // CraftBukkit
-     }
- 
-     protected int getBonemealAgeIncrease(Level world) {
-@@ -160,8 +178,9 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (world instanceof ServerLevel worldserver) {
--            if (entity instanceof Ravager && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+            if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
-                 worldserver.destroyBlock(pos, true, entity);
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
deleted file mode 100644
index 4631339b55..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/level/block/DaylightDetectorBlock.java
-+++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java
-@@ -74,6 +74,7 @@
- 
-         i = Mth.clamp(i, 0, 15);
-         if ((Integer) state.getValue(DaylightDetectorBlock.POWER) != i) {
-+            i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, ((Integer) state.getValue(DaylightDetectorBlock.POWER)), i).getNewCurrent(); // CraftBukkit - Call BlockRedstoneEvent
-             world.setBlock(pos, (BlockState) state.setValue(DaylightDetectorBlock.POWER, i), 3);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DecoratedPotBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
deleted file mode 100644
index 6b148eba67..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DecoratedPotBlock.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/level/block/DecoratedPotBlock.java
-+++ b/net/minecraft/world/level/block/DecoratedPotBlock.java
-@@ -240,6 +240,11 @@
- 
-         if (world instanceof ServerLevel worldserver) {
-             if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) {
-+                // CraftBukkit start - call EntityChangeBlockEvent
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, this.getFluidState(state).createLegacyBlock())) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(blockposition, (BlockState) state.setValue(DecoratedPotBlock.CRACKED, true), 4);
-                 world.destroyBlock(blockposition, true, projectile);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DetectorRailBlock.java.patch
deleted file mode 100644
index de7bae1c90..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DetectorRailBlock.java.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/net/minecraft/world/level/block/DetectorRailBlock.java
-+++ b/net/minecraft/world/level/block/DetectorRailBlock.java
-@@ -26,6 +26,7 @@
- import net.minecraft.world.level.block.state.properties.RailShape;
- import net.minecraft.world.level.redstone.Orientation;
- import net.minecraft.world.phys.AABB;
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
- 
- public class DetectorRailBlock extends BaseRailBlock {
- 
-@@ -51,6 +52,7 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide) {
-             if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) {
-                 this.checkPressed(world, pos, state);
-@@ -77,6 +79,7 @@
- 
-     private void checkPressed(Level world, BlockPos pos, BlockState state) {
-         if (this.canSurvive(state, world, pos)) {
-+            if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly
-             boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED);
-             boolean flag1 = false;
-             List<AbstractMinecart> list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> {
-@@ -88,7 +91,17 @@
-             }
- 
-             BlockState iblockdata1;
-+            // CraftBukkit start
-+            if (flag != flag1) {
-+                org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
- 
-+                BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, flag ? 15 : 0, flag1 ? 15 : 0);
-+                world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+                flag1 = eventRedstone.getNewCurrent() > 0;
-+            }
-+            // CraftBukkit end
-+
-             if (flag1 && !flag) {
-                 iblockdata1 = (BlockState) state.setValue(DetectorRailBlock.POWERED, true);
-                 world.setBlock(pos, iblockdata1, 3);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DiodeBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DiodeBlock.java.patch
deleted file mode 100644
index 91c2eb8ada..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DiodeBlock.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/level/block/DiodeBlock.java
-+++ b/net/minecraft/world/level/block/DiodeBlock.java
-@@ -23,6 +23,7 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
- import net.minecraft.world.ticks.TickPriority;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public abstract class DiodeBlock extends HorizontalDirectionalBlock {
- 
-@@ -59,8 +60,18 @@
-             boolean flag1 = this.shouldTurnOn(world, pos, state);
- 
-             if (flag && !flag1) {
-+                // CraftBukkit start
-+                if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(pos, (BlockState) state.setValue(DiodeBlock.POWERED, false), 2);
-             } else if (!flag) {
-+                // CraftBukkit start
-+                if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.setBlock(pos, (BlockState) state.setValue(DiodeBlock.POWERED, true), 2);
-                 if (!flag1) {
-                     world.scheduleTick(pos, (Block) this, this.getDelay(state), TickPriority.VERY_HIGH);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DispenserBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DispenserBlock.java.patch
deleted file mode 100644
index 71019ffd00..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DispenserBlock.java.patch
+++ /dev/null
@@ -1,61 +0,0 @@
---- a/net/minecraft/world/level/block/DispenserBlock.java
-+++ b/net/minecraft/world/level/block/DispenserBlock.java
-@@ -52,6 +52,7 @@
-     private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior();
-     public static final Map<Item, DispenseItemBehavior> DISPENSER_REGISTRY = new IdentityHashMap();
-     private static final int TRIGGER_DURATION = 4;
-+    public static boolean eventFired = false; // CraftBukkit
- 
-     @Override
-     public MapCodec<? extends DispenserBlock> codec() {
-@@ -79,8 +80,9 @@
-             if (tileentity instanceof DispenserBlockEntity) {
-                 DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) tileentity;
- 
--                player.openMenu(tileentitydispenser);
-+                if (player.openMenu(tileentitydispenser).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
-                 player.awardStat(tileentitydispenser instanceof DropperBlockEntity ? Stats.INSPECT_DROPPER : Stats.INSPECT_DISPENSER);
-+                } // Paper - Fix InventoryOpenEvent cancellation
-             }
-         }
- 
-@@ -88,7 +90,7 @@
-     }
- 
-     public void dispenseFrom(ServerLevel world, BlockState state, BlockPos pos) {
--        DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DISPENSER).orElse((Object) null);
-+        DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DISPENSER).orElse(null); // CraftBukkit - decompile error
- 
-         if (tileentitydispenser == null) {
-             DispenserBlock.LOGGER.warn("Ignoring dispensing attempt for Dispenser without matching block entity at {}", pos);
-@@ -97,13 +99,17 @@
-             int i = tileentitydispenser.getRandomSlot(world.random);
- 
-             if (i < 0) {
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent
-                 world.levelEvent(1001, pos, 0);
-                 world.gameEvent((Holder) GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState()));
-+                } // Paper - Add BlockFailedDispenseEvent
-             } else {
-                 ItemStack itemstack = tileentitydispenser.getItem(i);
-                 DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, itemstack);
- 
-                 if (idispensebehavior != DispenseItemBehavior.NOOP) {
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent
-+                    DispenserBlock.eventFired = false; // CraftBukkit - reset event status
-                     tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack));
-                 }
- 
-@@ -111,6 +117,12 @@
-         }
-     }
- 
-+    // Paper start - Fix NPE with equippable and items without behavior
-+    public static DispenseItemBehavior getDispenseBehavior(BlockSource pointer, ItemStack stack) {
-+        return ((DispenserBlock) pointer.state().getBlock()).getDispenseMethod(pointer.level(), stack);
-+    }
-+    // Paper end - Fix NPE with equippable and items without behavior
-+
-     protected DispenseItemBehavior getDispenseMethod(Level world, ItemStack stack) {
-         if (!stack.isItemEnabled(world.enabledFeatures())) {
-             return DispenserBlock.DEFAULT_BEHAVIOR;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DoorBlock.java.patch
deleted file mode 100644
index ea278f700f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoorBlock.java.patch
+++ /dev/null
@@ -1,37 +0,0 @@
---- a/net/minecraft/world/level/block/DoorBlock.java
-+++ b/net/minecraft/world/level/block/DoorBlock.java
-@@ -38,6 +38,7 @@
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
- 
- public class DoorBlock extends Block {
- 
-@@ -222,9 +223,24 @@
- 
-     @Override
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
--        boolean flag1 = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
-+        // CraftBukkit start
-+        BlockPos otherHalf = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
- 
--        if (!this.defaultBlockState().is(sourceBlock) && flag1 != (Boolean) state.getValue(DoorBlock.POWERED)) {
-+        org.bukkit.World bworld = world.getWorld();
-+        org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+        org.bukkit.block.Block blockTop = bworld.getBlockAt(otherHalf.getX(), otherHalf.getY(), otherHalf.getZ());
-+
-+        int power = bukkitBlock.getBlockPower();
-+        int powerTop = blockTop.getBlockPower();
-+        if (powerTop > power) power = powerTop;
-+        int oldPower = (Boolean) state.getValue(DoorBlock.POWERED) ? 15 : 0;
-+
-+        if (oldPower == 0 ^ power == 0) {
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, oldPower, power);
-+            world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+            boolean flag1 = eventRedstone.getNewCurrent() > 0;
-+            // CraftBukkit end
-             if (flag1 != (Boolean) state.getValue(DoorBlock.OPEN)) {
-                 this.playSound((Entity) null, world, pos, flag1);
-                 world.gameEvent((Entity) null, (Holder) (flag1 ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE), pos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DoublePlantBlock.java.patch
deleted file mode 100644
index a59d6fc4f3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DoublePlantBlock.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/level/block/DoublePlantBlock.java
-+++ b/net/minecraft/world/level/block/DoublePlantBlock.java
-@@ -98,11 +98,16 @@
-     }
- 
-     @Override
--    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
--        super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool);
-+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
-+        super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
-     }
- 
-     protected static void preventDropFromBottomPart(Level world, BlockPos pos, BlockState state, Player player) {
-+        // CraftBukkit start
-+        if (((net.minecraft.server.level.ServerLevel)world).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, pos).isCancelled()) { // Paper
-+            return;
-+        }
-+        // CraftBukkit end
-         DoubleBlockHalf blockpropertydoubleblockhalf = (DoubleBlockHalf) state.getValue(DoublePlantBlock.HALF);
- 
-         if (blockpropertydoubleblockhalf == DoubleBlockHalf.UPPER) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DragonEggBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DragonEggBlock.java.patch
deleted file mode 100644
index b73b669baf..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DragonEggBlock.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/level/block/DragonEggBlock.java
-+++ b/net/minecraft/world/level/block/DragonEggBlock.java
-@@ -15,6 +15,7 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.BlockFromToEvent; // CraftBukkit
- 
- public class DragonEggBlock extends FallingBlock {
- 
-@@ -53,6 +54,18 @@
-             BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16));
- 
-             if (world.getBlockState(blockposition1).isAir() && worldborder.isWithinBounds(blockposition1)) {
-+                // CraftBukkit start
-+                org.bukkit.block.Block from = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+                org.bukkit.block.Block to = world.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
-+                BlockFromToEvent event = new BlockFromToEvent(from, to);
-+                org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+
-+                blockposition1 = new BlockPos(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ());
-+                // CraftBukkit end
-                 if (world.isClientSide) {
-                     for (int j = 0; j < 128; ++j) {
-                         double d0 = world.random.nextDouble();
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/DropperBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/DropperBlock.java.patch
deleted file mode 100644
index 0a8d6b5e2e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/DropperBlock.java.patch
+++ /dev/null
@@ -1,75 +0,0 @@
---- a/net/minecraft/world/level/block/DropperBlock.java
-+++ b/net/minecraft/world/level/block/DropperBlock.java
-@@ -8,6 +8,7 @@
- import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
- import net.minecraft.core.dispenser.DispenseItemBehavior;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.world.CompoundContainer;
- import net.minecraft.world.Container;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
-@@ -19,12 +20,15 @@
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.inventory.InventoryMoveItemEvent;
-+// CraftBukkit end
- 
- public class DropperBlock extends DispenserBlock {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-     public static final MapCodec<DropperBlock> CODEC = simpleCodec(DropperBlock::new);
--    private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior();
-+    private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior(true); // CraftBukkit
- 
-     @Override
-     public MapCodec<DropperBlock> codec() {
-@@ -47,7 +51,7 @@
- 
-     @Override
-     public void dispenseFrom(ServerLevel world, BlockState state, BlockPos pos) {
--        DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DROPPER).orElse((Object) null);
-+        DispenserBlockEntity tileentitydispenser = (DispenserBlockEntity) world.getBlockEntity(pos, BlockEntityType.DROPPER).orElse(null); // CraftBukkit - decompile error
- 
-         if (tileentitydispenser == null) {
-             DropperBlock.LOGGER.warn("Ignoring dispensing attempt for Dropper without matching block entity at {}", pos);
-@@ -56,6 +60,7 @@
-             int i = tileentitydispenser.getRandomSlot(world.random);
- 
-             if (i < 0) {
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent
-                 world.levelEvent(1001, pos, 0);
-             } else {
-                 ItemStack itemstack = tileentitydispenser.getItem(i);
-@@ -66,10 +71,28 @@
-                     ItemStack itemstack1;
- 
-                     if (iinventory == null) {
-+                        if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent
-                         itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack);
-                     } else {
--                        itemstack1 = HopperBlockEntity.addItem(tileentitydispenser, iinventory, itemstack.copyWithCount(1), enumdirection.getOpposite());
--                        if (itemstack1.isEmpty()) {
-+                        // CraftBukkit start - Fire event when pushing items into other inventories
-+                        CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack.copyWithCount(1));
-+
-+                        org.bukkit.inventory.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 {
-+                            destinationInventory = iinventory.getOwner().getInventory();
-+                        }
-+
-+                        InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentitydispenser.getOwner().getInventory(), oitemstack, destinationInventory, true);
-+                        world.getCraftServer().getPluginManager().callEvent(event);
-+                        if (event.isCancelled()) {
-+                            return;
-+                        }
-+                        itemstack1 = HopperBlockEntity.addItem(tileentitydispenser, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection.getOpposite());
-+                        if (event.getItem().equals(oitemstack) && itemstack1.isEmpty()) {
-+                            // CraftBukkit end
-                             itemstack1 = itemstack.copy();
-                             itemstack1.shrink(1);
-                         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/EndGatewayBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/EndGatewayBlock.java.patch
deleted file mode 100644
index 2e2055c634..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/EndGatewayBlock.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/world/level/block/EndGatewayBlock.java
-+++ b/net/minecraft/world/level/block/EndGatewayBlock.java
-@@ -22,6 +22,9 @@
- import net.minecraft.world.level.material.Fluid;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+// CraftBukkit end
- 
- public class EndGatewayBlock extends BaseEntityBlock implements Portal {
- 
-@@ -89,7 +92,12 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (entity.canUsePortal(false)) {
-+            // Paper start - call EntityPortalEnterEvent
-+            org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.END_GATEWAY); // Paper - add portal type
-+            if (!event.callEvent()) return;
-+            // Paper end - call EntityPortalEnterEvent
-             BlockEntity tileentity = world.getBlockEntity(pos);
- 
-             if (!world.isClientSide && tileentity instanceof TheEndGatewayBlockEntity) {
-@@ -112,7 +120,7 @@
-         if (tileentity instanceof TheEndGatewayBlockEntity tileentityendgateway) {
-             Vec3 vec3d = tileentityendgateway.getPortalPosition(world, pos);
- 
--            return vec3d == null ? null : (entity instanceof ThrownEnderpearl ? new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET) : new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET));
-+            return vec3d == null ? null : (entity instanceof ThrownEnderpearl ? new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, PlayerTeleportEvent.TeleportCause.END_GATEWAY) : new TeleportTransition(world, vec3d, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, PlayerTeleportEvent.TeleportCause.END_GATEWAY)); // CraftBukkit
-         } else {
-             return null;
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/EndPortalBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/EndPortalBlock.java.patch
deleted file mode 100644
index c075ce1cd8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/EndPortalBlock.java.patch
+++ /dev/null
@@ -1,91 +0,0 @@
---- a/net/minecraft/world/level/block/EndPortalBlock.java
-+++ b/net/minecraft/world/level/block/EndPortalBlock.java
-@@ -19,12 +19,23 @@
- import net.minecraft.world.level.block.entity.TheEndPortalBlockEntity;
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.feature.EndPlatformFeature;
- import net.minecraft.world.level.material.Fluid;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.event.CraftPortalEvent;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.entity.EntityPortalEnterEvent;
-+import org.bukkit.event.player.PlayerRespawnEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+// CraftBukkit end
- 
- public class EndPortalBlock extends BaseEntityBlock implements Portal {
- 
-@@ -57,10 +68,17 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (entity.canUsePortal(false)) {
-+            // CraftBukkit start - Entity in portal
-+            EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) return; // Paper - make cancellable
-+            // CraftBukkit end
-             if (!world.isClientSide && world.dimension() == Level.END && entity instanceof ServerPlayer) {
-                 ServerPlayer entityplayer = (ServerPlayer) entity;
- 
-+                if (world.paperConfig().misc.disableEndCredits) entityplayer.seenCredits = true; // Paper - Option to disable end credits
-                 if (!entityplayer.seenCredits) {
-                     entityplayer.showEndCredits();
-                     return;
-@@ -74,11 +92,11 @@
- 
-     @Override
-     public TeleportTransition getPortalDestination(ServerLevel world, Entity entity, BlockPos pos) {
--        ResourceKey<Level> resourcekey = world.dimension() == Level.END ? Level.OVERWORLD : Level.END;
-+        ResourceKey<Level> resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends
-         ServerLevel worldserver1 = world.getServer().getLevel(resourcekey);
- 
-         if (worldserver1 == null) {
--            return null;
-+            return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist
-         } else {
-             boolean flag = resourcekey == Level.END;
-             BlockPos blockposition1 = flag ? ServerLevel.END_SPAWN_POINT : worldserver1.getSharedSpawnPos();
-@@ -87,7 +105,7 @@
-             Set set;
- 
-             if (flag) {
--                EndPlatformFeature.createEndPlatform(worldserver1, BlockPos.containing(vec3d).below(), true);
-+                EndPlatformFeature.createEndPlatform(worldserver1, BlockPos.containing(vec3d).below(), true, entity); // CraftBukkit
-                 f = Direction.WEST.toYRot();
-                 set = Relative.union(Relative.DELTA, Set.of(Relative.X_ROT));
-                 if (entity instanceof ServerPlayer) {
-@@ -99,13 +117,21 @@
-                 if (entity instanceof ServerPlayer) {
-                     ServerPlayer entityplayer = (ServerPlayer) entity;
- 
--                    return entityplayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING);
-+                    return entityplayer.findRespawnPositionAndUseSpawnBlock(false, TeleportTransition.DO_NOTHING, PlayerRespawnEvent.RespawnReason.END_PORTAL); // CraftBukkit
-                 }
- 
-                 vec3d = entity.adjustSpawnLocation(worldserver1, blockposition1).getBottomCenter();
-             }
- 
--            return new TeleportTransition(worldserver1, vec3d, Vec3.ZERO, f, 0.0F, set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET));
-+            // CraftBukkit start
-+            CraftPortalEvent event = entity.callPortalEvent(entity, CraftLocation.toBukkit(vec3d, worldserver1.getWorld(), f, entity.getXRot()), PlayerTeleportEvent.TeleportCause.END_PORTAL, 0, 0);
-+            if (event == null) {
-+                return null;
-+            }
-+            Location to = event.getTo();
-+
-+            return new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), entity.getDeltaMovement(), to.getYaw(), to.getPitch(), set, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), PlayerTeleportEvent.TeleportCause.END_PORTAL);
-+            // CraftBukkit end
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/EnderChestBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/EnderChestBlock.java.patch
deleted file mode 100644
index 0b1e52d838..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/EnderChestBlock.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/level/block/EnderChestBlock.java
-+++ b/net/minecraft/world/level/block/EnderChestBlock.java
-@@ -78,14 +78,16 @@
-         PlayerEnderChestContainer playerEnderChestContainer = player.getEnderChestInventory();
-         if (playerEnderChestContainer != null && world.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) {
-             BlockPos blockPos = pos.above();
--            if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) {
-+            if (world.getBlockState(blockPos).isRedstoneConductor(world, blockPos)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic
-                 return InteractionResult.SUCCESS;
-             } else {
--                if (world instanceof ServerLevel serverLevel) {
--                    playerEnderChestContainer.setActiveChest(enderChestBlockEntity);
--                    player.openMenu(
--                        new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE)
--                    );
-+                // Paper start - Fix InventoryOpenEvent cancellation - moved up;
-+                playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations
-+                if (world instanceof ServerLevel serverLevel && player.openMenu(
-+                    new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE)
-+                ).isPresent()) {
-+                // Paper end - Fix InventoryOpenEvent cancellation - moved up;
-+                    // Paper - Fix InventoryOpenEvent cancellation - moved up;
-                     player.awardStat(Stats.OPEN_ENDERCHEST);
-                     PiglinAi.angerNearbyPiglins(serverLevel, player, true);
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FarmBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FarmBlock.java.patch
deleted file mode 100644
index d862f84ba3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FarmBlock.java.patch
+++ /dev/null
@@ -1,112 +0,0 @@
---- a/net/minecraft/world/level/block/FarmBlock.java
-+++ b/net/minecraft/world/level/block/FarmBlock.java
-@@ -29,6 +29,10 @@
- import net.minecraft.world.level.pathfinder.PathComputationType;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityInteractEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class FarmBlock extends Block {
- 
-@@ -89,31 +93,56 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         int i = (Integer) state.getValue(FarmBlock.MOISTURE);
-+        if (i > 0 && world.paperConfig().tickRates.wetFarmland != 1 && (world.paperConfig().tickRates.wetFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks
-+        if (i == 0 && world.paperConfig().tickRates.dryFarmland != 1 && (world.paperConfig().tickRates.dryFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks
- 
-         if (!FarmBlock.isNearWater(world, pos) && !world.isRainingAt(pos.above())) {
-             if (i > 0) {
--                world.setBlock(pos, (BlockState) state.setValue(FarmBlock.MOISTURE, i - 1), 2);
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(world, pos, (BlockState) state.setValue(FarmBlock.MOISTURE, i - 1), 2); // CraftBukkit
-             } else if (!FarmBlock.shouldMaintainFarmland(world, pos)) {
-                 FarmBlock.turnToDirt((Entity) null, state, world, pos);
-             }
-         } else if (i < 7) {
--            world.setBlock(pos, (BlockState) state.setValue(FarmBlock.MOISTURE, 7), 2);
-+            org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(world, pos, (BlockState) state.setValue(FarmBlock.MOISTURE, 7), 2); // CraftBukkit
-         }
- 
-     }
- 
-     @Override
-     public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
-+        super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
-         if (world instanceof ServerLevel worldserver) {
-             if (world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
-+                // CraftBukkit start - Interact soil
-+                org.bukkit.event.Cancellable cancellable;
-+                if (entity instanceof Player) {
-+                    cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                } else {
-+                    cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                    world.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
-+                }
-+
-+                if (cancellable.isCancelled()) {
-+                    return;
-+                }
-+
-+                if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 FarmBlock.turnToDirt(entity, state, world, pos);
-             }
-         }
- 
--        super.fallOn(world, state, pos, entity, fallDistance);
-+        // super.fallOn(world, iblockdata, blockposition, entity, f); // CraftBukkit - moved up
-     }
- 
-     public static void turnToDirt(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) {
-+        // CraftBukkit start
-+        if (CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
-+            return;
-+        }
-+        // CraftBukkit end
-         BlockState iblockdata1 = pushEntitiesUp(state, Blocks.DIRT.defaultBlockState(), world, pos);
- 
-         world.setBlockAndUpdate(pos, iblockdata1);
-@@ -125,19 +154,28 @@
-     }
- 
-     private static boolean isNearWater(LevelReader world, BlockPos pos) {
--        Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator();
-+        // Paper start - Perf: remove abstract block iteration
-+        int xOff = pos.getX();
-+        int yOff = pos.getY();
-+        int zOff = pos.getZ();
- 
--        BlockPos blockposition1;
--
--        do {
--            if (!iterator.hasNext()) {
--                return false;
-+        for (int dz = -4; dz <= 4; ++dz) {
-+            int z = dz + zOff;
-+            for (int dx = -4; dx <= 4; ++dx) {
-+                int x = xOff + dx;
-+                for (int dy = 0; dy <= 1; ++dy) {
-+                    int y = dy + yOff;
-+                    net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4);
-+                    net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState();
-+                    if (fluid.is(FluidTags.WATER)) {
-+                        return true;
-+                    }
-+                }
-             }
-+        }
- 
--            blockposition1 = (BlockPos) iterator.next();
--        } while (!world.getFluidState(blockposition1).is(FluidTags.WATER));
--
--        return true;
-+        return false;
-+        // Paper end - Perf: remove abstract block iteration
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FenceGateBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FenceGateBlock.java.patch
deleted file mode 100644
index 78872a0616..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FenceGateBlock.java.patch
+++ /dev/null
@@ -1,20 +0,0 @@
---- a/net/minecraft/world/level/block/FenceGateBlock.java
-+++ b/net/minecraft/world/level/block/FenceGateBlock.java
-@@ -173,6 +173,17 @@
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
-         if (!world.isClientSide) {
-             boolean flag1 = world.hasNeighborSignal(pos);
-+            // CraftBukkit start
-+            boolean oldPowered = state.getValue(FenceGateBlock.POWERED);
-+            if (oldPowered != flag1) {
-+                int newPower = flag1 ? 15 : 0;
-+                int oldPower = oldPowered ? 15 : 0;
-+                org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+                org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, newPower);
-+                world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+                flag1 = eventRedstone.getNewCurrent() > 0;
-+            }
-+            // CraftBukkit end
- 
-             if ((Boolean) state.getValue(FenceGateBlock.POWERED) != flag1) {
-                 world.setBlock(pos, (BlockState) ((BlockState) state.setValue(FenceGateBlock.POWERED, flag1)).setValue(FenceGateBlock.OPEN, flag1), 2);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FireBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FireBlock.java.patch
deleted file mode 100644
index 5d840efc2e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FireBlock.java.patch
+++ /dev/null
@@ -1,205 +0,0 @@
---- a/net/minecraft/world/level/block/FireBlock.java
-+++ b/net/minecraft/world/level/block/FireBlock.java
-@@ -14,6 +14,7 @@
- import net.minecraft.tags.BiomeTags;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.item.context.BlockPlaceContext;
-+import net.minecraft.world.item.context.UseOnContext;
- import net.minecraft.world.level.BlockGetter;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.Level;
-@@ -28,6 +29,12 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.block.CraftBlockState;
-+import org.bukkit.craftbukkit.block.CraftBlockStates;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.block.BlockBurnEvent;
-+import org.bukkit.event.block.BlockFadeEvent;
-+// CraftBukkit end
- 
- public class FireBlock extends BaseFireBlock {
- 
-@@ -100,7 +107,25 @@
- 
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
--        return this.canSurvive(state, world, pos) ? this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState();
-+        // CraftBukkit start
-+        if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation
-+        if (!this.canSurvive(state, world, pos)) {
-+            // Suppress during worldgen
-+            if (!(world instanceof Level world1)) {
-+                return Blocks.AIR.defaultBlockState();
-+            }
-+            CraftBlockState blockState = CraftBlockStates.getBlockState(world1, pos);
-+            blockState.setData(Blocks.AIR.defaultBlockState());
-+
-+            BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState);
-+            world1.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (!event.isCancelled()) {
-+                return blockState.getHandle();
-+            }
-+        }
-+        return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation"
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -146,10 +171,10 @@
- 
-     @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random));
-+        world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option
-         if (world.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
-             if (!state.canSurvive(world, pos)) {
--                world.removeBlock(pos, false);
-+                this.fireExtinguished(world, pos); // CraftBukkit - invalid place location
-             }
- 
-             BlockState iblockdata1 = world.getBlockState(pos.below());
-@@ -157,7 +182,7 @@
-             int i = (Integer) state.getValue(FireBlock.AGE);
- 
-             if (!flag && world.isRaining() && this.isNearRain(world, pos) && random.nextFloat() < 0.2F + (float) i * 0.03F) {
--                world.removeBlock(pos, false);
-+                this.fireExtinguished(world, pos); // CraftBukkit - extinguished by rain
-             } else {
-                 int j = Math.min(15, i + random.nextInt(3) / 2);
- 
-@@ -171,14 +196,14 @@
-                         BlockPos blockposition1 = pos.below();
- 
-                         if (!world.getBlockState(blockposition1).isFaceSturdy(world, blockposition1, Direction.UP) || i > 3) {
--                            world.removeBlock(pos, false);
-+                            this.fireExtinguished(world, pos); // CraftBukkit
-                         }
- 
-                         return;
-                     }
- 
-                     if (i == 15 && random.nextInt(4) == 0 && !this.canBurn(world.getBlockState(pos.below()))) {
--                        world.removeBlock(pos, false);
-+                        this.fireExtinguished(world, pos); // CraftBukkit
-                         return;
-                     }
-                 }
-@@ -186,12 +211,14 @@
-                 boolean flag1 = world.getBiome(pos).is(BiomeTags.INCREASED_FIRE_BURNOUT);
-                 int k = flag1 ? -50 : 0;
- 
--                this.checkBurnOut(world, pos.east(), 300 + k, random, i);
--                this.checkBurnOut(world, pos.west(), 300 + k, random, i);
--                this.checkBurnOut(world, pos.below(), 250 + k, random, i);
--                this.checkBurnOut(world, pos.above(), 250 + k, random, i);
--                this.checkBurnOut(world, pos.north(), 300 + k, random, i);
--                this.checkBurnOut(world, pos.south(), 300 + k, random, i);
-+                // CraftBukkit start - add source blockposition to burn calls
-+                this.trySpread(world, pos.east(), 300 + k, random, i, pos);
-+                this.trySpread(world, pos.west(), 300 + k, random, i, pos);
-+                this.trySpread(world, pos.below(), 250 + k, random, i, pos);
-+                this.trySpread(world, pos.above(), 250 + k, random, i, pos);
-+                this.trySpread(world, pos.north(), 300 + k, random, i, pos);
-+                this.trySpread(world, pos.south(), 300 + k, random, i, pos);
-+                // CraftBukkit end
-                 BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
- 
-                 for (int l = -1; l <= 1; ++l) {
-@@ -217,7 +244,15 @@
-                                     if (i2 > 0 && random.nextInt(k1) <= i2 && (!world.isRaining() || !this.isNearRain(world, blockposition_mutableblockposition))) {
-                                         int j2 = Math.min(15, i + random.nextInt(5) / 4);
- 
--                                        world.setBlock(blockposition_mutableblockposition, this.getStateWithAge(world, blockposition_mutableblockposition, j2), 3);
-+                                        // CraftBukkit start - Call to stop spread of fire
-+                                        if (world.getBlockState(blockposition_mutableblockposition).getBlock() != Blocks.FIRE) {
-+                                            if (CraftEventFactory.callBlockIgniteEvent(world, blockposition_mutableblockposition, pos).isCancelled()) {
-+                                                continue;
-+                                            }
-+
-+                                            CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition_mutableblockposition, this.getStateWithAge(world, blockposition_mutableblockposition, j2), 3); // CraftBukkit
-+                                        }
-+                                        // CraftBukkit end
-                                     }
-                                 }
-                             }
-@@ -241,24 +276,47 @@
-         return state.hasProperty(BlockStateProperties.WATERLOGGED) && (Boolean) state.getValue(BlockStateProperties.WATERLOGGED) ? 0 : this.igniteOdds.getInt(state.getBlock());
-     }
- 
--    private void checkBurnOut(Level world, BlockPos pos, int spreadFactor, RandomSource random, int currentAge) {
--        int k = this.getBurnOdds(world.getBlockState(pos));
-+    private void trySpread(Level world, BlockPos blockposition, int i, RandomSource randomsource, int j, BlockPos sourceposition) { // CraftBukkit add sourceposition
-+        int k = this.getBurnOdds(world.getBlockState(blockposition));
- 
--        if (random.nextInt(spreadFactor) < k) {
--            BlockState iblockdata = world.getBlockState(pos);
-+        if (randomsource.nextInt(i) < k) {
-+            BlockState iblockdata = world.getBlockState(blockposition);
- 
--            if (random.nextInt(currentAge + 10) < 5 && !world.isRainingAt(pos)) {
--                int l = Math.min(currentAge + random.nextInt(5) / 4, 15);
-+            // CraftBukkit start
-+            org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
-+            org.bukkit.block.Block sourceBlock = world.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ());
- 
--                world.setBlock(pos, this.getStateWithAge(world, pos, l), 3);
-+            BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                return;
-+            }
-+
-+            if (iblockdata.getBlock() instanceof TntBlock && !CraftEventFactory.callTNTPrimeEvent(world, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) {
-+                return;
-+            }
-+            // CraftBukkit end
-+
-+            if (randomsource.nextInt(j + 10) < 5 && !world.isRainingAt(blockposition)) {
-+                int l = Math.min(j + randomsource.nextInt(5) / 4, 15);
-+
-+                world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3);
-             } else {
--                world.removeBlock(pos, false);
-+                if(iblockdata.getBlock() != Blocks.TNT) world.removeBlock(blockposition, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down
-             }
- 
-             Block block = iblockdata.getBlock();
- 
-             if (block instanceof TntBlock) {
--                TntBlock.explode(world, pos);
-+                // Paper start - TNTPrimeEvent
-+                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition);
-+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.FIRE, null).callEvent()) {
-+                    return;
-+                }
-+                world.removeBlock(blockposition, false);
-+                // Paper end - TNTPrimeEvent
-+                TntBlock.explode(world, blockposition);
-             }
-         }
- 
-@@ -310,13 +368,15 @@
-     }
- 
-     @Override
--    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
--        super.onPlace(state, world, pos, oldState, notify);
--        world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random));
-+    // CraftBukkit start - context
-+    protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext context) {
-+        super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, context);
-+        // CraftBukkit end
-+        world.scheduleTick(blockposition, (Block) this, FireBlock.getFireTickDelay(world)); // Paper - Add fire-tick-delay option
-     }
- 
--    private static int getFireTickDelay(RandomSource random) {
--        return 30 + random.nextInt(10);
-+    private static int getFireTickDelay(Level world) { // Paper - Add fire-tick-delay option
-+        return world.paperConfig().environment.fireTickDelay + world.random.nextInt(10); // Paper - Add fire-tick-delay option
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FrogspawnBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FrogspawnBlock.java.patch
deleted file mode 100644
index c5fefd2f6b..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FrogspawnBlock.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/level/block/FrogspawnBlock.java
-+++ b/net/minecraft/world/level/block/FrogspawnBlock.java
-@@ -89,6 +89,7 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (entity.getType().equals(EntityType.FALLING_BLOCK)) {
-             this.destroyBlock(world, pos);
-         }
-@@ -101,6 +102,11 @@
-     }
- 
-     private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) {
-+        // Paper start - Call BlockFadeEvent
-+        if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
-+            return;
-+        }
-+        // Paper end - Call BlockFadeEvent
-         this.destroyBlock(world, pos);
-         world.playSound(null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F);
-         this.spawnTadpoles(world, pos, random);
-@@ -121,7 +127,7 @@
-                 int k = random.nextInt(1, 361);
-                 tadpole.moveTo(d, (double)pos.getY() - 0.5, e, (float)k, 0.0F);
-                 tadpole.setPersistenceRequired();
--                world.addFreshEntity(tadpole);
-+                world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason
-             }
-         }
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FrostedIceBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FrostedIceBlock.java.patch
deleted file mode 100644
index 9d1efc3454..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FrostedIceBlock.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/level/block/FrostedIceBlock.java
-+++ b/net/minecraft/world/level/block/FrostedIceBlock.java
-@@ -42,6 +42,7 @@
- 
-     @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-+        if (!world.paperConfig().environment.frostedIce.enabled) return; // Paper - Frosted ice options
-         if ((random.nextInt(3) == 0 || this.fewerNeigboursThan(world, pos, 4))
-             && world.getMaxLocalRawBrightness(pos) > 11 - state.getValue(AGE) - state.getLightBlock()
-             && this.slightlyMelt(state, world, pos)) {
-@@ -51,11 +52,11 @@
-                 mutableBlockPos.setWithOffset(pos, direction);
-                 BlockState blockState = world.getBlockState(mutableBlockPos);
-                 if (blockState.is(this) && !this.slightlyMelt(blockState, world, mutableBlockPos)) {
--                    world.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, 20, 40));
-+                    world.scheduleTick(mutableBlockPos, this, Mth.nextInt(random, world.paperConfig().environment.frostedIce.delay.min, world.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options
-                 }
-             }
-         } else {
--            world.scheduleTick(pos, this, Mth.nextInt(random, 20, 40));
-+            world.scheduleTick(pos, this, Mth.nextInt(random, world.paperConfig().environment.frostedIce.delay.min, world.paperConfig().environment.frostedIce.delay.max)); // Paper - Frosted ice options
-         }
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/FungusBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/FungusBlock.java.patch
deleted file mode 100644
index 6508c8df14..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/FungusBlock.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/level/block/FungusBlock.java
-+++ b/net/minecraft/world/level/block/FungusBlock.java
-@@ -74,6 +74,13 @@
-     @Override
-     public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
-         this.getFeature(world).ifPresent((holder) -> {
-+            // CraftBukkit start
-+            if (this == Blocks.WARPED_FUNGUS) {
-+                SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS;
-+            } else if (this == Blocks.CRIMSON_FUNGUS) {
-+                SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS;
-+            }
-+            // CraftBukkit end
-             ((ConfiguredFeature) holder.value()).place(world, world.getChunkSource().getGenerator(), random, pos);
-         });
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/GrindstoneBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/GrindstoneBlock.java.patch
deleted file mode 100644
index 860a309048..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/GrindstoneBlock.java.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- a/net/minecraft/world/level/block/GrindstoneBlock.java
-+++ b/net/minecraft/world/level/block/GrindstoneBlock.java
-@@ -152,8 +152,9 @@
-     @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
-             player.awardStat(Stats.INTERACT_WITH_GRINDSTONE);
-+            } // Paper - Fix InventoryOpenEvent cancellation
-         }
- 
-         return InteractionResult.SUCCESS;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
deleted file mode 100644
index b55ee5aef9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
+++ /dev/null
@@ -1,39 +0,0 @@
---- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
-+++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
-@@ -44,16 +44,34 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < this.growPerTickProbability) {
-+        // Spigot start
-+        int modifier;
-+        if (this == Blocks.KELP) {
-+            modifier = world.spigotConfig.kelpModifier;
-+        } else if (this == Blocks.TWISTING_VINES) {
-+            modifier = world.spigotConfig.twistingVinesModifier;
-+        } else if (this == Blocks.WEEPING_VINES) {
-+            modifier = world.spigotConfig.weepingVinesModifier;
-+        } else {
-+            modifier = world.spigotConfig.caveVinesModifier;
-+        }
-+        if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
-+            // Spigot end
-             BlockPos blockposition1 = pos.relative(this.growthDirection);
- 
-             if (this.canGrowInto(world.getBlockState(blockposition1))) {
--                world.setBlockAndUpdate(blockposition1, this.getGrowIntoState(state, world.random));
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - Fix Spigot growth modifiers
-             }
-         }
- 
-     }
- 
-+    // Paper start - Fix Spigot growth modifiers
-+    protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
-+        return this.getGrowIntoState(state, random);
-+    }
-+    // Paper end - Fix Spigot growth modifiers
-+
-     protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
-         return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE);
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/IceBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/IceBlock.java.patch
deleted file mode 100644
index 003b651052..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/IceBlock.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/level/block/IceBlock.java
-+++ b/net/minecraft/world/level/block/IceBlock.java
-@@ -34,8 +34,13 @@
-     }
- 
-     @Override
--    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
--        super.playerDestroy(world, player, pos, state, blockEntity, tool);
-+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
-+        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
-+        // Paper start - Improve Block#breakNaturally API
-+        this.afterDestroy(world, pos, tool);
-+    }
-+    public void afterDestroy(Level world, BlockPos pos, ItemStack tool) {
-+        // Paper end - Improve Block#breakNaturally API
-         if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) {
-             if (world.dimensionType().ultraWarm()) {
-                 world.removeBlock(pos, false);
-@@ -60,6 +65,11 @@
-     }
- 
-     protected void melt(BlockState state, Level world, BlockPos pos) {
-+        // CraftBukkit start
-+        if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, world.dimensionType().ultraWarm() ? Blocks.AIR.defaultBlockState() : Blocks.WATER.defaultBlockState()).isCancelled()) {
-+            return;
-+        }
-+        // CraftBukkit end
-         if (world.dimensionType().ultraWarm()) {
-             world.removeBlock(pos, false);
-         } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/InfestedBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/InfestedBlock.java.patch
deleted file mode 100644
index 49d54e7dd2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/InfestedBlock.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/world/level/block/InfestedBlock.java
-+++ b/net/minecraft/world/level/block/InfestedBlock.java
-@@ -19,6 +19,7 @@
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.Property;
-+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit
- 
- public class InfestedBlock extends Block {
- 
-@@ -54,7 +55,7 @@
- 
-         if (entitysilverfish != null) {
-             entitysilverfish.moveTo((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, 0.0F, 0.0F);
--            world.addFreshEntity(entitysilverfish);
-+            world.addFreshEntity(entitysilverfish, SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason
-             entitysilverfish.spawnAnim();
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
deleted file mode 100644
index 67743248ac..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
+++ /dev/null
@@ -1,127 +0,0 @@
---- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
-+++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
-@@ -17,6 +17,11 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.material.Fluid;
- import net.minecraft.world.level.material.Fluids;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlockState;
-+import org.bukkit.craftbukkit.block.CraftBlockStates;
-+import org.bukkit.event.block.CauldronLevelChangeEvent;
-+// CraftBukkit end
- 
- public class LayeredCauldronBlock extends AbstractCauldronBlock {
- 
-@@ -62,41 +67,86 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (world instanceof ServerLevel worldserver) {
-             if (entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) {
--                entity.clearFire();
--                if (entity.mayInteract(worldserver, pos)) {
--                    this.handleEntityOnFireInside(state, world, pos);
-+                // CraftBukkit start - moved down
-+                // entity.clearFire();
-+                if ((entity instanceof net.minecraft.world.entity.player.Player || worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(worldserver, pos)) { // Paper - Fixes MC-248588
-+                    if (this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities
-+                        entity.clearFire();
-+                    }
-+                    // CraftBukkit end
-                 }
-             }
-         }
- 
-     }
- 
--    private void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) {
-+    // CraftBukkit start
-+    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent
-+    private boolean handleEntityOnFireInside(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) {
-         if (this.precipitationType == Biome.Precipitation.SNOW) {
--            LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos);
-+            return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL)), world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-         } else {
--            LayeredCauldronBlock.lowerFillLevel(state, world, pos);
-+            return LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-+            // CraftBukkit end
-         }
- 
-     }
-+    // Paper start - fix powdered snow cauldron extinguishing entities
-+    protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (this.precipitationType == Biome.Precipitation.SNOW) {
-+            return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-+        } else {
-+            return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-+        }
-+    }
-+    // Paper end - fix powdered snow cauldron extinguishing entities
- 
-     public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) {
--        int i = (Integer) state.getValue(LayeredCauldronBlock.LEVEL) - 1;
--        BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, i);
-+        // CraftBukkit start
-+        LayeredCauldronBlock.lowerFillLevel(state, world, pos, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN);
-+    }
- 
--        world.setBlockAndUpdate(pos, iblockdata1);
--        world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-+    public static boolean lowerFillLevel(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) {
-+        int i = (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) - 1;
-+        BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) iblockdata.setValue(LayeredCauldronBlock.LEVEL, i);
-+
-+        return LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata1, entity, reason);
-     }
- 
-+    // CraftBukkit start
-+    // Paper start - Call CauldronLevelChangeEvent
-+    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
-+        return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true);
-+    }
-+
-+    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
-+    // Paper end - Call CauldronLevelChangeEvent
-+        CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition);
-+        newState.setData(newBlock);
-+
-+        CauldronLevelChangeEvent event = new CauldronLevelChangeEvent(
-+                world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()),
-+                (entity == null) ? null : entity.getBukkitEntity(), reason, newState
-+        );
-+        world.getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return false;
-+        }
-+        newState.update(true);
-+        if (sendGameEvent) world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent
-+        return true;
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) {
-         if (CauldronBlock.shouldHandlePrecipitation(world, precipitation) && (Integer) state.getValue(LayeredCauldronBlock.LEVEL) != 3 && precipitation == this.precipitationType) {
-             BlockState iblockdata1 = (BlockState) state.cycle(LayeredCauldronBlock.LEVEL);
- 
--            world.setBlockAndUpdate(pos, iblockdata1);
--            world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-+            LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
-         }
-     }
- 
-@@ -115,8 +165,11 @@
-         if (!this.isFull(state)) {
-             BlockState iblockdata1 = (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL) + 1);
- 
--            world.setBlockAndUpdate(pos, iblockdata1);
--            world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-+            // CraftBukkit start
-+            if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.levelEvent(1047, pos, 0);
-         }
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LeavesBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LeavesBlock.java.patch
deleted file mode 100644
index 4e3cc600f3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LeavesBlock.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/level/block/LeavesBlock.java
-+++ b/net/minecraft/world/level/block/LeavesBlock.java
-@@ -26,6 +26,7 @@
- import net.minecraft.world.level.material.Fluids;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.LeavesDecayEvent; // CraftBukkit
- 
- public class LeavesBlock extends Block implements SimpleWaterloggedBlock {
- 
-@@ -59,6 +60,14 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (this.decaying(state)) {
-+            // CraftBukkit start
-+            LeavesDecayEvent event = new LeavesDecayEvent(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled() || world.getBlockState(pos).getBlock() != this) {
-+                return;
-+            }
-+            // CraftBukkit end
-             dropResources(state, world, pos);
-             world.removeBlock(pos, false);
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LecternBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LecternBlock.java.patch
deleted file mode 100644
index a928fea703..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LecternBlock.java.patch
+++ /dev/null
@@ -1,69 +0,0 @@
---- a/net/minecraft/world/level/block/LecternBlock.java
-+++ b/net/minecraft/world/level/block/LecternBlock.java
-@@ -153,7 +153,24 @@
-         BlockEntity tileentity = world.getBlockEntity(pos);
- 
-         if (tileentity instanceof LecternBlockEntity tileentitylectern) {
--            tileentitylectern.setBook(stack.consumeAndReturn(1, user));
-+            // Paper start - Add PlayerInsertLecternBookEvent
-+            ItemStack eventSourcedBookStack = null;
-+            if (user instanceof final net.minecraft.server.level.ServerPlayer serverPlayer) {
-+                final io.papermc.paper.event.player.PlayerInsertLecternBookEvent event = new io.papermc.paper.event.player.PlayerInsertLecternBookEvent(
-+                    serverPlayer.getBukkitEntity(),
-+                    org.bukkit.craftbukkit.block.CraftBlock.at(world, pos),
-+                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack.copyWithCount(1))
-+                );
-+                if (!event.callEvent()) return;
-+                eventSourcedBookStack = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getBook());
-+            }
-+            if (eventSourcedBookStack == null) {
-+                eventSourcedBookStack = stack.consumeAndReturn(1, user);
-+            } else {
-+                stack.consume(1, user);
-+            }
-+            tileentitylectern.setBook(eventSourcedBookStack);
-+            // Paper end - Add PlayerInsertLecternBookEvent
-             LecternBlock.resetBookState(user, world, pos, state, true);
-             world.playSound((Player) null, pos, SoundEvents.BOOK_PUT, SoundSource.BLOCKS, 1.0F, 1.0F);
-         }
-@@ -175,6 +192,16 @@
-     }
- 
-     private static void changePowered(Level world, BlockPos pos, BlockState state, boolean powered) {
-+        // Paper start - Call BlockRedstoneEvent properly
-+        final int currentRedstoneLevel = state.getValue(LecternBlock.POWERED) ? 15 : 0, targetRedstoneLevel = powered ? 15 : 0;
-+        if (currentRedstoneLevel != targetRedstoneLevel) {
-+            final org.bukkit.event.block.BlockRedstoneEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(world, pos, currentRedstoneLevel, targetRedstoneLevel);
-+
-+            if (event.getNewCurrent() != targetRedstoneLevel) {
-+                return;
-+            }
-+        }
-+        // Paper end - Call BlockRedstoneEvent properly
-         world.setBlock(pos, (BlockState) state.setValue(LecternBlock.POWERED, powered), 3);
-         LecternBlock.updateBelow(world, pos, state);
-     }
-@@ -206,11 +233,12 @@
-     }
- 
-     private void popBook(BlockState state, Level world, BlockPos pos) {
--        BlockEntity tileentity = world.getBlockEntity(pos);
-+        BlockEntity tileentity = world.getBlockEntity(pos, false); // CraftBukkit - don't validate, type may be changed already
- 
-         if (tileentity instanceof LecternBlockEntity tileentitylectern) {
-             Direction enumdirection = (Direction) state.getValue(LecternBlock.FACING);
-             ItemStack itemstack = tileentitylectern.getBook().copy();
-+            if (itemstack.isEmpty()) return; // CraftBukkit - SPIGOT-5500
-             float f = 0.25F * (float) enumdirection.getStepX();
-             float f1 = 0.25F * (float) enumdirection.getStepZ();
-             ItemEntity entityitem = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) f, (double) (pos.getY() + 1), (double) pos.getZ() + 0.5D + (double) f1, itemstack);
-@@ -282,8 +310,7 @@
-     private void openScreen(Level world, BlockPos pos, Player player) {
-         BlockEntity tileentity = world.getBlockEntity(pos);
- 
--        if (tileentity instanceof LecternBlockEntity) {
--            player.openMenu((LecternBlockEntity) tileentity);
-+        if (tileentity instanceof LecternBlockEntity && player.openMenu((LecternBlockEntity) tileentity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
-             player.awardStat(Stats.INTERACT_WITH_LECTERN);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LeverBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LeverBlock.java.patch
deleted file mode 100644
index 325443e6f3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LeverBlock.java.patch
+++ /dev/null
@@ -1,31 +0,0 @@
---- a/net/minecraft/world/level/block/LeverBlock.java
-+++ b/net/minecraft/world/level/block/LeverBlock.java
-@@ -31,6 +31,7 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
- 
- public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock {
- 
-@@ -102,6 +103,20 @@
-                 LeverBlock.makeParticle(iblockdata1, world, pos, 1.0F);
-             }
-         } else {
-+            // CraftBukkit start - Interact Lever
-+            boolean powered = state.getValue(LeverBlock.POWERED); // Old powered state
-+            org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            int old = (powered) ? 15 : 0;
-+            int current = (!powered) ? 15 : 0;
-+
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
-+            world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+            if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
-+                return InteractionResult.SUCCESS;
-+            }
-+            // CraftBukkit end
-+
-             this.pull(state, world, pos, (Player) null);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LightningRodBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LightningRodBlock.java.patch
deleted file mode 100644
index c97ad7d96c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LightningRodBlock.java.patch
+++ /dev/null
@@ -1,33 +0,0 @@
---- a/net/minecraft/world/level/block/LightningRodBlock.java
-+++ b/net/minecraft/world/level/block/LightningRodBlock.java
-@@ -24,6 +24,11 @@
- import net.minecraft.world.level.material.Fluids;
- import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+// CraftBukkit end
-+
- public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBlock {
- 
-     public static final MapCodec<LightningRodBlock> CODEC = simpleCodec(LightningRodBlock::new);
-@@ -76,6 +81,18 @@
-     }
- 
-     public void onLightningStrike(BlockState state, Level world, BlockPos pos) {
-+        // CraftBukkit start
-+        boolean powered = state.getValue(LightningRodBlock.POWERED);
-+        int old = (powered) ? 15 : 0;
-+        int current = (!powered) ? 15 : 0;
-+
-+        BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), old, current);
-+        world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+        if (eventRedstone.getNewCurrent() <= 0) {
-+            return;
-+        }
-+        // CraftBukkit end
-         world.setBlock(pos, (BlockState) state.setValue(LightningRodBlock.POWERED, true), 3);
-         this.updateNeighbours(state, world, pos);
-         world.scheduleTick(pos, (Block) this, 8);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LiquidBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LiquidBlock.java.patch
deleted file mode 100644
index 42df446a82..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LiquidBlock.java.patch
+++ /dev/null
@@ -1,78 +0,0 @@
---- a/net/minecraft/world/level/block/LiquidBlock.java
-+++ b/net/minecraft/world/level/block/LiquidBlock.java
-@@ -42,7 +42,7 @@
- public class LiquidBlock extends Block implements BucketPickup {
- 
-     private static final Codec<FlowingFluid> FLOWING_FLUID = BuiltInRegistries.FLUID.byNameCodec().comapFlatMap((fluidtype) -> {
--        DataResult dataresult;
-+        DataResult<FlowingFluid> dataresult; // CraftBukkit - decompile error
- 
-         if (fluidtype instanceof FlowingFluid fluidtypeflowing) {
-             dataresult = DataResult.success(fluidtypeflowing);
-@@ -141,11 +141,31 @@
-     @Override
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
-         if (this.shouldSpreadLiquid(world, pos, state)) {
--            world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world));
-+            world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava
-         }
- 
-     }
- 
-+    // Paper start - Configurable speed for water flowing over lava
-+    public int getFlowSpeed(Level world, BlockPos blockposition) {
-+        if (net.minecraft.core.registries.BuiltInRegistries.FLUID.wrapAsHolder(this.fluid).is(FluidTags.WATER)) {
-+            if (
-+                isLava(world, blockposition.north(1)) ||
-+                isLava(world, blockposition.south(1)) ||
-+                isLava(world, blockposition.west(1)) ||
-+                isLava(world, blockposition.east(1))
-+            ) {
-+                return world.paperConfig().environment.waterOverLavaFlowSpeed;
-+            }
-+        }
-+        return this.fluid.getTickDelay(world);
-+    }
-+    private static boolean isLava(Level world, BlockPos blockPos) {
-+        final FluidState fluidState = world.getFluidIfLoaded(blockPos);
-+        return fluidState != null && fluidState.is(FluidTags.LAVA);
-+    }
-+    // Paper end - Configurable speed for water flowing over lava
-+
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
-         if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) {
-@@ -158,7 +178,7 @@
-     @Override
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
-         if (this.shouldSpreadLiquid(world, pos, state)) {
--            world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world));
-+            world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava
-         }
- 
-     }
-@@ -175,14 +195,20 @@
-                 if (world.getFluidState(blockposition1).is(FluidTags.WATER)) {
-                     Block block = world.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
- 
--                    world.setBlockAndUpdate(pos, block.defaultBlockState());
--                    this.fizz(world, pos);
-+                    // CraftBukkit start
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, block.defaultBlockState())) {
-+                        this.fizz(world, pos);
-+                    }
-+                    // CraftBukkit end
-                     return false;
-                 }
- 
-                 if (flag && world.getBlockState(blockposition1).is(Blocks.BLUE_ICE)) {
--                    world.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState());
--                    this.fizz(world, pos);
-+                    // CraftBukkit start
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, pos, Blocks.BASALT.defaultBlockState())) {
-+                        this.fizz(world, pos);
-+                    }
-+                    // CraftBukkit end
-                     return false;
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/LoomBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/LoomBlock.java.patch
deleted file mode 100644
index 9fc3631803..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/LoomBlock.java.patch
+++ /dev/null
@@ -1,13 +0,0 @@
---- a/net/minecraft/world/level/block/LoomBlock.java
-+++ b/net/minecraft/world/level/block/LoomBlock.java
-@@ -33,8 +33,9 @@
-     @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
-             player.awardStat(Stats.INTERACT_WITH_LOOM);
-+            } // Paper - Fix InventoryOpenEvent cancellation
-         }
- 
-         return InteractionResult.SUCCESS;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/MagmaBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/MagmaBlock.java.patch
deleted file mode 100644
index 6febd2458e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/MagmaBlock.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/MagmaBlock.java
-+++ b/net/minecraft/world/level/block/MagmaBlock.java
-@@ -30,7 +30,7 @@
-     @Override
-     public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
-         if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) {
--            entity.hurt(world.damageSources().hotFloor(), 1.0F);
-+            entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // CraftBukkit
-         }
- 
-         super.stepOn(world, pos, state, entity);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/MultifaceSpreader.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/MultifaceSpreader.java.patch
deleted file mode 100644
index 31dd40a615..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/MultifaceSpreader.java.patch
+++ /dev/null
@@ -1,43 +0,0 @@
---- a/net/minecraft/world/level/block/MultifaceSpreader.java
-+++ b/net/minecraft/world/level/block/MultifaceSpreader.java
-@@ -156,7 +156,7 @@
-                     world.getChunk(growPos.pos()).markPosForPostprocessing(growPos.pos());
-                 }
- 
--                return world.setBlock(growPos.pos(), iblockdata1, 2);
-+                return org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, growPos.source(), growPos.pos(), iblockdata1, 2); // CraftBukkit
-             } else {
-                 return false;
-             }
-@@ -174,19 +174,19 @@
-         SAME_POSITION {
-             @Override
-             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) {
--                return new MultifaceSpreader.SpreadPos(pos, newDirection);
-+                return new MultifaceSpreader.SpreadPos(pos, newDirection, pos); // CraftBukkit
-             }
-         },
-         SAME_PLANE {
-             @Override
-             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) {
--                return new MultifaceSpreader.SpreadPos(pos.relative(newDirection), oldDirection);
-+                return new MultifaceSpreader.SpreadPos(pos.relative(newDirection), oldDirection, pos); // CraftBukkit
-             }
-         },
-         WRAP_AROUND {
-             @Override
-             public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection) {
--                return new MultifaceSpreader.SpreadPos(pos.relative(newDirection).relative(oldDirection), newDirection.getOpposite());
-+                return new MultifaceSpreader.SpreadPos(pos.relative(newDirection).relative(oldDirection), newDirection.getOpposite(), pos); // CraftBukkit
-             }
-         };
- 
-@@ -195,7 +195,7 @@
-         public abstract MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction newDirection, Direction oldDirection);
-     }
- 
--    public static record SpreadPos(BlockPos pos, Direction face) {
-+    public static record SpreadPos(BlockPos pos, Direction face, BlockPos source) { // CraftBukkit
- 
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/MushroomBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/MushroomBlock.java.patch
deleted file mode 100644
index 7fa4b44f28..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/MushroomBlock.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/level/block/MushroomBlock.java
-+++ b/net/minecraft/world/level/block/MushroomBlock.java
-@@ -19,6 +19,9 @@
- import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.TreeType;
-+// CraftBukkit end
- 
- public class MushroomBlock extends BushBlock implements BonemealableBlock {
- 
-@@ -48,7 +51,7 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if (random.nextInt(25) == 0) {
-+        if (random.nextFloat() < (world.spigotConfig.mushroomModifier / (100.0f * 25))) { // Spigot - SPIGOT-7159: Better modifier resolution
-             int i = 5;
-             boolean flag = true;
-             Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, -1, -4), pos.offset(4, 1, 4)).iterator();
-@@ -65,6 +68,7 @@
-             }
- 
-             BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1);
-+            final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event
- 
-             for (int j = 0; j < 4; ++j) {
-                 if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) {
-@@ -75,7 +79,7 @@
-             }
- 
-             if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) {
--                world.setBlock(blockposition2, state, 2);
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event
-             }
-         }
- 
-@@ -101,6 +105,7 @@
-             return false;
-         } else {
-             world.removeBlock(pos, false);
-+            SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit
-             if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) {
-                 return true;
-             } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherPortalBlock.java.patch
deleted file mode 100644
index 5b83537d76..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherPortalBlock.java.patch
+++ /dev/null
@@ -1,163 +0,0 @@
---- a/net/minecraft/world/level/block/NetherPortalBlock.java
-+++ b/net/minecraft/world/level/block/NetherPortalBlock.java
-@@ -32,12 +32,19 @@
- import net.minecraft.world.level.block.state.properties.EnumProperty;
- import net.minecraft.world.level.border.WorldBorder;
- import net.minecraft.world.level.dimension.DimensionType;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.portal.PortalShape;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
- import org.slf4j.Logger;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.event.CraftPortalEvent;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.entity.EntityPortalEnterEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+// CraftBukkit end
- 
- public class NetherPortalBlock extends Block implements Portal {
- 
-@@ -71,16 +78,21 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if (world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) {
-+        if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot
-             while (world.getBlockState(pos).is((Block) this)) {
-                 pos = pos.below();
-             }
- 
-             if (world.getBlockState(pos).isValidSpawn(world, pos, EntityType.ZOMBIFIED_PIGLIN)) {
--                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(world, pos.above(), EntitySpawnReason.STRUCTURE);
-+                // CraftBukkit - set spawn reason to NETHER_PORTAL
-+                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(world, pos.above(), EntitySpawnReason.STRUCTURE, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL);
- 
-                 if (entity != null) {
-                     entity.setPortalCooldown();
-+                    // Paper start - Add option to nerf pigmen from nether portals
-+                    entity.fromNetherPortal = true;
-+                    if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false;
-+                    // Paper end - Add option to nerf pigmen from nether portals
-                     Entity entity1 = entity.getVehicle();
- 
-                     if (entity1 != null) {
-@@ -103,7 +115,13 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (entity.canUsePortal(false)) {
-+            // CraftBukkit start - Entity in portal
-+            EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) return; // Paper - make cancellable
-+            // CraftBukkit end
-             entity.setAsInsidePortal(this, pos);
-         }
- 
-@@ -121,51 +139,80 @@
-     @Nullable
-     @Override
-     public TeleportTransition getPortalDestination(ServerLevel world, Entity entity, BlockPos pos) {
--        ResourceKey<Level> resourcekey = world.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
-+        // CraftBukkit start
-+        ResourceKey<Level> resourcekey = world.getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER;
-         ServerLevel worldserver1 = world.getServer().getLevel(resourcekey);
-+        // Paper start - Add EntityPortalReadyEvent
-+        io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER);
-+        if (!portalReadyEvent.callEvent()) {
-+            entity.portalProcess = null;
-+            return null;
-+        }
-+        worldserver1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle();
-+        // Paper end - Add EntityPortalReadyEvent
- 
-         if (worldserver1 == null) {
--            return null;
-+            return null; // Paper - keep previous behavior of not firing PlayerTeleportEvent if the target world doesn't exist
-         } else {
--            boolean flag = worldserver1.dimension() == Level.NETHER;
-+            boolean flag = worldserver1.getTypeKey() == LevelStem.NETHER;
-+            // CraftBukkit end
-             WorldBorder worldborder = worldserver1.getWorldBorder();
-             double d0 = DimensionType.getTeleportationScale(world.dimensionType(), worldserver1.dimensionType());
-             BlockPos blockposition1 = worldborder.clampToBounds(entity.getX() * d0, entity.getY(), entity.getZ() * d0);
-+            // Paper start - Configurable portal search radius
-+            int portalSearchRadius = worldserver1.paperConfig().environment.portalSearchRadius;
-+            if (entity.level().paperConfig().environment.portalSearchVanillaDimensionScaling && flag) { // flag = is going to nether
-+                portalSearchRadius = (int) (portalSearchRadius / worldserver1.dimensionType().coordinateScale());
-+            }
-+            // Paper end - Configurable portal search radius
-+            // CraftBukkit start
-+            CraftPortalEvent event = entity.callPortalEvent(entity, CraftLocation.toBukkit(blockposition1, worldserver1.getWorld()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, worldserver1.paperConfig().environment.portalCreateRadius); // Paper - use custom portal search radius
-+            if (event == null) {
-+                return null;
-+            }
-+            worldserver1 = ((CraftWorld) event.getTo().getWorld()).getHandle();
-+            worldborder = worldserver1.getWorldBorder();
-+            blockposition1 = worldborder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ());
- 
--            return this.getExitPortal(worldserver1, entity, pos, blockposition1, flag, worldborder);
-+            return this.getExitPortal(worldserver1, entity, pos, blockposition1, flag, worldborder, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius());
-         }
-     }
- 
-     @Nullable
--    private TeleportTransition getExitPortal(ServerLevel world, Entity entity, BlockPos pos, BlockPos scaledPos, boolean inNether, WorldBorder worldBorder) {
--        Optional<BlockPos> optional = world.getPortalForcer().findClosestPortalPosition(scaledPos, inNether, worldBorder);
-+    private TeleportTransition getExitPortal(ServerLevel worldserver, Entity entity, BlockPos blockposition, BlockPos blockposition1, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) {
-+        Optional<BlockPos> optional = worldserver.getPortalForcer().findClosestPortalPosition(blockposition1, worldborder, searchRadius);
-         BlockUtil.FoundRectangle blockutil_rectangle;
-         TeleportTransition.PostTeleportTransition teleporttransition_a;
- 
-         if (optional.isPresent()) {
-             BlockPos blockposition2 = (BlockPos) optional.get();
--            BlockState iblockdata = world.getBlockState(blockposition2);
-+            BlockState iblockdata = worldserver.getBlockState(blockposition2);
- 
-             blockutil_rectangle = BlockUtil.getLargestRectangleAround(blockposition2, (Direction.Axis) iblockdata.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, (blockposition3) -> {
--                return world.getBlockState(blockposition3) == iblockdata;
-+                return worldserver.getBlockState(blockposition3) == iblockdata;
-             });
-             teleporttransition_a = TeleportTransition.PLAY_PORTAL_SOUND.then((entity1) -> {
-                 entity1.placePortalTicket(blockposition2);
-             });
--        } else {
--            Direction.Axis enumdirection_enumaxis = (Direction.Axis) entity.level().getBlockState(pos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
--            Optional<BlockUtil.FoundRectangle> optional1 = world.getPortalForcer().createPortal(scaledPos, enumdirection_enumaxis);
-+        } else if (canCreatePortal) {
-+            Direction.Axis enumdirection_enumaxis = (Direction.Axis) entity.level().getBlockState(blockposition).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
-+            Optional<BlockUtil.FoundRectangle> optional1 = worldserver.getPortalForcer().createPortal(blockposition1, enumdirection_enumaxis, entity, createRadius);
-+            // CraftBukkit end
- 
-             if (optional1.isEmpty()) {
--                NetherPortalBlock.LOGGER.error("Unable to create a portal, likely target out of worldborder");
-+                // BlockPortal.LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit
-                 return null;
-             }
- 
-             blockutil_rectangle = (BlockUtil.FoundRectangle) optional1.get();
-             teleporttransition_a = TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET);
-+            // CraftBukkit start
-+        } else {
-+            return null;
-+            // CraftBukkit end
-         }
- 
--        return NetherPortalBlock.getDimensionTransitionFromExit(entity, pos, blockutil_rectangle, world, teleporttransition_a);
-+        return NetherPortalBlock.getDimensionTransitionFromExit(entity, blockposition, blockutil_rectangle, worldserver, teleporttransition_a);
-     }
- 
-     private static TeleportTransition getDimensionTransitionFromExit(Entity entity, BlockPos pos, BlockUtil.FoundRectangle exitPortalRectangle, ServerLevel world, TeleportTransition.PostTeleportTransition postDimensionTransition) {
-@@ -203,7 +250,7 @@
-         Vec3 vec3d1 = new Vec3((double) blockposition.getX() + (flag ? d2 : d4), (double) blockposition.getY() + d3, (double) blockposition.getZ() + (flag ? d4 : d2));
-         Vec3 vec3d2 = PortalShape.findCollisionFreePosition(vec3d1, world, entity, entitysize);
- 
--        return new TeleportTransition(world, vec3d2, Vec3.ZERO, (float) i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postDimensionTransition);
-+        return new TeleportTransition(world, vec3d2, Vec3.ZERO, (float) i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postDimensionTransition, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // CraftBukkit
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherWartBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherWartBlock.java.patch
deleted file mode 100644
index 849dc04024..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/NetherWartBlock.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/level/block/NetherWartBlock.java
-+++ b/net/minecraft/world/level/block/NetherWartBlock.java
-@@ -52,9 +52,9 @@
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         int i = (Integer) state.getValue(NetherWartBlock.AGE);
- 
--        if (i < 3 && random.nextInt(10) == 0) {
-+        if (i < 3 && random.nextFloat() < (world.spigotConfig.wartModifier / (100.0f * 10))) { // Spigot - SPIGOT-7159: Better modifier resolution
-             state = (BlockState) state.setValue(NetherWartBlock.AGE, i + 1);
--            world.setBlock(pos, state, 2);
-+            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit
-         }
- 
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/NoteBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/NoteBlock.java.patch
deleted file mode 100644
index b1d5c77400..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/NoteBlock.java.patch
+++ /dev/null
@@ -1,78 +0,0 @@
---- a/net/minecraft/world/level/block/NoteBlock.java
-+++ b/net/minecraft/world/level/block/NoteBlock.java
-@@ -68,11 +68,13 @@
- 
-     @Override
-     public BlockState getStateForPlacement(BlockPlaceContext ctx) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return this.defaultBlockState(); // Paper - place without considering instrument
-         return this.setInstrument(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState());
-     }
- 
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return state; // Paper - prevent noteblock instrument from updating
-         boolean flag = direction.getAxis() == Direction.Axis.Y;
- 
-         return flag ? this.setInstrument(world, pos, state) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-@@ -80,11 +82,13 @@
- 
-     @Override
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) return; // Paper - prevent noteblock powered-state from updating
-         boolean flag1 = world.hasNeighborSignal(pos);
- 
-         if (flag1 != (Boolean) state.getValue(NoteBlock.POWERED)) {
-             if (flag1) {
-                 this.playNote((Entity) null, state, world, pos);
-+                state = world.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event
-             }
- 
-             world.setBlock(pos, (BlockState) state.setValue(NoteBlock.POWERED, flag1), 3);
-@@ -94,6 +98,13 @@
- 
-     private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) {
-         if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) {
-+            // CraftBukkit start
-+            // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
-+            // if (event.isCancelled()) {
-+            //     return;
-+            // }
-+            // CraftBukkit end
-+            // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event?
-             world.blockEvent(pos, this, 0, 0);
-             world.gameEvent(entity, (Holder) GameEvent.NOTE_BLOCK_PLAY, pos);
-         }
-@@ -108,7 +119,7 @@
-     @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            state = (BlockState) state.cycle(NoteBlock.NOTE);
-+            if (!io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableNoteblockUpdates) state = (BlockState) state.cycle(NoteBlock.NOTE); // Paper - prevent noteblock note from updating
-             world.setBlock(pos, state, 3);
-             this.playNote(player, state, world, pos);
-             player.awardStat(Stats.TUNE_NOTEBLOCK);
-@@ -132,10 +143,14 @@
-     @Override
-     protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
-         NoteBlockInstrument blockpropertyinstrument = (NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT);
-+        // Paper start - move NotePlayEvent call to fix instrument/note changes
-+        org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, blockpropertyinstrument, state.getValue(NOTE));
-+        if (event.isCancelled()) return false;
-+        // Paper end - move NotePlayEvent call to fix instrument/note changes
-         float f;
- 
-         if (blockpropertyinstrument.isTunable()) {
--            int k = (Integer) state.getValue(NoteBlock.NOTE);
-+            int k = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes
- 
-             f = NoteBlock.getPitchFromNote(k);
-             world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D);
-@@ -154,7 +169,7 @@
- 
-             holder = Holder.direct(SoundEvent.createVariableRangeEvent(minecraftkey));
-         } else {
--            holder = blockpropertyinstrument.getSoundEvent();
-+            holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes
-         }
- 
-         world.playSeededSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, holder, SoundSource.RECORDS, 3.0F, f, world.random.nextLong());
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ObserverBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ObserverBlock.java.patch
deleted file mode 100644
index 0718f3ab29..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ObserverBlock.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/level/block/ObserverBlock.java
-+++ b/net/minecraft/world/level/block/ObserverBlock.java
-@@ -18,6 +18,8 @@
- import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils;
- import net.minecraft.world.level.redstone.Orientation;
- 
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
-+
- public class ObserverBlock extends DirectionalBlock {
- 
-     public static final MapCodec<ObserverBlock> CODEC = simpleCodec(ObserverBlock::new);
-@@ -51,8 +53,18 @@
-     @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if ((Boolean) state.getValue(ObserverBlock.POWERED)) {
-+            // CraftBukkit start
-+            if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(ObserverBlock.POWERED, false), 2);
-         } else {
-+            // CraftBukkit start
-+            if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(ObserverBlock.POWERED, true), 2);
-             world.scheduleTick(pos, (Block) this, 2);
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PitcherCropBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/PitcherCropBlock.java.patch
deleted file mode 100644
index bdd3b319d7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PitcherCropBlock.java.patch
+++ /dev/null
@@ -1,28 +0,0 @@
---- a/net/minecraft/world/level/block/PitcherCropBlock.java
-+++ b/net/minecraft/world/level/block/PitcherCropBlock.java
-@@ -107,6 +107,7 @@
- 
-     @Override
-     public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (world instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-             serverLevel.destroyBlock(pos, true, entity);
-         }
-@@ -131,7 +132,7 @@
-     @Override
-     public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         float f = CropBlock.getGrowthSpeed(this, world, pos);
--        boolean bl = random.nextInt((int)(25.0F / f) + 1) == 0;
-+        boolean bl = random.nextFloat() < (world.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / f) + 1))); // Paper - Fix Spigot growth modifiers
-         if (bl) {
-             this.grow(world, state, pos, 1);
-         }
-@@ -141,7 +142,7 @@
-         int i = Math.min(state.getValue(AGE) + amount, 4);
-         if (this.canGrow(world, pos, state, i)) {
-             BlockState blockState = state.setValue(AGE, Integer.valueOf(i));
--            world.setBlock(pos, blockState, 2);
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, blockState, 2)) return; // Paper
-             if (isDouble(i)) {
-                 world.setBlock(pos.above(), blockState.setValue(HALF, DoubleBlockHalf.UPPER), 3);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
deleted file mode 100644
index 3536598553..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
+++ /dev/null
@@ -1,96 +0,0 @@
---- a/net/minecraft/world/level/block/PointedDripstoneBlock.java
-+++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java
-@@ -136,6 +136,11 @@
-                 ServerLevel worldserver = (ServerLevel) world;
- 
-                 if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) {
-+                    // CraftBukkit start
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     world.destroyBlock(blockposition, true);
-                 }
-             }
-@@ -146,7 +151,7 @@
-     @Override
-     public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
-         if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) {
--            entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite());
-+            entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite().directBlock(world, pos)); // CraftBukkit
-         } else {
-             super.fallOn(world, state, pos, entity, fallDistance);
-         }
-@@ -214,10 +219,13 @@
-                             if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) {
-                                 BlockState iblockdata1 = Blocks.CLAY.defaultBlockState();
- 
--                                world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1);
-+                                // Paper start - Call BlockFormEvent
-+                                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) {
-                                 Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos);
-                                 world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1));
-                                 world.levelEvent(1504, blockposition1, 0);
-+                                }
-+                                // Paper end - Call BlockFormEvent
-                             } else {
-                                 BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype);
- 
-@@ -391,15 +399,15 @@
-         if (PointedDripstoneBlock.isUnmergedTipWithDirection(iblockdata, direction.getOpposite())) {
-             PointedDripstoneBlock.createMergedTips(iblockdata, world, blockposition1);
-         } else if (iblockdata.isAir() || iblockdata.is(Blocks.WATER)) {
--            PointedDripstoneBlock.createDripstone(world, blockposition1, direction, DripstoneThickness.TIP);
-+            PointedDripstoneBlock.createDripstone(world, blockposition1, direction, DripstoneThickness.TIP, pos); // CraftBukkit
-         }
- 
-     }
- 
--    private static void createDripstone(LevelAccessor world, BlockPos pos, Direction direction, DripstoneThickness thickness) {
--        BlockState iblockdata = (BlockState) ((BlockState) ((BlockState) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, direction)).setValue(PointedDripstoneBlock.THICKNESS, thickness)).setValue(PointedDripstoneBlock.WATERLOGGED, world.getFluidState(pos).getType() == Fluids.WATER);
-+    private static void createDripstone(LevelAccessor generatoraccess, BlockPos blockposition, Direction enumdirection, DripstoneThickness dripstonethickness, BlockPos source) { // CraftBukkit
-+        BlockState iblockdata = (BlockState) ((BlockState) ((BlockState) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, enumdirection)).setValue(PointedDripstoneBlock.THICKNESS, dripstonethickness)).setValue(PointedDripstoneBlock.WATERLOGGED, generatoraccess.getFluidState(blockposition).getType() == Fluids.WATER);
- 
--        world.setBlock(pos, iblockdata, 3);
-+        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, source, blockposition, iblockdata, 3); // CraftBukkit
-     }
- 
-     private static void createMergedTips(BlockState state, LevelAccessor world, BlockPos pos) {
-@@ -414,8 +422,8 @@
-             blockposition1 = pos.below();
-         }
- 
--        PointedDripstoneBlock.createDripstone(world, blockposition2, Direction.DOWN, DripstoneThickness.TIP_MERGE);
--        PointedDripstoneBlock.createDripstone(world, blockposition1, Direction.UP, DripstoneThickness.TIP_MERGE);
-+        PointedDripstoneBlock.createDripstone(world, blockposition2, Direction.DOWN, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
-+        PointedDripstoneBlock.createDripstone(world, blockposition1, Direction.UP, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
-     }
- 
-     public static void spawnDripParticle(Level world, BlockPos pos, BlockState state) {
-@@ -448,7 +456,7 @@
- 
-             return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, enumdirection.getAxisDirection(), bipredicate, (iblockdata1) -> {
-                 return PointedDripstoneBlock.isTip(iblockdata1, allowMerged);
--            }, range).orElse((Object) null);
-+            }, range).orElse(null); // CraftBukkit - decompile error
-         }
-     }
- 
-@@ -564,7 +572,7 @@
-             return PointedDripstoneBlock.canDripThrough(world, blockposition1, iblockdata);
-         };
- 
--        return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.DOWN.getAxisDirection(), bipredicate, predicate, 11).orElse((Object) null);
-+        return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.DOWN.getAxisDirection(), bipredicate, predicate, 11).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     @Nullable
-@@ -573,7 +581,7 @@
-             return PointedDripstoneBlock.canDripThrough(world, blockposition1, iblockdata);
-         };
- 
--        return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.UP.getAxisDirection(), bipredicate, PointedDripstoneBlock::canDrip, 11).orElse((Object) null);
-+        return (BlockPos) PointedDripstoneBlock.findBlockVertical(world, pos, Direction.UP.getAxisDirection(), bipredicate, PointedDripstoneBlock::canDrip, 11).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     public static Fluid getCauldronFillFluidType(ServerLevel world, BlockPos pos) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/PowderSnowBlock.java.patch
deleted file mode 100644
index 0f012c2b02..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PowderSnowBlock.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/level/block/PowderSnowBlock.java
-+++ b/net/minecraft/world/level/block/PowderSnowBlock.java
-@@ -59,6 +59,7 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!(entity instanceof LivingEntity) || entity.getInBlockState().is((Block) this)) {
-             entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D));
-             if (world.isClientSide) {
-@@ -73,7 +74,12 @@
- 
-         entity.setIsInPowderSnow(true);
-         if (world instanceof ServerLevel worldserver) {
--            if (entity.isOnFire() && (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player) && entity.mayInteract(worldserver, pos)) {
-+            // CraftBukkit start
-+            if (entity.isOnFire() && entity.mayInteract(worldserver, pos)) {
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.destroyBlock(pos, false);
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PoweredRailBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/PoweredRailBlock.java.patch
deleted file mode 100644
index e0443a21f6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PoweredRailBlock.java.patch
+++ /dev/null
@@ -1,24 +0,0 @@
---- a/net/minecraft/world/level/block/PoweredRailBlock.java
-+++ b/net/minecraft/world/level/block/PoweredRailBlock.java
-@@ -11,6 +11,7 @@
- import net.minecraft.world.level.block.state.properties.EnumProperty;
- import net.minecraft.world.level.block.state.properties.Property;
- import net.minecraft.world.level.block.state.properties.RailShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class PoweredRailBlock extends BaseRailBlock {
- 
-@@ -120,6 +121,13 @@
-         boolean flag1 = world.hasNeighborSignal(pos) || this.findPoweredRailSignal(world, pos, state, true, 0) || this.findPoweredRailSignal(world, pos, state, false, 0);
- 
-         if (flag1 != flag) {
-+            // CraftBukkit start
-+            int power = flag ? 15 : 0;
-+            int newPower = CraftEventFactory.callRedstoneChange(world, pos, power, 15 - power).getNewCurrent();
-+            if (newPower == power) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(PoweredRailBlock.POWERED, flag1), 3);
-             world.updateNeighborsAt(pos.below(), this);
-             if (((RailShape) state.getValue(PoweredRailBlock.SHAPE)).isSlope()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/PressurePlateBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/PressurePlateBlock.java.patch
deleted file mode 100644
index 4b6ba046eb..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/PressurePlateBlock.java.patch
+++ /dev/null
@@ -1,61 +0,0 @@
---- a/net/minecraft/world/level/block/PressurePlateBlock.java
-+++ b/net/minecraft/world/level/block/PressurePlateBlock.java
-@@ -5,6 +5,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.LivingEntity;
-+import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
-@@ -12,6 +13,8 @@
- import net.minecraft.world.level.block.state.properties.BlockSetType;
- import net.minecraft.world.level.block.state.properties.BlockStateProperties;
- import net.minecraft.world.level.block.state.properties.BooleanProperty;
-+import org.bukkit.event.entity.EntityInteractEvent;
-+// CraftBukkit end
- 
- public class PressurePlateBlock extends BasePressurePlateBlock {
- 
-@@ -44,7 +47,7 @@
- 
-     @Override
-     protected int getSignalStrength(Level world, BlockPos pos) {
--        Class oclass;
-+        Class<? extends Entity> oclass; // CraftBukkit
- 
-         switch (this.type.pressurePlateSensitivity()) {
-             case EVERYTHING:
-@@ -59,7 +62,31 @@
- 
-         Class<? extends Entity> oclass1 = oclass;
- 
--        return getEntityCount(world, PressurePlateBlock.TOUCH_AABB.move(pos), oclass1) > 0 ? 15 : 0;
-+        // CraftBukkit start - Call interact event when turning on a pressure plate
-+        for (Entity entity : getEntities(world, PressurePlateBlock.TOUCH_AABB.move(pos), oclass)) {
-+            if (this.getSignalForState(world.getBlockState(pos)) == 0) {
-+                org.bukkit.World bworld = world.getWorld();
-+                org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager();
-+                org.bukkit.event.Cancellable cancellable;
-+
-+                if (entity instanceof Player) {
-+                    cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                } else {
-+                    cancellable = new EntityInteractEvent(entity.getBukkitEntity(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                    manager.callEvent((EntityInteractEvent) cancellable);
-+                }
-+
-+                // We only want to block turning the plate on if all events are cancelled
-+                if (cancellable.isCancelled()) {
-+                    continue;
-+                }
-+            }
-+
-+            return 15;
-+        }
-+
-+        return 0;
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedStoneOreBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
deleted file mode 100644
index 29e5229732..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
+++ /dev/null
@@ -1,102 +0,0 @@
---- a/net/minecraft/world/level/block/RedStoneOreBlock.java
-+++ b/net/minecraft/world/level/block/RedStoneOreBlock.java
-@@ -20,6 +20,10 @@
- import net.minecraft.world.level.block.state.StateDefinition;
- import net.minecraft.world.level.block.state.properties.BooleanProperty;
- import net.minecraft.world.phys.BlockHitResult;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityInteractEvent;
-+// CraftBukkit end
- 
- public class RedStoneOreBlock extends Block {
- 
-@@ -38,14 +42,27 @@
- 
-     @Override
-     protected void attack(BlockState state, Level world, BlockPos pos, Player player) {
--        RedStoneOreBlock.interact(state, world, pos);
-+        RedStoneOreBlock.interact(state, world, pos, player); // CraftBukkit - add entityhuman
-         super.attack(state, world, pos, player);
-     }
- 
-     @Override
-     public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
-         if (!entity.isSteppingCarefully()) {
--            RedStoneOreBlock.interact(state, world, pos);
-+            // CraftBukkit start
-+            if (entity instanceof Player) {
-+                org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                if (!event.isCancelled()) {
-+                    RedStoneOreBlock.interact(world.getBlockState(pos), world, pos, entity); // add entity
-+                }
-+            } else {
-+                EntityInteractEvent event = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                world.getCraftServer().getPluginManager().callEvent(event);
-+                if (!event.isCancelled()) {
-+                    RedStoneOreBlock.interact(world.getBlockState(pos), world, pos, entity); // add entity
-+                }
-+            }
-+            // CraftBukkit end
-         }
- 
-         super.stepOn(world, pos, state, entity);
-@@ -56,16 +73,21 @@
-         if (world.isClientSide) {
-             RedStoneOreBlock.spawnParticles(world, pos);
-         } else {
--            RedStoneOreBlock.interact(state, world, pos);
-+            RedStoneOreBlock.interact(state, world, pos, player); // CraftBukkit - add entityhuman
-         }
- 
-         return (InteractionResult) (stack.getItem() instanceof BlockItem && (new BlockPlaceContext(player, hand, stack, hit)).canPlace() ? InteractionResult.PASS : InteractionResult.SUCCESS);
-     }
- 
--    private static void interact(BlockState state, Level world, BlockPos pos) {
--        RedStoneOreBlock.spawnParticles(world, pos);
--        if (!(Boolean) state.getValue(RedStoneOreBlock.LIT)) {
--            world.setBlock(pos, (BlockState) state.setValue(RedStoneOreBlock.LIT, true), 3);
-+    private static void interact(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) { // CraftBukkit - add Entity
-+        RedStoneOreBlock.spawnParticles(world, blockposition);
-+        if (!(Boolean) iblockdata.getValue(RedStoneOreBlock.LIT)) {
-+            // CraftBukkit start
-+            if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(RedStoneOreBlock.LIT, true))) {
-+                return;
-+            }
-+            // CraftBukkit end
-+            world.setBlock(blockposition, (BlockState) iblockdata.setValue(RedStoneOreBlock.LIT, true), 3);
-         }
- 
-     }
-@@ -78,6 +100,11 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if ((Boolean) state.getValue(RedStoneOreBlock.LIT)) {
-+            // CraftBukkit start
-+            if (CraftEventFactory.callBlockFadeEvent(world, pos, state.setValue(RedStoneOreBlock.LIT, false)).isCancelled()) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.setValue(RedStoneOreBlock.LIT, false), 3);
-         }
- 
-@@ -86,10 +113,17 @@
-     @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
--        if (dropExperience) {
--            this.tryDropExperience(world, pos, tool, UniformInt.of(1, 5));
-+        // CraftBukkit start - Delegated to getExpDrop
-+    }
-+
-+    @Override
-+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
-+        if (flag) {
-+            return this.tryDropExperience(worldserver, blockposition, itemstack, UniformInt.of(1, 5));
-         }
- 
-+        return 0;
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneLampBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
deleted file mode 100644
index dc1b67c34c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/world/level/block/RedstoneLampBlock.java
-+++ b/net/minecraft/world/level/block/RedstoneLampBlock.java
-@@ -13,6 +13,8 @@
- import net.minecraft.world.level.block.state.properties.BooleanProperty;
- import net.minecraft.world.level.redstone.Orientation;
- 
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
-+
- public class RedstoneLampBlock extends Block {
- 
-     public static final MapCodec<RedstoneLampBlock> CODEC = simpleCodec(RedstoneLampBlock::new);
-@@ -43,6 +45,11 @@
-                 if (flag1) {
-                     world.scheduleTick(pos, (Block) this, 4);
-                 } else {
-+                    // CraftBukkit start
-+                    if (CraftEventFactory.callRedstoneChange(world, pos, 0, 15).getNewCurrent() != 15) {
-+                        return;
-+                    }
-+                    // CraftBukkit end
-                     world.setBlock(pos, (BlockState) state.cycle(RedstoneLampBlock.LIT), 2);
-                 }
-             }
-@@ -53,6 +60,11 @@
-     @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if ((Boolean) state.getValue(RedstoneLampBlock.LIT) && !world.hasNeighborSignal(pos)) {
-+            // CraftBukkit start
-+            if (CraftEventFactory.callRedstoneChange(world, pos, 15, 0).getNewCurrent() != 0) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlock(pos, (BlockState) state.cycle(RedstoneLampBlock.LIT), 2);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
deleted file mode 100644
index 86e3e80aca..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
-+++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
-@@ -88,9 +88,14 @@
-                 ServerPlayer entityplayer = (ServerPlayer) player;
- 
-                 if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) {
--                    entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true);
-+                    if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent
-                     world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
-                     return InteractionResult.SUCCESS_SERVER;
-+                    // Paper start - Add PlayerSetSpawnEvent
-+                    } else {
-+                        return InteractionResult.FAIL;
-+                    }
-+                    // Paper end - Add PlayerSetSpawnEvent
-                 }
-             }
- 
-@@ -127,15 +132,16 @@
-     }
- 
-     private void explode(BlockState state, Level world, final BlockPos explodedPos) {
-+        org.bukkit.block.BlockState blockState = org.bukkit.craftbukkit.block.CraftBlock.at(world, explodedPos).getState(); // CraftBukkit - capture BlockState before remove block
-         world.removeBlock(explodedPos, false);
--        Stream stream = Direction.Plane.HORIZONTAL.stream();
-+        Stream<Direction> stream = Direction.Plane.HORIZONTAL.stream(); // CraftBukkit - decompile error
- 
-         Objects.requireNonNull(explodedPos);
-         boolean flag = stream.map(explodedPos::relative).anyMatch((blockposition1) -> {
-             return RespawnAnchorBlock.isWaterThatWouldFlow(blockposition1, world);
-         });
-         final boolean flag1 = flag || world.getFluidState(explodedPos.above()).is(FluidTags.WATER);
--        ExplosionDamageCalculator explosiondamagecalculator = new ExplosionDamageCalculator(this) {
-+        ExplosionDamageCalculator explosiondamagecalculator = new ExplosionDamageCalculator() { // CraftBukkit - decompile error
-             @Override
-             public Optional<Float> getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState) {
-                 return pos.equals(explodedPos) && flag1 ? Optional.of(Blocks.WATER.getExplosionResistance()) : super.getBlockExplosionResistance(explosion, world, pos, blockState, fluidState);
-@@ -143,7 +149,7 @@
-         };
-         Vec3 vec3d = explodedPos.getCenter();
- 
--        world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK);
-+        world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state
-     }
- 
-     public static boolean canSetSpawn(Level world) {

From e9680a5afedcf05341b332870e0c542a17d78efa Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 18:16:46 -0800
Subject: [PATCH 137/285] partial: net.minecraft.world.level.block

---
 .../world/level/block/SaplingBlock.java.patch |  54 ++++++++
 .../level/block/ScaffoldingBlock.java.patch   |  11 ++
 .../world/level/block/SculkBlock.java.patch   |  16 +++
 .../level/block/SculkCatalystBlock.java.patch |  21 ++++
 .../level/block/SculkSensorBlock.java.patch   |  77 ++++++++++++
 .../level/block/SculkShriekerBlock.java.patch |  28 +++++
 .../level/block/SculkSpreader.java.patch      |  55 ++++++++
 .../level/block/SculkVeinBlock.java.patch     |  31 +++++
 .../level/block/ShulkerBoxBlock.java.patch    |  34 ++---
 .../world/level/block/SignBlock.java.patch    |  49 ++++----
 .../level/block/SmithingTableBlock.java.patch |  10 +-
 .../world/level/block/SmokerBlock.java.patch  |   8 +-
 .../level/block/SnifferEggBlock.java.patch    |  46 +++----
 .../level/block/SnowLayerBlock.java.patch     |  14 +++
 .../world/level/block/SpawnerBlock.java.patch |  24 ++++
 .../world/level/block/SpongeBlock.java.patch  |  98 +++++++++++++++
 .../block/SpreadingSnowyDirtBlock.java.patch  |  25 ++++
 .../world/level/block/StemBlock.java.patch    |  38 ++++++
 .../level/block/StonecutterBlock.java.patch   |  10 +-
 .../level/block/SugarCaneBlock.java.patch     |  20 +++
 .../block/SweetBerryBushBlock.java.patch      |  48 +++++++
 .../world/level/block/TargetBlock.java.patch  |  34 +++--
 .../world/level/block/TntBlock.java.patch     |  91 ++++++++++++++
 .../level/block/TrapDoorBlock.java.patch      |  44 +++++++
 .../level/block/TripWireBlock.java.patch      | 108 ++++++++++++++++
 .../level/block/TripWireHookBlock.java.patch  |  21 ++++
 .../level/block/TurtleEggBlock.java.patch     |  64 ++++++++++
 .../world/level/block/VineBlock.java.patch    |  68 ++++++++++
 .../block/WallHangingSignBlock.java.patch     |   6 +-
 .../level/block/WaterlilyBlock.java.patch     |  17 +++
 .../world/level/block/WebBlock.java.patch     |   6 +-
 .../WeightedPressurePlateBlock.java.patch     |  39 ++++++
 .../level/block/WitherRoseBlock.java.patch    |  16 +++
 .../level/block/WitherSkullBlock.java.patch   |  39 ++++++
 .../world/level/block/SaplingBlock.java.patch | 117 ------------------
 .../level/block/ScaffoldingBlock.java.patch   |  11 --
 .../world/level/block/SculkBlock.java.patch   |  16 ---
 .../level/block/SculkCatalystBlock.java.patch |  21 ----
 .../level/block/SculkSensorBlock.java.patch   |  88 -------------
 .../level/block/SculkShriekerBlock.java.patch |  30 -----
 .../level/block/SculkSpreader.java.patch      |  98 ---------------
 .../level/block/SculkVeinBlock.java.patch     |  60 ---------
 .../level/block/SnowLayerBlock.java.patch     |  14 ---
 .../world/level/block/SpawnerBlock.java.patch |  26 ----
 .../world/level/block/SpongeBlock.java.patch  | 114 -----------------
 .../block/SpreadingSnowyDirtBlock.java.patch  |  25 ----
 .../world/level/block/StemBlock.java.patch    |  47 -------
 .../level/block/SugarCaneBlock.java.patch     |  21 ----
 .../block/SweetBerryBushBlock.java.patch      |  62 ----------
 .../world/level/block/TntBlock.java.patch     | 102 ---------------
 .../level/block/TrapDoorBlock.java.patch      |  51 --------
 .../level/block/TripWireBlock.java.patch      | 114 -----------------
 .../level/block/TripWireHookBlock.java.patch  |  34 -----
 .../level/block/TurtleEggBlock.java.patch     |  76 ------------
 .../world/level/block/VineBlock.java.patch    |  79 ------------
 .../level/block/WaterlilyBlock.java.patch     |  27 ----
 .../WeightedPressurePlateBlock.java.patch     |  49 --------
 .../level/block/WitherRoseBlock.java.patch    |  15 ---
 .../level/block/WitherSkullBlock.java.patch   |  50 --------
 59 files changed, 1165 insertions(+), 1452 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch (67%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/SignBlock.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/SmithingTableBlock.java.patch (64%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/SmokerBlock.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/SnifferEggBlock.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/StonecutterBlock.java.patch (63%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/TargetBlock.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/WallHangingSignBlock.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/WebBlock.java.patch (77%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SaplingBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSensorBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSpreader.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SculkVeinBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SnowLayerBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SpawnerBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SpongeBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/StemBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SugarCaneBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/TntBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/TrapDoorBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireHookBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/TurtleEggBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/VineBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/WaterlilyBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/WitherRoseBlock.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/WitherSkullBlock.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
new file mode 100644
index 0000000000..9cb7276a2c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/level/block/SaplingBlock.java
++++ b/net/minecraft/world/level/block/SaplingBlock.java
+@@ -26,6 +_,7 @@
+     protected static final float AABB_OFFSET = 6.0F;
+     protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0);
+     protected final TreeGrower treeGrower;
++    public static org.bukkit.TreeType treeType; // CraftBukkit
+ 
+     @Override
+     public MapCodec<? extends SaplingBlock> codec() {
+@@ -45,7 +_,7 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+-        if (level.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextInt(7) == 0) {
++        if (level.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextFloat() < (level.spigotConfig.saplingModifier / (100.0f * 7))) { // Spigot - SPIGOT-7159: Better modifier resolution
+             this.advanceTree(level, pos, state, random);
+         }
+     }
+@@ -54,7 +_,33 @@
+         if (state.getValue(STAGE) == 0) {
+             level.setBlock(pos, state.cycle(STAGE), 4);
+         } else {
+-            this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++            // CraftBukkit start
++            if (level.captureTreeGeneration) {
++                this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++            } else {
++                level.captureTreeGeneration = true;
++                this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++                level.captureTreeGeneration = false;
++                if (level.capturedBlockStates.size() > 0) {
++                    org.bukkit.TreeType treeType = SaplingBlock.treeType;
++                    SaplingBlock.treeType = null;
++                    org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld());
++                    java.util.List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
++                    level.capturedBlockStates.clear();
++                    org.bukkit.event.world.StructureGrowEvent event = null;
++                    if (treeType != null) {
++                        event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks);
++                        org.bukkit.Bukkit.getPluginManager().callEvent(event);
++                    }
++                    if (event == null || !event.isCancelled()) {
++                        for (org.bukkit.block.BlockState blockstate : blocks) {
++                            org.bukkit.craftbukkit.block.CapturedBlockState.setBlockState(blockstate);
++                            level.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed
++                        }
++                    }
++                }
++            }
++            // CraftBukkit end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
new file mode 100644
index 0000000000..8ad4e9b5f3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/ScaffoldingBlock.java
++++ b/net/minecraft/world/level/block/ScaffoldingBlock.java
+@@ -119,7 +_,7 @@
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         int distance = getDistance(level, pos);
+         BlockState blockState = state.setValue(DISTANCE, Integer.valueOf(distance)).setValue(BOTTOM, Boolean.valueOf(this.isBottom(level, pos, distance)));
+-        if (blockState.getValue(DISTANCE) == 7) {
++        if (blockState.getValue(DISTANCE) == 7&& !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, blockState.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state
+             if (state.getValue(DISTANCE) == 7) {
+                 FallingBlockEntity.fall(level, pos, blockState);
+             } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
new file mode 100644
index 0000000000..681606e369
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/SculkBlock.java
++++ b/net/minecraft/world/level/block/SculkBlock.java
+@@ -37,8 +_,11 @@
+                 if (random.nextInt(growthSpawnCost) < charge) {
+                     BlockPos blockPos = pos1.above();
+                     BlockState randomGrowthState = this.getRandomGrowthState(level, blockPos, random, spreader.isWorldGeneration());
+-                    level.setBlock(blockPos, randomGrowthState, 3);
+-                    level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++                    // CraftBukkit start - Call BlockSpreadEvent
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, randomGrowthState, 3)) {
++                        level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++                    }
++                    // CraftBukkit end
+                 }
+ 
+                 return Math.max(0, charge - growthSpawnCost);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
new file mode 100644
index 0000000000..5ba297feff
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/SculkCatalystBlock.java
++++ b/net/minecraft/world/level/block/SculkCatalystBlock.java
+@@ -61,8 +_,16 @@
+     @Override
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++        // CraftBukkit start - Delegate to getExpDrop
++    }
++
++    @Override
++    public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         if (dropExperience) {
+-            this.tryDropExperience(level, pos, stack, this.xpRange);
++            return this.tryDropExperience(level, pos, stack, this.xpRange);
+         }
+-    }
++
++        return 0;
++        // CraftBukkit end
++     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch
new file mode 100644
index 0000000000..900eb7905e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSensorBlock.java.patch
@@ -0,0 +1,77 @@
+--- a/net/minecraft/world/level/block/SculkSensorBlock.java
++++ b/net/minecraft/world/level/block/SculkSensorBlock.java
+@@ -108,6 +_,18 @@
+             && level.getBlockEntity(pos) instanceof SculkSensorBlockEntity sculkSensorBlockEntity
+             && level instanceof ServerLevel serverLevel
+             && sculkSensorBlockEntity.getVibrationUser().canReceiveVibration(serverLevel, pos, GameEvent.STEP, GameEvent.Context.of(state))) {
++            // CraftBukkit start
++            org.bukkit.event.Cancellable cancellable;
++            if (entity instanceof net.minecraft.world.entity.player.Player player) {
++                cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(player, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++            } else {
++                cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++            }
++            if (cancellable.isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             sculkSensorBlockEntity.getListener().forceScheduleVibration(serverLevel, GameEvent.STEP, GameEvent.Context.of(entity), entity.position());
+         }
+ 
+@@ -200,10 +_,19 @@
+     }
+ 
+     public static boolean canActivate(BlockState state) {
+-        return getPhase(state) == SculkSensorPhase.INACTIVE;
++        return state.getBlock() instanceof SculkSensorBlock &&  getPhase(state) == SculkSensorPhase.INACTIVE; // Paper - Check for a valid type
+     }
+ 
+     public static void deactivate(Level level, BlockPos pos, BlockState state) {
++        // CraftBukkit start
++        org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), state.getValue(SculkSensorBlock.POWER), 0);
++        level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++        if (eventRedstone.getNewCurrent() > 0) {
++            level.setBlock(pos, state.setValue(SculkSensorBlock.POWER, eventRedstone.getNewCurrent()), 3);
++            return;
++        }
++        // CraftBukkit end
+         level.setBlock(pos, state.setValue(PHASE, SculkSensorPhase.COOLDOWN).setValue(POWER, Integer.valueOf(0)), 3);
+         level.scheduleTick(pos, state.getBlock(), 10);
+         updateNeighbours(level, pos, state);
+@@ -215,6 +_,15 @@
+     }
+ 
+     public void activate(@Nullable Entity entity, Level level, BlockPos pos, BlockState state, int power, int frequency) {
++        // CraftBukkit start
++        org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), state.getValue(SculkSensorBlock.POWER), power);
++        level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++        if (eventRedstone.getNewCurrent() <= 0) {
++            return;
++        }
++        power = eventRedstone.getNewCurrent();
++        // CraftBukkit end
+         level.setBlock(pos, state.setValue(PHASE, SculkSensorPhase.ACTIVE).setValue(POWER, Integer.valueOf(power)), 3);
+         level.scheduleTick(pos, state.getBlock(), this.getActiveTicks());
+         updateNeighbours(level, pos, state);
+@@ -292,8 +_,16 @@
+     @Override
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++        // CraftBukkit start - Delegate to getExpDrop
++    }
++
++    @Override
++    public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         if (dropExperience) {
+-            this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
++            return this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
+         }
+-    }
++
++        return 0;
++        // CraftBukkit end
++     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
new file mode 100644
index 0000000000..ff23c12bec
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
++++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
+@@ -66,6 +_,7 @@
+         if (level instanceof ServerLevel serverLevel) {
+             ServerPlayer serverPlayer = SculkShriekerBlockEntity.tryGetPlayer(entity);
+             if (serverPlayer != null) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(serverPlayer, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null).isCancelled()) return; // CraftBukkit
+                 serverLevel.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent(sculkShrieker -> sculkShrieker.tryShriek(serverLevel, serverPlayer));
+             }
+         }
+@@ -144,9 +_,16 @@
+     @Override
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++        // CraftBukkit start - Delegate to getExpDrop
++    }
++
++    @Override
++    public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         if (dropExperience) {
+-            this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
++            return this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
+         }
++        return 0;
++        // CraftBukkit end
+     }
+ 
+     @Nullable
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
new file mode 100644
index 0000000000..07f35283e7
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/level/block/SculkSpreader.java
++++ b/net/minecraft/world/level/block/SculkSpreader.java
+@@ -25,6 +_,7 @@
+ import net.minecraft.core.Vec3i;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.nbt.NbtOps;
++import net.minecraft.nbt.Tag;
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.sounds.SoundEvents;
+ import net.minecraft.sounds.SoundSource;
+@@ -50,6 +_,7 @@
+     private final int additionalDecayRate;
+     private List<SculkSpreader.ChargeCursor> cursors = new ArrayList<>();
+     private static final Logger LOGGER = LogUtils.getLogger();
++    public net.minecraft.world.level.Level level; // CraftBukkit
+ 
+     public SculkSpreader(
+         boolean isWorldGeneration, TagKey<Block> replaceableBlocks, int growthSpawnCoat, int noGrowthRadius, int chargeDecayRate, int additionalDecayRate
+@@ -114,7 +_,7 @@
+             int min = Math.min(list.size(), 32);
+ 
+             for (int i = 0; i < min; i++) {
+-                this.addCursor(list.get(i));
++                this.addCursor(list.get(i), false); // Paper - don't fire event for block entity loading
+             }
+         }
+     }
+@@ -130,13 +_,25 @@
+     public void addCursors(BlockPos pos, int charge) {
+         while (charge > 0) {
+             int min = Math.min(charge, 1000);
+-            this.addCursor(new SculkSpreader.ChargeCursor(pos, min));
++            this.addCursor(new SculkSpreader.ChargeCursor(pos, min), true); // Paper - allow firing event for other causes
+             charge -= min;
+         }
+     }
+ 
+-    private void addCursor(SculkSpreader.ChargeCursor cursor) {
++    private void addCursor(SculkSpreader.ChargeCursor cursor, boolean fireEvent) { // Paper - add boolean to conditionally fire SculkBloomEvent
+         if (this.cursors.size() < 32) {
++            // CraftBukkit start
++            if (!this.isWorldGeneration() && fireEvent) { // CraftBukkit - SPIGOT-7475: Don't call event during world generation // Paper - add boolean to conditionally fire SculkBloomEvent
++                org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, cursor.pos);
++                org.bukkit.event.block.SculkBloomEvent event = new org.bukkit.event.block.SculkBloomEvent(bukkitBlock, cursor.getCharge());
++                org.bukkit.Bukkit.getPluginManager().callEvent(event);
++                if (event.isCancelled()) {
++                    return;
++                }
++
++                cursor.charge = event.getCharge();
++            }
++            // CraftBukkit end
+             this.cursors.add(cursor);
+         }
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
new file mode 100644
index 0000000000..ae401d6908
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/SculkVeinBlock.java
++++ b/net/minecraft/world/level/block/SculkVeinBlock.java
+@@ -90,14 +_,14 @@
+     public int attemptUseCharge(
+         SculkSpreader.ChargeCursor cursor, LevelAccessor level, BlockPos pos, RandomSource random, SculkSpreader spreader, boolean shouldConvertBlocks
+     ) {
+-        if (shouldConvertBlocks && this.attemptPlaceSculk(spreader, level, cursor.getPos(), random)) {
++        if (shouldConvertBlocks && this.attemptPlaceSculk(spreader, level, cursor.getPos(), random, pos)) { // CraftBukkit - add source block
+             return cursor.getCharge() - 1;
+         } else {
+             return random.nextInt(spreader.chargeDecayRate()) == 0 ? Mth.floor(cursor.getCharge() * 0.5F) : cursor.getCharge();
+         }
+     }
+ 
+-    private boolean attemptPlaceSculk(SculkSpreader spreader, LevelAccessor level, BlockPos pos, RandomSource random) {
++    private boolean attemptPlaceSculk(SculkSpreader spreader, LevelAccessor level, BlockPos pos, RandomSource random, BlockPos sourceBlock) { // CraftBukkit
+         BlockState blockState = level.getBlockState(pos);
+         TagKey<Block> tagKey = spreader.replaceableBlocks();
+ 
+@@ -108,6 +_,11 @@
+                 if (blockState1.is(tagKey)) {
+                     BlockState blockState2 = Blocks.SCULK.defaultBlockState();
+                     level.setBlock(blockPos, blockState2, 3);
++                    // CraftBukkit start - Call BlockSpreadEvent
++                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, sourceBlock, blockPos, blockState2, 3)) {
++                        return false;
++                    }
++                    // CraftBukkit end
+                     Block.pushEntitiesUp(blockState1, blockState2, level, blockPos);
+                     level.playSound(null, blockPos, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
+                     this.veinSpreader.spreadAll(blockState2, level, blockPos, spreader.isWorldGeneration());
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
similarity index 67%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
index ef705416f0..ebbcb447db 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
@@ -1,48 +1,48 @@
 --- a/net/minecraft/world/level/block/ShulkerBoxBlock.java
 +++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java
-@@ -98,8 +98,8 @@
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (world instanceof ServerLevel serverLevel
-             && world.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity
--            && canOpen(state, world, pos, shulkerBoxBlockEntity)) {
+@@ -100,8 +_,8 @@
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (level instanceof ServerLevel serverLevel
+             && level.getBlockEntity(pos) instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity
+-            && canOpen(state, level, pos, shulkerBoxBlockEntity)) {
 -            player.openMenu(shulkerBoxBlockEntity);
-+            && canOpen(state, world, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check
-+            && player.openMenu(shulkerBoxBlockEntity).isPresent()) {  // Paper - Fix InventoryOpenEvent cancellation
++            && canOpen(state, level, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check
++            && player.openMenu(shulkerBoxBlockEntity).isPresent()) {  // Paper - Fix InventoryOpenEvent cancellation) {
              player.awardStat(Stats.OPEN_SHULKER_BOX);
              PiglinAi.angerNearbyPiglins(serverLevel, player, true);
          }
-@@ -137,7 +137,7 @@
+@@ -139,7 +_,7 @@
                  itemEntity.setDefaultPickUpDelay();
-                 world.addFreshEntity(itemEntity);
+                 level.addFreshEntity(itemEntity);
              } else {
 -                shulkerBoxBlockEntity.unpackLootTable(player);
 +                shulkerBoxBlockEntity.unpackLootTable(player, true); // Paper - force clear loot table so replenish data isn't persisted in the stack
              }
          }
  
-@@ -147,7 +147,15 @@
+@@ -149,7 +_,15 @@
      @Override
-     protected List<ItemStack> getDrops(BlockState state, LootParams.Builder builder) {
-         BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
+     protected List<ItemStack> getDrops(BlockState state, LootParams.Builder params) {
+         BlockEntity blockEntity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
 +        Runnable reAdd = null; // Paper
          if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) {
 +            // Paper start - clear loot table if it was already used
-+            if (shulkerBoxBlockEntity.lootableData().getLastFill() != -1 || !builder.getLevel().paperConfig().lootables.retainUnlootedShulkerBoxLootTableOnNonPlayerBreak) {
++            if (shulkerBoxBlockEntity.lootableData().getLastFill() != -1 || !params.getLevel().paperConfig().lootables.retainUnlootedShulkerBoxLootTableOnNonPlayerBreak) {
 +                net.minecraft.resources.ResourceKey<net.minecraft.world.level.storage.loot.LootTable> lootTableResourceKey = shulkerBoxBlockEntity.getLootTable();
 +                reAdd = () -> shulkerBoxBlockEntity.setLootTable(lootTableResourceKey);
 +                shulkerBoxBlockEntity.setLootTable(null);
 +            }
 +            // Paper end
-             builder = builder.withDynamicDrop(CONTENTS, lootConsumer -> {
+             params = params.withDynamicDrop(CONTENTS, output -> {
                  for (int i = 0; i < shulkerBoxBlockEntity.getContainerSize(); i++) {
-                     lootConsumer.accept(shulkerBoxBlockEntity.getItem(i));
-@@ -155,7 +163,13 @@
+                     output.accept(shulkerBoxBlockEntity.getItem(i));
+@@ -157,7 +_,13 @@
              });
          }
  
 +        // Paper start - re-set loot table if it was cleared
 +        try {
-         return super.getDrops(state, builder);
+         return super.getDrops(state, params);
 +        } finally {
 +            if (reAdd != null) reAdd.run();
 +        }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/SignBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
index 5dac1e3a2d..36c68ab94b 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
@@ -1,33 +1,31 @@
 --- a/net/minecraft/world/level/block/SignBlock.java
 +++ b/net/minecraft/world/level/block/SignBlock.java
-@@ -140,7 +140,7 @@
-             } else if (flag1) {
-                 return InteractionResult.SUCCESS_SERVER;
-             } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag)) {
--                this.openTextEdit(player, tileentitysign, flag);
-+                this.openTextEdit(player, tileentitysign, flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent
+@@ -134,7 +_,7 @@
+             } else if (!this.otherPlayerIsEditingSign(player, signBlockEntity)
+                 && player.mayBuild()
+                 && this.hasEditableText(player, signBlockEntity, isFacingFrontText)) {
+-                this.openTextEdit(player, signBlockEntity, isFacingFrontText);
++                this.openTextEdit(player, signBlockEntity, isFacingFrontText, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent
                  return InteractionResult.SUCCESS_SERVER;
              } else {
                  return InteractionResult.PASS;
-@@ -185,10 +185,36 @@
-         return blockpropertywood;
+@@ -176,7 +_,33 @@
+         return woodType;
      }
  
 +    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent
-     public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) {
--        blockEntity.setAllowedPlayerEditor(player.getUUID());
--        player.openTextEdit(blockEntity, front);
+     public void openTextEdit(Player player, SignBlockEntity signEntity, boolean isFrontText) {
 +        // Paper start - Add PlayerOpenSignEvent
-+        this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN);
-     }
-+    public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) {
-+        org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity();
-+        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos());
++        this.openTextEdit(player, signEntity, isFrontText, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN);
++    }
++    public void openTextEdit(Player player, SignBlockEntity signEntity, boolean isFrontText, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) {
++        org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) player.getBukkitEntity();
++        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(signEntity.getLevel(), signEntity.getBlockPos());
 +        org.bukkit.craftbukkit.block.CraftSign<?> bukkitSign = (org.bukkit.craftbukkit.block.CraftSign<?>) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock);
 +        io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent(
 +            bukkitPlayer,
 +            bukkitSign,
-+            flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK,
++            isFrontText ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK,
 +            cause);
 +        if (!event.callEvent()) return;
 +        if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) {
@@ -37,22 +35,19 @@
 +                case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT;
 +                case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN;
 +            };
-+        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) {
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(player, signEntity, isFrontText, legacyCause)) {
 +        // Paper end - Add PlayerOpenSignEvent
 +            return;
 +        }
 +        } // Paper - Add PlayerOpenSignEvent
-+        tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID());
-+        entityhuman.openTextEdit(tileentitysign, flag);
-+    }
- 
-     private boolean otherPlayerIsEditingSign(Player player, SignBlockEntity blockEntity) {
-         UUID uuid = blockEntity.getPlayerWhoMayEdit();
-@@ -199,6 +225,6 @@
+         signEntity.setAllowedPlayerEditor(player.getUUID());
+         player.openTextEdit(signEntity, isFrontText);
+     }
+@@ -189,6 +_,6 @@
      @Nullable
      @Override
-     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockState state, BlockEntityType<T> type) {
--        return createTickerHelper(type, BlockEntityType.SIGN, SignBlockEntity::tick);
+     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
+-        return createTickerHelper(blockEntityType, BlockEntityType.SIGN, SignBlockEntity::tick);
 +        return null; // Craftbukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SmithingTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch
similarity index 64%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/SmithingTableBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch
index e7724b2941..a2b4048bcb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SmithingTableBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SmithingTableBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/SmithingTableBlock.java
 +++ b/net/minecraft/world/level/block/SmithingTableBlock.java
-@@ -38,8 +38,9 @@
+@@ -38,8 +_,9 @@
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_SMITHING_TABLE);
 +            } // Paper - Fix InventoryOpenEvent cancellation
          }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SmokerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/SmokerBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch
index 1d94e2a7cc..27b9bafcfd 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SmokerBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SmokerBlock.java.patch
@@ -1,12 +1,12 @@
 --- a/net/minecraft/world/level/block/SmokerBlock.java
 +++ b/net/minecraft/world/level/block/SmokerBlock.java
-@@ -44,8 +44,7 @@
+@@ -44,8 +_,7 @@
      @Override
-     protected void openContainer(Level world, BlockPos pos, Player player) {
-         BlockEntity blockEntity = world.getBlockEntity(pos);
+     protected void openContainer(Level level, BlockPos pos, Player player) {
+         BlockEntity blockEntity = level.getBlockEntity(pos);
 -        if (blockEntity instanceof SmokerBlockEntity) {
 -            player.openMenu((MenuProvider)blockEntity);
-+        if (blockEntity instanceof SmokerBlockEntity && player.openMenu((MenuProvider)blockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++        if (blockEntity instanceof SmokerBlockEntity smoker && player.openMenu(smoker).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_SMOKER);
          }
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SnifferEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/SnifferEggBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch
index ff63f628fe..aa5c5e2817 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SnifferEggBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SnifferEggBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/SnifferEggBlock.java
 +++ b/net/minecraft/world/level/block/SnifferEggBlock.java
-@@ -61,12 +61,31 @@
+@@ -61,12 +_,31 @@
          return this.getHatchLevel(state) == 2;
      }
  
@@ -13,41 +13,41 @@
 +    // Paper end - Call BlockFadeEvent
 +
      @Override
-     public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+     public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
          if (!this.isReadyToHatch(state)) {
 +            // Paper start
-+            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2)) {
-+                this.rescheduleTick(world, pos);
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2)) {
++                this.rescheduleTick(level, pos);
 +                return;
 +            }
 +            // Paper end
-             world.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
--            world.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2);
+             level.playSound(null, pos, SoundEvents.SNIFFER_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
+-            level.setBlock(pos, state.setValue(HATCH, Integer.valueOf(this.getHatchLevel(state) + 1)), 2);
          } else {
 +            // Paper start - Call BlockFadeEvent
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, state.getFluidState().createLegacyBlock()).isCancelled()) {
-+                this.rescheduleTick(world, pos);
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, state.getFluidState().createLegacyBlock()).isCancelled()) {
++                this.rescheduleTick(level, pos);
 +                return;
 +            }
 +            // Paper end - Call BlockFadeEvent
-             world.playSound(null, pos, SoundEvents.SNIFFER_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
-             world.destroyBlock(pos, false);
-             Sniffer sniffer = EntityType.SNIFFER.create(world, EntitySpawnReason.BREEDING);
-@@ -74,7 +93,7 @@
-                 Vec3 vec3 = pos.getCenter();
+             level.playSound(null, pos, SoundEvents.SNIFFER_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
+             level.destroyBlock(pos, false);
+             Sniffer sniffer = EntityType.SNIFFER.create(level, EntitySpawnReason.BREEDING);
+@@ -74,7 +_,7 @@
+                 Vec3 center = pos.getCenter();
                  sniffer.setBaby(true);
-                 sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F);
--                world.addFreshEntity(sniffer);
-+                world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason
+                 sniffer.moveTo(center.x(), center.y(), center.z(), Mth.wrapDegrees(level.random.nextFloat() * 360.0F), 0.0F);
+-                level.addFreshEntity(sniffer);
++                level.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason
              }
          }
      }
-@@ -86,7 +105,7 @@
-             world.levelEvent(3009, pos, 0);
+@@ -86,7 +_,7 @@
+             level.levelEvent(3009, pos, 0);
          }
  
--        int i = bl ? 12000 : 24000;
-+        int i = bl ? world.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : world.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper
-         int j = i / 3;
-         world.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(state));
-         world.scheduleTick(pos, this, j + world.random.nextInt(300));
+-        int i = flag ? 12000 : 24000;
++        int i = flag ? level.paperConfig().entities.sniffer.boostedHatchTime.or(BOOSTED_HATCH_TIME_TICKS) : level.paperConfig().entities.sniffer.hatchTime.or(REGULAR_HATCH_TIME_TICKS); // Paper
+         int i1 = i / 3;
+         level.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(state));
+         level.scheduleTick(pos, this, i1 + level.random.nextInt(300));
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch
new file mode 100644
index 0000000000..37be3cda95
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SnowLayerBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/SnowLayerBlock.java
++++ b/net/minecraft/world/level/block/SnowLayerBlock.java
+@@ -123,6 +_,11 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (level.getBrightness(LightLayer.BLOCK, pos) > 11) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             dropResources(state, level, pos);
+             level.removeBlock(pos, false);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch
new file mode 100644
index 0000000000..eebbd991a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpawnerBlock.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/block/SpawnerBlock.java
++++ b/net/minecraft/world/level/block/SpawnerBlock.java
+@@ -46,11 +_,19 @@
+     @Override
+     protected void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++        // CraftBukkit start - Delegate to getExpDrop
++    }
++
++    @Override
++    public int getExpDrop(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+         if (dropExperience) {
+             int i = 15 + level.random.nextInt(15) + level.random.nextInt(15);
+-            this.popExperience(level, pos, i);
++            // this.popExperience(level, pos, i);
++            return  i;
+         }
+-    }
++        return 0;
++        // CraftBukkit end
++     }
+ 
+     @Override
+     public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
new file mode 100644
index 0000000000..f39e9bfdf1
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
@@ -0,0 +1,98 @@
+--- a/net/minecraft/world/level/block/SpongeBlock.java
++++ b/net/minecraft/world/level/block/SpongeBlock.java
+@@ -50,7 +_,8 @@
+     }
+ 
+     private boolean removeWaterBreadthFirstSearch(Level level, BlockPos pos) {
+-        return BlockPos.breadthFirstTraversal(
++        org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit - Use BlockStateListPopulator
++        BlockPos.breadthFirstTraversal(
+                 pos,
+                 6,
+                 65,
+@@ -63,16 +_,18 @@
+                     if (blockPos.equals(pos)) {
+                         return BlockPos.TraversalNodeStatus.ACCEPT;
+                     } else {
+-                        BlockState blockState = level.getBlockState(blockPos);
+-                        FluidState fluidState = level.getFluidState(blockPos);
++                        // CraftBukkit start
++                        BlockState blockState = blockList.getBlockState(blockPos);
++                        FluidState fluidState = blockList.getFluidState(blockPos);
++                        // CraftBukkit end
+                         if (!fluidState.is(FluidTags.WATER)) {
+                             return BlockPos.TraversalNodeStatus.SKIP;
+                         } else if (blockState.getBlock() instanceof BucketPickup bucketPickup
+-                            && !bucketPickup.pickupBlock(null, level, blockPos, blockState).isEmpty()) {
++                            && !bucketPickup.pickupBlock(null, blockList, blockPos, blockState).isEmpty()) { // CraftBukkit
+                             return BlockPos.TraversalNodeStatus.ACCEPT;
+                         } else {
+                             if (blockState.getBlock() instanceof LiquidBlock) {
+-                                level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3);
++                                blockList.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+                             } else {
+                                 if (!blockState.is(Blocks.KELP)
+                                     && !blockState.is(Blocks.KELP_PLANT)
+@@ -81,16 +_,57 @@
+                                     return BlockPos.TraversalNodeStatus.SKIP;
+                                 }
+ 
+-                                BlockEntity blockEntity = blockState.hasBlockEntity() ? level.getBlockEntity(blockPos) : null;
+-                                dropResources(blockState, level, blockPos, blockEntity);
+-                                level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3);
++                                // CraftBukkit start
++                                // BlockEntity blockEntity = blockState.hasBlockEntity() ? level.getBlockEntity(blockPos) : null;
++                                // dropResources(blockState, level, blockPos, blockEntity);
++                                // level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3);
++                                blockList.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 3);
++                                // CraftBukkit end
+                             }
+ 
+                             return BlockPos.TraversalNodeStatus.ACCEPT;
+                         }
+                     }
+                 }
+-            )
+-            > 1;
++            );
++        // CraftBukkit start
++        java.util.List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = blockList.getList(); // Is a clone
++        if (!blocks.isEmpty()) {
++            final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++            org.bukkit.event.block.SpongeAbsorbEvent event = new org.bukkit.event.block.SpongeAbsorbEvent(bblock, (java.util.List<org.bukkit.block.BlockState>) (java.util.List) blocks);
++            level.getCraftServer().getPluginManager().callEvent(event);
++
++            if (event.isCancelled()) {
++                return false;
++            }
++
++            for (org.bukkit.craftbukkit.block.CraftBlockState block : blocks) {
++                BlockPos blockposition1 = block.getPosition();
++                BlockState iblockdata = level.getBlockState(blockposition1);
++                FluidState fluid = level.getFluidState(blockposition1);
++
++                if (fluid.is(FluidTags.WATER)) {
++                    if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock(null, blockList, blockposition1, iblockdata).isEmpty()) {
++                        // NOP
++                    } else if (iblockdata.getBlock() instanceof LiquidBlock) {
++                        // NOP
++                    } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) {
++                        BlockEntity tileentity = iblockdata.hasBlockEntity() ? level.getBlockEntity(blockposition1) : null;
++
++                        // Paper start - Fix SpongeAbsortEvent handling
++                        if (block.getHandle().isAir()) {
++                        dropResources(iblockdata, level, blockposition1, tileentity);
++                        }
++                        // Paper end - Fix SpongeAbsortEvent handling
++                    }
++                }
++                level.setBlock(blockposition1, block.getHandle(), block.getFlag());
++            }
++
++            return true;
++        }
++        return false;
++        // CraftBukkit end
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
new file mode 100644
index 0000000000..0e8433bda8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
+@@ -39,7 +_,13 @@
+ 
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
++        if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks
+         if (!canBeGrass(state, level, pos)) {
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             level.setBlockAndUpdate(pos, Blocks.DIRT.defaultBlockState());
+         } else {
+             if (level.getMaxLocalRawBrightness(pos.above()) >= 9) {
+@@ -48,7 +_,7 @@
+                 for (int i = 0; i < 4; i++) {
+                     BlockPos blockPos = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
+                     if (level.getBlockState(blockPos).is(Blocks.DIRT) && canPropagate(blockState, level, blockPos)) {
+-                        level.setBlockAndUpdate(blockPos, blockState.setValue(SNOWY, Boolean.valueOf(isSnowySetting(level.getBlockState(blockPos.above())))));
++                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState.setValue(SNOWY, Boolean.valueOf(isSnowySetting(level.getBlockState(blockPos.above()))))); // CraftBukkit
+                     }
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch
new file mode 100644
index 0000000000..3e1e7cca5a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/StemBlock.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/level/block/StemBlock.java
++++ b/net/minecraft/world/level/block/StemBlock.java
+@@ -80,11 +_,11 @@
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (level.getRawBrightness(pos, 0) >= 9) {
+             float growthSpeed = CropBlock.getGrowthSpeed(this, level, pos);
+-            if (random.nextInt((int)(25.0F / growthSpeed) + 1) == 0) {
++            if (random.nextFloat() < ((this == Blocks.PUMPKIN_STEM ? level.spigotConfig.pumpkinModifier : level.spigotConfig.melonModifier) / (100.0f * (Math.floor((25.0F / growthSpeed) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                 int ageValue = state.getValue(AGE);
+                 if (ageValue < 7) {
+                     state = state.setValue(AGE, Integer.valueOf(ageValue + 1));
+-                    level.setBlock(pos, state, 2);
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state, 2); // CraftBukkit
+                 } else {
+                     Direction randomDirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
+                     BlockPos blockPos = pos.relative(randomDirection);
+@@ -94,7 +_,11 @@
+                         Optional<Block> optional = registry.getOptional(this.fruit);
+                         Optional<Block> optional1 = registry.getOptional(this.attachedStem);
+                         if (optional.isPresent() && optional1.isPresent()) {
+-                            level.setBlockAndUpdate(blockPos, optional.get().defaultBlockState());
++                            // CraftBukkit start
++                            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, optional.get().defaultBlockState())) {
++                                return;
++                            }
++                            // CraftBukkit end
+                             level.setBlockAndUpdate(pos, optional1.get().defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, randomDirection));
+                         }
+                     }
+@@ -122,7 +_,7 @@
+     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+         int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.random, 2, 5));
+         BlockState blockState = state.setValue(AGE, Integer.valueOf(min));
+-        level.setBlock(pos, blockState, 2);
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, 2); // CraftBukkit
+         if (min == 7) {
+             blockState.randomTick(level, pos, level.random);
+         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/StonecutterBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/StonecutterBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch
index 33e97daa48..cf39a9ca01 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/StonecutterBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/StonecutterBlock.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/level/block/StonecutterBlock.java
 +++ b/net/minecraft/world/level/block/StonecutterBlock.java
-@@ -48,8 +48,9 @@
+@@ -48,8 +_,9 @@
      @Override
-     protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
-         if (!world.isClientSide) {
--            player.openMenu(state.getMenuProvider(world, pos));
-+            if (player.openMenu(state.getMenuProvider(world, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
+     protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
+         if (!level.isClientSide) {
+-            player.openMenu(state.getMenuProvider(level, pos));
++            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_STONECUTTER);
 +            } // Paper - Fix InventoryOpenEvent cancellation
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch
new file mode 100644
index 0000000000..009e5542fb
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SugarCaneBlock.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/block/SugarCaneBlock.java
++++ b/net/minecraft/world/level/block/SugarCaneBlock.java
+@@ -56,12 +_,13 @@
+                 i++;
+             }
+ 
+-            if (i < 3) {
++            if (i < level.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth height
+                 int ageValue = state.getValue(AGE);
+-                if (ageValue == 15) {
+-                    level.setBlockAndUpdate(pos.above(), this.defaultBlockState());
++                int modifier = level.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution
++                if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos.above(), this.defaultBlockState()); // CraftBukkit
+                     level.setBlock(pos, state.setValue(AGE, Integer.valueOf(0)), 4);
+-                } else {
++                } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                     level.setBlock(pos, state.setValue(AGE, Integer.valueOf(ageValue + 1)), 4);
+                 }
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
new file mode 100644
index 0000000000..8b96fa4a77
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/level/block/SweetBerryBushBlock.java
++++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java
+@@ -68,15 +_,17 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         int ageValue = state.getValue(AGE);
+-        if (ageValue < 3 && random.nextInt(5) == 0 && level.getRawBrightness(pos.above(), 0) >= 9) {
++        if (ageValue < 3 && random.nextFloat() < (level.spigotConfig.sweetBerryModifier / (100.0f * 5)) && level.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
+             BlockState blockState = state.setValue(AGE, Integer.valueOf(ageValue + 1));
+-            level.setBlock(pos, blockState, 2);
++            // level.setBlock(pos, blockState, 2);
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, 2)) return; // CraftBukkit
+             level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
+         }
+     }
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) {
+             entity.makeStuckInBlock(state, new Vec3(0.8F, 0.75, 0.8F));
+             if (level instanceof ServerLevel serverLevel && state.getValue(AGE) != 0) {
+@@ -85,7 +_,7 @@
+                     double abs = Math.abs(vec3.x());
+                     double abs1 = Math.abs(vec3.z());
+                     if (abs >= 0.003F || abs1 >= 0.003F) {
+-                        entity.hurtServer(serverLevel, level.damageSources().sweetBerryBush(), 1.0F);
++                        entity.hurtServer(serverLevel, level.damageSources().sweetBerryBush().directBlock(level, pos), 1.0F); // CraftBukkit
+                     }
+                 }
+             }
+@@ -109,7 +_,15 @@
+         boolean flag = ageValue == 3;
+         if (ageValue > 1) {
+             int i = 1 + level.random.nextInt(2);
+-            popResource(level, pos, new ItemStack(Items.SWEET_BERRIES, i + (flag ? 1 : 0)));
++            // CraftBukkit start - useWithoutItem is always MAIN_HAND
++            org.bukkit.event.player.PlayerHarvestBlockEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerHarvestBlockEvent(level, pos, player, InteractionHand.MAIN_HAND, java.util.Collections.singletonList(new ItemStack(Items.SWEET_BERRIES, i + (flag ? 1 : 0))));
++            if (event.isCancelled()) {
++                return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
++            }
++            for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
++                popResource(level, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemStack));
++            }
++            // CraftBukkit end
+             level.playSound(null, pos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + level.random.nextFloat() * 0.4F);
+             BlockState blockState = state.setValue(AGE, Integer.valueOf(1));
+             level.setBlock(pos, blockState, 2);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TargetBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/TargetBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch
index 5478fd0968..eeb2fc28bb 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TargetBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TargetBlock.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/level/block/TargetBlock.java
 +++ b/net/minecraft/world/level/block/TargetBlock.java
-@@ -42,6 +42,10 @@
+@@ -42,6 +_,10 @@
      @Override
-     protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {
-         int i = updateRedstoneOutput(world, state, hit, projectile);
+     protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+         int i = updateRedstoneOutput(level, state, hit, projectile);
 +        // Paper start - Add TargetHitEvent
 +    }
 +    private static void awardTargetHitCriteria(Projectile projectile, BlockHitResult hit, int i) {
@@ -11,16 +11,15 @@
          if (projectile.getOwner() instanceof ServerPlayer serverPlayer) {
              serverPlayer.awardStat(Stats.TARGET_HIT);
              CriteriaTriggers.TARGET_BLOCK_HIT.trigger(serverPlayer, projectile, hit.getLocation(), i);
-@@ -51,10 +55,31 @@
-     private static int updateRedstoneOutput(LevelAccessor world, BlockState state, BlockHitResult hitResult, Entity entity) {
-         int i = getRedstoneStrength(hitResult, hitResult.getLocation());
-         int j = entity instanceof AbstractArrow ? 20 : 8;
+@@ -51,9 +_,29 @@
+     private static int updateRedstoneOutput(LevelAccessor level, BlockState state, BlockHitResult hit, Entity projectile) {
+         int redstoneStrength = getRedstoneStrength(hit, hit.getLocation());
+         int i = projectile instanceof AbstractArrow ? 20 : 8;
 +        // Paper start - Add TargetHitEvent
 +        boolean shouldAward = false;
-+        if (entity instanceof Projectile) {
-+            final Projectile projectile = (Projectile) entity;
-+            final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, hitResult.getBlockPos());
-+            final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(hitResult.getDirection());
++        if (projectile instanceof Projectile) {
++            final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, hit.getBlockPos());
++            final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(hit.getDirection());
 +            final io.papermc.paper.event.block.TargetHitEvent targetHitEvent = new io.papermc.paper.event.block.TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i);
 +            if (targetHitEvent.callEvent()) {
 +                i = targetHitEvent.getSignalStrength();
@@ -30,16 +29,15 @@
 +            }
 +        }
 +        // Paper end - Add TargetHitEvent
-         if (!world.getBlockTicks().hasScheduledTick(hitResult.getBlockPos(), state.getBlock())) {
-             setOutputPower(world, state, i, hitResult.getBlockPos(), j);
+         if (!level.getBlockTicks().hasScheduledTick(hit.getBlockPos(), state.getBlock())) {
+             setOutputPower(level, state, redstoneStrength, hit.getBlockPos(), i);
          }
- 
++
 +        // Paper start - Award Hit Criteria after Block Update
 +        if (shouldAward) {
-+            awardTargetHitCriteria((Projectile) entity, hitResult, i);
++            awardTargetHitCriteria((Projectile) projectile, hit, i);
 +        }
 +        // Paper end - Award Hit Criteria after Block Update
-+
-         return i;
-     }
  
+         return redstoneStrength;
+     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch
new file mode 100644
index 0000000000..b03b95ae3c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TntBlock.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/level/block/TntBlock.java
++++ b/net/minecraft/world/level/block/TntBlock.java
+@@ -45,7 +_,13 @@
+     @Override
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+         if (!oldState.is(state.getBlock())) {
+-            if (level.hasNeighborSignal(pos)) {
++            if (level.hasNeighborSignal(pos) && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
++                // Paper start - TNTPrimeEvent
++                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) {
++                    return;
++                }
++                // Paper end - TNTPrimeEvent
+                 explode(level, pos);
+                 level.removeBlock(pos, false);
+             }
+@@ -54,7 +_,13 @@
+ 
+     @Override
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
+-        if (level.hasNeighborSignal(pos)) {
++        if (level.hasNeighborSignal(pos) && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
++            // Paper start - TNTPrimeEvent
++            org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++            if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) {
++                return;
++            }
++            // Paper end - TNTPrimeEvent
+             explode(level, pos);
+             level.removeBlock(pos, false);
+         }
+@@ -62,7 +_,7 @@
+ 
+     @Override
+     public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) {
+-        if (!level.isClientSide() && !player.isCreative() && state.getValue(UNSTABLE)) {
++        if (!level.isClientSide() && !player.isCreative() && state.getValue(UNSTABLE) && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.BLOCK_BREAK, player, null)) { // CraftBukkit - TNTPrimeEvent
+             explode(level, pos);
+         }
+ 
+@@ -71,6 +_,13 @@
+ 
+     @Override
+     public void wasExploded(ServerLevel level, BlockPos pos, Explosion explosion) {
++        // Paper start - TNTPrimeEvent
++        org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++        org.bukkit.entity.Entity source = explosion.getDirectSourceEntity() != null ? explosion.getDirectSourceEntity().getBukkitEntity() : null;
++        if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) {
++            return;
++        }
++        // Paper end - TNTPrimeEvent
+         PrimedTnt primedTnt = new PrimedTnt(level, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, explosion.getIndirectSourceEntity());
+         int fuse = primedTnt.getFuse();
+         primedTnt.setFuse((short)(level.random.nextInt(fuse / 4) + fuse / 8));
+@@ -97,6 +_,17 @@
+         if (!stack.is(Items.FLINT_AND_STEEL) && !stack.is(Items.FIRE_CHARGE)) {
+             return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
+         } else {
++            // CraftBukkit start - TNTPrimeEvent
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, pos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.PLAYER, player, null)) {
++                return InteractionResult.CONSUME;
++            }
++            // CraftBukkit end
++            // Paper start - TNTPrimeEvent
++            org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++            if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) {
++                return InteractionResult.FAIL;
++            }
++            // Paper end - TNTPrimeEvent
+             explode(level, pos, player);
+             level.setBlock(pos, Blocks.AIR.defaultBlockState(), 11);
+             Item item = stack.getItem();
+@@ -117,6 +_,17 @@
+             BlockPos blockPos = hit.getBlockPos();
+             Entity owner = projectile.getOwner();
+             if (projectile.isOnFire() && projectile.mayInteract(serverLevel, blockPos)) {
++                // CraftBukkit start
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockPos, state.getFluidState().createLegacyBlock()) || !org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(level, blockPos, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state
++                    return;
++                }
++                // CraftBukkit end
++                // Paper start - TNTPrimeEvent
++                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos);
++                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) {
++                    return;
++                }
++                // Paper end - TNTPrimeEvent
+                 explode(level, blockPos, owner instanceof LivingEntity ? (LivingEntity)owner : null);
+                 level.removeBlock(blockPos, false);
+             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch
new file mode 100644
index 0000000000..dac679ac7e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TrapDoorBlock.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/TrapDoorBlock.java
++++ b/net/minecraft/world/level/block/TrapDoorBlock.java
+@@ -146,7 +_,40 @@
+         if (!level.isClientSide) {
+             boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+             if (hasNeighborSignal != state.getValue(POWERED)) {
+-                if (state.getValue(OPEN) != hasNeighborSignal) {
++                // if (state.getValue(OPEN) != hasNeighborSignal) {
++                // CraftBukkit start
++                org.bukkit.World bworld = level.getWorld();
++                org.bukkit.block.Block bblock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++                int power = bblock.getBlockPower();
++                int oldPower = state.getValue(TrapDoorBlock.OPEN) ? 15 : 0;
++
++                if (oldPower == 0 ^ power == 0 || neighborBlock.defaultBlockState().isSignalSource()) {
++                    org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bblock, oldPower, power);
++                    level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++                    hasNeighborSignal = eventRedstone.getNewCurrent() > 0;
++                }
++                // CraftBukkit end
++                // Paper start - break redstone on trapdoors early
++                boolean open = state.getValue(TrapDoorBlock.OPEN) != hasNeighborSignal;
++                // note: this must run before any state for this block/its neighborus are written to the world
++                // we allow the redstone event to fire so that plugins can block
++                if (hasNeighborSignal && open) { // if we are now powered and it caused the trap door to open
++                    // in this case, first check for the redstone on top first
++                    BlockPos abovePos = pos.above();
++                    BlockState above = level.getBlockState(abovePos);
++                    if (above.getBlock() instanceof RedStoneWireBlock) {
++                        level.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS);
++                        Block.popResource(level, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE));
++                        // now check that this didn't change our state
++                        if (level.getBlockState(pos) != state) {
++                            // our state was changed, so we cannot propagate this update
++                            return;
++                        }
++                    }
++                }
++                if (open) {
++                // Paper end - break redstone on trapdoors early
+                     state = state.setValue(OPEN, Boolean.valueOf(hasNeighborSignal));
+                     this.playSound(null, level, pos, hasNeighborSignal);
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch
new file mode 100644
index 0000000000..4be069ee99
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireBlock.java.patch
@@ -0,0 +1,108 @@
+--- a/net/minecraft/world/level/block/TripWireBlock.java
++++ b/net/minecraft/world/level/block/TripWireBlock.java
+@@ -72,6 +_,7 @@
+ 
+     @Override
+     public BlockState getStateForPlacement(BlockPlaceContext context) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return this.defaultBlockState(); // Paper - place tripwire without updating
+         BlockGetter level = context.getLevel();
+         BlockPos clickedPos = context.getClickedPos();
+         return this.defaultBlockState()
+@@ -92,6 +_,7 @@
+         BlockState neighborState,
+         RandomSource random
+     ) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent tripwire from updating
+         return direction.getAxis().isHorizontal()
+             ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(this.shouldConnectTo(neighborState, direction)))
+             : super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
+@@ -99,6 +_,7 @@
+ 
+     @Override
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
+         if (!oldState.is(state.getBlock())) {
+             this.updateSource(level, pos, state);
+         }
+@@ -106,6 +_,7 @@
+ 
+     @Override
+     protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
+         if (!isMoving && !state.is(newState.getBlock())) {
+             this.updateSource(level, pos, state.setValue(POWERED, Boolean.valueOf(true)));
+         }
+@@ -113,6 +_,7 @@
+ 
+     @Override
+     public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent disarming tripwires
+         if (!level.isClientSide && !player.getMainHandItem().isEmpty() && player.getMainHandItem().is(Items.SHEARS)) {
+             level.setBlock(pos, state.setValue(DISARMED, Boolean.valueOf(true)), 4);
+             level.gameEvent(player, GameEvent.SHEAR, pos);
+@@ -122,6 +_,7 @@
+     }
+ 
+     private void updateSource(Level level, BlockPos pos, BlockState state) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
+         for (Direction direction : new Direction[]{Direction.SOUTH, Direction.WEST}) {
+             for (int i = 1; i < 42; i++) {
+                 BlockPos blockPos = pos.relative(direction, i);
+@@ -147,6 +_,8 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwires from detecting collision
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (!level.isClientSide) {
+             if (!state.getValue(POWERED)) {
+                 this.checkPressed(level, pos, List.of(entity));
+@@ -156,6 +_,7 @@
+ 
+     @Override
+     protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwire pressed check
+         if (level.getBlockState(pos).getValue(POWERED)) {
+             this.checkPressed(level, pos);
+         }
+@@ -179,6 +_,40 @@
+                 }
+             }
+         }
++
++        // CraftBukkit start - Call interact even when triggering connected tripwire
++        if (flag != poweredValue && poweredValue && blockState.getValue(TripWireBlock.ATTACHED)) {
++            org.bukkit.World bworld = level.getWorld();
++            org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++            org.bukkit.block.Block block = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            boolean allowed = false;
++
++            // If all the events are cancelled block the tripwire trigger, else allow
++            for (Object object : entities) {
++                if (object != null) {
++                    org.bukkit.event.Cancellable cancellable;
++
++                    if (object instanceof Player) {
++                        cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) object, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++                    } else if (object instanceof Entity) {
++                        cancellable = new org.bukkit.event.entity.EntityInteractEvent(((Entity) object).getBukkitEntity(), block);
++                        manager.callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++                    } else {
++                        continue;
++                    }
++
++                    if (!cancellable.isCancelled()) {
++                        allowed = true;
++                        break;
++                    }
++                }
++            }
++
++            if (!allowed) {
++                return;
++            }
++        }
++        // CraftBukkit end
+ 
+         if (flag != poweredValue) {
+             blockState = blockState.setValue(POWERED, Boolean.valueOf(flag));
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
new file mode 100644
index 0000000000..8a437b9c80
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/TripWireHookBlock.java
++++ b/net/minecraft/world/level/block/TripWireHookBlock.java
+@@ -173,9 +_,18 @@
+                 notifyNeighbors(block, level, blockPosx, opposite);
+                 emitState(level, blockPosx, flag2, flag3, flag, flag1);
+             }
++            // CraftBukkit start
++            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), 15, 0);
++            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++            if (eventRedstone.getNewCurrent() > 0) {
++                return;
++            }
++            // CraftBukkit end
+ 
+             emitState(level, pos, flag2, flag3, flag, flag1);
+             if (!attaching) {
++                if (level.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update
+                 level.setBlock(pos, blockState1.setValue(FACING, direction), 3);
+                 if (shouldNotifyNeighbours) {
+                     notifyNeighbors(block, level, pos, direction);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch
new file mode 100644
index 0000000000..e7880ebb4f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TurtleEggBlock.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/level/block/TurtleEggBlock.java
++++ b/net/minecraft/world/level/block/TurtleEggBlock.java
+@@ -74,6 +_,19 @@
+             && level instanceof ServerLevel serverLevel
+             && this.canDestroyEgg(serverLevel, entity)
+             && level.random.nextInt(chance) == 0) {
++            // CraftBukkit start - Step on eggs
++            org.bukkit.event.Cancellable cancellable;
++            if (entity instanceof Player) {
++                cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++            } else {
++                cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos));
++                level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++            }
++
++            if (cancellable.isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
+             this.decreaseEggs(serverLevel, pos, state);
+         }
+     }
+@@ -95,10 +_,20 @@
+         if (this.shouldUpdateHatchLevel(level) && onSand(level, pos)) {
+             int hatchValue = state.getValue(HATCH);
+             if (hatchValue < 2) {
++                // CraftBukkit start - Call BlockGrowEvent
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(TurtleEggBlock.HATCH, hatchValue + 1), 2)) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
+-                level.setBlock(pos, state.setValue(HATCH, Integer.valueOf(hatchValue + 1)), 2);
++                // level.setBlock(pos, state.setValue(HATCH, Integer.valueOf(hatchValue + 1)), 2); // CraftBukkit - handled above
+                 level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
+             } else {
++                // CraftBukkit start - Call BlockFadeEvent
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
+                 level.removeBlock(pos, false);
+                 level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(state));
+@@ -110,7 +_,7 @@
+                         turtle.setAge(-24000);
+                         turtle.setHomePos(pos);
+                         turtle.moveTo(pos.getX() + 0.3 + i * 0.2, pos.getY(), pos.getZ() + 0.3, 0.0F, 0.0F);
+-                        level.addFreshEntity(turtle);
++                        level.addFreshEntity(turtle, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit
+                     }
+                 }
+             }
+@@ -138,8 +_,8 @@
+     }
+ 
+     @Override
+-    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
+-        super.playerDestroy(level, player, pos, state, te, stack);
++    public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
++        super.playerDestroy(level, player, pos, state, te, stack, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
+         this.decreaseEggs(level, pos, state);
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch
new file mode 100644
index 0000000000..f5fb874107
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/VineBlock.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/world/level/block/VineBlock.java
++++ b/net/minecraft/world/level/block/VineBlock.java
+@@ -191,7 +_,7 @@
+     @Override
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (level.getGameRules().getBoolean(GameRules.RULE_DO_VINES_SPREAD)) {
+-            if (random.nextInt(4) == 0) {
++            if (random.nextFloat() < (level.spigotConfig.vineModifier / (100.0f * 4))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                 Direction random1 = Direction.getRandom(random);
+                 BlockPos blockPos = pos.above();
+                 if (random1.getAxis().isHorizontal() && !state.getValue(getPropertyForFace(random1))) {
+@@ -205,28 +_,31 @@
+                             boolean value1 = state.getValue(getPropertyForFace(counterClockWise));
+                             BlockPos blockPos2 = blockPos1.relative(clockWise);
+                             BlockPos blockPos3 = blockPos1.relative(counterClockWise);
++                            // CraftBukkit start - Call BlockSpreadEvent
++                            BlockPos source = pos;
+                             if (value && isAcceptableNeighbour(level, blockPos2, clockWise)) {
+-                                level.setBlock(blockPos1, this.defaultBlockState().setValue(getPropertyForFace(clockWise), Boolean.valueOf(true)), 2);
++                                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, blockPos1, this.defaultBlockState().setValue(getPropertyForFace(clockWise), Boolean.valueOf(true)), 2);
+                             } else if (value1 && isAcceptableNeighbour(level, blockPos3, counterClockWise)) {
+-                                level.setBlock(blockPos1, this.defaultBlockState().setValue(getPropertyForFace(counterClockWise), Boolean.valueOf(true)), 2);
++                                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, blockPos1, this.defaultBlockState().setValue(getPropertyForFace(counterClockWise), Boolean.valueOf(true)), 2);
+                             } else {
+                                 Direction opposite = random1.getOpposite();
+                                 if (value && level.isEmptyBlock(blockPos2) && isAcceptableNeighbour(level, pos.relative(clockWise), opposite)) {
+-                                    level.setBlock(blockPos2, this.defaultBlockState().setValue(getPropertyForFace(opposite), Boolean.valueOf(true)), 2);
++                                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, blockPos2, this.defaultBlockState().setValue(getPropertyForFace(opposite), Boolean.valueOf(true)), 2);
+                                 } else if (value1 && level.isEmptyBlock(blockPos3) && isAcceptableNeighbour(level, pos.relative(counterClockWise), opposite)) {
+-                                    level.setBlock(blockPos3, this.defaultBlockState().setValue(getPropertyForFace(opposite), Boolean.valueOf(true)), 2);
++                                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, blockPos3, this.defaultBlockState().setValue(getPropertyForFace(opposite), Boolean.valueOf(true)), 2);
+                                 } else if (random.nextFloat() < 0.05 && isAcceptableNeighbour(level, blockPos1.above(), Direction.UP)) {
+-                                    level.setBlock(blockPos1, this.defaultBlockState().setValue(UP, Boolean.valueOf(true)), 2);
++                                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, source, blockPos1, this.defaultBlockState().setValue(UP, Boolean.valueOf(true)), 2);
+                                 }
++                                // CraftBukkit end
+                             }
+                         } else if (isAcceptableNeighbour(level, blockPos1, random1)) {
+-                            level.setBlock(pos, state.setValue(getPropertyForFace(random1), Boolean.valueOf(true)), 2);
++                            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, (BlockState) state.setValue(VineBlock.getPropertyForFace(random1), true), 2); // CraftBukkit
+                         }
+                     }
+                 } else {
+                     if (random1 == Direction.UP && pos.getY() < level.getMaxY()) {
+                         if (this.canSupportAtFace(level, pos, random1)) {
+-                            level.setBlock(pos, state.setValue(UP, Boolean.valueOf(true)), 2);
++                            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(UP, Boolean.valueOf(true)), 2); // CraftBukkit
+                             return;
+                         }
+ 
+@@ -244,7 +_,7 @@
+                             }
+ 
+                             if (this.hasHorizontalConnection(blockState1)) {
+-                                level.setBlock(blockPos, blockState1, 2);
++                                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState1, 2); // CraftBukkit
+                             }
+ 
+                             return;
+@@ -258,7 +_,7 @@
+                             BlockState blockState2 = blockState.isAir() ? this.defaultBlockState() : blockState;
+                             BlockState blockState3 = this.copyRandomFaces(state, blockState2, random);
+                             if (blockState2 != blockState3 && this.hasHorizontalConnection(blockState3)) {
+-                                level.setBlock(blockPos1, blockState3, 2);
++                                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos1, blockState3, 2); // CraftBukkit
+                             }
+                         }
+                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WallHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
index 73be8c6267..0543306a35 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/WallHangingSignBlock.java
 +++ b/net/minecraft/world/level/block/WallHangingSignBlock.java
-@@ -179,6 +179,6 @@
+@@ -187,6 +_,6 @@
      @Nullable
      @Override
-     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockState state, BlockEntityType<T> type) {
--        return createTickerHelper(type, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
+     public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
+-        return createTickerHelper(blockEntityType, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
 +        return null; // Craftbukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch
new file mode 100644
index 0000000000..7176715848
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WaterlilyBlock.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/level/block/WaterlilyBlock.java
++++ b/net/minecraft/world/level/block/WaterlilyBlock.java
+@@ -29,8 +_,14 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         super.entityInside(state, level, pos, entity);
+         if (level instanceof ServerLevel && entity instanceof AbstractBoat) {
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                return;
++            }
++            // CraftBukkit end
+             level.destroyBlock(new BlockPos(pos), true, entity);
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WebBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch
similarity index 77%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/WebBlock.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch
index 1204a1e128..e774bcdbb1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WebBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WebBlock.java.patch
@@ -1,10 +1,10 @@
 --- a/net/minecraft/world/level/block/WebBlock.java
 +++ b/net/minecraft/world/level/block/WebBlock.java
-@@ -24,6 +24,7 @@
+@@ -24,6 +_,7 @@
  
      @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
          Vec3 vec3 = new Vec3(0.25, 0.05F, 0.25);
          if (entity instanceof LivingEntity livingEntity && livingEntity.hasEffect(MobEffects.WEAVING)) {
              vec3 = new Vec3(0.5, 0.25, 0.5);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
new file mode 100644
index 0000000000..26787da952
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
++++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+@@ -6,6 +_,7 @@
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.util.Mth;
+ import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+@@ -39,7 +_,27 @@
+ 
+     @Override
+     protected int getSignalStrength(Level level, BlockPos pos) {
+-        int min = Math.min(getEntityCount(level, TOUCH_AABB.move(pos), Entity.class), this.maxWeight);
++        // CraftBukkit start
++        // int min = Math.min(getEntityCount(level, TOUCH_AABB.move(pos), Entity.class), this.maxWeight);
++        int min = 0;
++        for (Entity entity : getEntities(level, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class)) {
++            org.bukkit.event.Cancellable cancellable;
++
++            if (entity instanceof Player) {
++                cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++            } else {
++                cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++                level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++            }
++
++            // We only want to block turning the plate on if all events are cancelled
++            if (!cancellable.isCancelled()) {
++                min++;
++            }
++        }
++
++        min = Math.min(min, this.maxWeight);
++        // CraftBukkit end
+         if (min > 0) {
+             float f = (float)Math.min(this.maxWeight, min) / this.maxWeight;
+             return Mth.ceil(f * 15.0F);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch
new file mode 100644
index 0000000000..6a3cdd4e5a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WitherRoseBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/WitherRoseBlock.java
++++ b/net/minecraft/world/level/block/WitherRoseBlock.java
+@@ -63,11 +_,12 @@
+ 
+     @Override
+     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (level instanceof ServerLevel serverLevel
+             && level.getDifficulty() != Difficulty.PEACEFUL
+             && entity instanceof LivingEntity livingEntity
+             && !livingEntity.isInvulnerableTo(serverLevel, level.damageSources().wither())) {
+-            livingEntity.addEffect(this.getBeeInteractionEffect());
++            livingEntity.addEffect(this.getBeeInteractionEffect(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WITHER_ROSE); // CraftBukkit
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch
new file mode 100644
index 0000000000..63d29d1d8d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/WitherSkullBlock.java
++++ b/net/minecraft/world/level/block/WitherSkullBlock.java
+@@ -51,6 +_,7 @@
+     }
+ 
+     public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) {
++        if (level.captureBlockStates) return; // CraftBukkit
+         if (!level.isClientSide) {
+             BlockState blockState = blockEntity.getBlockState();
+             boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL);
+@@ -59,7 +_,7 @@
+                 if (blockPatternMatch != null) {
+                     WitherBoss witherBoss = EntityType.WITHER.create(level, EntitySpawnReason.TRIGGERED);
+                     if (witherBoss != null) {
+-                        CarvedPumpkinBlock.clearPatternBlocks(level, blockPatternMatch);
++                        // CarvedPumpkinBlock.clearPatternBlocks(level, blockPatternMatch); // CraftBukkit - move down
+                         BlockPos pos1 = blockPatternMatch.getBlock(1, 2, 0).getPos();
+                         witherBoss.moveTo(
+                             pos1.getX() + 0.5,
+@@ -70,12 +_,18 @@
+                         );
+                         witherBoss.yBodyRot = blockPatternMatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
+                         witherBoss.makeInvulnerable();
++                        // CraftBukkit start
++                        if (!level.addFreshEntity(witherBoss, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BUILD_WITHER)) {
++                            return;
++                        }
++                        CarvedPumpkinBlock.clearPatternBlocks(level, blockPatternMatch); // CraftBukkit - from above
++                        // CraftBukkit end
+ 
+                         for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, witherBoss.getBoundingBox().inflate(50.0))) {
+                             CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, witherBoss);
+                         }
+ 
+-                        level.addFreshEntity(witherBoss);
++                        // level.addFreshEntity(witherBoss); // CraftBukkit - moved up
+                         CarvedPumpkinBlock.updatePatternBlocks(level, blockPatternMatch);
+                     }
+                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SaplingBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SaplingBlock.java.patch
deleted file mode 100644
index dbd9b130a0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SaplingBlock.java.patch
+++ /dev/null
@@ -1,117 +0,0 @@
---- a/net/minecraft/world/level/block/SaplingBlock.java
-+++ b/net/minecraft/world/level/block/SaplingBlock.java
-@@ -10,12 +10,19 @@
- import net.minecraft.world.level.LevelReader;
- import net.minecraft.world.level.block.grower.TreeGrower;
- import net.minecraft.world.level.block.state.BlockBehaviour;
--import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.StateDefinition;
- import net.minecraft.world.level.block.state.properties.BlockStateProperties;
- import net.minecraft.world.level.block.state.properties.IntegerProperty;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.Location;
-+import org.bukkit.TreeType;
-+import org.bukkit.block.BlockState;
-+import org.bukkit.craftbukkit.block.CapturedBlockState;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.world.StructureGrowEvent;
-+// CraftBukkit end
- 
- public class SaplingBlock extends BushBlock implements BonemealableBlock {
- 
-@@ -28,6 +35,7 @@
-     protected static final float AABB_OFFSET = 6.0F;
-     protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
-     protected final TreeGrower treeGrower;
-+    public static TreeType treeType; // CraftBukkit
- 
-     @Override
-     public MapCodec<? extends SaplingBlock> codec() {
-@@ -37,48 +45,74 @@
-     protected SaplingBlock(TreeGrower generator, BlockBehaviour.Properties settings) {
-         super(settings);
-         this.treeGrower = generator;
--        this.registerDefaultState((BlockState) ((BlockState) this.stateDefinition.any()).setValue(SaplingBlock.STAGE, 0));
-+        this.registerDefaultState((net.minecraft.world.level.block.state.BlockState) ((net.minecraft.world.level.block.state.BlockState) this.stateDefinition.any()).setValue(SaplingBlock.STAGE, 0));
-     }
- 
-     @Override
--    protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
-+    protected VoxelShape getShape(net.minecraft.world.level.block.state.BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
-         return SaplingBlock.SHAPE;
-     }
- 
-     @Override
--    protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
--        if (world.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextInt(7) == 0) {
-+    protected void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-+        if (world.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0f * 7))) { // Spigot - SPIGOT-7159: Better modifier resolution
-             this.advanceTree(world, pos, state, random);
-         }
- 
-     }
- 
--    public void advanceTree(ServerLevel world, BlockPos pos, BlockState state, RandomSource random) {
-+    public void advanceTree(ServerLevel world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, RandomSource random) {
-         if ((Integer) state.getValue(SaplingBlock.STAGE) == 0) {
--            world.setBlock(pos, (BlockState) state.cycle(SaplingBlock.STAGE), 4);
-+            world.setBlock(pos, (net.minecraft.world.level.block.state.BlockState) state.cycle(SaplingBlock.STAGE), 4);
-         } else {
--            this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random);
-+            // CraftBukkit start
-+            if (world.captureTreeGeneration) {
-+                this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random);
-+            } else {
-+                world.captureTreeGeneration = true;
-+                this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random);
-+                world.captureTreeGeneration = false;
-+                if (world.capturedBlockStates.size() > 0) {
-+                    TreeType treeType = SaplingBlock.treeType;
-+                    SaplingBlock.treeType = null;
-+                    Location location = CraftLocation.toBukkit(pos, world.getWorld());
-+                    java.util.List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
-+                    world.capturedBlockStates.clear();
-+                    StructureGrowEvent event = null;
-+                    if (treeType != null) {
-+                        event = new StructureGrowEvent(location, treeType, false, null, blocks);
-+                        org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+                    }
-+                    if (event == null || !event.isCancelled()) {
-+                        for (BlockState blockstate : blocks) {
-+                            CapturedBlockState.setBlockState(blockstate);
-+                            world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed
-+                        }
-+                    }
-+                }
-+            }
-+            // CraftBukkit end
-         }
- 
-     }
- 
-     @Override
--    public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state) {
-+    public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
-         return true;
-     }
- 
-     @Override
--    public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) {
-+    public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
-         return (double) world.random.nextFloat() < 0.45D;
-     }
- 
-     @Override
--    public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
-+    public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
-         this.advanceTree(world, pos, state, random);
-     }
- 
-     @Override
--    protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
-+    protected void createBlockStateDefinition(StateDefinition.Builder<Block, net.minecraft.world.level.block.state.BlockState> builder) {
-         builder.add(SaplingBlock.STAGE);
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
deleted file mode 100644
index d5960ee388..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/world/level/block/ScaffoldingBlock.java
-+++ b/net/minecraft/world/level/block/ScaffoldingBlock.java
-@@ -103,7 +103,7 @@
-         int i = ScaffoldingBlock.getDistance(world, pos);
-         BlockState iblockdata1 = (BlockState) ((BlockState) state.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(world, pos, i));
- 
--        if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7) {
-+        if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, iblockdata1.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state
-             if ((Integer) state.getValue(ScaffoldingBlock.DISTANCE) == 7) {
-                 FallingBlockEntity.fall(world, pos, iblockdata1);
-             } else {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkBlock.java.patch
deleted file mode 100644
index b7e23a6303..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkBlock.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/world/level/block/SculkBlock.java
-+++ b/net/minecraft/world/level/block/SculkBlock.java
-@@ -43,8 +43,11 @@
-                     BlockPos blockposition2 = blockposition1.above();
-                     BlockState iblockdata = this.getRandomGrowthState(world, blockposition2, random, spreadManager.isWorldGeneration());
- 
--                    world.setBlock(blockposition2, iblockdata, 3);
--                    world.playSound((Player) null, blockposition1, iblockdata.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    // CraftBukkit start - Call BlockSpreadEvent
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, catalystPos, blockposition2, iblockdata, 3)) {
-+                        world.playSound((Player) null, blockposition1, iblockdata.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    }
-+                    // CraftBukkit end
-                 }
- 
-                 return Math.max(0, i - j);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkCatalystBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
deleted file mode 100644
index 12314e3c0c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/level/block/SculkCatalystBlock.java
-+++ b/net/minecraft/world/level/block/SculkCatalystBlock.java
-@@ -63,9 +63,16 @@
-     @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
--        if (dropExperience) {
--            this.tryDropExperience(world, pos, tool, this.xpRange);
-+        // CraftBukkit start - Delegate to getExpDrop
-+    }
-+
-+    @Override
-+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
-+        if (flag) {
-+            return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange);
-         }
- 
-+        return 0;
-+        // CraftBukkit end
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSensorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSensorBlock.java.patch
deleted file mode 100644
index 8e63b99d10..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSensorBlock.java.patch
+++ /dev/null
@@ -1,88 +0,0 @@
---- a/net/minecraft/world/level/block/SculkSensorBlock.java
-+++ b/net/minecraft/world/level/block/SculkSensorBlock.java
-@@ -43,6 +43,10 @@
- import net.minecraft.world.level.pathfinder.PathComputationType;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+// CraftBukkit end
- 
- public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterloggedBlock {
- 
-@@ -104,6 +108,18 @@
-     @Override
-     public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
-         if (!world.isClientSide() && SculkSensorBlock.canActivate(state) && entity.getType() != EntityType.WARDEN) {
-+            // CraftBukkit start
-+            org.bukkit.event.Cancellable cancellable;
-+            if (entity instanceof Player) {
-+                cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+            } else {
-+                cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                world.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
-+            }
-+            if (cancellable.isCancelled()) {
-+                return;
-+            }
-+            // CraftBukkit end
-             BlockEntity tileentity = world.getBlockEntity(pos);
- 
-             if (tileentity instanceof SculkSensorBlockEntity) {
-@@ -198,10 +214,19 @@
-     }
- 
-     public static boolean canActivate(BlockState state) {
--        return SculkSensorBlock.getPhase(state) == SculkSensorPhase.INACTIVE;
-+        return state.getBlock() instanceof SculkSensorBlock && SculkSensorBlock.getPhase(state) == SculkSensorPhase.INACTIVE; // Paper - Check for a valid type
-     }
- 
-     public static void deactivate(Level world, BlockPos pos, BlockState state) {
-+        // CraftBukkit start
-+        BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), state.getValue(SculkSensorBlock.POWER), 0);
-+        world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+        if (eventRedstone.getNewCurrent() > 0) {
-+            world.setBlock(pos, state.setValue(SculkSensorBlock.POWER, eventRedstone.getNewCurrent()), 3);
-+            return;
-+        }
-+        // CraftBukkit end
-         world.setBlock(pos, (BlockState) ((BlockState) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.COOLDOWN)).setValue(SculkSensorBlock.POWER, 0), 3);
-         world.scheduleTick(pos, state.getBlock(), 10);
-         SculkSensorBlock.updateNeighbours(world, pos, state);
-@@ -213,6 +238,15 @@
-     }
- 
-     public void activate(@Nullable Entity sourceEntity, Level world, BlockPos pos, BlockState state, int power, int frequency) {
-+        // CraftBukkit start
-+        BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), state.getValue(SculkSensorBlock.POWER), power);
-+        world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+        if (eventRedstone.getNewCurrent() <= 0) {
-+            return;
-+        }
-+        power = eventRedstone.getNewCurrent();
-+        // CraftBukkit end
-         world.setBlock(pos, (BlockState) ((BlockState) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.ACTIVE)).setValue(SculkSensorBlock.POWER, power), 3);
-         world.scheduleTick(pos, state.getBlock(), this.getActiveTicks());
-         SculkSensorBlock.updateNeighbours(world, pos, state);
-@@ -293,9 +327,16 @@
-     @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
--        if (dropExperience) {
--            this.tryDropExperience(world, pos, tool, ConstantInt.of(5));
-+        // CraftBukkit start - Delegate to getExpDrop
-+    }
-+
-+    @Override
-+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
-+        if (flag) {
-+            return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5));
-         }
- 
-+        return 0;
-+        // CraftBukkit end
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkShriekerBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
deleted file mode 100644
index 8c254d2936..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
+++ /dev/null
@@ -1,30 +0,0 @@
---- a/net/minecraft/world/level/block/SculkShriekerBlock.java
-+++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
-@@ -63,6 +63,7 @@
-             ServerPlayer entityplayer = SculkShriekerBlockEntity.tryGetPlayer(entity);
- 
-             if (entityplayer != null) {
-+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(entityplayer, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null).isCancelled()) return; // CraftBukkit
-                 worldserver.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent((sculkshriekerblockentity) -> {
-                     sculkshriekerblockentity.tryShriek(worldserver, entityplayer);
-                 });
-@@ -140,10 +141,17 @@
-     @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
--        if (dropExperience) {
--            this.tryDropExperience(world, pos, tool, ConstantInt.of(5));
-+        // CraftBukkit start - Delegate to getExpDrop
-+    }
-+
-+    @Override
-+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
-+        if (flag) {
-+            return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5));
-         }
- 
-+        return 0;
-+        // CraftBukkit end
-     }
- 
-     @Nullable
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSpreader.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSpreader.java.patch
deleted file mode 100644
index a334f182c5..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkSpreader.java.patch
+++ /dev/null
@@ -1,98 +0,0 @@
---- a/net/minecraft/world/level/block/SculkSpreader.java
-+++ b/net/minecraft/world/level/block/SculkSpreader.java
-@@ -30,6 +30,7 @@
- import net.minecraft.core.Vec3i;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
-@@ -37,9 +38,14 @@
- import net.minecraft.tags.TagKey;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.entity.player.Player;
-+import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.block.state.BlockState;
- import org.slf4j.Logger;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.SculkBloomEvent;
-+// CraftBukkit end
- 
- public class SculkSpreader {
- 
-@@ -57,6 +63,7 @@
-     private final int additionalDecayRate;
-     private List<SculkSpreader.ChargeCursor> cursors = new ArrayList();
-     private static final Logger LOGGER = LogUtils.getLogger();
-+    public Level level; // CraftBukkit
- 
-     public SculkSpreader(boolean worldGen, TagKey<Block> replaceableTag, int extraBlockChance, int maxDistance, int spreadChance, int decayChance) {
-         this.isWorldGeneration = worldGen;
-@@ -111,7 +118,7 @@
-     public void load(CompoundTag nbt) {
-         if (nbt.contains("cursors", 9)) {
-             this.cursors.clear();
--            DataResult dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().parse(new Dynamic(NbtOps.INSTANCE, nbt.getList("cursors", 10)));
-+            DataResult<List<SculkSpreader.ChargeCursor>> dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getList("cursors", 10))); // CraftBukkit - decompile error
-             Logger logger = SculkSpreader.LOGGER;
- 
-             Objects.requireNonNull(logger);
-@@ -119,14 +126,14 @@
-             int i = Math.min(list.size(), 32);
- 
-             for (int j = 0; j < i; ++j) {
--                this.addCursor((SculkSpreader.ChargeCursor) list.get(j));
-+                this.addCursor((SculkSpreader.ChargeCursor) list.get(j), false); // Paper - don't fire event for block entity loading
-             }
-         }
- 
-     }
- 
-     public void save(CompoundTag nbt) {
--        DataResult dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.cursors);
-+        DataResult<Tag> dataresult = SculkSpreader.ChargeCursor.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.cursors); // CraftBukkit - decompile error
-         Logger logger = SculkSpreader.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -139,14 +146,27 @@
-         while (charge > 0) {
-             int j = Math.min(charge, 1000);
- 
--            this.addCursor(new SculkSpreader.ChargeCursor(pos, j));
-+            this.addCursor(new SculkSpreader.ChargeCursor(pos, j), true); // Paper - allow firing event for other causes
-             charge -= j;
-         }
- 
-     }
- 
--    private void addCursor(SculkSpreader.ChargeCursor cursor) {
-+    private void addCursor(SculkSpreader.ChargeCursor cursor, boolean fireEvent) { // Paper - add boolean to conditionally fire SculkBloomEvent
-         if (this.cursors.size() < 32) {
-+            // CraftBukkit start
-+            if (!this.isWorldGeneration() && fireEvent) { // CraftBukkit - SPIGOT-7475: Don't call event during world generation // Paper - add boolean to conditionally fire SculkBloomEvent
-+                CraftBlock bukkitBlock = CraftBlock.at(this.level, cursor.pos);
-+                SculkBloomEvent event = new SculkBloomEvent(bukkitBlock, cursor.getCharge());
-+                Bukkit.getPluginManager().callEvent(event);
-+                if (event.isCancelled()) {
-+                    return;
-+                }
-+
-+                cursor.charge = event.getCharge();
-+            }
-+            // CraftBukkit end
-+
-             this.cursors.add(cursor);
-         }
-     }
-@@ -244,7 +264,7 @@
-             this.charge = charge;
-             this.decayDelay = decay;
-             this.updateDelay = update;
--            this.facings = (Set) faces.orElse((Object) null);
-+            this.facings = (Set) faces.orElse(null); // CraftBukkit - decompile error
-         }
- 
-         public ChargeCursor(BlockPos pos, int charge) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkVeinBlock.java.patch
deleted file mode 100644
index 4d009bd7c1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SculkVeinBlock.java.patch
+++ /dev/null
@@ -1,60 +0,0 @@
---- a/net/minecraft/world/level/block/SculkVeinBlock.java
-+++ b/net/minecraft/world/level/block/SculkVeinBlock.java
-@@ -101,28 +101,33 @@
- 
-     @Override
-     public int attemptUseCharge(SculkSpreader.ChargeCursor cursor, LevelAccessor world, BlockPos catalystPos, RandomSource random, SculkSpreader spreadManager, boolean shouldConvertToBlock) {
--        return shouldConvertToBlock && this.attemptPlaceSculk(spreadManager, world, cursor.getPos(), random) ? cursor.getCharge() - 1 : (random.nextInt(spreadManager.chargeDecayRate()) == 0 ? Mth.floor((float) cursor.getCharge() * 0.5F) : cursor.getCharge());
-+        // CraftBukkit - add source block
-+        return shouldConvertToBlock && this.attemptPlaceSculk(spreadManager, world, cursor.getPos(), random, catalystPos) ? cursor.getCharge() - 1 : (random.nextInt(spreadManager.chargeDecayRate()) == 0 ? Mth.floor((float) cursor.getCharge() * 0.5F) : cursor.getCharge());
-     }
- 
--    private boolean attemptPlaceSculk(SculkSpreader spreadManager, LevelAccessor world, BlockPos pos, RandomSource random) {
--        BlockState iblockdata = world.getBlockState(pos);
--        TagKey<Block> tagkey = spreadManager.replaceableBlocks();
--        Iterator iterator = Direction.allShuffled(random).iterator();
-+    private boolean attemptPlaceSculk(SculkSpreader sculkspreader, LevelAccessor generatoraccess, BlockPos blockposition, RandomSource randomsource, BlockPos sourceBlock) { // CraftBukkit
-+        BlockState iblockdata = generatoraccess.getBlockState(blockposition);
-+        TagKey<Block> tagkey = sculkspreader.replaceableBlocks();
-+        Iterator iterator = Direction.allShuffled(randomsource).iterator();
- 
-         while (iterator.hasNext()) {
-             Direction enumdirection = (Direction) iterator.next();
- 
-             if (hasFace(iblockdata, enumdirection)) {
--                BlockPos blockposition1 = pos.relative(enumdirection);
--                BlockState iblockdata1 = world.getBlockState(blockposition1);
-+                BlockPos blockposition1 = blockposition.relative(enumdirection);
-+                BlockState iblockdata1 = generatoraccess.getBlockState(blockposition1);
- 
-                 if (iblockdata1.is(tagkey)) {
-                     BlockState iblockdata2 = Blocks.SCULK.defaultBlockState();
- 
--                    world.setBlock(blockposition1, iblockdata2, 3);
--                    Block.pushEntitiesUp(iblockdata1, iblockdata2, world, blockposition1);
--                    world.playSound((Player) null, blockposition1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
--                    this.veinSpreader.spreadAll(iblockdata2, world, blockposition1, spreadManager.isWorldGeneration());
-+                    // CraftBukkit start - Call BlockSpreadEvent
-+                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, sourceBlock, blockposition1, iblockdata2, 3)) {
-+                        return false;
-+                    }
-+                    // CraftBukkit end
-+                    Block.pushEntitiesUp(iblockdata1, iblockdata2, generatoraccess, blockposition1);
-+                    generatoraccess.playSound((Player) null, blockposition1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    this.veinSpreader.spreadAll(iblockdata2, generatoraccess, blockposition1, sculkspreader.isWorldGeneration());
-                     Direction enumdirection1 = enumdirection.getOpposite();
-                     Direction[] aenumdirection = SculkVeinBlock.DIRECTIONS;
-                     int i = aenumdirection.length;
-@@ -132,10 +137,10 @@
- 
-                         if (enumdirection2 != enumdirection1) {
-                             BlockPos blockposition2 = blockposition1.relative(enumdirection2);
--                            BlockState iblockdata3 = world.getBlockState(blockposition2);
-+                            BlockState iblockdata3 = generatoraccess.getBlockState(blockposition2);
- 
-                             if (iblockdata3.is((Block) this)) {
--                                this.onDischarged(world, iblockdata3, blockposition2, random);
-+                                this.onDischarged(generatoraccess, iblockdata3, blockposition2, randomsource);
-                             }
-                         }
-                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SnowLayerBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SnowLayerBlock.java.patch
deleted file mode 100644
index 5db7dbeea9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SnowLayerBlock.java.patch
+++ /dev/null
@@ -1,14 +0,0 @@
---- a/net/minecraft/world/level/block/SnowLayerBlock.java
-+++ b/net/minecraft/world/level/block/SnowLayerBlock.java
-@@ -99,6 +99,11 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (world.getBrightness(LightLayer.BLOCK, pos) > 11) {
-+            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
-+                return;
-+            }
-+            // CraftBukkit end
-             dropResources(state, world, pos);
-             world.removeBlock(pos, false);
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpawnerBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SpawnerBlock.java.patch
deleted file mode 100644
index 8f4ea72cba..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpawnerBlock.java.patch
+++ /dev/null
@@ -1,26 +0,0 @@
---- a/net/minecraft/world/level/block/SpawnerBlock.java
-+++ b/net/minecraft/world/level/block/SpawnerBlock.java
-@@ -45,12 +45,20 @@
-     @Override
-     protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-         super.spawnAfterBreak(state, world, pos, tool, dropExperience);
--        if (dropExperience) {
--            int i = 15 + world.random.nextInt(15) + world.random.nextInt(15);
-+        // CraftBukkit start - Delegate to getExpDrop
-+    }
- 
--            this.popExperience(world, pos, i);
-+    @Override
-+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
-+        if (flag) {
-+            int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15);
-+
-+            // this.popExperience(worldserver, blockposition, i);
-+            return i;
-         }
- 
-+        return 0;
-+        // CraftBukkit end
-     }
- 
-     @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpongeBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SpongeBlock.java.patch
deleted file mode 100644
index d0c197c400..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpongeBlock.java.patch
+++ /dev/null
@@ -1,114 +0,0 @@
---- a/net/minecraft/world/level/block/SpongeBlock.java
-+++ b/net/minecraft/world/level/block/SpongeBlock.java
-@@ -15,6 +15,13 @@
- import net.minecraft.world.level.material.FluidState;
- import net.minecraft.world.level.redstone.Orientation;
- 
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.craftbukkit.block.CraftBlockState;
-+import org.bukkit.craftbukkit.util.BlockStateListPopulator;
-+import org.bukkit.event.block.SpongeAbsorbEvent;
-+// CraftBukkit end
-+
- public class SpongeBlock extends Block {
- 
-     public static final MapCodec<SpongeBlock> CODEC = simpleCodec(SpongeBlock::new);
-@@ -53,7 +60,8 @@
-     }
- 
-     private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) {
--        return BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> {
-+        BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator
-+        BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> {
-             Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS;
-             int i = aenumdirection.length;
- 
-@@ -67,8 +75,10 @@
-             if (blockposition1.equals(pos)) {
-                 return BlockPos.TraversalNodeStatus.ACCEPT;
-             } else {
--                BlockState iblockdata = world.getBlockState(blockposition1);
--                FluidState fluid = world.getFluidState(blockposition1);
-+                // CraftBukkit start
-+                BlockState iblockdata = blockList.getBlockState(blockposition1);
-+                FluidState fluid = blockList.getFluidState(blockposition1);
-+                // CraftBukkit end
- 
-                 if (!fluid.is(FluidTags.WATER)) {
-                     return BlockPos.TraversalNodeStatus.SKIP;
-@@ -78,27 +88,68 @@
-                     if (block instanceof BucketPickup) {
-                         BucketPickup ifluidsource = (BucketPickup) block;
- 
--                        if (!ifluidsource.pickupBlock((Player) null, world, blockposition1, iblockdata).isEmpty()) {
-+                        if (!ifluidsource.pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) { // CraftBukkit
-                             return BlockPos.TraversalNodeStatus.ACCEPT;
-                         }
-                     }
- 
-                     if (iblockdata.getBlock() instanceof LiquidBlock) {
--                        world.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
-+                        blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
-                     } else {
-                         if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) {
-                             return BlockPos.TraversalNodeStatus.SKIP;
-                         }
- 
--                        BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null;
-+                        // CraftBukkit start
-+                        // TileEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null;
- 
--                        dropResources(iblockdata, world, blockposition1, tileentity);
--                        world.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
-+                        // dropResources(iblockdata, world, blockposition1, tileentity);
-+                        blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
-+                        // CraftBukkit end
-                     }
- 
-                     return BlockPos.TraversalNodeStatus.ACCEPT;
-                 }
-             }
--        }) > 1;
-+        });
-+        // CraftBukkit start
-+        List<CraftBlockState> blocks = blockList.getList(); // Is a clone
-+        if (!blocks.isEmpty()) {
-+            final org.bukkit.block.Block bblock = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+
-+            SpongeAbsorbEvent event = new SpongeAbsorbEvent(bblock, (List<org.bukkit.block.BlockState>) (List) blocks);
-+            world.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
-+                return false;
-+            }
-+
-+            for (CraftBlockState block : blocks) {
-+                BlockPos blockposition1 = block.getPosition();
-+                BlockState iblockdata = world.getBlockState(blockposition1);
-+                FluidState fluid = world.getFluidState(blockposition1);
-+
-+                if (fluid.is(FluidTags.WATER)) {
-+                    if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) {
-+                        // NOP
-+                    } else if (iblockdata.getBlock() instanceof LiquidBlock) {
-+                        // NOP
-+                    } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) {
-+                        BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null;
-+
-+                        // Paper start - Fix SpongeAbsortEvent handling
-+                        if (block.getHandle().isAir()) {
-+                        dropResources(iblockdata, world, blockposition1, tileentity);
-+                        }
-+                        // Paper end - Fix SpongeAbsortEvent handling
-+                    }
-+                }
-+                world.setBlock(blockposition1, block.getHandle(), block.getFlag());
-+            }
-+
-+            return true;
-+        }
-+        return false;
-+        // CraftBukkit end
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
deleted file mode 100644
index 5777f59736..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
+++ /dev/null
@@ -1,25 +0,0 @@
---- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
-+++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
-@@ -43,7 +43,13 @@
- 
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-+        if (this instanceof GrassBlock && world.paperConfig().tickRates.grassSpread != 1 && (world.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks
-         if (!SpreadingSnowyDirtBlock.canBeGrass(state, world, pos)) {
-+            // CraftBukkit start
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
-+                return;
-+            }
-+            // CraftBukkit end
-             world.setBlockAndUpdate(pos, Blocks.DIRT.defaultBlockState());
-         } else {
-             if (world.getMaxLocalRawBrightness(pos.above()) >= 9) {
-@@ -53,7 +59,7 @@
-                     BlockPos blockposition1 = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
- 
-                     if (world.getBlockState(blockposition1).is(Blocks.DIRT) && SpreadingSnowyDirtBlock.canPropagate(iblockdata1, world, blockposition1)) {
--                        world.setBlockAndUpdate(blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(world.getBlockState(blockposition1.above()))));
-+                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(world.getBlockState(blockposition1.above())))); // CraftBukkit
-                     }
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/StemBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/StemBlock.java.patch
deleted file mode 100644
index 625e104922..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/StemBlock.java.patch
+++ /dev/null
@@ -1,47 +0,0 @@
---- a/net/minecraft/world/level/block/StemBlock.java
-+++ b/net/minecraft/world/level/block/StemBlock.java
-@@ -26,6 +26,7 @@
- import net.minecraft.world.level.block.state.properties.IntegerProperty;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class StemBlock extends BushBlock implements BonemealableBlock {
- 
-@@ -74,12 +75,12 @@
-         if (world.getRawBrightness(pos, 0) >= 9) {
-             float f = CropBlock.getGrowthSpeed(this, world, pos);
- 
--            if (random.nextInt((int) (25.0F / f) + 1) == 0) {
-+            if (random.nextFloat() < ((this == Blocks.PUMPKIN_STEM ? world.spigotConfig.pumpkinModifier : world.spigotConfig.melonModifier) / (100.0f * (Math.floor((25.0F / f) + 1))))) { // Spigot - SPIGOT-7159: Better modifier resolution
-                 int i = (Integer) state.getValue(StemBlock.AGE);
- 
-                 if (i < 7) {
-                     state = (BlockState) state.setValue(StemBlock.AGE, i + 1);
--                    world.setBlock(pos, state, 2);
-+                    CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit
-                 } else {
-                     Direction enumdirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
-                     BlockPos blockposition1 = pos.relative(enumdirection);
-@@ -91,7 +92,11 @@
-                         Optional<Block> optional1 = iregistry.getOptional(this.attachedStem);
- 
-                         if (optional.isPresent() && optional1.isPresent()) {
--                            world.setBlockAndUpdate(blockposition1, ((Block) optional.get()).defaultBlockState());
-+                            // CraftBukkit start
-+                            if (!CraftEventFactory.handleBlockGrowEvent(world, blockposition1, ((Block) optional.get()).defaultBlockState())) {
-+                                return;
-+                            }
-+                            // CraftBukkit end
-                             world.setBlockAndUpdate(pos, (BlockState) ((Block) optional1.get()).defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, enumdirection));
-                         }
-                     }
-@@ -121,7 +126,7 @@
-         int i = Math.min(7, (Integer) state.getValue(StemBlock.AGE) + Mth.nextInt(world.random, 2, 5));
-         BlockState iblockdata1 = (BlockState) state.setValue(StemBlock.AGE, i);
- 
--        world.setBlock(pos, iblockdata1, 2);
-+        CraftEventFactory.handleBlockGrowEvent(world, pos, iblockdata1, 2); // CraftBukkit
-         if (i == 7) {
-             iblockdata1.randomTick(world, pos, world.random);
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SugarCaneBlock.java.patch
deleted file mode 100644
index 903b77b2e2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SugarCaneBlock.java.patch
+++ /dev/null
@@ -1,21 +0,0 @@
---- a/net/minecraft/world/level/block/SugarCaneBlock.java
-+++ b/net/minecraft/world/level/block/SugarCaneBlock.java
-@@ -59,13 +59,14 @@
-                 ;
-             }
- 
--            if (i < 3) {
-+            if (i < world.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth heigh
-                 int j = (Integer) state.getValue(SugarCaneBlock.AGE);
- 
--                if (j == 15) {
--                    world.setBlockAndUpdate(pos.above(), this.defaultBlockState());
-+                int modifier = world.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution
-+                if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
-+                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos.above(), this.defaultBlockState()); // CraftBukkit
-                     world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, 0), 4);
--                } else {
-+                } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
-                     world.setBlock(pos, (BlockState) state.setValue(SugarCaneBlock.AGE, j + 1), 4);
-                 }
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
deleted file mode 100644
index c0fadbe893..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
+++ /dev/null
@@ -1,62 +0,0 @@
---- a/net/minecraft/world/level/block/SweetBerryBushBlock.java
-+++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java
-@@ -28,6 +28,12 @@
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import java.util.Collections;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.event.player.PlayerHarvestBlockEvent;
-+// CraftBukkit end
- 
- public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock {
- 
-@@ -67,10 +73,10 @@
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         int i = (Integer) state.getValue(SweetBerryBushBlock.AGE);
- 
--        if (i < 3 && random.nextInt(5) == 0 && world.getRawBrightness(pos.above(), 0) >= 9) {
-+        if (i < 3 && random.nextFloat() < (world.spigotConfig.sweetBerryModifier / (100.0f * 5)) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
-             BlockState iblockdata1 = (BlockState) state.setValue(SweetBerryBushBlock.AGE, i + 1);
- 
--            world.setBlock(pos, iblockdata1, 2);
-+            if (!CraftEventFactory.handleBlockGrowEvent(world, pos, iblockdata1, 2)) return; // CraftBukkit
-             world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
-         }
- 
-@@ -78,6 +84,7 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) {
-             entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D));
-             if (world instanceof ServerLevel) {
-@@ -91,7 +98,7 @@
-                         double d1 = Math.abs(vec3d.z());
- 
-                         if (d0 >= 0.003000000026077032D || d1 >= 0.003000000026077032D) {
--                            entity.hurtServer(worldserver, world.damageSources().sweetBerryBush(), 1.0F);
-+                            entity.hurtServer(worldserver, world.damageSources().sweetBerryBush().directBlock(world, pos), 1.0F); // CraftBukkit
-                         }
-                     }
- 
-@@ -118,7 +125,15 @@
-         if (i > 1) {
-             int j = 1 + world.random.nextInt(2);
- 
--            popResource(world, pos, new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0)));
-+            // CraftBukkit start - useWithoutItem is always MAIN_HAND
-+            PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(world, pos, player, InteractionHand.MAIN_HAND, Collections.singletonList(new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0))));
-+            if (event.isCancelled()) {
-+                return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
-+            }
-+            for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
-+                popResource(world, pos, CraftItemStack.asNMSCopy(itemStack));
-+            }
-+            // CraftBukkit end
-             world.playSound((Player) null, pos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + world.random.nextFloat() * 0.4F);
-             BlockState iblockdata1 = (BlockState) state.setValue(SweetBerryBushBlock.AGE, 1);
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TntBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/TntBlock.java.patch
deleted file mode 100644
index 42cf7a6881..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TntBlock.java.patch
+++ /dev/null
@@ -1,102 +0,0 @@
---- a/net/minecraft/world/level/block/TntBlock.java
-+++ b/net/minecraft/world/level/block/TntBlock.java
-@@ -28,6 +28,10 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.redstone.Orientation;
- import net.minecraft.world.phys.BlockHitResult;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.block.TNTPrimeEvent.PrimeCause;
-+// CraftBukkit end
- 
- public class TntBlock extends Block {
- 
-@@ -47,7 +51,13 @@
-     @Override
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
-         if (!oldState.is(state.getBlock())) {
--            if (world.hasNeighborSignal(pos)) {
-+            if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
-+                // Paper start - TNTPrimeEvent
-+                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) {
-+                    return;
-+                }
-+                // Paper end - TNTPrimeEvent
-                 TntBlock.explode(world, pos);
-                 world.removeBlock(pos, false);
-             }
-@@ -57,7 +67,13 @@
- 
-     @Override
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
--        if (world.hasNeighborSignal(pos)) {
-+        if (world.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
-+            // Paper start - TNTPrimeEvent
-+            org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+            if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.REDSTONE, null).callEvent()) {
-+                return;
-+            }
-+            // Paper end - TNTPrimeEvent
-             TntBlock.explode(world, pos);
-             world.removeBlock(pos, false);
-         }
-@@ -66,7 +82,7 @@
- 
-     @Override
-     public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
--        if (!world.isClientSide() && !player.isCreative() && (Boolean) state.getValue(TntBlock.UNSTABLE)) {
-+        if (!world.isClientSide() && !player.isCreative() && (Boolean) state.getValue(TntBlock.UNSTABLE) && CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.BLOCK_BREAK, player, null)) { // CraftBukkit - TNTPrimeEvent
-             TntBlock.explode(world, pos);
-         }
- 
-@@ -75,6 +91,13 @@
- 
-     @Override
-     public void wasExploded(ServerLevel world, BlockPos pos, Explosion explosion) {
-+        // Paper start - TNTPrimeEvent
-+        org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+        org.bukkit.entity.Entity source = explosion.getDirectSourceEntity() != null ? explosion.getDirectSourceEntity().getBukkitEntity() : null;
-+        if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, source).callEvent()) {
-+            return;
-+        }
-+        // Paper end - TNTPrimeEvent
-         PrimedTnt entitytntprimed = new PrimedTnt(world, (double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, explosion.getIndirectSourceEntity());
-         int i = entitytntprimed.getFuse();
- 
-@@ -101,6 +124,17 @@
-         if (!stack.is(Items.FLINT_AND_STEEL) && !stack.is(Items.FIRE_CHARGE)) {
-             return super.useItemOn(stack, state, world, pos, player, hand, hit);
-         } else {
-+            // CraftBukkit start - TNTPrimeEvent
-+            if (!CraftEventFactory.callTNTPrimeEvent(world, pos, PrimeCause.PLAYER, player, null)) {
-+                return InteractionResult.CONSUME;
-+            }
-+            // CraftBukkit end
-+            // Paper start - TNTPrimeEvent
-+            org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
-+            if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.ITEM, player.getBukkitEntity()).callEvent()) {
-+                return InteractionResult.FAIL;
-+            }
-+            // Paper end - TNTPrimeEvent
-             TntBlock.explode(world, pos, player);
-             world.setBlock(pos, Blocks.AIR.defaultBlockState(), 11);
-             Item item = stack.getItem();
-@@ -123,6 +157,17 @@
-             Entity entity = projectile.getOwner();
- 
-             if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition)) {
-+                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state
-+                    return;
-+                }
-+                // CraftBukkit end
-+                // Paper start - TNTPrimeEvent
-+                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition);
-+                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.PROJECTILE, projectile.getBukkitEntity()).callEvent()) {
-+                    return;
-+                }
-+                // Paper end - TNTPrimeEvent
-                 TntBlock.explode(world, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null);
-                 world.removeBlock(blockposition, false);
-             }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TrapDoorBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/TrapDoorBlock.java.patch
deleted file mode 100644
index 0f858bdcd3..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TrapDoorBlock.java.patch
+++ /dev/null
@@ -1,51 +0,0 @@
---- a/net/minecraft/world/level/block/TrapDoorBlock.java
-+++ b/net/minecraft/world/level/block/TrapDoorBlock.java
-@@ -37,6 +37,7 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
- 
- public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock {
- 
-@@ -143,7 +144,39 @@
-             boolean flag1 = world.hasNeighborSignal(pos);
- 
-             if (flag1 != (Boolean) state.getValue(TrapDoorBlock.POWERED)) {
--                if ((Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1) {
-+                // CraftBukkit start
-+                org.bukkit.World bworld = world.getWorld();
-+                org.bukkit.block.Block bblock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+
-+                int power = bblock.getBlockPower();
-+                int oldPower = (Boolean) state.getValue(TrapDoorBlock.OPEN) ? 15 : 0;
-+
-+                if (oldPower == 0 ^ power == 0 || sourceBlock.defaultBlockState().isSignalSource()) {
-+                    BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bblock, oldPower, power);
-+                    world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+                    flag1 = eventRedstone.getNewCurrent() > 0;
-+                }
-+                // CraftBukkit end
-+                // Paper start - break redstone on trapdoors early
-+                boolean open = (Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1;
-+                // note: this must run before any state for this block/its neighborus are written to the world
-+                // we allow the redstone event to fire so that plugins can block
-+                if (flag1 && open) { // if we are now powered and it caused the trap door to open
-+                    // in this case, first check for the redstone on top first
-+                    BlockPos abovePos = pos.above();
-+                    BlockState above = world.getBlockState(abovePos);
-+                    if (above.getBlock() instanceof RedStoneWireBlock) {
-+                        world.setBlock(abovePos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_NEIGHBORS);
-+                        Block.popResource(world, abovePos, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.REDSTONE));
-+                        // now check that this didn't change our state
-+                        if (world.getBlockState(pos) != state) {
-+                            // our state was changed, so we cannot propagate this update
-+                            return;
-+                        }
-+                    }
-+                }
-+                if (open) {
-+                // Paper end - break redstone on trapdoors early
-                     state = (BlockState) state.setValue(TrapDoorBlock.OPEN, flag1);
-                     this.playSound((Player) null, world, pos, flag1);
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireBlock.java.patch
deleted file mode 100644
index b081063a3f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireBlock.java.patch
+++ /dev/null
@@ -1,114 +0,0 @@
---- a/net/minecraft/world/level/block/TripWireBlock.java
-+++ b/net/minecraft/world/level/block/TripWireBlock.java
-@@ -28,6 +28,7 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit
- 
- public class TripWireBlock extends Block {
- 
-@@ -67,6 +68,7 @@
- 
-     @Override
-     public BlockState getStateForPlacement(BlockPlaceContext ctx) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return this.defaultBlockState(); // Paper - place tripwire without updating
-         Level world = ctx.getLevel();
-         BlockPos blockposition = ctx.getClickedPos();
- 
-@@ -75,11 +77,13 @@
- 
-     @Override
-     protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent tripwire from updating
-         return direction.getAxis().isHorizontal() ? (BlockState) state.setValue((Property) TripWireBlock.PROPERTY_BY_DIRECTION.get(direction), this.shouldConnectTo(neighborState, direction)) : super.updateShape(state, world, tickView, pos, direction, neighborPos, neighborState, random);
-     }
- 
-     @Override
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
-         if (!oldState.is(state.getBlock())) {
-             this.updateSource(world, pos, state);
-         }
-@@ -87,6 +91,7 @@
- 
-     @Override
-     protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
-         if (!moved && !state.is(newState.getBlock())) {
-             this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true));
-         }
-@@ -94,6 +99,7 @@
- 
-     @Override
-     public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return state; // Paper - prevent disarming tripwires
-         if (!world.isClientSide && !player.getMainHandItem().isEmpty() && player.getMainHandItem().is(Items.SHEARS)) {
-             world.setBlock(pos, (BlockState) state.setValue(TripWireBlock.DISARMED, true), 4);
-             world.gameEvent((Entity) player, (Holder) GameEvent.SHEAR, pos);
-@@ -103,6 +109,7 @@
-     }
- 
-     private void updateSource(Level world, BlockPos pos, BlockState state) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent adjacent tripwires from updating
-         Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST};
-         int i = aenumdirection.length;
-         int j = 0;
-@@ -140,6 +147,8 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwires from detecting collision
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (!world.isClientSide) {
-             if (!(Boolean) state.getValue(TripWireBlock.POWERED)) {
-                 this.checkPressed(world, pos, List.of(entity));
-@@ -149,6 +158,7 @@
- 
-     @Override
-     protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().blockUpdates.disableTripwireUpdates) return; // Paper - prevent tripwire pressed check
-         if ((Boolean) world.getBlockState(pos).getValue(TripWireBlock.POWERED)) {
-             this.checkPressed(world, pos);
-         }
-@@ -179,6 +189,40 @@
-             }
-         }
- 
-+        // CraftBukkit start - Call interact even when triggering connected tripwire
-+        if (flag != flag1 && flag1 && (Boolean)iblockdata.getValue(TripWireBlock.ATTACHED)) {
-+            org.bukkit.World bworld = world.getWorld();
-+            org.bukkit.plugin.PluginManager manager = world.getCraftServer().getPluginManager();
-+            org.bukkit.block.Block block = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+            boolean allowed = false;
-+
-+            // If all of the events are cancelled block the tripwire trigger, else allow
-+            for (Object object : entities) {
-+                if (object != null) {
-+                    org.bukkit.event.Cancellable cancellable;
-+
-+                    if (object instanceof Player) {
-+                        cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) object, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                    } else if (object instanceof Entity) {
-+                        cancellable = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block);
-+                        manager.callEvent((EntityInteractEvent) cancellable);
-+                    } else {
-+                        continue;
-+                    }
-+
-+                    if (!cancellable.isCancelled()) {
-+                        allowed = true;
-+                        break;
-+                    }
-+                }
-+            }
-+
-+            if (!allowed) {
-+                return;
-+            }
-+        }
-+        // CraftBukkit end
-+
-         if (flag1 != flag) {
-             iblockdata = (BlockState) iblockdata.setValue(TripWireBlock.POWERED, flag1);
-             world.setBlock(pos, iblockdata, 3);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireHookBlock.java.patch
deleted file mode 100644
index 85f745ec8f..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TripWireHookBlock.java.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/net/minecraft/world/level/block/TripWireHookBlock.java
-+++ b/net/minecraft/world/level/block/TripWireHookBlock.java
-@@ -31,6 +31,10 @@
- import net.minecraft.world.level.redstone.Orientation;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockRedstoneEvent;
-+// CraftBukkit end
- 
- public class TripWireHookBlock extends Block {
- 
-@@ -174,10 +178,20 @@
-                 world.setBlock(blockposition1, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection1), 3);
-                 TripWireHookBlock.notifyNeighbors(block, world, blockposition1, enumdirection1);
-                 TripWireHookBlock.emitState(world, blockposition1, flag4, flag5, flag2, flag3);
-+            }
-+
-+            // CraftBukkit start
-+            BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, pos), 15, 0);
-+            world.getCraftServer().getPluginManager().callEvent(eventRedstone);
-+
-+            if (eventRedstone.getNewCurrent() > 0) {
-+                return;
-             }
-+            // CraftBukkit end
- 
-             TripWireHookBlock.emitState(world, pos, flag4, flag5, flag2, flag3);
-             if (!flag) {
-+                if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update
-                 world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3);
-                 if (flag1) {
-                     TripWireHookBlock.notifyNeighbors(block, world, pos, enumdirection);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/TurtleEggBlock.java.patch
deleted file mode 100644
index c27f68ac72..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/TurtleEggBlock.java.patch
+++ /dev/null
@@ -1,76 +0,0 @@
---- a/net/minecraft/world/level/block/TurtleEggBlock.java
-+++ b/net/minecraft/world/level/block/TurtleEggBlock.java
-@@ -31,6 +31,11 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.event.entity.EntityInteractEvent;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class TurtleEggBlock extends Block {
- 
-@@ -74,6 +79,19 @@
-     private void destroyEgg(Level world, BlockState state, BlockPos pos, Entity entity, int inverseChance) {
-         if (state.is(Blocks.TURTLE_EGG) && world instanceof ServerLevel worldserver) {
-             if (this.canDestroyEgg(worldserver, entity) && world.random.nextInt(inverseChance) == 0) {
-+                // CraftBukkit start - Step on eggs
-+                org.bukkit.event.Cancellable cancellable;
-+                if (entity instanceof Player) {
-+                    cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+                } else {
-+                    cancellable = new EntityInteractEvent(entity.getBukkitEntity(), CraftBlock.at(worldserver, pos));
-+                    worldserver.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
-+                }
-+
-+                if (cancellable.isCancelled()) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 this.decreaseEggs(worldserver, pos, state);
-             }
-         }
-@@ -100,10 +118,20 @@
-             int i = (Integer) state.getValue(TurtleEggBlock.HATCH);
- 
-             if (i < 2) {
-+                // CraftBukkit start - Call BlockGrowEvent
-+                if (!CraftEventFactory.handleBlockGrowEvent(world, pos, state.setValue(TurtleEggBlock.HATCH, i + 1), 2)) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
--                world.setBlock(pos, (BlockState) state.setValue(TurtleEggBlock.HATCH, i + 1), 2);
-+                // worldserver.setBlock(blockposition, (IBlockData) iblockdata.setValue(BlockTurtleEgg.HATCH, i + 1), 2); // CraftBukkit - handled above
-                 world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
-             } else {
-+                // CraftBukkit start - Call BlockFadeEvent
-+                if (CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
-                 world.removeBlock(pos, false);
-                 world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(state));
-@@ -116,7 +144,7 @@
-                         entityturtle.setAge(-24000);
-                         entityturtle.setHomePos(pos);
-                         entityturtle.moveTo((double) pos.getX() + 0.3D + (double) j * 0.2D, (double) pos.getY(), (double) pos.getZ() + 0.3D, 0.0F, 0.0F);
--                        world.addFreshEntity(entityturtle);
-+                        world.addFreshEntity(entityturtle, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit
-                     }
-                 }
-             }
-@@ -147,8 +175,8 @@
-     }
- 
-     @Override
--    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
--        super.playerDestroy(world, player, pos, state, blockEntity, tool);
-+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) { // Paper - fix drops not preventing stats/food exhaustion
-+        super.playerDestroy(world, player, pos, state, blockEntity, tool, includeDrops, dropExp); // Paper - fix drops not preventing stats/food exhaustion
-         this.decreaseEggs(world, pos, state);
-     }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/VineBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/VineBlock.java.patch
deleted file mode 100644
index 0403549dc8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/VineBlock.java.patch
+++ /dev/null
@@ -1,79 +0,0 @@
---- a/net/minecraft/world/level/block/VineBlock.java
-+++ b/net/minecraft/world/level/block/VineBlock.java
-@@ -24,6 +24,7 @@
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
- 
- public class VineBlock extends Block {
- 
-@@ -184,7 +185,7 @@
-     @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (world.getGameRules().getBoolean(GameRules.RULE_DO_VINES_SPREAD)) {
--            if (random.nextInt(4) == 0) {
-+            if (random.nextFloat() < (world.spigotConfig.vineModifier / (100.0f * 4))) { // Spigot - SPIGOT-7159: Better modifier resolution
-                 Direction enumdirection = Direction.getRandom(random);
-                 BlockPos blockposition1 = pos.above();
-                 BlockPos blockposition2;
-@@ -203,30 +204,34 @@
-                             BlockPos blockposition3 = blockposition2.relative(enumdirection1);
-                             BlockPos blockposition4 = blockposition2.relative(enumdirection2);
- 
-+                            // CraftBukkit start - Call BlockSpreadEvent
-+                            BlockPos source = pos;
-+
-                             if (flag && VineBlock.isAcceptableNeighbour(world, blockposition3, enumdirection1)) {
--                                world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection1), true), 2);
-+                                CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection1), true), 2);
-                             } else if (flag1 && VineBlock.isAcceptableNeighbour(world, blockposition4, enumdirection2)) {
--                                world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection2), true), 2);
-+                                CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection2), true), 2);
-                             } else {
-                                 Direction enumdirection3 = enumdirection.getOpposite();
- 
-                                 if (flag && world.isEmptyBlock(blockposition3) && VineBlock.isAcceptableNeighbour(world, pos.relative(enumdirection1), enumdirection3)) {
--                                    world.setBlock(blockposition3, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2);
-+                                    CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition3, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2);
-                                 } else if (flag1 && world.isEmptyBlock(blockposition4) && VineBlock.isAcceptableNeighbour(world, pos.relative(enumdirection2), enumdirection3)) {
--                                    world.setBlock(blockposition4, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2);
-+                                    CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition4, (BlockState) this.defaultBlockState().setValue(VineBlock.getPropertyForFace(enumdirection3), true), 2);
-                                 } else if ((double) random.nextFloat() < 0.05D && VineBlock.isAcceptableNeighbour(world, blockposition2.above(), Direction.UP)) {
--                                    world.setBlock(blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.UP, true), 2);
-+                                    CraftEventFactory.handleBlockSpreadEvent(world, source, blockposition2, (BlockState) this.defaultBlockState().setValue(VineBlock.UP, true), 2);
-                                 }
-+                                // CraftBukkit end
-                             }
-                         } else if (VineBlock.isAcceptableNeighbour(world, blockposition2, enumdirection)) {
--                            world.setBlock(pos, (BlockState) state.setValue(VineBlock.getPropertyForFace(enumdirection), true), 2);
-+                            CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(VineBlock.getPropertyForFace(enumdirection), true), 2); // CraftBukkit
-                         }
- 
-                     }
-                 } else {
-                     if (enumdirection == Direction.UP && pos.getY() < world.getMaxY()) {
-                         if (this.canSupportAtFace(world, pos, enumdirection)) {
--                            world.setBlock(pos, (BlockState) state.setValue(VineBlock.UP, true), 2);
-+                            CraftEventFactory.handleBlockGrowEvent(world, pos, (BlockState) state.setValue(VineBlock.UP, true), 2); // CraftBukkit
-                             return;
-                         }
- 
-@@ -246,7 +251,7 @@
-                             }
- 
-                             if (this.hasHorizontalConnection(iblockdata2)) {
--                                world.setBlock(blockposition1, iblockdata2, 2);
-+                                CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, iblockdata2, 2); // CraftBukkit
-                             }
- 
-                             return;
-@@ -261,7 +266,7 @@
-                             BlockState iblockdata4 = this.copyRandomFaces(state, iblockdata3, random);
- 
-                             if (iblockdata3 != iblockdata4 && this.hasHorizontalConnection(iblockdata4)) {
--                                world.setBlock(blockposition2, iblockdata4, 2);
-+                                CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, iblockdata4, 2); // CraftBukkit
-                             }
-                         }
-                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WaterlilyBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/WaterlilyBlock.java.patch
deleted file mode 100644
index 279e050da1..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WaterlilyBlock.java.patch
+++ /dev/null
@@ -1,27 +0,0 @@
---- a/net/minecraft/world/level/block/WaterlilyBlock.java
-+++ b/net/minecraft/world/level/block/WaterlilyBlock.java
-@@ -13,6 +13,9 @@
- import net.minecraft.world.level.material.Fluids;
- import net.minecraft.world.phys.shapes.CollisionContext;
- import net.minecraft.world.phys.shapes.VoxelShape;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class WaterlilyBlock extends BushBlock {
- 
-@@ -30,8 +33,14 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         super.entityInside(state, world, pos, entity);
-         if (world instanceof ServerLevel && entity instanceof AbstractBoat) {
-+            // CraftBukkit start
-+            if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
-+                return;
-+            }
-+            // CraftBukkit end
-             world.destroyBlock(new BlockPos(pos), true, entity);
-         }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
deleted file mode 100644
index 67ce37fdf0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
+++ /dev/null
@@ -1,49 +0,0 @@
---- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
-+++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
-@@ -6,6 +6,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.state.BlockBehaviour;
- import net.minecraft.world.level.block.state.BlockState;
-@@ -13,6 +14,8 @@
- import net.minecraft.world.level.block.state.properties.BlockSetType;
- import net.minecraft.world.level.block.state.properties.BlockStateProperties;
- import net.minecraft.world.level.block.state.properties.IntegerProperty;
-+import org.bukkit.event.entity.EntityInteractEvent;
-+// CraftBukkit end
- 
- public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
- 
-@@ -39,8 +42,28 @@
- 
-     @Override
-     protected int getSignalStrength(Level world, BlockPos pos) {
--        int i = Math.min(getEntityCount(world, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class), this.maxWeight);
-+        // CraftBukkit start
-+        // int i = Math.min(getEntityCount(world, BlockPressurePlateWeighted.TOUCH_AABB.move(blockposition), Entity.class), this.maxWeight);
-+        int i = 0;
-+        for (Entity entity : getEntities(world, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class)) {
-+            org.bukkit.event.Cancellable cancellable;
- 
-+            if (entity instanceof Player) {
-+                cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
-+            } else {
-+                cancellable = new EntityInteractEvent(entity.getBukkitEntity(), world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
-+                world.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
-+            }
-+
-+            // We only want to block turning the plate on if all events are cancelled
-+            if (!cancellable.isCancelled()) {
-+                i++;
-+            }
-+        }
-+
-+        i = Math.min(i, this.maxWeight);
-+        // CraftBukkit end
-+
-         if (i > 0) {
-             float f = (float) Math.min(this.maxWeight, i) / (float) this.maxWeight;
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherRoseBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherRoseBlock.java.patch
deleted file mode 100644
index c456abf513..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherRoseBlock.java.patch
+++ /dev/null
@@ -1,15 +0,0 @@
---- a/net/minecraft/world/level/block/WitherRoseBlock.java
-+++ b/net/minecraft/world/level/block/WitherRoseBlock.java
-@@ -63,10 +63,11 @@
- 
-     @Override
-     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
-         if (world instanceof ServerLevel worldserver) {
-             if (world.getDifficulty() != Difficulty.PEACEFUL && entity instanceof LivingEntity entityliving) {
-                 if (!entityliving.isInvulnerableTo(worldserver, world.damageSources().wither())) {
--                    entityliving.addEffect(this.getBeeInteractionEffect());
-+                    entityliving.addEffect(this.getBeeInteractionEffect(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WITHER_ROSE); // CraftBukkit
-                 }
-             }
-         }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherSkullBlock.java.patch
deleted file mode 100644
index e34748cc0e..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/WitherSkullBlock.java.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- a/net/minecraft/world/level/block/WitherSkullBlock.java
-+++ b/net/minecraft/world/level/block/WitherSkullBlock.java
-@@ -26,6 +26,10 @@
- import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder;
- import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
- 
-+// CraftBukkit start
-+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
-+// CraftBukkit end
-+
- public class WitherSkullBlock extends SkullBlock {
- 
-     public static final MapCodec<WitherSkullBlock> CODEC = simpleCodec(WitherSkullBlock::new);
-@@ -58,6 +62,7 @@
-     }
- 
-     public static void checkSpawn(Level world, BlockPos pos, SkullBlockEntity blockEntity) {
-+        if (world.captureBlockStates) return; // CraftBukkit
-         if (!world.isClientSide) {
-             BlockState iblockdata = blockEntity.getBlockState();
-             boolean flag = iblockdata.is(Blocks.WITHER_SKELETON_SKULL) || iblockdata.is(Blocks.WITHER_SKELETON_WALL_SKULL);
-@@ -69,12 +74,18 @@
-                     WitherBoss entitywither = (WitherBoss) EntityType.WITHER.create(world, EntitySpawnReason.TRIGGERED);
- 
-                     if (entitywither != null) {
--                        CarvedPumpkinBlock.clearPatternBlocks(world, shapedetector_shapedetectorcollection);
-+                        // BlockPumpkinCarved.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - move down
-                         BlockPos blockposition1 = shapedetector_shapedetectorcollection.getBlock(1, 2, 0).getPos();
- 
-                         entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F);
-                         entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
-                         entitywither.makeInvulnerable();
-+                        // CraftBukkit start
-+                        if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) {
-+                            return;
-+                        }
-+                        CarvedPumpkinBlock.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - from above
-+                        // CraftBukkit end
-                         Iterator iterator = world.getEntitiesOfClass(ServerPlayer.class, entitywither.getBoundingBox().inflate(50.0D)).iterator();
- 
-                         while (iterator.hasNext()) {
-@@ -83,7 +94,7 @@
-                             CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayer, (Entity) entitywither);
-                         }
- 
--                        world.addFreshEntity(entitywither);
-+                        // world.addFreshEntity(entitywither); // CraftBukkit - moved up
-                         CarvedPumpkinBlock.updatePatternBlocks(world, shapedetector_shapedetectorcollection);
-                     }
- 

From 92aec10cc4ceb1b55c0f764c49863dc8514b46b7 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 18:40:23 -0800
Subject: [PATCH 138/285] net.minecraft.server.rcon.thread

---
 .../rcon/thread/QueryThreadGs4.java.patch     | 20 ++---
 .../server/rcon/thread/RconClient.java.patch  | 36 +++++++++
 .../server/rcon/thread/RconThread.java.patch  | 16 ++--
 .../server/rcon/thread/RconClient.java.patch  | 80 -------------------
 4 files changed, 54 insertions(+), 98 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch (94%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/rcon/thread/RconThread.java.patch (58%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconClient.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch
similarity index 94%
rename from paper-server/patches/unapplied/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch
rename to paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch
index 6a8d04320c..d1a66ee01a 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/QueryThreadGs4.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/server/rcon/thread/QueryThreadGs4.java
 +++ b/net/minecraft/server/rcon/thread/QueryThreadGs4.java
-@@ -106,13 +106,32 @@
+@@ -106,13 +_,32 @@
                          NetworkDataOutputStream networkDataOutputStream = new NetworkDataOutputStream(1460);
                          networkDataOutputStream.write(0);
-                         networkDataOutputStream.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
+                         networkDataOutputStream.writeBytes(this.getIdentBytes(requestPacket.getSocketAddress()));
 -                        networkDataOutputStream.writeString(this.serverName);
-+
++                        // Paper start
 +                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
 +                            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC;
 +                        com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
@@ -19,7 +19,7 @@
 +                            .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
 +                            .build();
 +                        com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
-+                            new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
++                            new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, requestPacket.getAddress(), queryResponse);
 +                        queryEvent.callEvent();
 +                        queryResponse = queryEvent.getResponse();
 +
@@ -36,10 +36,10 @@
 +                        networkDataOutputStream.writeShort((short) queryResponse.getPort());
 +                        networkDataOutputStream.writeString(queryResponse.getHostname());
 +                        // Paper end
-                         this.sendTo(networkDataOutputStream.toByteArray(), packet);
+                         this.sendTo(networkDataOutputStream.toByteArray(), requestPacket);
                          LOGGER.debug("Status [{}]", socketAddress);
                      }
-@@ -147,31 +166,75 @@
+@@ -147,31 +_,75 @@
              this.rulesResponse.writeString("splitnum");
              this.rulesResponse.write(128);
              this.rulesResponse.write(0);
@@ -68,7 +68,7 @@
 +            com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
 +                com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL;
 +            com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
-+                new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
++                new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, requestPacket.getAddress(), queryResponse);
 +            queryEvent.callEvent();
 +            queryResponse = queryEvent.getResponse();
              this.rulesResponse.writeString("hostname");
@@ -119,8 +119,8 @@
              this.rulesResponse.write(1);
              this.rulesResponse.writeString("player_");
              this.rulesResponse.write(0);
--            String[] strings = this.serverInterface.getPlayerNames();
-+            String[] strings = queryResponse.getPlayers().toArray(String[]::new);
+-            String[] playerNames = this.serverInterface.getPlayerNames();
++            String[] playerNames = queryResponse.getPlayers().toArray(String[]::new);
  
-             for (String string : strings) {
+             for (String string : playerNames) {
                  this.rulesResponse.writeString(string);
diff --git a/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch
new file mode 100644
index 0000000000..e84fb83f9d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconClient.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/server/rcon/thread/RconClient.java
++++ b/net/minecraft/server/rcon/thread/RconClient.java
+@@ -23,11 +_,14 @@
+     private final Socket client;
+     private final byte[] buf = new byte[1460];
+     private final String rconPassword;
+-    private final ServerInterface serverInterface;
++    // CraftBukkit start
++    private final net.minecraft.server.dedicated.DedicatedServer serverInterface;
++    private final net.minecraft.server.rcon.RconConsoleSource rconConsoleSource;
++    // CraftBukkit end
+ 
+     RconClient(ServerInterface serverInterface, String rconPassword, Socket client) {
+         super("RCON Client " + client.getInetAddress());
+-        this.serverInterface = serverInterface;
++        this.serverInterface = (net.minecraft.server.dedicated.DedicatedServer) serverInterface; // CraftBukkit
+         this.client = client;
+ 
+         try {
+@@ -37,6 +_,7 @@
+         }
+ 
+         this.rconPassword = rconPassword;
++        this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, client.getRemoteSocketAddress()); // CraftBukkit
+     }
+ 
+     @Override
+@@ -67,7 +_,7 @@
+                                 String string1 = PktUtils.stringFromByteArray(this.buf, i1, i);
+ 
+                                 try {
+-                                    this.sendCmdResponse(i3, this.serverInterface.runCommand(string1));
++                                    this.sendCmdResponse(i3, this.serverInterface.runCommand(this.rconConsoleSource, string1)); // CraftBukkit
+                                 } catch (Exception var15) {
+                                     this.sendCmdResponse(i3, "Error executing: " + string1 + " (" + var15.getMessage() + ")");
+                                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconThread.java.patch b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconThread.java.patch
rename to paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch
index 5bc2ff1f90..62a462ca73 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconThread.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/rcon/thread/RconThread.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/server/rcon/thread/RconThread.java
 +++ b/net/minecraft/server/rcon/thread/RconThread.java
-@@ -57,7 +57,7 @@
+@@ -57,7 +_,7 @@
      @Nullable
-     public static RconThread create(ServerInterface server) {
-         DedicatedServerProperties dedicatedServerProperties = server.getProperties();
--        String string = server.getServerIp();
-+        String string = dedicatedServerProperties.rconIp; // Paper - Configurable rcon ip
-         if (string.isEmpty()) {
-             string = "0.0.0.0";
+     public static RconThread create(ServerInterface serverInterface) {
+         DedicatedServerProperties properties = serverInterface.getProperties();
+-        String serverIp = serverInterface.getServerIp();
++        String serverIp = properties.rconIp; // Paper - Configurable rcon ip
+         if (serverIp.isEmpty()) {
+             serverIp = "0.0.0.0";
          }
-@@ -104,6 +104,14 @@
+@@ -104,6 +_,14 @@
  
          this.clients.clear();
      }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconClient.java.patch b/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconClient.java.patch
deleted file mode 100644
index 1aa09ff9d7..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/rcon/thread/RconClient.java.patch
+++ /dev/null
@@ -1,80 +0,0 @@
---- a/net/minecraft/server/rcon/thread/RconClient.java
-+++ b/net/minecraft/server/rcon/thread/RconClient.java
-@@ -8,9 +8,12 @@
- import java.net.Socket;
- import java.nio.charset.StandardCharsets;
- import java.util.Locale;
-+import org.slf4j.Logger;
- import net.minecraft.server.ServerInterface;
-+// CraftBukkit start
-+import net.minecraft.server.dedicated.DedicatedServer;
- import net.minecraft.server.rcon.PktUtils;
--import org.slf4j.Logger;
-+import net.minecraft.server.rcon.RconConsoleSource;
- 
- public class RconClient extends GenericThread {
- 
-@@ -24,11 +27,14 @@
-     private final Socket client;
-     private final byte[] buf = new byte[1460];
-     private final String rconPassword;
--    private final ServerInterface serverInterface;
-+    // CraftBukkit start
-+    private final DedicatedServer serverInterface;
-+    private final RconConsoleSource rconConsoleSource;
-+    // CraftBukkit end
- 
-     RconClient(ServerInterface server, String password, Socket socket) {
-         super("RCON Client " + String.valueOf(socket.getInetAddress()));
--        this.serverInterface = server;
-+        this.serverInterface = (DedicatedServer) server; // CraftBukkit
-         this.client = socket;
- 
-         try {
-@@ -38,11 +44,14 @@
-         }
- 
-         this.rconPassword = password;
-+        this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, socket.getRemoteSocketAddress()); // CraftBukkit
-     }
- 
-     public void run() {
--        while (true) {
--            try {
-+        // CraftBukkit start - decompile error: switch try / while statement
-+        try {
-+            while (true) {
-+                // CraftBukkit end
-                 if (!this.running) {
-                     return;
-                 }
-@@ -71,7 +80,7 @@
-                                 String s = PktUtils.stringFromByteArray(this.buf, j, i);
- 
-                                 try {
--                                    this.sendCmdResponse(l, this.serverInterface.runCommand(s));
-+                                    this.sendCmdResponse(l, this.serverInterface.runCommand(this.rconConsoleSource, s)); // CraftBukkit
-                                 } catch (Exception exception) {
-                                     this.sendCmdResponse(l, "Error executing: " + s + " (" + exception.getMessage() + ")");
-                                 }
-@@ -98,6 +107,7 @@
-                             continue;
-                     }
-                 }
-+        } // CraftBukkit - decompile error: switch try / while statement
-             } catch (IOException ioexception) {
-                 return;
-             } catch (Exception exception1) {
-@@ -109,8 +119,10 @@
-                 this.running = false;
-             }
- 
--            return;
--        }
-+            // CraftBukkit start - decompile error: switch try / while statement
-+            // return;
-+        // }
-+        // CraftBukkit end
-     }
- 
-     private void send(int sessionToken, int responseType, String message) throws IOException {

From 773f7aada7ff50b5d780d179d04163228183f42d Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sat, 14 Dec 2024 21:43:20 -0500
Subject: [PATCH 139/285] net/minecraft/world/level/dimension/end

---
 .../dimension/end/EndDragonFight.java.patch   | 151 ++++++++----------
 1 file changed, 67 insertions(+), 84 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch (56%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
similarity index 56%
rename from paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
index 5514be96df..b93d5faef8 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -1,63 +1,52 @@
 --- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
 +++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
-@@ -74,6 +74,7 @@
+@@ -70,8 +_,9 @@
      private static final int GATEWAY_DISTANCE = 96;
      public static final int DRAGON_SPAWN_Y = 128;
      private final Predicate<Entity> validPlayer;
 +    private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name
-     public final ServerBossEvent dragonEvent;
-     public final ServerLevel level;
-     private final BlockPos origin;
-@@ -102,7 +103,7 @@
-     }
- 
-     public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) {
--        this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true);
-+        this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name
-         this.gateways = new ObjectArrayList();
-         this.ticksSinceLastPlayerScan = 21;
-         this.skipArenaLoadedCheck = false;
-@@ -111,14 +112,20 @@
-         this.origin = origin;
-         this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE.and(EntitySelector.withinDistance((double) origin.getX(), (double) (128 + origin.getY()), (double) origin.getZ(), 192.0D));
-         this.needsStateScanning = data.needsStateScanning;
--        this.dragonUUID = (UUID) data.dragonUUID.orElse((Object) null);
-+        this.dragonUUID = (UUID) data.dragonUUID.orElse(null); // CraftBukkit - decompile error
-         this.dragonKilled = data.dragonKilled;
-         this.previouslyKilled = data.previouslyKilled;
+     public final ServerBossEvent dragonEvent = (ServerBossEvent)new ServerBossEvent(
+-            Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS
++            DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS // Paper
+         )
+         .setPlayBossMusic(true)
+         .setCreateWorldFog(true);
+@@ -112,7 +_,12 @@
          if (data.isRespawning) {
              this.respawnStage = DragonRespawnAnimation.START;
          }
+-
 +        // Paper start - Add config to disable ender dragon legacy check
-+        if (data == EndDragonFight.Data.DEFAULT && !world.paperConfig().entities.spawning.scanForLegacyEnderDragon) {
++        if (data == EndDragonFight.Data.DEFAULT && !level.paperConfig().entities.spawning.scanForLegacyEnderDragon) {
 +            this.needsStateScanning = false;
 +            this.dragonKilled = true;
 +        }
 +        // Paper end - Add config to disable ender dragon legacy check
- 
--        this.portalLocation = (BlockPos) data.exitPortalLocation.orElse((Object) null);
-+        this.portalLocation = (BlockPos) data.exitPortalLocation.orElse(null); // CraftBukkit - decompile error
-         this.gateways.addAll((Collection) data.gateways.orElseGet(() -> {
-             ObjectArrayList<Integer> objectarraylist = new ObjectArrayList(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers()));
- 
-@@ -206,9 +213,9 @@
-             this.dragonUUID = entityenderdragon.getUUID();
-             EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon);
+         this.portalLocation = data.exitPortalLocation.orElse(null);
+         this.gateways.addAll(data.gateways.orElseGet(() -> {
+             ObjectArrayList<Integer> list = new ObjectArrayList<>(ContiguousSet.create(Range.closedOpen(0, 20), DiscreteDomain.integers()));
+@@ -209,9 +_,9 @@
+             this.dragonUUID = enderDragon.getUUID();
+             LOGGER.info("Found that there's a dragon still alive ({})", enderDragon);
              this.dragonKilled = false;
--            if (!flag) {
-+            if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon
-                 EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it.");
--                entityenderdragon.discard();
-+                entityenderdragon.discard(null); // CraftBukkit - add Bukkit remove cause
+-            if (!hasActiveExitPortal) {
++            if (!hasActiveExitPortal && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon
+                 LOGGER.info("But we didn't have a portal, let's remove it.");
+-                enderDragon.discard();
++                enderDragon.discard(null); // CraftBukkit - add Bukkit remove cause
                  this.dragonUUID = null;
              }
          }
-@@ -404,9 +411,23 @@
+@@ -366,12 +_,22 @@
              this.dragonEvent.setVisible(false);
              this.spawnExitPortal(true);
              this.spawnNewGateway();
 -            if (!this.previouslyKilled) {
--                this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
+-                this.level
+-                    .setBlockAndUpdate(
+-                        this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)),
+-                        Blocks.DRAGON_EGG.defaultBlockState()
+-                    );
 +            // Paper start - Add DragonEggFormEvent
 +            BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin));
 +            org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition);
@@ -70,20 +59,17 @@
 +                // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState());
 +            } else {
 +                eggEvent.setCancelled(true);
-             }
++            }
 +            if (eggEvent.callEvent()) {
 +                eggEvent.getNewState().update(true);
 +                // Paper end - Add DragonEggFormEvent
-+            }
+             }
  
              this.previouslyKilled = true;
-             this.dragonKilled = true;
-@@ -419,7 +440,25 @@
-     @VisibleForTesting
-     public void removeAllGateways() {
+@@ -385,6 +_,24 @@
          this.gateways.clear();
-+    }
-+
+     }
+ 
 +    // Paper start - More DragonBattle API
 +    public boolean spawnNewGatewayIfPossible() {
 +        if (!this.gateways.isEmpty()) {
@@ -99,12 +85,13 @@
 +            endCrystals.addAll(this.level.getEntitiesOfClass(EndCrystal.class, spike.getTopBoundingBox()));
 +        }
 +        return endCrystals;
-     }
++    }
 +    // Paper end - More DragonBattle API
- 
++
      private void spawnNewGateway() {
          if (!this.gateways.isEmpty()) {
-@@ -449,6 +488,11 @@
+             int i = this.gateways.remove(this.gateways.size() - 1);
+@@ -413,6 +_,11 @@
              }
          }
  
@@ -113,18 +100,18 @@
 +            this.portalLocation = this.portalLocation.atY(this.level.getMinY() + 1);
 +        }
 +        // Paper end - Prevent "softlocked" exit portal generation
-         if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) {
-             int i = Mth.positiveCeilDiv(4, 16);
- 
-@@ -469,6 +513,7 @@
-             entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F);
-             this.level.addFreshEntity(entityenderdragon);
-             this.dragonUUID = entityenderdragon.getUUID();
+         if (endPodiumFeature.place(
+             FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation
+         )) {
+@@ -432,6 +_,7 @@
+             enderDragon.moveTo(this.origin.getX(), 128 + this.origin.getY(), this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F);
+             this.level.addFreshEntity(enderDragon);
+             this.dragonUUID = enderDragon.getUUID();
 +            this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn
          }
  
-         return entityenderdragon;
-@@ -480,6 +525,10 @@
+         return enderDragon;
+@@ -443,6 +_,10 @@
              this.ticksSinceDragonSeen = 0;
              if (dragon.hasCustomName()) {
                  this.dragonEvent.setName(dragon.getDisplayName());
@@ -134,8 +121,8 @@
 +                // Paper end - ensure reset EnderDragon boss event name
              }
          }
- 
-@@ -513,7 +562,13 @@
+     }
+@@ -470,7 +_,13 @@
          return this.previouslyKilled;
      }
  
@@ -148,64 +135,60 @@
 +    public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal
 +        // Paper end - Perf: Do crystal-portal proximity check before entity lookup
          if (this.dragonKilled && this.respawnStage == null) {
-             BlockPos blockposition = this.portalLocation;
+             BlockPos blockPos = this.portalLocation;
+             if (blockPos == null) {
+@@ -485,6 +_,22 @@
  
-@@ -531,6 +586,22 @@
-                 blockposition = this.portalLocation;
+                 blockPos = this.portalLocation;
              }
- 
 +            // Paper start - Perf: Do crystal-portal proximity check before entity lookup
 +            if (placedEndCrystalPos != null) {
 +                // The end crystal must be 0 or 1 higher than the portal origin
-+                int dy = placedEndCrystalPos.getY() - blockposition.getY();
++                int dy = placedEndCrystalPos.getY() - blockPos.getY();
 +                if (dy != 0 && dy != 1) {
 +                    return false;
 +                }
 +                // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other
-+                int dx = placedEndCrystalPos.getX() - blockposition.getX();
-+                int dz = placedEndCrystalPos.getZ() - blockposition.getZ();
++                int dx = placedEndCrystalPos.getX() - blockPos.getX();
++                int dz = placedEndCrystalPos.getZ() - blockPos.getZ();
 +                if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) {
 +                    return false;
 +                }
 +            }
 +            // Paper end - Perf: Do crystal-portal proximity check before entity lookup
 +
-             List<EndCrystal> list = Lists.newArrayList();
-             BlockPos blockposition1 = blockposition.above(1);
-             Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
-@@ -540,19 +611,19 @@
-                 List<EndCrystal> list1 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockposition1.relative(enumdirection, 2)));
  
-                 if (list1.isEmpty()) {
+             List<EndCrystal> list = Lists.newArrayList();
+             BlockPos blockPos1 = blockPos.above(1);
+@@ -492,18 +_,19 @@
+             for (Direction direction : Direction.Plane.HORIZONTAL) {
+                 List<EndCrystal> entitiesOfClass = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockPos1.relative(direction, 2)));
+                 if (entitiesOfClass.isEmpty()) {
 -                    return;
 +                    return false; // CraftBukkit - return value
                  }
  
-                 list.addAll(list1);
+                 list.addAll(entitiesOfClass);
              }
  
-             EndDragonFight.LOGGER.debug("Found all crystals, respawning dragon.");
+             LOGGER.debug("Found all crystals, respawning dragon.");
 -            this.respawnDragon(list);
 +            return this.respawnDragon(list); // CraftBukkit - return value
          }
--
 +        return false; // CraftBukkit - return value
      }
  
 -    public void respawnDragon(List<EndCrystal> crystals) {
-+    public boolean respawnDragon(List<EndCrystal> list) { // CraftBukkit - return boolean
++    public boolean respawnDragon(List<EndCrystal> crystals) { // CraftBukkit - return boolean
          if (this.dragonKilled && this.respawnStage == null) {
-             for (BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection = this.findExitPortal(); shapedetector_shapedetectorcollection != null; shapedetector_shapedetectorcollection = this.findExitPortal()) {
-                 for (int i = 0; i < this.exitPortalPattern.getWidth(); ++i) {
-@@ -571,9 +642,10 @@
-             this.respawnStage = DragonRespawnAnimation.START;
+             for (BlockPattern.BlockPatternMatch blockPatternMatch = this.findExitPortal(); blockPatternMatch != null; blockPatternMatch = this.findExitPortal()) {
+                 for (int i = 0; i < this.exitPortalPattern.getWidth(); i++) {
+@@ -522,7 +_,9 @@
              this.respawnTime = 0;
              this.spawnExitPortal(false);
--            this.respawnCrystals = crystals;
-+            this.respawnCrystals = list;
+             this.respawnCrystals = crystals;
 +            return true; // CraftBukkit - return value
          }
--
 +        return false; // CraftBukkit - return value
      }
  

From 1b5b2d8d45a3dec67364c5970f27a05975247528 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 18:43:42 -0800
Subject: [PATCH 140/285] net.minecraft.world.level.chunk.status

---
 .../chunk/status/ChunkStatusTasks.java.patch  | 56 ++++++++-----------
 1 file changed, 24 insertions(+), 32 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch (58%)

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch
index 9f0303442e..30e279c283 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java.patch
@@ -1,46 +1,38 @@
 --- a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
 +++ b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-@@ -36,7 +36,7 @@
-     static CompletableFuture<ChunkAccess> generateStructureStarts(WorldGenContext context, ChunkStep step, StaticCache2D<GenerationChunkHolder> chunks, ChunkAccess chunk) {
-         ServerLevel worldserver = context.level();
- 
--        if (worldserver.getServer().getWorldData().worldGenOptions().generateStructures()) {
-+        if (worldserver.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
-             context.generator().createStructures(worldserver.registryAccess(), worldserver.getChunkSource().getGeneratorState(), worldserver.structureManager(), chunk, context.structureManager(), worldserver.dimension());
-         }
- 
-@@ -151,7 +151,7 @@
-             if (protochunk instanceof ImposterProtoChunk protochunkextension) {
-                 chunk1 = protochunkextension.getWrapped();
-             } else {
--                chunk1 = new LevelChunk(worldserver, protochunk, (chunk1) -> {
-+                chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix
-                     ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities());
-                 });
-                 generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false));
-@@ -168,10 +168,61 @@
-         }, context.mainThreadExecutor());
+@@ -35,7 +_,7 @@
+         WorldGenContext worldGenContext, ChunkStep step, StaticCache2D<GenerationChunkHolder> cache, ChunkAccess chunk
+     ) {
+         ServerLevel serverLevel = worldGenContext.level();
+-        if (serverLevel.getServer().getWorldData().worldGenOptions().generateStructures()) {
++        if (serverLevel.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
+             worldGenContext.generator()
+                 .createStructures(
+                     serverLevel.registryAccess(),
+@@ -196,9 +_,60 @@
+         }, worldGenContext.mainThreadExecutor());
      }
  
--    private static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) {
-+    public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) { // Paper - public
-         if (!entities.isEmpty()) {
--            world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD));
+-    private static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> entityTags) {
++    public static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> entityTags) { // Paper - public
+         if (!entityTags.isEmpty()) {
+-            level.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entityTags, level, EntitySpawnReason.LOAD));
+-        }
+-    }
 +            // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
-+            world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
++            level.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entityTags, level, EntitySpawnReason.LOAD).filter((entity) -> {
 +                boolean needsRemoval = false;
-+                net.minecraft.server.dedicated.DedicatedServer server = world.getCraftServer().getServer();
-+                if (!world.getChunkSource().spawnFriendlies && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
++                net.minecraft.server.dedicated.DedicatedServer server = level.getCraftServer().getServer();
++                if (!level.getChunkSource().spawnFriendlies && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
 +                    entity.discard(null); // CraftBukkit - add Bukkit remove cause
 +                    needsRemoval = true;
 +                }
-+                checkDupeUUID(world, entity); // Paper - duplicate uuid resolving
++                checkDupeUUID(level, entity); // Paper - duplicate uuid resolving
 +                return !needsRemoval;
 +            }));
 +            // CraftBukkit end
-         }
- 
-     }
++        }
++    }
 +
 +    // Paper start - duplicate uuid resolving
 +    // rets true if to prevent the entity from being added
@@ -58,7 +50,7 @@
 +        }
 +
 +        if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved()
-+            && Objects.equals(other.getEncodeId(), entity.getEncodeId())
++            && java.util.Objects.equals(other.getEncodeId(), entity.getEncodeId())
 +            && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange
 +        ) {
 +            entity.discard(null);

From aaf151c03d48f51b10f278e26143dd8ea9db6acb Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 18:55:46 -0800
Subject: [PATCH 141/285] net.minecraft.world.entity.ai.goal

---
 .../entity/ai/goal/BreakDoorGoal.java.patch   |  4 +-
 .../entity/ai/goal/EatBlockGoal.java.patch    | 35 ++++++++++++
 .../world/entity/ai/goal/FloatGoal.java.patch |  2 +-
 .../entity/ai/goal/FollowOwnerGoal.java.patch | 10 ++--
 .../world/entity/ai/goal/Goal.java.patch      | 13 +++--
 .../entity/ai/goal/RemoveBlockGoal.java.patch | 42 ++++++++++++++
 .../ai/goal/RunAroundLikeCrazyGoal.java.patch | 11 ++++
 .../ai/goal/SitWhenOrderedToGoal.java.patch   |  2 +-
 .../world/entity/ai/goal/SwellGoal.java.patch | 12 ++--
 .../world/entity/ai/goal/TemptGoal.java.patch | 27 +++++++++
 .../entity/ai/goal/EatBlockGoal.java.patch    | 46 ----------------
 .../entity/ai/goal/RemoveBlockGoal.java.patch | 55 -------------------
 .../ai/goal/RunAroundLikeCrazyGoal.java.patch | 22 --------
 .../world/entity/ai/goal/TemptGoal.java.patch | 44 ---------------
 14 files changed, 137 insertions(+), 188 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch (98%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch (51%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/Goal.java.patch (82%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch (96%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch (60%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
similarity index 98%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
index 8323bfa7ee..9fd1ccd71c 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
-@@ -72,9 +72,16 @@
+@@ -73,9 +_,16 @@
          }
  
          if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) {
@@ -16,5 +16,5 @@
 -            this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos)));
 +            this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999
          }
- 
      }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
new file mode 100644
index 0000000000..8cc09baa9d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+@@ -26,6 +_,11 @@
+ 
+     @Override
+     public boolean canUse() {
++        // Paper start - Fix MC-210802
++        if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) {
++            return false;
++        }
++        // Paper end
+         if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) {
+             return false;
+         } else {
+@@ -60,8 +_,9 @@
+         this.eatAnimationTick = Math.max(0, this.eatAnimationTick - 1);
+         if (this.eatAnimationTick == this.adjustedTickDelay(4)) {
+             BlockPos blockPos = this.mob.blockPosition();
+-            if (IS_TALL_GRASS.test(this.level.getBlockState(blockPos))) {
+-                if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++            final BlockState blockState = this.level.getBlockState(blockPos); // Paper - fix wrong block state
++            if (IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state
+                     this.level.destroyBlock(blockPos, false);
+                 }
+ 
+@@ -69,7 +_,7 @@
+             } else {
+                 BlockPos blockPos1 = blockPos.below();
+                 if (this.level.getBlockState(blockPos1).is(Blocks.GRASS_BLOCK)) {
+-                    if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockPos1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state
+                         this.level.levelEvent(2001, blockPos1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
+                         this.level.setBlock(blockPos1, Blocks.DIRT.defaultBlockState(), 2);
+                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
index ed90ccea87..0fab13edb2 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/FloatGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/FloatGoal.java
-@@ -9,6 +9,7 @@
+@@ -9,6 +_,7 @@
  
      public FloatGoal(Mob mob) {
          this.mob = mob;
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
similarity index 51%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
index 4cdf53ef91..32807a319e 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
@@ -1,11 +1,11 @@
 --- a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java
-@@ -72,7 +72,7 @@
+@@ -72,7 +_,7 @@
      public void tick() {
-         boolean bl = this.tamable.shouldTryTeleportToOwner();
-         if (!bl) {
--            this.tamable.getLookControl().setLookAt(this.owner, 10.0F, (float)this.tamable.getMaxHeadXRot());
-+            if (this.tamable.distanceToSqr(this.owner) <= 16 * 16) this.tamable.getLookControl().setLookAt(this.owner, 10.0F, (float)this.tamable.getMaxHeadXRot()); // Paper - Limit pet look distance
+         boolean shouldTryTeleportToOwner = this.tamable.shouldTryTeleportToOwner();
+         if (!shouldTryTeleportToOwner) {
+-            this.tamable.getLookControl().setLookAt(this.owner, 10.0F, this.tamable.getMaxHeadXRot());
++            if (this.tamable.distanceToSqr(this.owner) <= 16 * 16) this.tamable.getLookControl().setLookAt(this.owner, 10.0F, this.tamable.getMaxHeadXRot()); // Paper - Limit pet look distance
          }
  
          if (--this.timeToRecalcPath <= 0) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/Goal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
similarity index 82%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/Goal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
index a354e63b19..34a28aaf49 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/Goal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
@@ -1,9 +1,10 @@
 --- a/net/minecraft/world/entity/ai/goal/Goal.java
 +++ b/net/minecraft/world/entity/ai/goal/Goal.java
-@@ -46,6 +46,16 @@
+@@ -46,6 +_,17 @@
          return this.flags;
      }
  
++
 +    // Paper start - Mob Goal API
 +    public boolean hasFlag(final Goal.Flag flag) {
 +        return this.flags.contains(flag);
@@ -12,13 +13,13 @@
 +    public void addFlag(final Goal.Flag flag) {
 +        this.flags.add(flag);
 +    }
-+    // Paper end - Mob Goal API
 +
-     protected int adjustedTickDelay(int ticks) {
-         return this.requiresUpdateEveryTick() ? ticks : reducedTickDelay(ticks);
++    // Paper end - Mob Goal API
+     protected int adjustedTickDelay(int adjustment) {
+         return this.requiresUpdateEveryTick() ? adjustment : reducedTickDelay(adjustment);
      }
-@@ -62,7 +72,19 @@
-         return (ServerLevel)world;
+@@ -62,7 +_,19 @@
+         return (ServerLevel)level;
      }
  
 +    // Paper start - Mob goal api
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
new file mode 100644
index 0000000000..16ce65055d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+@@ -104,6 +_,11 @@
+             }
+ 
+             if (this.ticksSinceReachedGoal > 60) {
++                // CraftBukkit start - Step on eggs
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityInteractEvent(this.removerMob, org.bukkit.craftbukkit.block.CraftBlock.at(level, posWithBlock))) {
++                    return;
++                }
++                // CraftBukkit end
+                 level.removeBlock(posWithBlock, false);
+                 if (!level.isClientSide) {
+                     for (int i = 0; i < 20; i++) {
+@@ -124,13 +_,16 @@
+ 
+     @Nullable
+     private BlockPos getPosWithBlock(BlockPos pos, BlockGetter level) {
+-        if (level.getBlockState(pos).is(this.blockToRemove)) {
++        net.minecraft.world.level.block.state.BlockState block = level.getBlockStateIfLoaded(pos); // Paper - Prevent AI rules from loading chunks
++        if (block == null) return null; // Paper - Prevent AI rules from loading chunks
++        if (block.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks
+             return pos;
+         } else {
+             BlockPos[] blockPoss = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()};
+ 
+             for (BlockPos blockPos : blockPoss) {
+-                if (level.getBlockState(blockPos).is(this.blockToRemove)) {
++                net.minecraft.world.level.block.state.BlockState block2 = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent AI rules from loading chunks
++                if (block2 != null && block2.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks
+                     return blockPos;
+                 }
+             }
+@@ -141,7 +_,7 @@
+ 
+     @Override
+     protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+-        ChunkAccess chunk = level.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false);
++        ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks
+         return chunk != null
+             && chunk.getBlockState(pos).is(this.blockToRemove)
+             && chunk.getBlockState(pos.above()).isAir()
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
new file mode 100644
index 0000000000..bab3ed29fe
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+@@ -58,7 +_,7 @@
+             if (firstPassenger instanceof Player player) {
+                 int temper = this.horse.getTemper();
+                 int maxTemper = this.horse.getMaxTemper();
+-                if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper) {
++                if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
+                     this.horse.tameWithName(player);
+                     return;
+                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
similarity index 96%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
index 64830b4a5b..d467f6a9a1 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java
-@@ -22,7 +22,7 @@
+@@ -20,7 +_,7 @@
      @Override
      public boolean canUse() {
          if (!this.mob.isTame()) {
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch
similarity index 60%
rename from paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch
rename to paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch
index 885e0fd98c..acde7d31dd 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/SwellGoal.java.patch
@@ -1,17 +1,17 @@
 --- a/net/minecraft/world/entity/ai/goal/SwellGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java
-@@ -21,7 +21,14 @@
-         return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0;
+@@ -21,6 +_,14 @@
+         return this.creeper.getSwellDir() > 0 || target != null && this.creeper.distanceToSqr(target) < 9.0;
      }
  
 +    // Paper start - Fix MC-179072
-     @Override
++    @Override
 +    public boolean canContinueToUse() {
-+        return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse();
++        return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && this.canUse();
 +    }
 +    // Paper end
 +
-+    @Override
++
+     @Override
      public void start() {
          this.creeper.getNavigation().stop();
-         this.target = this.creeper.getTarget();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
new file mode 100644
index 0000000000..96b15bc9f4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java
++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java
+@@ -21,7 +_,7 @@
+     private double pRotX;
+     private double pRotY;
+     @Nullable
+-    protected Player player;
++    protected LivingEntity player; // CraftBukkit
+     private int calmDown;
+     private boolean isRunning;
+     private final Predicate<ItemStack> items;
+@@ -44,6 +_,15 @@
+         } else {
+             this.player = getServerLevel(this.mob)
+                 .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob);
++            // CraftBukkit start
++            if (this.player != null) {
++                org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT);
++                if (event.isCancelled()) {
++                    return false;
++                }
++                this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++            }
++            // CraftBukkit end
+             return this.player != null;
+         }
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
deleted file mode 100644
index 72de1e0703..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
+++ /dev/null
@@ -1,46 +0,0 @@
---- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
-+++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
-@@ -11,6 +11,10 @@
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
-+
- public class EatBlockGoal extends Goal {
- 
-     private static final int EAT_ANIMATION_TICKS = 40;
-@@ -27,6 +31,11 @@
- 
-     @Override
-     public boolean canUse() {
-+        // Paper start - Fix MC-210802
-+        if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) {
-+            return false;
-+        }
-+        // Paper end
-         if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) {
-             return false;
-         } else {
-@@ -63,8 +72,9 @@
-         if (this.eatAnimationTick == this.adjustedTickDelay(4)) {
-             BlockPos blockposition = this.mob.blockPosition();
- 
--            if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) {
--                if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+            final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state
-+            if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state
-+                if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state
-                     this.level.destroyBlock(blockposition, false);
-                 }
- 
-@@ -73,7 +83,7 @@
-                 BlockPos blockposition1 = blockposition.below();
- 
-                 if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
--                    if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+                    if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state
-                         this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
-                         this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2);
-                     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
deleted file mode 100644
index 2111952674..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
-+++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
-@@ -21,6 +21,10 @@
- import net.minecraft.world.level.chunk.ChunkAccess;
- import net.minecraft.world.level.chunk.status.ChunkStatus;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class RemoveBlockGoal extends MoveToBlockGoal {
- 
-@@ -97,6 +101,11 @@
-             }
- 
-             if (this.ticksSinceReachedGoal > 60) {
-+                // CraftBukkit start - Step on eggs
-+                if (!CraftEventFactory.callEntityInteractEvent(this.removerMob, CraftBlock.at(world, blockposition1))) {
-+                    return;
-+                }
-+                // CraftBukkit end
-                 world.removeBlock(blockposition1, false);
-                 if (!world.isClientSide) {
-                     for (int i = 0; i < 20; ++i) {
-@@ -118,7 +127,9 @@
- 
-     @Nullable
-     private BlockPos getPosWithBlock(BlockPos pos, BlockGetter world) {
--        if (world.getBlockState(pos).is(this.blockToRemove)) {
-+        net.minecraft.world.level.block.state.BlockState block = world.getBlockStateIfLoaded(pos); // Paper - Prevent AI rules from loading chunks
-+        if (block == null) return null; // Paper - Prevent AI rules from loading chunks
-+        if (block.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks
-             return pos;
-         } else {
-             BlockPos[] ablockposition = new BlockPos[]{pos.below(), pos.west(), pos.east(), pos.north(), pos.south(), pos.below().below()};
-@@ -128,7 +139,8 @@
-             for (int j = 0; j < i; ++j) {
-                 BlockPos blockposition1 = ablockposition1[j];
- 
--                if (world.getBlockState(blockposition1).is(this.blockToRemove)) {
-+                net.minecraft.world.level.block.state.BlockState block2 = world.getBlockStateIfLoaded(blockposition1); // Paper - Prevent AI rules from loading chunks
-+                if (block2 != null && block2.is(this.blockToRemove)) { // Paper - Prevent AI rules from loading chunks
-                     return blockposition1;
-                 }
-             }
-@@ -139,7 +151,7 @@
- 
-     @Override
-     protected boolean isValidTarget(LevelReader world, BlockPos pos) {
--        ChunkAccess ichunkaccess = world.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false);
-+        ChunkAccess ichunkaccess = world.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks
- 
-         return ichunkaccess == null ? false : ichunkaccess.getBlockState(pos).is(this.blockToRemove) && ichunkaccess.getBlockState(pos.above()).isAir() && ichunkaccess.getBlockState(pos.above(2)).isAir();
-     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
deleted file mode 100644
index 4f41b508ea..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-+++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-@@ -6,6 +6,10 @@
- import net.minecraft.world.entity.animal.horse.AbstractHorse;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.phys.Vec3;
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+// CraftBukkit end
- 
- public class RunAroundLikeCrazyGoal extends Goal {
- 
-@@ -63,7 +67,7 @@
-                 int i = this.horse.getTemper();
-                 int j = this.horse.getMaxTemper();
- 
--                if (j > 0 && this.horse.getRandom().nextInt(j) < i) {
-+                if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
-                     this.horse.tameWithName(entityhuman);
-                     return;
-                 }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
deleted file mode 100644
index 8d33e61ef6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
+++ /dev/null
@@ -1,44 +0,0 @@
---- a/net/minecraft/world/entity/ai/goal/TemptGoal.java
-+++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java
-@@ -8,9 +8,15 @@
- import net.minecraft.world.entity.PathfinderMob;
- import net.minecraft.world.entity.ai.attributes.Attributes;
- import net.minecraft.world.entity.ai.targeting.TargetingConditions;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.entity.EntityTargetEvent;
-+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
-+// CraftBukkit end
-+
- public class TemptGoal extends Goal {
- 
-     private static final TargetingConditions TEMPT_TARGETING = TargetingConditions.forNonCombat().ignoreLineOfSight();
-@@ -23,7 +29,7 @@
-     private double pRotX;
-     private double pRotY;
-     @Nullable
--    protected Player player;
-+    protected LivingEntity player; // CraftBukkit
-     private int calmDown;
-     private boolean isRunning;
-     private final Predicate<ItemStack> items;
-@@ -47,6 +53,15 @@
-             return false;
-         } else {
-             this.player = getServerLevel((Entity) this.mob).getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob);
-+            // CraftBukkit start
-+            if (this.player != null) {
-+                EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, EntityTargetEvent.TargetReason.TEMPT);
-+                if (event.isCancelled()) {
-+                    return false;
-+                }
-+                this.player = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle();
-+            }
-+            // CraftBukkit end
-             return this.player != null;
-         }
-     }

From 7ac9b00916a5f937bf6618d1385e1d2cadfafe54 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 19:52:00 -0800
Subject: [PATCH 142/285] PlayerList

---
 .../server/players/PlayerList.java.patch      | 1142 +++++++++++++++
 .../server/players/PlayerList.java.patch      | 1276 -----------------
 2 files changed, 1142 insertions(+), 1276 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/players/PlayerList.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
new file mode 100644
index 0000000000..90f421d2a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -0,0 +1,1142 @@
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -112,14 +_,16 @@
+     private static final int SEND_PLAYER_INFO_INTERVAL = 600;
+     private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
+     private final MinecraftServer server;
+-    public final List<ServerPlayer> players = Lists.newArrayList();
++    public final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
+     private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap();
+     private final UserBanList bans = new UserBanList(USERBANLIST_FILE);
+     private final IpBanList ipBans = new IpBanList(IPBANLIST_FILE);
+     private final ServerOpList ops = new ServerOpList(OPLIST_FILE);
+     private final UserWhiteList whitelist = new UserWhiteList(WHITELIST_FILE);
+-    private final Map<UUID, ServerStatsCounter> stats = Maps.newHashMap();
+-    private final Map<UUID, PlayerAdvancements> advancements = Maps.newHashMap();
++    // CraftBukkit start
++    // private final Map<UUID, ServerStatsCounter> stats = Maps.newHashMap();
++    // private final Map<UUID, PlayerAdvancements> advancements = Maps.newHashMap();
++    // CraftBukkit end
+     public final PlayerDataStorage playerIo;
+     private boolean doWhiteList;
+     private final LayeredRegistryAccess<RegistryLayer> registries;
+@@ -130,14 +_,26 @@
+     private static final boolean ALLOW_LOGOUTIVATOR = false;
+     private int sendAllPlayerInfoIn;
+ 
++    // CraftBukkit start
++    private org.bukkit.craftbukkit.CraftServer cserver;
++    private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>();
++    public @Nullable String collideRuleTeamName; // Paper - Configurable player collision
++
+     public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registries, PlayerDataStorage playerIo, int maxPlayers) {
++        this.cserver = server.server = new org.bukkit.craftbukkit.CraftServer((net.minecraft.server.dedicated.DedicatedServer) server, this);
++        server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper
++        // CraftBukkit end
+         this.server = server;
+         this.registries = registries;
+         this.maxPlayers = maxPlayers;
+         this.playerIo = playerIo;
+     }
+ 
++    abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
++
+     public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
++        player.isRealPlayer = true; // Paper
++        player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
+         GameProfile gameProfile = player.getGameProfile();
+         GameProfileCache profileCache = this.server.getProfileCache();
+         String string;
+@@ -150,30 +_,94 @@
+         }
+ 
+         Optional<CompoundTag> optional = this.load(player);
+-        ResourceKey<Level> resourceKey = optional.<ResourceKey<Level>>flatMap(
+-                compoundTag -> DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundTag.get("Dimension"))).resultOrPartial(LOGGER::error)
++        // CraftBukkit start - Better rename detection
++        if (optional.isPresent()) {
++            CompoundTag nbttagcompound = optional.get();
++            if (nbttagcompound.contains("bukkit")) {
++                CompoundTag bukkit = nbttagcompound.getCompound("bukkit");
++                string = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : string;
++            }
++        }
++        // CraftBukkit end
++        // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found
++        ResourceKey<Level> resourceKey = null; // Paper
++        boolean[] invalidPlayerWorld = {false};
++        bukkitData: if (optional.isPresent()) {
++            // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
++            final org.bukkit.World bWorld;
++            if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) {
++                bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast")));
++            } else if (optional.get().contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name
++                bWorld = org.bukkit.Bukkit.getServer().getWorld(optional.get().getString("world"));
++            } else {
++                break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section
++            }
++            if (bWorld != null) {
++                resourceKey = ((org.bukkit.craftbukkit.CraftWorld) bWorld).getHandle().dimension();
++            } else {
++                resourceKey = Level.OVERWORLD;
++                invalidPlayerWorld[0] = true;
++            }
++        }
++        if (resourceKey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data
++        // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers
++        resourceKey = optional.<ResourceKey<Level>>flatMap(
++                compoundTag -> {
++                    com.mojang.serialization.DataResult<ResourceKey<Level>> dataResult = DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundTag.get("Dimension")));
++                    final Optional<ResourceKey<Level>> result = dataResult.resultOrPartial(LOGGER::error);
++                    invalidPlayerWorld[0] = result.isEmpty(); // reset to main world spawn if no valid world is found
++                    return result;
++                }
+             )
+-            .orElse(Level.OVERWORLD);
++            .orElse(Level.OVERWORLD); // revert to vanilla default main world, this isn't an "invalid world" since no player data existed
++        }
++        // Paper end
+         ServerLevel level = this.server.getLevel(resourceKey);
+         ServerLevel serverLevel;
+         if (level == null) {
+             LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourceKey);
+             serverLevel = this.server.overworld();
++            invalidPlayerWorld[0] = true; // Paper - reset to main world if no world with parsed value is found
+         } else {
+             serverLevel = level;
+         }
+ 
++        // Paper start - Entity#getEntitySpawnReason
++        if (optional.isEmpty()) {
++            player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login
++            // Paper start - reset to main world spawn if first spawn or invalid world
++        }
++        if (optional.isEmpty() || invalidPlayerWorld[0]) {
++            // Paper end - reset to main world spawn if first spawn or invalid world
++            player.moveTo(player.adjustSpawnLocation(serverLevel, serverLevel.getSharedSpawnPos()).getBottomCenter(), serverLevel.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored
++        }
++        // Paper end - Entity#getEntitySpawnReason
+         player.setServerLevel(serverLevel);
+         String loggableAddress = connection.getLoggableAddress(this.server.logIPs());
+-        LOGGER.info(
+-            "{}[{}] logged in with entity id {} at ({}, {}, {})",
+-            player.getName().getString(),
+-            loggableAddress,
+-            player.getId(),
+-            player.getX(),
+-            player.getY(),
+-            player.getZ()
+-        );
++        // Spigot start - spawn location event
++        org.bukkit.entity.Player spawnPlayer = player.getBukkitEntity();
++        org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(spawnPlayer, spawnPlayer.getLocation());
++        this.cserver.getPluginManager().callEvent(ev);
++
++        org.bukkit.Location loc = ev.getSpawnLocation();
++        serverLevel = ((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle();
++
++        player.spawnIn(serverLevel);
++        player.gameMode.setLevel((ServerLevel) player.level());
++        // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world)
++        player.setPosRaw(loc.getX(), loc.getY(), loc.getZ());
++        player.setRot(loc.getYaw(), loc.getPitch());
++        // Paper end - set raw so we aren't fully joined to the world
++        // Spigot end
++        // LOGGER.info( // CraftBukkit - Moved message to after join
++        //     "{}[{}] logged in with entity id {} at ({}, {}, {})",
++        //     player.getName().getString(),
++        //     loggableAddress,
++        //     player.getId(),
++        //     player.getX(),
++        //     player.getY(),
++        //     player.getZ()
++        // );
+         LevelData levelData = serverLevel.getLevelData();
+         player.loadGameTypes(optional.orElse(null));
+         ServerGamePacketListenerImpl serverGamePacketListenerImpl = new ServerGamePacketListenerImpl(this.server, connection, player, cookie);
+@@ -190,8 +_,8 @@
+                 levelData.isHardcore(),
+                 this.server.levelKeys(),
+                 this.getMaxPlayers(),
+-                this.viewDistance,
+-                this.simulationDistance,
++                serverLevel.spigotConfig.viewDistance,// Spigot - view distance
++                serverLevel.spigotConfig.simulationDistance,
+                 _boolean1,
+                 !_boolean,
+                 _boolean2,
+@@ -199,6 +_,7 @@
+                 this.server.enforceSecureProfile()
+             )
+         );
++        player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
+         serverGamePacketListenerImpl.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+         serverGamePacketListenerImpl.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities()));
+         serverGamePacketListenerImpl.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected));
+@@ -218,24 +_,117 @@
+             mutableComponent = Component.translatable("multiplayer.player.joined.renamed", player.getDisplayName(), string);
+         }
+ 
+-        this.broadcastSystemMessage(mutableComponent.withStyle(ChatFormatting.YELLOW), false);
++        // CraftBukkit start
++        mutableComponent.withStyle(ChatFormatting.YELLOW);
++        Component joinMessage = mutableComponent; // Paper - Adventure
+         serverGamePacketListenerImpl.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
+         ServerStatus status = this.server.getStatus();
+         if (status != null && !cookie.transferred()) {
+             player.sendServerStatus(status);
+         }
+ 
+-        player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players));
++        // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
+         this.players.add(player);
++        this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
+         this.playersByUUID.put(player.getUUID(), player);
+-        this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)));
+-        this.sendLevelInfo(player, serverLevel);
++        // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below
++        // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks
++        player.supressTrackerForLogin = true;
+         serverLevel.addNewPlayer(player);
+-        this.server.getCustomBossEvents().onPlayerConnect(player);
+-        this.sendActivePlayerEffects(player);
++        this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below serverLevel.addPlayerJoin(player);
++        // Paper end - Fire PlayerJoinEvent when Player is actually ready
+         player.loadAndSpawnEnderpearls(optional);
+         player.loadAndSpawnParentVehicle(optional);
++        // CraftBukkit start
++        org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity();
++
++        // Ensure that player inventory is populated with its viewer
++        player.containerMenu.transferTo(player.containerMenu, bukkitPlayer);
++
++        org.bukkit.event.player.PlayerJoinEvent playerJoinEvent = new org.bukkit.event.player.PlayerJoinEvent(bukkitPlayer, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure
++        this.cserver.getPluginManager().callEvent(playerJoinEvent);
++
++        if (!player.connection.isAcceptingMessages()) {
++            return;
++        }
++
++        final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
++
++        if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
++            joinMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure
++            this.server.getPlayerList().broadcastSystemMessage(joinMessage, false); // Paper - Adventure
++        }
++        // CraftBukkit end
++
++        // CraftBukkit start - sendAll above replaced with this loop
++        ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player
++
++        final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join
++        for (int i = 0; i < this.players.size(); ++i) {
++            ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i);
++
++            if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) {
++                // Paper start - Add Listing API for Player
++                if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) {
++                // Paper end - Add Listing API for Player
++                entityplayer1.connection.send(packet);
++                // Paper start - Add Listing API for Player
++                } else {
++                    entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false));
++                }
++                // Paper end - Add Listing API for Player
++            }
++
++            if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player
++                continue;
++            }
++
++            onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join
++        }
++        // Paper start - Use single player info update packet on join
++        if (!onlinePlayers.isEmpty()) {
++            player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player
++        }
++        // Paper end - Use single player info update packet on join
++        player.sentListPacket = true;
++        player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
++        ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now
++        // CraftBukkit end
++
++        //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE
++
++        this.sendLevelInfo(player, serverLevel);
++
++        // CraftBukkit start - Only add if the player wasn't moved in the event
++        if (player.level() == serverLevel && !serverLevel.players().contains(player)) {
++            serverLevel.addNewPlayer(player);
++            this.server.getCustomBossEvents().onPlayerConnect(player);
++        }
++
++        serverLevel = player.serverLevel(); // CraftBukkit - Update in case join event changed it
++        // CraftBukkit end
++        this.sendActivePlayerEffects(player);
++        // Paper - move loading pearls / parent vehicle up
+         player.initInventoryMenu();
++        // CraftBukkit - Moved from above, added world
++        // Paper start - Configurable player collision; Add to collideRule team if needed
++        final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
++        final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName);
++        if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) {
++            scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
++        }
++        // Paper end - Configurable player collision
++        PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), loggableAddress, player.getId(), serverLevel.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
++        // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead
++        if (player.isDeadOrDying()) {
++            net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = serverLevel.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
++                    .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
++            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
++                    new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, player.chunkPosition(), plains),
++                    serverLevel.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null)
++            );
++        }
++        // Paper end - Send empty chunk
+     }
+ 
+     public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) {
+@@ -258,30 +_,31 @@
+     }
+ 
+     public void addWorldborderListener(ServerLevel level) {
++        if (this.playerIo != null) return; // CraftBukkit
+         level.getWorldBorder().addListener(new BorderChangeListener() {
+             @Override
+             public void onBorderSizeSet(WorldBorder border, double size) {
+-                PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border));
++                PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); // CraftBukkit
+             }
+ 
+             @Override
+             public void onBorderSizeLerping(WorldBorder border, double oldSize, double newSize, long time) {
+-                PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border));
++                PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); // CraftBukkit
+             }
+ 
+             @Override
+             public void onBorderCenterSet(WorldBorder border, double x, double z) {
+-                PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border));
++                PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); // CraftBukkit
+             }
+ 
+             @Override
+             public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
+-                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border));
++                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); // CraftBukkit
+             }
+ 
+             @Override
+             public void onBorderSetWarningBlocks(WorldBorder border, int warningBlocks) {
+-                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border));
++                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); // CraftBukkit
+             }
+ 
+             @Override
+@@ -309,67 +_,175 @@
+     }
+ 
+     protected void save(ServerPlayer player) {
++        if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
+         this.playerIo.save(player);
+-        ServerStatsCounter serverStatsCounter = this.stats.get(player.getUUID());
++        ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
+         if (serverStatsCounter != null) {
+             serverStatsCounter.save();
+         }
+ 
+-        PlayerAdvancements playerAdvancements = this.advancements.get(player.getUUID());
++        PlayerAdvancements playerAdvancements = player.getAdvancements(); // CraftBukkit
+         if (playerAdvancements != null) {
+             playerAdvancements.save();
+         }
+     }
+ 
+-    public void remove(ServerPlayer player) {
++    public net.kyori.adventure.text.Component remove(ServerPlayer player) { // CraftBukkit - return string // Paper - return Component
++        // Paper start - Fix kick event leave message not being sent
++        return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName())));
++    }
++    public net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) {
++        // Paper end - Fix kick event leave message not being sent
+         ServerLevel serverLevel = player.serverLevel();
+         player.awardStat(Stats.LEAVE_GAME);
++        // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
++        // See SPIGOT-5799, SPIGOT-6145
++        if (player.containerMenu != player.inventoryMenu) {
++            player.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason
++        }
++
++        org.bukkit.event.player.PlayerQuitEvent playerQuitEvent = new org.bukkit.event.player.PlayerQuitEvent(player.getBukkitEntity(), leaveMessage, player.quitReason); // Paper - Adventure & Add API for quit reason
++        this.cserver.getPluginManager().callEvent(playerQuitEvent);
++        player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
++
++        player.doTick(); // SPIGOT-924
++        // CraftBukkit end
++
++        // Paper start - Configurable player collision; Remove from collideRule team if needed
++        if (this.collideRuleTeamName != null) {
++            final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard();
++            final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName);
++            if (player.getTeam() == team && team != null) {
++                scoreBoard.removePlayerFromTeam(player.getScoreboardName(), team);
++            }
++        }
++        // Paper end - Configurable player collision
++
++        // Paper - Drop carried item when player has disconnected
++        if (!player.containerMenu.getCarried().isEmpty()) {
++            net.minecraft.world.item.ItemStack carried = player.containerMenu.getCarried();
++            player.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY);
++            player.drop(carried, false);
++        }
++        // Paper end - Drop carried item when player has disconnected
+         this.save(player);
+         if (player.isPassenger()) {
+             Entity rootVehicle = player.getRootVehicle();
+             if (rootVehicle.hasExactlyOnePlayerPassenger()) {
+                 LOGGER.debug("Removing player mount");
+                 player.stopRiding();
+-                rootVehicle.getPassengersAndSelf().forEach(entity -> entity.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER));
++                rootVehicle.getPassengersAndSelf().forEach(entity -> {
++                    // Paper start - Fix villager boat exploit
++                    if (entity instanceof net.minecraft.world.entity.npc.AbstractVillager villager) {
++                        final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer();
++                        if (human != null) {
++                            villager.setTradingPlayer(null);
++                        }
++                    }
++                    // Paper end - Fix villager boat exploit
++                    entity.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, org.bukkit.event.entity.EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause
++                });
+             }
+         }
+ 
+         player.unRide();
+ 
+         for (ThrownEnderpearl thrownEnderpearl : player.getEnderPearls()) {
+-            thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
++            // Paper start - Allow using old ender pearl behavior
++            if (!thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) {
++                thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, org.bukkit.event.entity.EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause
++            } else {
++                thrownEnderpearl.cachedOwner = null;
++            }
++            // Paper end - Allow using old ender pearl behavior
+         }
+ 
+         serverLevel.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
++        player.retireScheduler(); // Paper - Folia schedulers
+         player.getAdvancements().stopListening();
+         this.players.remove(player);
++        this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
+         this.server.getCustomBossEvents().onPlayerDisconnect(player);
+         UUID uuid = player.getUUID();
+         ServerPlayer serverPlayer = this.playersByUUID.get(uuid);
+         if (serverPlayer == player) {
+             this.playersByUUID.remove(uuid);
+-            this.stats.remove(uuid);
+-            this.advancements.remove(uuid);
+-        }
+-
+-        this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
++            // CraftBukkit start
++            // this.stats.remove(uuid);
++            // this.advancements.remove(uuid);
++            // CraftBukkit end
++        }
++
++        // CraftBukkit start
++        // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
++        ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()));
++        for (int i = 0; i < this.players.size(); i++) {
++            ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i);
++
++            if (entityplayer2.getBukkitEntity().canSee(player.getBukkitEntity())) {
++                entityplayer2.connection.send(packet);
++            } else {
++                entityplayer2.getBukkitEntity().onEntityRemove(player);
++            }
++        }
++        // This removes the scoreboard (and player reference) for the specific player in the manager
++        this.cserver.getScoreboardManager().removePlayer(player.getBukkitEntity());
++        // CraftBukkit end
++        return playerQuitEvent.quitMessage(); // Paper - Adventure
+     }
+ 
+-    @Nullable
+-    public Component canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) {
+-        if (this.bans.isBanned(gameProfile)) {
+-            UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
+-            MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
+-            if (userBanListEntry.getExpires() != null) {
++    // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
++    public ServerPlayer canPlayerLogin(net.minecraft.server.network.ServerLoginPacketListenerImpl loginlistener, GameProfile gameProfile) {
++        // if (this.bans.isBanned(gameProfile)) {
++        //     UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
++        // Moved from processLogin
++        UUID uuid = gameProfile.getId();
++        List<ServerPlayer> list = Lists.newArrayList();
++
++        ServerPlayer entityplayer;
++
++        for (int i = 0; i < this.players.size(); ++i) {
++            entityplayer = (ServerPlayer) this.players.get(i);
++            if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameProfile.getName()))) { // Paper - validate usernames
++                list.add(entityplayer);
++            }
++        }
++
++        java.util.Iterator iterator = list.iterator();
++
++        while (iterator.hasNext()) {
++            entityplayer = (ServerPlayer) iterator.next();
++            this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
++            entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause
++        }
++
++        // Instead of kicking then returning, we need to store the kick reason
++        // in the event, check with plugins to see if it's ok, and THEN kick
++        // depending on the outcome.
++        SocketAddress socketAddress = loginlistener.connection.getRemoteAddress();
++
++        ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameProfile, ClientInformation.createDefault());
++        entity.transferCookieConnection = loginlistener;
++        org.bukkit.entity.Player player = entity.getBukkitEntity();
++        org.bukkit.event.player.PlayerLoginEvent event = new org.bukkit.event.player.PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketAddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress());
++
++        // Paper start - Fix MC-158900
++        UserBanListEntry gameprofilebanentry;
++        if (this.bans.isBanned(gameProfile) && (gameprofilebanentry = this.bans.get(gameProfile)) != null) {
++            // Paper end - Fix MC-158900
++            MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason());
++            if (gameprofilebanentry.getExpires() != null) {
+                 mutableComponent.append(
+-                    Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(userBanListEntry.getExpires()))
++                    Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires()))
+                 );
+             }
+ 
+-            return mutableComponent;
+-        } else if (!this.isWhiteListed(gameProfile)) {
+-            return Component.translatable("multiplayer.disconnect.not_whitelisted");
+-        } else if (this.ipBans.isBanned(socketAddress)) {
++            // return mutableComponent;
++            event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure
++        } else if (!this.isWhiteListed(gameProfile, event)) { // Paper - ProfileWhitelistVerifyEvent
++            // return Component.translatable("multiplayer.disconnect.not_whitelisted");
++            //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted
++        } else if (this.ipBans.isBanned(socketAddress) && getIpBans().get(socketAddress) != null && !this.getIpBans().get(socketAddress).hasExpired()) { // Paper - fix NPE with temp ip bans
+             IpBanListEntry ipBanListEntry = this.ipBans.get(socketAddress);
+             MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipBanListEntry.getReason());
+             if (ipBanListEntry.getExpires() != null) {
+@@ -378,69 +_,129 @@
+                 );
+             }
+ 
+-            return mutableComponent;
++            // return mutableComponent;
++            event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure
+         } else {
+-            return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
+-                ? Component.translatable("multiplayer.disconnect.server_full")
+-                : null;
+-        }
+-    }
+-
+-    public ServerPlayer getPlayerForLogin(GameProfile gameProfile, ClientInformation clientInformation) {
+-        return new ServerPlayer(this.server, this.server.overworld(), gameProfile, clientInformation);
+-    }
+-
+-    public boolean disconnectAllPlayersWithProfile(GameProfile gameProfile) {
+-        UUID id = gameProfile.getId();
+-        Set<ServerPlayer> set = Sets.newIdentityHashSet();
+-
+-        for (ServerPlayer serverPlayer : this.players) {
+-            if (serverPlayer.getUUID().equals(id)) {
+-                set.add(serverPlayer);
++            // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
++            //     ? Component.translatable("multiplayer.disconnect.server_full")
++            //     : null;
++            if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) {
++                event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
+             }
+         }
+-
+-        ServerPlayer serverPlayer1 = this.playersByUUID.get(gameProfile.getId());
+-        if (serverPlayer1 != null) {
+-            set.add(serverPlayer1);
+-        }
+-
+-        for (ServerPlayer serverPlayer2 : set) {
+-            serverPlayer2.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE);
+-        }
+-
+-        return !set.isEmpty();
+-    }
+-
+-    public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason) {
++        this.cserver.getPluginManager().callEvent(event);
++        if (event.getResult() != org.bukkit.event.player.PlayerLoginEvent.Result.ALLOWED) {
++            loginlistener.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure
++            return null;
++        }
++        return entity;
++    }
++
++    // CraftBukkit start - added EntityPlayer
++    public ServerPlayer getPlayerForLogin(GameProfile gameProfile, ClientInformation clientInformation, ServerPlayer player) {
++        player.updateOptions(clientInformation);
++        return player;
++        // CraftBukkit end
++    }
++
++    public boolean disconnectAllPlayersWithProfile(GameProfile gameProfile, ServerPlayer player) { // CraftBukkit - added ServerPlayer
++        // CraftBukkit start - Moved up
++        // UUID id = gameProfile.getId();
++        // Set<ServerPlayer> set = Sets.newIdentityHashSet();
++        //
++        // for (ServerPlayer serverPlayer : this.players) {
++        //     if (serverPlayer.getUUID().equals(id)) {
++        //         set.add(serverPlayer);
++        //     }
++        // }
++        //
++        // ServerPlayer serverPlayer1 = this.playersByUUID.get(gameProfile.getId());
++        // if (serverPlayer1 != null) {
++        //     set.add(serverPlayer1);
++        // }
++        //
++        // for (ServerPlayer serverPlayer2 : set) {
++        //     serverPlayer2.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE);
++        // }
++        //
++        // return !set.isEmpty();
++        return player == null;
++        // CraftBukkit end
++    }
++
++    // CraftBukkit start
++    public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason) {
++        return this.respawn(player, keepInventory, reason, eventReason, null);
++    }
++    public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) {
++        player.stopRiding(); // CraftBukkit
+         this.players.remove(player);
++        this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
+         player.serverLevel().removePlayerImmediately(player, reason);
+-        TeleportTransition teleportTransition = player.findRespawnPositionAndUseSpawnBlock(!keepInventory, TeleportTransition.DO_NOTHING);
+-        ServerLevel level = teleportTransition.newLevel();
+-        ServerPlayer serverPlayer = new ServerPlayer(this.server, level, player.getGameProfile(), player.clientInformation());
++        // TeleportTransition teleportTransition = player.findRespawnPositionAndUseSpawnBlock(!keepInventory, TeleportTransition.DO_NOTHING);
++        // ServerLevel level = teleportTransition.newLevel();
++        // ServerPlayer serverPlayer = new ServerPlayer(this.server, level, player.getGameProfile(), player.clientInformation());
++        ServerPlayer serverPlayer = player;
++        Level fromWorld = player.level();
++        player.wonGame = false;
++        // CraftBukkit end
+         serverPlayer.connection = player.connection;
+         serverPlayer.restoreFrom(player, keepInventory);
+         serverPlayer.setId(player.getId());
+         serverPlayer.setMainArm(player.getMainArm());
+-        if (!teleportTransition.missingRespawnBlock()) {
+-            serverPlayer.copyRespawnPosition(player);
+-        }
++        // CraftBukkit - not required, just copies old location into reused entity
++        // if (!teleportTransition.missingRespawnBlock()) {
++        //     serverPlayer.copyRespawnPosition(player);
++        // }
+ 
+         for (String string : player.getTags()) {
+             serverPlayer.addTag(string);
+         }
+-
++        // Paper start - Add PlayerPostRespawnEvent
++        boolean isBedSpawn = false;
++        boolean isRespawn = false;
++        // Paper end - Add PlayerPostRespawnEvent
++
++        // CraftBukkit start - fire PlayerRespawnEvent
++        TeleportTransition teleportTransition;
++        if (location == null) {
++            teleportTransition = player.findRespawnPositionAndUseSpawnBlock(!keepInventory, TeleportTransition.DO_NOTHING, eventReason);
++
++            if (!keepInventory) player.reset(); // SPIGOT-4785
++           // Paper start - Add PlayerPostRespawnEvent
++           if (teleportTransition == null) return player; // Early exit, mirrors belows early return for disconnected players in respawn event
++           isRespawn = true;
++           location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot());
++           // Paper end - Add PlayerPostRespawnEvent
++        } else {
++            teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), TeleportTransition.DO_NOTHING);
++        }
++        // Spigot Start
++        if (teleportTransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event
++            return player;
++        }
++        // Spigot End
++        ServerLevel level = teleportTransition.newLevel();
++        serverPlayer.spawnIn(level);
++        serverPlayer.unsetRemoved();
++        serverPlayer.setShiftKeyDown(false);
+         Vec3 vec3 = teleportTransition.position();
+-        serverPlayer.moveTo(vec3.x, vec3.y, vec3.z, teleportTransition.yRot(), teleportTransition.xRot());
++        serverPlayer.forceSetPositionRotation(vec3.x, vec3.y, vec3.z, teleportTransition.yRot(), teleportTransition.xRot());
++        level.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(vec3.x()) >> 4, net.minecraft.util.Mth.floor(vec3.z()) >> 4), 1, player.getId()); // Paper - post teleport ticket type
++        // CraftBukkit end
+         if (teleportTransition.missingRespawnBlock()) {
+             serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
++            serverPlayer.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent
+         }
+ 
+         byte b = (byte)(keepInventory ? 1 : 0);
+         ServerLevel serverLevel = serverPlayer.serverLevel();
+         LevelData levelData = serverLevel.getLevelData();
+         serverPlayer.connection.send(new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(serverLevel), b));
+-        serverPlayer.connection.teleport(serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), serverPlayer.getYRot(), serverPlayer.getXRot());
++        // serverPlayer.connection.teleport(serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), serverPlayer.getYRot(), serverPlayer.getXRot());
++        serverPlayer.connection.send(new ClientboundSetChunkCacheRadiusPacket(serverLevel.spigotConfig.viewDistance)); // Spigot
++        serverPlayer.connection.send(new ClientboundSetSimulationDistancePacket(serverLevel.spigotConfig.simulationDistance)); // Spigot
++        serverPlayer.connection.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(serverPlayer.position(), serverLevel.getWorld(), serverPlayer.getYRot(), serverPlayer.getXRot())); // CraftBukkit
+         serverPlayer.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
+         serverPlayer.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+         serverPlayer.connection
+@@ -448,10 +_,13 @@
+         this.sendActivePlayerEffects(serverPlayer);
+         this.sendLevelInfo(serverPlayer, level);
+         this.sendPlayerPermissionLevel(serverPlayer);
+-        level.addRespawnedPlayer(serverPlayer);
+-        this.players.add(serverPlayer);
+-        this.playersByUUID.put(serverPlayer.getUUID(), serverPlayer);
+-        serverPlayer.initInventoryMenu();
++        if (!serverPlayer.connection.isDisconnected()) {
++            level.addRespawnedPlayer(serverPlayer);
++            this.players.add(serverPlayer);
++            this.playersByName.put(serverPlayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT), serverPlayer); // Spigot
++            this.playersByUUID.put(serverPlayer.getUUID(), serverPlayer);
++        }
++        // serverPlayer.initInventoryMenu();
+         serverPlayer.setHealth(serverPlayer.getHealth());
+         BlockPos respawnPosition = serverPlayer.getRespawnPosition();
+         ServerLevel level1 = this.server.getLevel(serverPlayer.getRespawnDimension());
+@@ -472,7 +_,40 @@
+                         )
+                     );
+             }
+-        }
++            // Paper start - Add PlayerPostRespawnEvent
++            if (blockState.is(net.minecraft.tags.BlockTags.BEDS) && !teleportTransition.missingRespawnBlock()) {
++                isBedSpawn = true;
++            }
++            // Paper end - Add PlayerPostRespawnEvent
++        }
++        // Added from changeDimension
++        this.sendAllPlayerInfo(player); // Update health, etc...
++        player.onUpdateAbilities();
++        for (MobEffectInstance mobEffect : player.getActiveEffects()) {
++            player.connection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobEffect, false)); // blend = false
++        }
++
++        // Fire advancement trigger
++        player.triggerDimensionChangeTriggers(level);
++
++        // Don't fire on respawn
++        if (fromWorld != level) {
++            org.bukkit.event.player.PlayerChangedWorldEvent event = new org.bukkit.event.player.PlayerChangedWorldEvent(player.getBukkitEntity(), fromWorld.getWorld());
++            this.server.server.getPluginManager().callEvent(event);
++        }
++
++        // Save player file again if they were disconnected
++        if (player.connection.isDisconnected()) {
++            this.save(player);
++        }
++
++        // Paper start - Add PlayerPostRespawnEvent
++        if (isRespawn) {
++            cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(player.getBukkitEntity(), location, isBedSpawn));
++        }
++        // Paper end - Add PlayerPostRespawnEvent
++
++        // CraftBukkit end
+ 
+         return serverPlayer;
+     }
+@@ -482,24 +_,65 @@
+     }
+ 
+     public void sendActiveEffects(LivingEntity entity, ServerGamePacketListenerImpl connection) {
++        // Paper start - collect packets
++        this.sendActiveEffects(entity, connection::send);
++    }
++    public void sendActiveEffects(LivingEntity entity, java.util.function.Consumer<Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> packetConsumer) {
++        // Paper end - collect packets
+         for (MobEffectInstance mobEffectInstance : entity.getActiveEffects()) {
+-            connection.send(new ClientboundUpdateMobEffectPacket(entity.getId(), mobEffectInstance, false));
++            packetConsumer.accept(new ClientboundUpdateMobEffectPacket(entity.getId(), mobEffectInstance, false)); // Paper - collect packets
+         }
+     }
+ 
+     public void sendPlayerPermissionLevel(ServerPlayer player) {
++    // Paper start - avoid recalculating permissions if possible
++        this.sendPlayerPermissionLevel(player, true);
++    }
++
++    public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) {
++    // Paper end - avoid recalculating permissions if possible
+         GameProfile gameProfile = player.getGameProfile();
+         int profilePermissions = this.server.getProfilePermissions(gameProfile);
+-        this.sendPlayerPermissionLevel(player, profilePermissions);
++        this.sendPlayerPermissionLevel(player, profilePermissions, recalculatePermissions); // Paper - avoid recalculating permissions if possible
+     }
+ 
+     public void tick() {
+         if (++this.sendAllPlayerInfoIn > 600) {
+-            this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players));
++            // CraftBukkit start
++            for (int i = 0; i < this.players.size(); ++i) {
++                final ServerPlayer target = (ServerPlayer) this.players.get(i);
++
++                target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players.stream().filter(new Predicate<ServerPlayer>() {
++                    @Override
++                    public boolean test(ServerPlayer input) {
++                        return target.getBukkitEntity().canSee(input.getBukkitEntity());
++                    }
++                }).collect(java.util.stream.Collectors.toList())));
++            }
++            // CraftBukkit end
+             this.sendAllPlayerInfoIn = 0;
+         }
+     }
+ 
++    // CraftBukkit start - add a world/entity limited version
++    public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) {
++        for (int i = 0; i < this.players.size(); ++i) {
++            ServerPlayer entityplayer =  this.players.get(i);
++            if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
++                continue;
++            }
++            ((ServerPlayer) this.players.get(i)).connection.send(packet);
++        }
++    }
++
++    public void broadcastAll(Packet packet, Level world) {
++        for (int i = 0; i < world.players().size(); ++i) {
++            ((ServerPlayer) world.players().get(i)).connection.send(packet);
++        }
++
++    }
++    // CraftBukkit end
++
+     public void broadcastAll(Packet<?> packet) {
+         for (ServerPlayer serverPlayer : this.players) {
+             serverPlayer.connection.send(packet);
+@@ -575,6 +_,11 @@
+     }
+ 
+     private void sendPlayerPermissionLevel(ServerPlayer player, int permLevel) {
++        // Paper start - Add sendOpLevel API
++        this.sendPlayerPermissionLevel(player, permLevel, true);
++    }
++    public void sendPlayerPermissionLevel(ServerPlayer player, int permLevel, boolean recalculatePermissions) {
++        // Paper end - Add sendOpLevel API
+         if (player.connection != null) {
+             byte b;
+             if (permLevel <= 0) {
+@@ -588,11 +_,32 @@
+             player.connection.send(new ClientboundEntityEventPacket(player, b));
+         }
+ 
++        if (recalculatePermissions) { // Paper - Add sendOpLevel API
++        player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
+         this.server.getCommands().sendCommands(player);
++        } // Paper - Add sendOpLevel API
+     }
+ 
+     public boolean isWhiteListed(GameProfile profile) {
+-        return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile);
++        // Paper start - ProfileWhitelistVerifyEvent
++        return this.isWhiteListed(profile, null);
++    }
++    public boolean isWhiteListed(GameProfile gameprofile, @Nullable org.bukkit.event.player.PlayerLoginEvent loginEvent) {
++        boolean isOp = this.ops.contains(gameprofile);
++        boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile);
++        final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event;
++
++        final net.kyori.adventure.text.Component configuredMessage = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage);
++        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile), this.doWhiteList, isWhitelisted, isOp, configuredMessage);
++        event.callEvent();
++        if (!event.isWhitelisted()) {
++            if (loginEvent != null) {
++                loginEvent.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_WHITELIST, event.kickMessage() == null ? configuredMessage : event.kickMessage());
++            }
++            return false;
++        }
++        return true;
++        // Paper end - ProfileWhitelistVerifyEvent
+     }
+ 
+     public boolean isOp(GameProfile profile) {
+@@ -603,21 +_,17 @@
+ 
+     @Nullable
+     public ServerPlayer getPlayerByName(String username) {
+-        int size = this.players.size();
+-
+-        for (int i = 0; i < size; i++) {
+-            ServerPlayer serverPlayer = this.players.get(i);
+-            if (serverPlayer.getGameProfile().getName().equalsIgnoreCase(username)) {
+-                return serverPlayer;
+-            }
+-        }
+-
+-        return null;
++        return this.playersByName.get(username.toLowerCase(java.util.Locale.ROOT)); // Spigot
+     }
+ 
+     public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey<Level> dimension, Packet<?> packet) {
+         for (int i = 0; i < this.players.size(); i++) {
+             ServerPlayer serverPlayer = this.players.get(i);
++            // CraftBukkit start - Test if player receiving packet can see the source of the packet
++            if (except != null && !serverPlayer.getBukkitEntity().canSee(except.getBukkitEntity())) {
++               continue;
++            }
++            // CraftBukkit end
+             if (serverPlayer != except && serverPlayer.level().dimension() == dimension) {
+                 double d = x - serverPlayer.getX();
+                 double d1 = y - serverPlayer.getY();
+@@ -630,9 +_,11 @@
+     }
+ 
+     public void saveAll() {
++        io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
+         for (int i = 0; i < this.players.size(); i++) {
+             this.save(this.players.get(i));
+         }
++        return null; }); // Paper - ensure main
+     }
+ 
+     public UserWhiteList getWhiteList() {
+@@ -655,14 +_,18 @@
+     }
+ 
+     public void sendLevelInfo(ServerPlayer player, ServerLevel level) {
+-        WorldBorder worldBorder = this.server.overworld().getWorldBorder();
++        WorldBorder worldBorder = player.level().getWorldBorder(); // CraftBukkit
+         player.connection.send(new ClientboundInitializeBorderPacket(worldBorder));
+         player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
+         player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
+         if (level.isRaining()) {
+-            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
+-            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, level.getRainLevel(1.0F)));
+-            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
++            // CraftBukkit start - handle player weather
++            // player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
++            // player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, level.getRainLevel(1.0F)));
++            // player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
++            player.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false);
++            player.updateWeather(-level.rainLevel, level.rainLevel, -level.thunderLevel, level.thunderLevel);
++            // CraftBukkit end
+         }
+ 
+         player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F));
+@@ -671,8 +_,16 @@
+ 
+     public void sendAllPlayerInfo(ServerPlayer player) {
+         player.inventoryMenu.sendAllDataToRemote();
+-        player.resetSentInfo();
++        // entityplayer.resetSentInfo();
++        player.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange
++        player.refreshEntityData(player); // CraftBukkkit - SPIGOT-7218: sync metadata
+         player.connection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected));
++        // CraftBukkit start - from GameRules
++        int i = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23;
++        player.connection.send(new ClientboundEntityEventPacket(player, (byte) i));
++        float immediateRespawn = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN) ? 1.0F: 0.0F;
++        player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, immediateRespawn));
++        // CraftBukkit end
+     }
+ 
+     public int getPlayerCount() {
+@@ -688,6 +_,7 @@
+     }
+ 
+     public void setUsingWhiteList(boolean whitelistEnabled) {
++        new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); // Paper - WhitelistToggleEvent
+         this.doWhiteList = whitelistEnabled;
+     }
+ 
+@@ -725,10 +_,35 @@
+     }
+ 
+     public void removeAll() {
+-        for (int i = 0; i < this.players.size(); i++) {
+-            this.players.get(i).connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
+-        }
+-    }
++        // Paper start - Extract method to allow for restarting flag
++        this.removeAll(false);
++    }
++
++    public void removeAll(boolean isRestarting) {
++        // Paper end
++        // CraftBukkit start - disconnect safely
++        for (ServerPlayer player : this.players) {
++            if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here)
++            player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure
++        }
++        // CraftBukkit end
++
++        // Paper start - Configurable player collision; Remove collideRule team if it exists
++        if (this.collideRuleTeamName != null) {
++            final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
++            final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName);
++            if (team != null) scoreboard.removePlayerTeam(team);
++        }
++        // Paper end - Configurable player collision
++    }
++
++    // CraftBukkit start
++    public void broadcastMessage(Component[] iChatBaseComponents) {
++        for (Component component : iChatBaseComponents) {
++            this.broadcastSystemMessage(component, false);
++        }
++    }
++    // CraftBukkit end
+ 
+     public void broadcastSystemMessage(Component message, boolean bypassHiddenChat) {
+         this.broadcastSystemMessage(message, serverPlayer -> message, bypassHiddenChat);
+@@ -750,20 +_,39 @@
+     }
+ 
+     public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound boundChatType) {
+-        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, boundChatType);
++        // Paper start
++        this.broadcastChatMessage(message, sender, boundChatType, null);
++    }
++    public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound boundChatType, @Nullable Function<net.kyori.adventure.audience.Audience, Component> unsignedFunction) {
++        // Paper end
++        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, boundChatType, unsignedFunction); // Paper
+     }
+ 
+     private void broadcastChatMessage(
+         PlayerChatMessage message, Predicate<ServerPlayer> shouldFilterMessageTo, @Nullable ServerPlayer sender, ChatType.Bound boundChatType
+     ) {
++        // Paper start
++        this.broadcastChatMessage(message, shouldFilterMessageTo, sender, boundChatType, null);
++    }
++    public void broadcastChatMessage(PlayerChatMessage message, Predicate<ServerPlayer> shouldFilterMessageTo, @Nullable ServerPlayer sender, ChatType.Bound boundChatType, @Nullable Function<net.kyori.adventure.audience.Audience, Component> unsignedFunction) {
++        // Paper end
+         boolean flag = this.verifyChatTrusted(message);
+-        this.server.logChatMessage(message.decoratedContent(), boundChatType, flag ? null : "Not Secure");
++        this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), boundChatType, flag ? null : "Not Secure"); // Paper
+         OutgoingChatMessage outgoingChatMessage = OutgoingChatMessage.create(message);
+         boolean flag1 = false;
+ 
++        Packet<?> disguised = sender != null && unsignedFunction == null ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(outgoingChatMessage.content(), boundChatType) : null; // Paper - don't send player chat packets from vanished players
+         for (ServerPlayer serverPlayer : this.players) {
+             boolean flag2 = shouldFilterMessageTo.test(serverPlayer);
+-            serverPlayer.sendChatMessage(outgoingChatMessage, flag2, boundChatType);
++            // Paper start - don't send player chat packets from vanished players
++            if (sender != null && !serverPlayer.getBukkitEntity().canSee(sender.getBukkitEntity())) {
++                serverPlayer.connection.send(unsignedFunction != null
++                    ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(unsignedFunction.apply(serverPlayer.getBukkitEntity()), boundChatType)
++                    : disguised);
++                continue;
++            }
++            // Paper end
++            sender.sendChatMessage(outgoingChatMessage, flag2, boundChatType, unsignedFunction == null ? null : unsignedFunction.apply(serverPlayer.getBukkitEntity())); // Paper
+             flag1 |= flag2 && message.isFullyFiltered();
+         }
+ 
+@@ -772,18 +_,25 @@
+         }
+     }
+ 
+-    private boolean verifyChatTrusted(PlayerChatMessage message) {
++    public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public
+         return message.hasSignature() && !message.hasExpiredServer(Instant.now());
+     }
+ 
+-    public ServerStatsCounter getPlayerStats(Player player) {
+-        UUID uuid = player.getUUID();
+-        ServerStatsCounter serverStatsCounter = this.stats.get(uuid);
++    // CraftBukkit start
++    public ServerStatsCounter getPlayerStats(ServerPlayer player) {
++        ServerStatsCounter serverstatisticmanager = player.getStats();
++        return serverstatisticmanager == null ? this.getPlayerStats(player.getUUID(), player.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name
++    }
++
++    public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) {
++        ServerPlayer player = this.getPlayer(uuid);
++        ServerStatsCounter serverStatsCounter = player == null ? null : player.getStats();
++        // CraftBukkit end
+         if (serverStatsCounter == null) {
+             File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile();
+             File file1 = new File(file, uuid + ".json");
+             if (!file1.exists()) {
+-                File file2 = new File(file, player.getName().getString() + ".json");
++                File file2 = new File(file, displayName + ".json"); // CraftBukkit
+                 Path path = file2.toPath();
+                 if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file2.isFile()) {
+                     file2.renameTo(file1);
+@@ -791,7 +_,7 @@
+             }
+ 
+             serverStatsCounter = new ServerStatsCounter(this.server, file1);
+-            this.stats.put(uuid, serverStatsCounter);
++            // this.stats.put(uuid, serverStatsCounter); // CraftBukkit
+         }
+ 
+         return serverStatsCounter;
+@@ -799,11 +_,11 @@
+ 
+     public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) {
+         UUID uuid = player.getUUID();
+-        PlayerAdvancements playerAdvancements = this.advancements.get(uuid);
++        PlayerAdvancements playerAdvancements = player.getAdvancements(); // CraftBukkit
+         if (playerAdvancements == null) {
+             Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(uuid + ".json");
+             playerAdvancements = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player);
+-            this.advancements.put(uuid, playerAdvancements);
++            // this.advancements.put(uuid, playerAdvancements); // CraftBukkit
+         }
+ 
+         playerAdvancements.setPlayer(player);
+@@ -846,11 +_,34 @@
+     }
+ 
+     public void reloadResources() {
+-        for (PlayerAdvancements playerAdvancements : this.advancements.values()) {
+-            playerAdvancements.reload(this.server.getAdvancements());
++        // Paper start - API for updating recipes on clients
++        this.reloadAdvancementData();
++        this.reloadTagData();
++        this.reloadRecipes();
++    }
++    public void reloadAdvancementData() {
++        // Paper end - API for updating recipes on clients
++        // CraftBukkit start
++        // for (PlayerAdvancements playerAdvancements : this.advancements.values()) {
++        //     playerAdvancements.reload(this.server.getAdvancements());
++        // }
++        for (ServerPlayer player : this.players) {
++            player.getAdvancements().reload(this.server.getAdvancements());
++            player.getAdvancements().flushDirty(player); // CraftBukkit - trigger immediate flush of advancements
+         }
++        // CraftBukkit end
+ 
++        // Paper start - API for updating recipes on clients
++    }
++    public void reloadTagData() {
+         this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
++        // CraftBukkit start
++        // this.reloadRecipes(); // Paper - do not reload recipes just because tag data was reloaded
++        // Paper end - API for updating recipes on clients
++    }
++
++    public void reloadRecipes() {
++        // CraftBukkit end
+         RecipeManager recipeManager = this.server.getRecipeManager();
+         ClientboundUpdateRecipesPacket clientboundUpdateRecipesPacket = new ClientboundUpdateRecipesPacket(
+             recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()
diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/PlayerList.java.patch
deleted file mode 100644
index 06288fc4a0..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/players/PlayerList.java.patch
+++ /dev/null
@@ -1,1276 +0,0 @@
---- a/net/minecraft/server/players/PlayerList.java
-+++ b/net/minecraft/server/players/PlayerList.java
-@@ -76,6 +76,7 @@
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.server.network.CommonListenerCookie;
- import net.minecraft.server.network.ServerGamePacketListenerImpl;
-+import net.minecraft.server.network.ServerLoginPacketListenerImpl;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
- import net.minecraft.stats.ServerStatsCounter;
-@@ -84,7 +85,6 @@
- import net.minecraft.world.effect.MobEffectInstance;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.LivingEntity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.projectile.ThrownEnderpearl;
- import net.minecraft.world.item.crafting.RecipeManager;
- import net.minecraft.world.level.GameRules;
-@@ -104,6 +104,26 @@
- import net.minecraft.world.scores.PlayerTeam;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.stream.Collectors;
-+import net.minecraft.server.dedicated.DedicatedServer;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.CraftServer;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.util.CraftChatMessage;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerChangedWorldEvent;
-+import org.bukkit.event.player.PlayerJoinEvent;
-+import org.bukkit.event.player.PlayerLoginEvent;
-+import org.bukkit.event.player.PlayerQuitEvent;
-+import org.bukkit.event.player.PlayerRespawnEvent;
-+import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
-+import org.bukkit.event.player.PlayerSpawnChangeEvent;
-+// CraftBukkit end
-+
- public abstract class PlayerList {
- 
-     public static final File USERBANLIST_FILE = new File("banned-players.json");
-@@ -116,14 +136,16 @@
-     private static final int SEND_PLAYER_INFO_INTERVAL = 600;
-     private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
-     private final MinecraftServer server;
--    public final List<ServerPlayer> players = Lists.newArrayList();
-+    public final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
-     private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap();
-     private final UserBanList bans;
-     private final IpBanList ipBans;
-     private final ServerOpList ops;
-     private final UserWhiteList whitelist;
--    private final Map<UUID, ServerStatsCounter> stats;
--    private final Map<UUID, PlayerAdvancements> advancements;
-+    // CraftBukkit start
-+    // private final Map<UUID, ServerStatisticManager> stats;
-+    // private final Map<UUID, AdvancementDataPlayer> advancements;
-+    // CraftBukkit end
-     public final PlayerDataStorage playerIo;
-     private boolean doWhiteList;
-     private final LayeredRegistryAccess<RegistryLayer> registries;
-@@ -134,58 +156,137 @@
-     private static final boolean ALLOW_LOGOUTIVATOR = false;
-     private int sendAllPlayerInfoIn;
- 
-+    // CraftBukkit start
-+    private CraftServer cserver;
-+    private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>();
-+    public @Nullable String collideRuleTeamName; // Paper - Configurable player collision
-+
-     public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registryManager, PlayerDataStorage saveHandler, int maxPlayers) {
-+        this.cserver = server.server = new CraftServer((DedicatedServer) server, this);
-+        server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper
-+        // CraftBukkit end
-+
-         this.bans = new UserBanList(PlayerList.USERBANLIST_FILE);
-         this.ipBans = new IpBanList(PlayerList.IPBANLIST_FILE);
-         this.ops = new ServerOpList(PlayerList.OPLIST_FILE);
-         this.whitelist = new UserWhiteList(PlayerList.WHITELIST_FILE);
--        this.stats = Maps.newHashMap();
--        this.advancements = Maps.newHashMap();
-+        // CraftBukkit start
-+        // this.stats = Maps.newHashMap();
-+        // this.advancements = Maps.newHashMap();
-+        // CraftBukkit end
-         this.server = server;
-         this.registries = registryManager;
-         this.maxPlayers = maxPlayers;
-         this.playerIo = saveHandler;
-     }
-+    abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
- 
-     public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
-+        player.isRealPlayer = true; // Paper
-+        player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
-         GameProfile gameprofile = player.getGameProfile();
-         GameProfileCache usercache = this.server.getProfileCache();
--        Optional optional;
-+        // Optional optional; // CraftBukkit - decompile error
-         String s;
- 
-         if (usercache != null) {
--            optional = usercache.get(gameprofile.getId());
-+            Optional<GameProfile> optional = usercache.get(gameprofile.getId()); // CraftBukkit - decompile error
-             s = (String) optional.map(GameProfile::getName).orElse(gameprofile.getName());
-             usercache.add(gameprofile);
-         } else {
-             s = gameprofile.getName();
-         }
- 
--        optional = this.load(player);
--        ResourceKey<Level> resourcekey = (ResourceKey) optional.flatMap((nbttagcompound) -> {
--            DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension")));
-+        Optional<CompoundTag> optional = this.load(player); // CraftBukkit - decompile error
-+        ResourceKey<Level> resourcekey = null; // Paper
-+        // CraftBukkit start - Better rename detection
-+        if (optional.isPresent()) {
-+            CompoundTag nbttagcompound = optional.get();
-+            if (nbttagcompound.contains("bukkit")) {
-+                CompoundTag bukkit = nbttagcompound.getCompound("bukkit");
-+                s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s;
-+            }
-+        }
-+        // CraftBukkit end
-+        // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found
-+        boolean[] invalidPlayerWorld = {false};
-+        bukkitData: if (optional.isPresent()) {
-+            // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
-+            final org.bukkit.World bWorld;
-+            if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) {
-+                bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast")));
-+            } else if (optional.get().contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name
-+                bWorld = org.bukkit.Bukkit.getServer().getWorld(optional.get().getString("world"));
-+            } else {
-+                break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section
-+            }
-+            if (bWorld != null) {
-+                resourcekey = ((CraftWorld) bWorld).getHandle().dimension();
-+            } else {
-+                resourcekey = Level.OVERWORLD;
-+                invalidPlayerWorld[0] = true;
-+            }
-+        }
-+        if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data
-+        // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers
-+        resourcekey = optional.flatMap((nbttagcompound) -> {
-+            // Paper end
-+            DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error
-             Logger logger = PlayerList.LOGGER;
- 
-             Objects.requireNonNull(logger);
--            return dataresult.resultOrPartial(logger::error);
--        }).orElse(Level.OVERWORLD);
-+            // Paper start - reset to main world spawn if no valid world is found
-+            final Optional<ResourceKey<Level>> result = dataresult.resultOrPartial(logger::error);
-+            invalidPlayerWorld[0] = result.isEmpty();
-+            return result;
-+        }).orElse(Level.OVERWORLD); // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed
-+        }
-+        // Paper end
-         ServerLevel worldserver = this.server.getLevel(resourcekey);
-         ServerLevel worldserver1;
- 
-         if (worldserver == null) {
-             PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey);
-             worldserver1 = this.server.overworld();
-+            invalidPlayerWorld[0] = true; // Paper - reset to main world if no world with parsed value is found
-         } else {
-             worldserver1 = worldserver;
-         }
- 
-+        // Paper start - Entity#getEntitySpawnReason
-+        if (optional.isEmpty()) {
-+            player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login
-+            // Paper start - reset to main world spawn if first spawn or invalid world
-+        }
-+        if (optional.isEmpty() || invalidPlayerWorld[0]) {
-+            // Paper end - reset to main world spawn if first spawn or invalid world
-+            player.moveTo(player.adjustSpawnLocation(worldserver1, worldserver1.getSharedSpawnPos()).getBottomCenter(), worldserver1.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored
-+        }
-+        // Paper end - Entity#getEntitySpawnReason
-         player.setServerLevel(worldserver1);
-         String s1 = connection.getLoggableAddress(this.server.logIPs());
- 
--        PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{player.getName().getString(), s1, player.getId(), player.getX(), player.getY(), player.getZ()});
-+        // Spigot start - spawn location event
-+        Player spawnPlayer = player.getBukkitEntity();
-+        org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new org.spigotmc.event.player.PlayerSpawnLocationEvent(spawnPlayer, spawnPlayer.getLocation());
-+        this.cserver.getPluginManager().callEvent(ev);
-+
-+        Location loc = ev.getSpawnLocation();
-+        worldserver1 = ((CraftWorld) loc.getWorld()).getHandle();
-+
-+        player.spawnIn(worldserver1);
-+        player.gameMode.setLevel((ServerLevel) player.level());
-+        // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world)
-+        player.setPosRaw(loc.getX(), loc.getY(), loc.getZ());
-+        player.setRot(loc.getYaw(), loc.getPitch());
-+        // Paper end - set raw so we aren't fully joined to the world
-+        // Spigot end
-+
-+        // CraftBukkit - Moved message to after join
-+        // PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{entityplayer.getName().getString(), s1, entityplayer.getId(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()});
-         LevelData worlddata = worldserver1.getLevelData();
- 
--        player.loadGameTypes((CompoundTag) optional.orElse((Object) null));
-+        player.loadGameTypes((CompoundTag) optional.orElse(null)); // CraftBukkit - decompile error
-         ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, clientData);
- 
-         connection.setupInboundProtocol(GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess())), playerconnection);
-@@ -194,7 +295,9 @@
-         boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
-         boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
- 
--        playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), this.viewDistance, this.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile()));
-+        // Spigot - view distance
-+        playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.enforceSecureProfile()));
-+        player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
-         playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-         playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities()));
-         playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected));
-@@ -213,8 +316,10 @@
-         } else {
-             ichatmutablecomponent = Component.translatable("multiplayer.player.joined.renamed", player.getDisplayName(), s);
-         }
-+        // CraftBukkit start
-+        ichatmutablecomponent.withStyle(ChatFormatting.YELLOW);
-+        Component joinMessage = ichatmutablecomponent; // Paper - Adventure
- 
--        this.broadcastSystemMessage(ichatmutablecomponent.withStyle(ChatFormatting.YELLOW), false);
-         playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
-         ServerStatus serverping = this.server.getStatus();
- 
-@@ -222,17 +327,109 @@
-             player.sendServerStatus(serverping);
-         }
- 
--        player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players));
-+        // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
-         this.players.add(player);
-+        this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
-         this.playersByUUID.put(player.getUUID(), player);
--        this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)));
--        this.sendLevelInfo(player, worldserver1);
-+        // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below
-+
-+        // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks
-+        player.supressTrackerForLogin = true;
-         worldserver1.addNewPlayer(player);
--        this.server.getCustomBossEvents().onPlayerConnect(player);
--        this.sendActivePlayerEffects(player);
-+        this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer);
-         player.loadAndSpawnEnderpearls(optional);
-         player.loadAndSpawnParentVehicle(optional);
-+        // Paper end - Fire PlayerJoinEvent when Player is actually ready
-+        // CraftBukkit start
-+        CraftPlayer bukkitPlayer = player.getBukkitEntity();
-+
-+        // Ensure that player inventory is populated with its viewer
-+        player.containerMenu.transferTo(player.containerMenu, bukkitPlayer);
-+
-+        PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(bukkitPlayer, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure
-+        this.cserver.getPluginManager().callEvent(playerJoinEvent);
-+
-+        if (!player.connection.isAcceptingMessages()) {
-+            return;
-+        }
-+
-+        final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
-+
-+        if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
-+            joinMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure
-+            this.server.getPlayerList().broadcastSystemMessage(joinMessage, false); // Paper - Adventure
-+        }
-+        // CraftBukkit end
-+
-+        // CraftBukkit start - sendAll above replaced with this loop
-+        ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player
-+
-+        final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join
-+        for (int i = 0; i < this.players.size(); ++i) {
-+            ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i);
-+
-+            if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) {
-+                // Paper start - Add Listing API for Player
-+                if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) {
-+                // Paper end - Add Listing API for Player
-+                entityplayer1.connection.send(packet);
-+                // Paper start - Add Listing API for Player
-+                } else {
-+                    entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false));
-+                }
-+                // Paper end - Add Listing API for Player
-+            }
-+
-+            if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player
-+                continue;
-+            }
-+
-+            onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join
-+        }
-+        // Paper start - Use single player info update packet on join
-+        if (!onlinePlayers.isEmpty()) {
-+            player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player
-+        }
-+        // Paper end - Use single player info update packet on join
-+        player.sentListPacket = true;
-+        player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
-+        ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now
-+        // CraftBukkit end
-+
-+        //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE
-+
-+        this.sendLevelInfo(player, worldserver1);
-+
-+        // CraftBukkit start - Only add if the player wasn't moved in the event
-+        if (player.level() == worldserver1 && !worldserver1.players().contains(player)) {
-+            worldserver1.addNewPlayer(player);
-+            this.server.getCustomBossEvents().onPlayerConnect(player);
-+        }
-+
-+        worldserver1 = player.serverLevel(); // CraftBukkit - Update in case join event changed it
-+        // CraftBukkit end
-+        this.sendActivePlayerEffects(player);
-+        // Paper - move loading pearls / parent vehicle up
-         player.initInventoryMenu();
-+        // CraftBukkit - Moved from above, added world
-+        // Paper start - Configurable player collision; Add to collideRule team if needed
-+        final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
-+        final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName);
-+        if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) {
-+            scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
-+        }
-+        // Paper end - Configurable player collision
-+        PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
-+        // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead
-+        if (player.isDeadOrDying()) {
-+            net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
-+                    .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
-+            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
-+                    new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains),
-+                    worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null)
-+            );
-+        }
-+        // Paper end - Send empty chunk
-     }
- 
-     public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) {
-@@ -269,30 +466,31 @@
-     }
- 
-     public void addWorldborderListener(ServerLevel world) {
-+        if (this.playerIo != null) return; // CraftBukkit
-         world.getWorldBorder().addListener(new BorderChangeListener() {
-             @Override
-             public void onBorderSizeSet(WorldBorder border, double size) {
--                PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border));
-+                PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); // CraftBukkit
-             }
- 
-             @Override
-             public void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) {
--                PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border));
-+                PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); // CraftBukkit
-             }
- 
-             @Override
-             public void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) {
--                PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border));
-+                PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); // CraftBukkit
-             }
- 
-             @Override
-             public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
--                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border));
-+                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); // CraftBukkit
-             }
- 
-             @Override
-             public void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) {
--                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border));
-+                PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); // CraftBukkit
-             }
- 
-             @Override
-@@ -319,14 +517,15 @@
-     }
- 
-     protected void save(ServerPlayer player) {
-+        if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
-         this.playerIo.save(player);
--        ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) this.stats.get(player.getUUID());
-+        ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
- 
-         if (serverstatisticmanager != null) {
-             serverstatisticmanager.save();
-         }
- 
--        PlayerAdvancements advancementdataplayer = (PlayerAdvancements) this.advancements.get(player.getUUID());
-+        PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
- 
-         if (advancementdataplayer != null) {
-             advancementdataplayer.save();
-@@ -334,95 +533,216 @@
- 
-     }
- 
--    public void remove(ServerPlayer player) {
--        ServerLevel worldserver = player.serverLevel();
-+    public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component
-+        // Paper start - Fix kick event leave message not being sent
-+        return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())));
-+    }
-+    public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) {
-+        // Paper end - Fix kick event leave message not being sent
-+        ServerLevel worldserver = entityplayer.serverLevel();
- 
--        player.awardStat(Stats.LEAVE_GAME);
--        this.save(player);
--        if (player.isPassenger()) {
--            Entity entity = player.getRootVehicle();
-+        entityplayer.awardStat(Stats.LEAVE_GAME);
- 
-+        // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
-+        // See SPIGOT-5799, SPIGOT-6145
-+        if (entityplayer.containerMenu != entityplayer.inventoryMenu) {
-+            entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason
-+        }
-+
-+        PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - Adventure & Add API for quit reason
-+        this.cserver.getPluginManager().callEvent(playerQuitEvent);
-+        entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
-+
-+        entityplayer.doTick(); // SPIGOT-924
-+        // CraftBukkit end
-+
-+        // Paper start - Configurable player collision; Remove from collideRule team if needed
-+        if (this.collideRuleTeamName != null) {
-+            final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard();
-+            final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName);
-+            if (entityplayer.getTeam() == team && team != null) {
-+                scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team);
-+            }
-+        }
-+        // Paper end - Configurable player collision
-+
-+        // Paper - Drop carried item when player has disconnected
-+        if (!entityplayer.containerMenu.getCarried().isEmpty()) {
-+            net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried();
-+            entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY);
-+            entityplayer.drop(carried, false);
-+        }
-+        // Paper end - Drop carried item when player has disconnected
-+
-+        this.save(entityplayer);
-+        if (entityplayer.isPassenger()) {
-+            Entity entity = entityplayer.getRootVehicle();
-+
-             if (entity.hasExactlyOnePlayerPassenger()) {
-                 PlayerList.LOGGER.debug("Removing player mount");
--                player.stopRiding();
-+                entityplayer.stopRiding();
-                 entity.getPassengersAndSelf().forEach((entity1) -> {
--                    entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
-+                    // Paper start - Fix villager boat exploit
-+                    if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) {
-+                        final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer();
-+                        if (human != null) {
-+                            villager.setTradingPlayer(null);
-+                        }
-+                    }
-+                    // Paper end - Fix villager boat exploit
-+                    entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause
-                 });
-             }
-         }
- 
--        player.unRide();
--        Iterator iterator = player.getEnderPearls().iterator();
-+        entityplayer.unRide();
-+        Iterator iterator = entityplayer.getEnderPearls().iterator();
- 
-         while (iterator.hasNext()) {
-             ThrownEnderpearl entityenderpearl = (ThrownEnderpearl) iterator.next();
- 
--            entityenderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
-+            // Paper start - Allow using old ender pearl behavior
-+            if (!entityenderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) {
-+            entityenderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); // CraftBukkit - add Bukkit remove cause
-+            } else {
-+                entityenderpearl.cachedOwner = null;
-+            }
-+            // Paper end - Allow using old ender pearl behavior
-         }
- 
--        worldserver.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
--        player.getAdvancements().stopListening();
--        this.players.remove(player);
--        this.server.getCustomBossEvents().onPlayerDisconnect(player);
--        UUID uuid = player.getUUID();
-+        worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
-+        entityplayer.retireScheduler(); // Paper - Folia schedulers
-+        entityplayer.getAdvancements().stopListening();
-+        this.players.remove(entityplayer);
-+        this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
-+        this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer);
-+        UUID uuid = entityplayer.getUUID();
-         ServerPlayer entityplayer1 = (ServerPlayer) this.playersByUUID.get(uuid);
- 
--        if (entityplayer1 == player) {
-+        if (entityplayer1 == entityplayer) {
-             this.playersByUUID.remove(uuid);
--            this.stats.remove(uuid);
--            this.advancements.remove(uuid);
-+            // CraftBukkit start
-+            // this.stats.remove(uuid);
-+            // this.advancements.remove(uuid);
-+            // CraftBukkit end
-         }
- 
--        this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
-+        // CraftBukkit start
-+        // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID())));
-+        ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID()));
-+        for (int i = 0; i < this.players.size(); i++) {
-+            ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i);
-+
-+            if (entityplayer2.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) {
-+                entityplayer2.connection.send(packet);
-+            } else {
-+                entityplayer2.getBukkitEntity().onEntityRemove(entityplayer);
-+            }
-+        }
-+        // This removes the scoreboard (and player reference) for the specific player in the manager
-+        this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
-+        // CraftBukkit end
-+
-+        return playerQuitEvent.quitMessage(); // Paper - Adventure
-     }
- 
--    @Nullable
--    public Component canPlayerLogin(SocketAddress address, GameProfile profile) {
-+    // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
-+    public ServerPlayer canPlayerLogin(ServerLoginPacketListenerImpl loginlistener, GameProfile gameprofile) {
-         MutableComponent ichatmutablecomponent;
- 
--        if (this.bans.isBanned(profile)) {
--            UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(profile);
-+        // Moved from processLogin
-+        UUID uuid = gameprofile.getId();
-+        List<ServerPlayer> list = Lists.newArrayList();
- 
-+        ServerPlayer entityplayer;
-+
-+        for (int i = 0; i < this.players.size(); ++i) {
-+            entityplayer = (ServerPlayer) this.players.get(i);
-+            if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames
-+                list.add(entityplayer);
-+            }
-+        }
-+
-+        Iterator iterator = list.iterator();
-+
-+        while (iterator.hasNext()) {
-+            entityplayer = (ServerPlayer) iterator.next();
-+            this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
-+            entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause
-+        }
-+
-+        // Instead of kicking then returning, we need to store the kick reason
-+        // in the event, check with plugins to see if it's ok, and THEN kick
-+        // depending on the outcome.
-+        SocketAddress socketaddress = loginlistener.connection.getRemoteAddress();
-+
-+        ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile, ClientInformation.createDefault());
-+        entity.transferCookieConnection = loginlistener;
-+        Player player = entity.getBukkitEntity();
-+        PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress());
-+
-+        // Paper start - Fix MC-158900
-+        UserBanListEntry gameprofilebanentry;
-+        if (this.bans.isBanned(gameprofile) && (gameprofilebanentry = this.bans.get(gameprofile)) != null) {
-+            // Paper end - Fix MC-158900
-+
-             ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason());
-             if (gameprofilebanentry.getExpires() != null) {
-                 ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires())));
-             }
- 
--            return ichatmutablecomponent;
--        } else if (!this.isWhiteListed(profile)) {
--            return Component.translatable("multiplayer.disconnect.not_whitelisted");
--        } else if (this.ipBans.isBanned(address)) {
--            IpBanListEntry ipbanentry = this.ipBans.get(address);
-+            // return chatmessage;
-+            event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure
-+        } else if (!this.isWhiteListed(gameprofile, event)) { // Paper - ProfileWhitelistVerifyEvent
-+            //ichatmutablecomponent = Component.translatable("multiplayer.disconnect.not_whitelisted"); // Paper
-+            //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted
-+        } else if (this.getIpBans().isBanned(socketaddress) && getIpBans().get(socketaddress) != null && !this.getIpBans().get(socketaddress).hasExpired()) { // Paper - fix NPE with temp ip bans
-+            IpBanListEntry ipbanentry = this.ipBans.get(socketaddress);
- 
-             ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason());
-             if (ipbanentry.getExpires() != null) {
-                 ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned_ip.expiration", PlayerList.BAN_DATE_FORMAT.format(ipbanentry.getExpires())));
-             }
- 
--            return ichatmutablecomponent;
-+            // return chatmessage;
-+            event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure
-         } else {
--            return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(profile) ? Component.translatable("multiplayer.disconnect.server_full") : null;
-+            // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null;
-+            if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) {
-+                event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
-+            }
-         }
-+
-+        this.cserver.getPluginManager().callEvent(event);
-+        if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
-+            loginlistener.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure
-+            return null;
-+        }
-+        return entity;
-     }
- 
--    public ServerPlayer getPlayerForLogin(GameProfile profile, ClientInformation syncedOptions) {
--        return new ServerPlayer(this.server, this.server.overworld(), profile, syncedOptions);
-+    // CraftBukkit start - added EntityPlayer
-+    public ServerPlayer getPlayerForLogin(GameProfile gameprofile, ClientInformation clientinformation, ServerPlayer player) {
-+        player.updateOptions(clientinformation);
-+        return player;
-+        // CraftBukkit end
-     }
- 
--    public boolean disconnectAllPlayersWithProfile(GameProfile profile) {
--        UUID uuid = profile.getId();
--        Set<ServerPlayer> set = Sets.newIdentityHashSet();
-+    public boolean disconnectAllPlayersWithProfile(GameProfile gameprofile, ServerPlayer player) { // CraftBukkit - added EntityPlayer
-+        /* CraftBukkit startMoved up
-+        UUID uuid = gameprofile.getId();
-+        Set<EntityPlayer> set = Sets.newIdentityHashSet();
-         Iterator iterator = this.players.iterator();
- 
-         while (iterator.hasNext()) {
--            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-+            EntityPlayer entityplayer = (EntityPlayer) iterator.next();
- 
-             if (entityplayer.getUUID().equals(uuid)) {
-                 set.add(entityplayer);
-             }
-         }
- 
--        ServerPlayer entityplayer1 = (ServerPlayer) this.playersByUUID.get(profile.getId());
-+        EntityPlayer entityplayer1 = (EntityPlayer) this.playersByUUID.get(gameprofile.getId());
- 
-         if (entityplayer1 != null) {
-             set.add(entityplayer1);
-@@ -431,72 +751,160 @@
-         Iterator iterator1 = set.iterator();
- 
-         while (iterator1.hasNext()) {
--            ServerPlayer entityplayer2 = (ServerPlayer) iterator1.next();
-+            EntityPlayer entityplayer2 = (EntityPlayer) iterator1.next();
- 
-             entityplayer2.connection.disconnect(PlayerList.DUPLICATE_LOGIN_DISCONNECT_MESSAGE);
-         }
- 
-         return !set.isEmpty();
-+        */
-+        return player == null;
-+        // CraftBukkit end
-     }
- 
--    public ServerPlayer respawn(ServerPlayer player, boolean alive, Entity.RemovalReason removalReason) {
--        this.players.remove(player);
--        player.serverLevel().removePlayerImmediately(player, removalReason);
--        TeleportTransition teleporttransition = player.findRespawnPositionAndUseSpawnBlock(!alive, TeleportTransition.DO_NOTHING);
--        ServerLevel worldserver = teleporttransition.newLevel();
--        ServerPlayer entityplayer1 = new ServerPlayer(this.server, worldserver, player.getGameProfile(), player.clientInformation());
-+    // CraftBukkit start
-+    public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason) {
-+        return this.respawn(entityplayer, flag, entity_removalreason, reason, null);
-+    }
- 
--        entityplayer1.connection = player.connection;
--        entityplayer1.restoreFrom(player, alive);
--        entityplayer1.setId(player.getId());
--        entityplayer1.setMainArm(player.getMainArm());
-+    public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason, Location location) {
-+        entityplayer.stopRiding(); // CraftBukkit
-+        this.players.remove(entityplayer);
-+        this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
-+        entityplayer.serverLevel().removePlayerImmediately(entityplayer, entity_removalreason);
-+        /* CraftBukkit start
-+        TeleportTransition teleporttransition = entityplayer.findRespawnPositionAndUseSpawnBlock(!flag, TeleportTransition.DO_NOTHING);
-+        WorldServer worldserver = teleporttransition.newLevel();
-+        EntityPlayer entityplayer1 = new EntityPlayer(this.server, worldserver, entityplayer.getGameProfile(), entityplayer.clientInformation());
-+        // */
-+        ServerPlayer entityplayer1 = entityplayer;
-+        Level fromWorld = entityplayer.level();
-+        entityplayer.wonGame = false;
-+        // CraftBukkit end
-+
-+        entityplayer1.connection = entityplayer.connection;
-+        entityplayer1.restoreFrom(entityplayer, flag);
-+        entityplayer1.setId(entityplayer.getId());
-+        entityplayer1.setMainArm(entityplayer.getMainArm());
-+        // CraftBukkit - not required, just copies old location into reused entity
-+        /*
-         if (!teleporttransition.missingRespawnBlock()) {
--            entityplayer1.copyRespawnPosition(player);
-+            entityplayer1.copyRespawnPosition(entityplayer);
-         }
-+         */
-+        // CraftBukkit end
- 
--        Iterator iterator = player.getTags().iterator();
-+        Iterator iterator = entityplayer.getTags().iterator();
- 
-         while (iterator.hasNext()) {
-             String s = (String) iterator.next();
- 
-             entityplayer1.addTag(s);
-         }
-+        // Paper start - Add PlayerPostRespawnEvent
-+        boolean isBedSpawn = false;
-+        boolean isRespawn = false;
-+        // Paper end - Add PlayerPostRespawnEvent
- 
-+        // CraftBukkit start - fire PlayerRespawnEvent
-+        TeleportTransition teleporttransition;
-+        if (location == null) {
-+            teleporttransition = entityplayer.findRespawnPositionAndUseSpawnBlock(!flag, TeleportTransition.DO_NOTHING, reason);
-+
-+            if (!flag) entityplayer.reset(); // SPIGOT-4785
-+           // Paper start - Add PlayerPostRespawnEvent
-+           if (teleporttransition == null) return entityplayer; // Early exit, mirrors belows early return for disconnected players in respawn event
-+           isRespawn = true;
-+           location = CraftLocation.toBukkit(teleporttransition.position(), teleporttransition.newLevel().getWorld(), teleporttransition.yRot(), teleporttransition.xRot());
-+           // Paper end - Add PlayerPostRespawnEvent
-+        } else {
-+            teleporttransition = new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), TeleportTransition.DO_NOTHING);
-+        }
-+        // Spigot Start
-+        if (teleporttransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event
-+            return entityplayer;
-+        }
-+        // Spigot End
-+        ServerLevel worldserver = teleporttransition.newLevel();
-+        entityplayer1.spawnIn(worldserver);
-+        entityplayer1.unsetRemoved();
-+        entityplayer1.setShiftKeyDown(false);
-         Vec3 vec3d = teleporttransition.position();
- 
--        entityplayer1.moveTo(vec3d.x, vec3d.y, vec3d.z, teleporttransition.yRot(), teleporttransition.xRot());
-+        entityplayer1.forceSetPositionRotation(vec3d.x, vec3d.y, vec3d.z, teleporttransition.yRot(), teleporttransition.xRot());
-+        worldserver.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(vec3d.x()) >> 4, net.minecraft.util.Mth.floor(vec3d.z()) >> 4), 1, entityplayer.getId()); // Paper - post teleport ticket type
-+        // CraftBukkit end
-         if (teleporttransition.missingRespawnBlock()) {
-             entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
-+            entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent
-         }
- 
--        int i = alive ? 1 : 0;
-+        int i = flag ? 1 : 0;
-         ServerLevel worldserver1 = entityplayer1.serverLevel();
-         LevelData worlddata = worldserver1.getLevelData();
- 
-         entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver1), (byte) i));
--        entityplayer1.connection.teleport(entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot());
-+        entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
-+        entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot
-+        entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit
-         entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle()));
-         entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-         entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel));
-         this.sendActivePlayerEffects(entityplayer1);
-         this.sendLevelInfo(entityplayer1, worldserver);
-         this.sendPlayerPermissionLevel(entityplayer1);
--        worldserver.addRespawnedPlayer(entityplayer1);
--        this.players.add(entityplayer1);
--        this.playersByUUID.put(entityplayer1.getUUID(), entityplayer1);
--        entityplayer1.initInventoryMenu();
-+        if (!entityplayer.connection.isDisconnected()) {
-+            worldserver.addRespawnedPlayer(entityplayer1);
-+            this.players.add(entityplayer1);
-+            this.playersByName.put(entityplayer1.getScoreboardName().toLowerCase(java.util.Locale.ROOT), entityplayer1); // Spigot
-+            this.playersByUUID.put(entityplayer1.getUUID(), entityplayer1);
-+        }
-+        // entityplayer1.initInventoryMenu();
-         entityplayer1.setHealth(entityplayer1.getHealth());
-         BlockPos blockposition = entityplayer1.getRespawnPosition();
-         ServerLevel worldserver2 = this.server.getLevel(entityplayer1.getRespawnDimension());
- 
--        if (!alive && blockposition != null && worldserver2 != null) {
-+        if (!flag && blockposition != null && worldserver2 != null) {
-             BlockState iblockdata = worldserver2.getBlockState(blockposition);
- 
-             if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) {
-                 entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver.getRandom().nextLong()));
-             }
-+            // Paper start - Add PlayerPostRespawnEvent
-+            if (iblockdata.is(net.minecraft.tags.BlockTags.BEDS) && !teleporttransition.missingRespawnBlock()) {
-+                isBedSpawn = true;
-+            }
-+            // Paper end - Add PlayerPostRespawnEvent
-         }
-+        // Added from changeDimension
-+        this.sendAllPlayerInfo(entityplayer); // Update health, etc...
-+        entityplayer.onUpdateAbilities();
-+        for (MobEffectInstance mobEffect : entityplayer.getActiveEffects()) {
-+            entityplayer.connection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobEffect, false)); // blend = false
-+        }
- 
-+        // Fire advancement trigger
-+        entityplayer.triggerDimensionChangeTriggers(worldserver);
-+
-+        // Don't fire on respawn
-+        if (fromWorld != worldserver) {
-+            PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(entityplayer.getBukkitEntity(), fromWorld.getWorld());
-+            this.server.server.getPluginManager().callEvent(event);
-+        }
-+
-+        // Save player file again if they were disconnected
-+        if (entityplayer.connection.isDisconnected()) {
-+            this.save(entityplayer);
-+        }
-+
-+        // Paper start - Add PlayerPostRespawnEvent
-+        if (isRespawn) {
-+            cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn));
-+        }
-+        // Paper end - Add PlayerPostRespawnEvent
-+
-+        // CraftBukkit end
-+
-         return entityplayer1;
-     }
- 
-@@ -505,26 +913,48 @@
-     }
- 
-     public void sendActiveEffects(LivingEntity entity, ServerGamePacketListenerImpl networkHandler) {
-+        // Paper start - collect packets
-+        this.sendActiveEffects(entity, networkHandler::send);
-+    }
-+    public void sendActiveEffects(LivingEntity entity, java.util.function.Consumer<Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> packetConsumer) {
-+        // Paper end - collect packets
-         Iterator iterator = entity.getActiveEffects().iterator();
- 
-         while (iterator.hasNext()) {
-             MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
- 
--            networkHandler.send(new ClientboundUpdateMobEffectPacket(entity.getId(), mobeffect, false));
-+            packetConsumer.accept(new ClientboundUpdateMobEffectPacket(entity.getId(), mobeffect, false)); // Paper - collect packets
-         }
- 
-     }
- 
-     public void sendPlayerPermissionLevel(ServerPlayer player) {
-+    // Paper start - avoid recalculating permissions if possible
-+        this.sendPlayerPermissionLevel(player, true);
-+    }
-+
-+    public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) {
-+    // Paper end - avoid recalculating permissions if possible
-         GameProfile gameprofile = player.getGameProfile();
-         int i = this.server.getProfilePermissions(gameprofile);
- 
--        this.sendPlayerPermissionLevel(player, i);
-+        this.sendPlayerPermissionLevel(player, i, recalculatePermissions); // Paper - avoid recalculating permissions if possible
-     }
- 
-     public void tick() {
-         if (++this.sendAllPlayerInfoIn > 600) {
--            this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players));
-+            // CraftBukkit start
-+            for (int i = 0; i < this.players.size(); ++i) {
-+                final ServerPlayer target = (ServerPlayer) this.players.get(i);
-+
-+                target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players.stream().filter(new Predicate<ServerPlayer>() {
-+                    @Override
-+                    public boolean test(ServerPlayer input) {
-+                        return target.getBukkitEntity().canSee(input.getBukkitEntity());
-+                    }
-+                }).collect(Collectors.toList())));
-+            }
-+            // CraftBukkit end
-             this.sendAllPlayerInfoIn = 0;
-         }
- 
-@@ -541,6 +971,25 @@
- 
-     }
- 
-+    // CraftBukkit start - add a world/entity limited version
-+    public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) {
-+        for (int i = 0; i < this.players.size(); ++i) {
-+            ServerPlayer entityplayer =  this.players.get(i);
-+            if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
-+                continue;
-+            }
-+            ((ServerPlayer) this.players.get(i)).connection.send(packet);
-+        }
-+    }
-+
-+    public void broadcastAll(Packet packet, Level world) {
-+        for (int i = 0; i < world.players().size(); ++i) {
-+            ((ServerPlayer) world.players().get(i)).connection.send(packet);
-+        }
-+
-+    }
-+    // CraftBukkit end
-+
-     public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
-         Iterator iterator = this.players.iterator();
- 
-@@ -554,7 +1003,7 @@
- 
-     }
- 
--    public void broadcastSystemToTeam(Player source, Component message) {
-+    public void broadcastSystemToTeam(net.minecraft.world.entity.player.Player source, Component message) {
-         PlayerTeam scoreboardteam = source.getTeam();
- 
-         if (scoreboardteam != null) {
-@@ -573,7 +1022,7 @@
-         }
-     }
- 
--    public void broadcastSystemToAllExceptTeam(Player source, Component message) {
-+    public void broadcastSystemToAllExceptTeam(net.minecraft.world.entity.player.Player source, Component message) {
-         PlayerTeam scoreboardteam = source.getTeam();
- 
-         if (scoreboardteam == null) {
-@@ -619,7 +1068,7 @@
-     }
- 
-     public void deop(GameProfile profile) {
--        this.ops.remove((Object) profile);
-+        this.ops.remove(profile); // CraftBukkit - decompile error
-         ServerPlayer entityplayer = this.getPlayer(profile.getId());
- 
-         if (entityplayer != null) {
-@@ -629,6 +1078,11 @@
-     }
- 
-     private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) {
-+        // Paper start - Add sendOpLevel API
-+        this.sendPlayerPermissionLevel(player, permissionLevel, true);
-+    }
-+    public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) {
-+        // Paper end - Add sendOpLevel API
-         if (player.connection != null) {
-             byte b0;
- 
-@@ -643,36 +1097,53 @@
-             player.connection.send(new ClientboundEntityEventPacket(player, b0));
-         }
- 
-+        if (recalculatePermissions) { // Paper - Add sendOpLevel API
-+        player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
-         this.server.getCommands().sendCommands(player);
-+        } // Paper - Add sendOpLevel API
-     }
- 
-     public boolean isWhiteListed(GameProfile profile) {
--        return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile);
-+        // Paper start - ProfileWhitelistVerifyEvent
-+        return this.isWhiteListed(profile, null);
-     }
-+    public boolean isWhiteListed(GameProfile gameprofile, @Nullable org.bukkit.event.player.PlayerLoginEvent loginEvent) {
-+        boolean isOp = this.ops.contains(gameprofile);
-+        boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile);
-+        final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event;
- 
-+        final net.kyori.adventure.text.Component configuredMessage = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage);
-+        event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile), this.doWhiteList, isWhitelisted, isOp, configuredMessage);
-+        event.callEvent();
-+        if (!event.isWhitelisted()) {
-+            if (loginEvent != null) {
-+                loginEvent.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, event.kickMessage() == null ? configuredMessage : event.kickMessage());
-+            }
-+            return false;
-+        }
-+        return true;
-+        // Paper end - ProfileWhitelistVerifyEvent
-+    }
-+
-     public boolean isOp(GameProfile profile) {
-         return this.ops.contains(profile) || this.server.isSingleplayerOwner(profile) && this.server.getWorldData().isAllowCommands() || this.allowCommandsForAllPlayers;
-     }
- 
-     @Nullable
-     public ServerPlayer getPlayerByName(String name) {
--        int i = this.players.size();
--
--        for (int j = 0; j < i; ++j) {
--            ServerPlayer entityplayer = (ServerPlayer) this.players.get(j);
--
--            if (entityplayer.getGameProfile().getName().equalsIgnoreCase(name)) {
--                return entityplayer;
--            }
--        }
--
--        return null;
-+        return this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); // Spigot
-     }
- 
--    public void broadcast(@Nullable Player player, double x, double y, double z, double distance, ResourceKey<Level> worldKey, Packet<?> packet) {
-+    public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey<Level> worldKey, Packet<?> packet) {
-         for (int i = 0; i < this.players.size(); ++i) {
-             ServerPlayer entityplayer = (ServerPlayer) this.players.get(i);
- 
-+            // CraftBukkit start - Test if player receiving packet can see the source of the packet
-+            if (player != null && !entityplayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
-+               continue;
-+            }
-+            // CraftBukkit end
-+
-             if (entityplayer != player && entityplayer.level().dimension() == worldKey) {
-                 double d4 = x - entityplayer.getX();
-                 double d5 = y - entityplayer.getY();
-@@ -687,10 +1158,12 @@
-     }
- 
-     public void saveAll() {
-+        io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
-         for (int i = 0; i < this.players.size(); ++i) {
-             this.save((ServerPlayer) this.players.get(i));
-         }
- 
-+        return null; }); // Paper - ensure main
-     }
- 
-     public UserWhiteList getWhiteList() {
-@@ -712,15 +1185,19 @@
-     public void reloadWhiteList() {}
- 
-     public void sendLevelInfo(ServerPlayer player, ServerLevel world) {
--        WorldBorder worldborder = this.server.overworld().getWorldBorder();
-+        WorldBorder worldborder = player.level().getWorldBorder(); // CraftBukkit
- 
-         player.connection.send(new ClientboundInitializeBorderPacket(worldborder));
-         player.connection.send(new ClientboundSetTimePacket(world.getGameTime(), world.getDayTime(), world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
-         player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSharedSpawnPos(), world.getSharedSpawnAngle()));
-         if (world.isRaining()) {
--            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
--            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, world.getRainLevel(1.0F)));
--            player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, world.getThunderLevel(1.0F)));
-+            // CraftBukkit start - handle player weather
-+            // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F));
-+            // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, worldserver.getRainLevel(1.0F)));
-+            // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, worldserver.getThunderLevel(1.0F)));
-+            player.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false);
-+            player.updateWeather(-world.rainLevel, world.rainLevel, -world.thunderLevel, world.thunderLevel);
-+            // CraftBukkit end
-         }
- 
-         player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F));
-@@ -729,8 +1206,16 @@
- 
-     public void sendAllPlayerInfo(ServerPlayer player) {
-         player.inventoryMenu.sendAllDataToRemote();
--        player.resetSentInfo();
-+        // entityplayer.resetSentInfo();
-+        player.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange
-+        player.refreshEntityData(player); // CraftBukkkit - SPIGOT-7218: sync metadata
-         player.connection.send(new ClientboundSetHeldSlotPacket(player.getInventory().selected));
-+        // CraftBukkit start - from GameRules
-+        int i = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23;
-+        player.connection.send(new ClientboundEntityEventPacket(player, (byte) i));
-+        float immediateRespawn = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN) ? 1.0F: 0.0F;
-+        player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, immediateRespawn));
-+        // CraftBukkit end
-     }
- 
-     public int getPlayerCount() {
-@@ -746,6 +1231,7 @@
-     }
- 
-     public void setUsingWhiteList(boolean whitelistEnabled) {
-+        new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent(); // Paper - WhitelistToggleEvent
-         this.doWhiteList = whitelistEnabled;
-     }
- 
-@@ -786,11 +1272,35 @@
-     }
- 
-     public void removeAll() {
--        for (int i = 0; i < this.players.size(); ++i) {
--            ((ServerPlayer) this.players.get(i)).connection.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown"));
-+        // Paper start - Extract method to allow for restarting flag
-+        this.removeAll(false);
-+    }
-+
-+    public void removeAll(boolean isRestarting) {
-+        // Paper end
-+        // CraftBukkit start - disconnect safely
-+        for (ServerPlayer player : this.players) {
-+            if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here)
-+            player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure
-+        }
-+        // CraftBukkit end
-+
-+        // Paper start - Configurable player collision; Remove collideRule team if it exists
-+        if (this.collideRuleTeamName != null) {
-+            final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard();
-+            final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName);
-+            if (team != null) scoreboard.removePlayerTeam(team);
-         }
-+        // Paper end - Configurable player collision
-+    }
- 
-+    // CraftBukkit start
-+    public void broadcastMessage(Component[] iChatBaseComponents) {
-+        for (Component component : iChatBaseComponents) {
-+            this.broadcastSystemMessage(component, false);
-+        }
-     }
-+    // CraftBukkit end
- 
-     public void broadcastSystemMessage(Component message, boolean overlay) {
-         this.broadcastSystemMessage(message, (entityplayer) -> {
-@@ -819,24 +1329,43 @@
-     }
- 
-     public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params) {
-+        // Paper start
-+        this.broadcastChatMessage(message, sender, params, null);
-+    }
-+    public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params, @Nullable Function<net.kyori.adventure.audience.Audience, Component> unsignedFunction) {
-+        // Paper end
-         Objects.requireNonNull(sender);
--        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, params);
-+        this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, params, unsignedFunction); // Paper
-     }
- 
-     private void broadcastChatMessage(PlayerChatMessage message, Predicate<ServerPlayer> shouldSendFiltered, @Nullable ServerPlayer sender, ChatType.Bound params) {
-+        // Paper start
-+        this.broadcastChatMessage(message, shouldSendFiltered, sender, params, null);
-+    }
-+    public void broadcastChatMessage(PlayerChatMessage message, Predicate<ServerPlayer> shouldSendFiltered, @Nullable ServerPlayer sender, ChatType.Bound params, @Nullable Function<net.kyori.adventure.audience.Audience, Component> unsignedFunction) {
-+        // Paper end
-         boolean flag = this.verifyChatTrusted(message);
- 
--        this.server.logChatMessage(message.decoratedContent(), params, flag ? null : "Not Secure");
-+        this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), params, flag ? null : "Not Secure"); // Paper
-         OutgoingChatMessage outgoingchatmessage = OutgoingChatMessage.create(message);
-         boolean flag1 = false;
- 
-         boolean flag2;
-+        Packet<?> disguised = sender != null && unsignedFunction == null ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(outgoingchatmessage.content(), params) : null; // Paper - don't send player chat packets from vanished players
- 
-         for (Iterator iterator = this.players.iterator(); iterator.hasNext(); flag1 |= flag2 && message.isFullyFiltered()) {
-             ServerPlayer entityplayer1 = (ServerPlayer) iterator.next();
- 
-             flag2 = shouldSendFiltered.test(entityplayer1);
--            entityplayer1.sendChatMessage(outgoingchatmessage, flag2, params);
-+            // Paper start - don't send player chat packets from vanished players
-+            if (sender != null && !entityplayer1.getBukkitEntity().canSee(sender.getBukkitEntity())) {
-+                entityplayer1.connection.send(unsignedFunction != null
-+                    ? new net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket(unsignedFunction.apply(entityplayer1.getBukkitEntity()), params)
-+                    : disguised);
-+                continue;
-+            }
-+            // Paper end
-+            entityplayer1.sendChatMessage(outgoingchatmessage, flag2, params, unsignedFunction == null ? null : unsignedFunction.apply(entityplayer1.getBukkitEntity())); // Paper
-         }
- 
-         if (flag1 && sender != null) {
-@@ -845,20 +1374,27 @@
- 
-     }
- 
--    private boolean verifyChatTrusted(PlayerChatMessage message) {
-+    public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public
-         return message.hasSignature() && !message.hasExpiredServer(Instant.now());
-     }
- 
--    public ServerStatsCounter getPlayerStats(Player player) {
--        UUID uuid = player.getUUID();
--        ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) this.stats.get(uuid);
-+    // CraftBukkit start
-+    public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) {
-+        ServerStatsCounter serverstatisticmanager = entityhuman.getStats();
-+        return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name
-+    }
- 
-+    public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) {
-+        ServerPlayer entityhuman = this.getPlayer(uuid);
-+        ServerStatsCounter serverstatisticmanager = entityhuman == null ? null : (ServerStatsCounter) entityhuman.getStats();
-+        // CraftBukkit end
-+
-         if (serverstatisticmanager == null) {
-             File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile();
-             File file1 = new File(file, String.valueOf(uuid) + ".json");
- 
-             if (!file1.exists()) {
--                File file2 = new File(file, player.getName().getString() + ".json");
-+                File file2 = new File(file, displayName + ".json"); // CraftBukkit
-                 Path path = file2.toPath();
- 
-                 if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file2.isFile()) {
-@@ -867,7 +1403,7 @@
-             }
- 
-             serverstatisticmanager = new ServerStatsCounter(this.server, file1);
--            this.stats.put(uuid, serverstatisticmanager);
-+            // this.stats.put(uuid, serverstatisticmanager); // CraftBukkit
-         }
- 
-         return serverstatisticmanager;
-@@ -875,13 +1411,13 @@
- 
-     public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) {
-         UUID uuid = player.getUUID();
--        PlayerAdvancements advancementdataplayer = (PlayerAdvancements) this.advancements.get(uuid);
-+        PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
- 
-         if (advancementdataplayer == null) {
-             Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(String.valueOf(uuid) + ".json");
- 
-             advancementdataplayer = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player);
--            this.advancements.put(uuid, advancementdataplayer);
-+            // this.advancements.put(uuid, advancementdataplayer); // CraftBukkit
-         }
- 
-         advancementdataplayer.setPlayer(player);
-@@ -932,15 +1468,39 @@
-     }
- 
-     public void reloadResources() {
--        Iterator iterator = this.advancements.values().iterator();
-+        // Paper start - API for updating recipes on clients
-+        this.reloadAdvancementData();
-+        this.reloadTagData();
-+        this.reloadRecipes();
-+    }
-+    public void reloadAdvancementData() {
-+        // Paper end - API for updating recipes on clients
-+        // CraftBukkit start
-+        /*Iterator iterator = this.advancements.values().iterator();
- 
-         while (iterator.hasNext()) {
--            PlayerAdvancements advancementdataplayer = (PlayerAdvancements) iterator.next();
-+            AdvancementDataPlayer advancementdataplayer = (AdvancementDataPlayer) iterator.next();
- 
-             advancementdataplayer.reload(this.server.getAdvancements());
-+        }*/
-+
-+        for (ServerPlayer player : this.players) {
-+            player.getAdvancements().reload(this.server.getAdvancements());
-+            player.getAdvancements().flushDirty(player); // CraftBukkit - trigger immediate flush of advancements
-         }
-+        // CraftBukkit end
- 
-+        // Paper start - API for updating recipes on clients
-+    }
-+    public void reloadTagData() {
-         this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
-+        // CraftBukkit start
-+        // this.reloadRecipes(); // Paper - do not reload recipes just because tag data was reloaded
-+        // Paper end - API for updating recipes on clients
-+    }
-+
-+    public void reloadRecipes() {
-+        // CraftBukkit end
-         RecipeManager craftingmanager = this.server.getRecipeManager();
-         ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes());
-         Iterator iterator1 = this.players.iterator();

From 0262d9a165fb485acbff6d965baf02dc0839e44c Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 20:15:49 -0800
Subject: [PATCH 143/285] fix a bunch of compile issues

---
 build-data/paper.at                                      | 3 ---
 .../net/minecraft/server/MinecraftServer.java.patch      | 4 ++--
 .../server/dedicated/DedicatedServer.java.patch          | 4 ++--
 .../net/minecraft/world/entity/animal/Sheep.java.patch   | 8 --------
 .../net/minecraft/world/entity/animal/Turtle.java.patch  | 2 +-
 .../minecraft/world/inventory/CraftingMenu.java.patch    | 6 +++---
 .../net/minecraft/world/level/BlockGetter.java.patch     | 4 ++--
 .../sources/net/minecraft/world/level/Level.java.patch   | 4 ++--
 .../net/minecraft/world/level/block/Block.java.patch     | 4 ++--
 .../level/block/entity/BarrelBlockEntity.java.patch      | 2 +-
 .../block/entity/BrewingStandBlockEntity.java.patch      | 2 +-
 .../world/level/block/entity/ChestBlockEntity.java.patch | 2 +-
 .../block/entity/ChiseledBookShelfBlockEntity.java.patch | 2 +-
 .../block/entity/DecoratedPotBlockEntity.java.patch      | 2 +-
 .../level/block/entity/DispenserBlockEntity.java.patch   | 2 +-
 .../level/block/entity/HopperBlockEntity.java.patch      | 2 +-
 .../level/block/entity/JukeboxBlockEntity.java.patch     | 2 +-
 .../level/block/entity/LecternBlockEntity.java.patch     | 9 ++++++---
 .../world/level/chunk/ChunkGenerator.java.patch          | 2 +-
 19 files changed, 29 insertions(+), 37 deletions(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index 9b69a12479..8cb2a85b2a 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -30,8 +30,6 @@ public net.minecraft.network.chat.numbers.StyledFormat style
 public net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket <init>(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V
 public net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket blockState
 public net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket pos
-public net.minecraft.network.protocol.game.ClientboundTabListPacket footer
-public net.minecraft.network.protocol.game.ClientboundTabListPacket header
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket hasPos
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket hasRot
 public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket x
@@ -488,7 +486,6 @@ public net.minecraft.world.item.context.UseOnContext <init>(Lnet/minecraft/world
 public net.minecraft.world.item.crafting.RecipeManager recipes
 public net.minecraft.world.item.crafting.RecipeMap byKey
 public net.minecraft.world.item.crafting.RecipeMap byType
-public net.minecraft.world.item.enchantment.Enchantment definition
 public net.minecraft.world.item.enchantment.ItemEnchantments showInTooltip
 public net.minecraft.world.item.trading.MerchantOffer demand
 public net.minecraft.world.item.trading.MerchantOffer result
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index 2e1e050e3a..8b04cb50d9 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -331,7 +331,7 @@
 +            }
 +            worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
 +            if (this.options.has("forceUpgrade")) {
-+                net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
++                net.minecraft.server.Main.forceUpgrade(worldSession, net.minecraft.util.datafix.DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
 +                    return true;
 +                }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
 +            }
@@ -1061,7 +1061,7 @@
 -            this.getPlayerList().getPlayers().forEach(this::sendDifficultyUpdate);
 +    // Paper start - per level difficulty
 +    public void setDifficulty(ServerLevel level, Difficulty difficulty, boolean forceUpdate) {
-+        net.minecraft.world.level.storage.PrimaryLevelData worldData = level.serverLevelData;
++        net.minecraft.world.level.storage.PrimaryLevelData worldData = (net.minecraft.world.level.storage.PrimaryLevelData) level.serverLevelData;
 +        if (forceUpdate || !worldData.isDifficultyLocked()) {
 +            worldData.setDifficulty(worldData.isHardcore() ? Difficulty.HARD : difficulty);
 +            level.setSpawnSettings(worldData.getDifficulty() != Difficulty.PEACEFUL && ((net.minecraft.server.dedicated.DedicatedServer) this).settings.getProperties().spawnMonsters);
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 2deca3f0de..1fd2cae77f 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -29,7 +29,7 @@
 -    ) {
 -        super(serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory);
 +    // CraftBukkit start - Signature changed
-+    public DedicatedServer(joptsimple.OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings settings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
++    public DedicatedServer(joptsimple.OptionSet options, net.minecraft.server.WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings settings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
 +        super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
 +        // CraftBukkit end
          this.settings = settings;
@@ -432,7 +432,7 @@
 +    }
 +
 +    @Override
-+    public CommandSender getBukkitSender(CommandSourceStack wrapper) {
++    public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
 +        return this.console;
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
index 07a19159a6..c85c408ef3 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/entity/animal/Sheep.java
 +++ b/net/minecraft/world/entity/animal/Sheep.java
-@@ -40,7 +_,6 @@
- import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
--import net.minecraft.world.item.DyeColor;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
 @@ -158,7 +_,19 @@
          ItemStack itemInHand = player.getItemInHand(hand);
          if (itemInHand.is(Items.SHEARS)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
index e9232e5c94..6a7f48e29a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
@@ -35,7 +35,7 @@
              RandomSource random = this.animal.getRandom();
              if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
 -                this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), random.nextInt(7) + 1));
-+                if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), , event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, loveCause)); // Paper - Add EntityFertilizeEggEvent event
++                if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, loveCause)); // Paper - Add EntityFertilizeEggEvent event
              }
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
index a569fb6dd8..f490a6a0d3 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -49,13 +49,13 @@
 +
 +    // CraftBukkit start
 +    @Override
-+    public CraftInventoryView getBukkitView() {
++    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
 +        if (this.bukkitEntity != null) {
 +            return this.bukkitEntity;
 +        }
 +
-+        CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
-+        this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++        org.bukkit.craftbukkit.inventory.CraftInventoryCrafting inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryCrafting(this.craftSlots, this.resultSlots);
++        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
 +        return this.bukkitEntity;
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
index 4ba53de04b..f329019055 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BlockGetter.java.patch
@@ -47,10 +47,10 @@
 +            // copied the last function parameter (listed below)
 +            Vec3 vec3d = traverseContext.getFrom().subtract(traverseContext.getTo());
 +
-+            return BlockHitResult.miss(traverseContext.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
++            return BlockHitResult.miss(traverseContext.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(traverseContext.getTo()));
 +        }
 +        // Paper end - Prevent raytrace from loading chunks
-+        if (blockState.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate
++        if (blockState.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, traversePos)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate
 +            FluidState fluidState = blockState.getFluidState(); // Paper - Perf: don't need to go to world state again
              Vec3 from = traverseContext.getFrom();
              Vec3 to = traverseContext.getTo();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index c9dd5027af..8a80c15b82 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -130,7 +130,7 @@
 +        java.util.function.Function<org.spigotmc.SpigotWorldConfig, // Spigot - create per world config
 +        io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator // Paper - create paper world config
      ) {
-+        this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
++        this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot
 +        this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
 +        this.generator = gen;
 +        this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
@@ -587,7 +587,7 @@
 +            // Paper start - Prevent block entity and entity crashes
 +            final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
 +            MinecraftServer.LOGGER.error(msg, var6);
-+            getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
++            getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var6))); // Paper - ServerExceptionEvent
 +            entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
 +            // Paper end - Prevent block entity and entity crashes
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
index 06e6874b95..70ed7bd204 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
@@ -86,10 +86,10 @@
 +        popExperience(level, pos, amount, null);
 +    }
 +    public void popExperience(ServerLevel level, BlockPos pos, int amount, net.minecraft.world.entity.Entity entity) {
-+        // Paper end - add entity paramete
++        // Paper end - add entity parameter
          if (level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
 -            ExperienceOrb.award(level, Vec3.atCenterOf(pos), amount);
-+            ExperienceOrb.award(level, Vec3.atCenterOf(pos), amount, entity); // Paper
++            ExperienceOrb.award(level, Vec3.atCenterOf(pos), amount, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
index 05d3f943e3..24372abac2 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -24,7 +24,7 @@
 +    }
 +
 +    @Override
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
index 769760f7d6..c96cf8d8fd 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -56,7 +56,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
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
index 9b690b9eea..d3845d7cfa 100644
--- 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
@@ -20,7 +20,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index 85aac7b304..6248b82627 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -24,7 +24,7 @@
 +    }
 +
 +    @Override
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
index afb231c15d..0b6a7181a9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
@@ -25,7 +25,7 @@
 +    }
 +
 +    @Override
-+    public java.util.List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
index b7ffd808ca..1951b2f1e9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
@@ -20,7 +20,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
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
index 23642e8281..f8fb1daa33 100644
--- 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
@@ -20,7 +20,7 @@
 +        this.transaction.remove(who);
 +    }
 +
-+    public java.util.List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index cd2c18e407..0789c1b70b 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -26,7 +26,7 @@
 +    }
 +
 +    @Override
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
index 5f04d187fe..ccef0d0f0c 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -1,10 +1,13 @@
 --- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
-@@ -33,6 +_,51 @@
+@@ -32,7 +_,53 @@
+     public static final int NUM_DATA = 1;
      public static final int SLOT_BOOK = 0;
      public static final int NUM_SLOTS = 1;
-     public final Container bookAccess = new Container() {
-+        // CraftBukkit start - add fields and methods
+-    public final Container bookAccess = new Container() {
++    // CraftBukkit start - add fields and methods
++    public final Container bookAccess = new LecternInventory();
++    public class LecternInventory implements Container {
 +        public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +        private int maxStack = 1;
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
index bdd2e9ec24..32ce3a4e7d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -137,7 +137,7 @@
          if (structureStart.isValid()) {
 +            // CraftBukkit start
 +            BoundingBox box = structureStart.getBoundingBox();
-+            org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureStart.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunkPos.x, chunkPos.z);
++            org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureManager.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunkPos.x, chunkPos.z);
 +            org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +            if (event.isCancelled()) {
 +                return true;

From 35afd218f53cf9970dd8a1f5cc18e364492c0762 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sun, 15 Dec 2024 05:32:25 +0100
Subject: [PATCH 144/285] net/minecraft/server/level

---
 .../server/level/ChunkHolder.java.patch       |  155 +-
 .../server/level/ChunkMap.java.patch          |  375 ++++
 .../server/level/DistanceManager.java.patch   |  128 ++
 .../server/level/ServerChunkCache.java.patch  |  171 +-
 .../server/level/ServerEntity.java.patch      |  134 ++
 .../server/level/ServerLevel.java.patch       | 1217 +++++++++++
 .../server/level/ServerPlayer.java.patch      | 1684 +++++++++++++++
 .../level/ServerPlayerGameMode.java.patch     |  299 ++-
 .../server/level/TicketType.java.patch        |   16 +-
 .../server/level/WorldGenRegion.java.patch    |   75 +-
 .../server/level/ChunkMap.java.patch          |  433 ----
 .../server/level/DistanceManager.java.patch   |  152 --
 .../server/level/ServerEntity.java.patch      |  179 --
 .../server/level/ServerLevel.java.patch       | 1261 -----------
 .../server/level/ServerPlayer.java.patch      | 1891 -----------------
 .../paper/configuration/Configurations.java   |    2 +-
 .../configuration/PaperConfigurations.java    |    2 +-
 .../org/bukkit/craftbukkit/CraftServer.java   |    2 +-
 .../org/bukkit/craftbukkit/CraftWorld.java    |    7 +-
 19 files changed, 3854 insertions(+), 4329 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ChunkHolder.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ServerChunkCache.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ServerPlayerGameMode.java.patch (63%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/TicketType.java.patch (63%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/WorldGenRegion.java.patch (58%)
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch
rename to paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
index 4516416d44..9cb5de711a 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
@@ -1,42 +1,20 @@
 --- a/net/minecraft/server/level/ChunkHolder.java
 +++ b/net/minecraft/server/level/ChunkHolder.java
-@@ -28,14 +28,18 @@
- import net.minecraft.world.level.chunk.status.ChunkStatus;
- import net.minecraft.world.level.lighting.LevelLightEngine;
- 
-+// CraftBukkit start
-+import net.minecraft.server.MinecraftServer;
-+// CraftBukkit end
-+
- public class ChunkHolder extends GenerationChunkHolder {
- 
+@@ -33,9 +_,9 @@
      public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
-     private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+     private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK);
      private final LevelHeightAccessor levelHeightAccessor;
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture;
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture;
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture;
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 +    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
 +    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
 +    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
      public int oldTicketLevel;
      private int ticketLevel;
      private int queueLevel;
-@@ -58,9 +62,9 @@
-         this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
-         this.blockChangedLightSectionFilter = new BitSet();
-         this.skyChangedLightSectionFilter = new BitSet();
--        this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null);
--        this.sendSync = CompletableFuture.completedFuture((Object) null);
--        this.saveSync = CompletableFuture.completedFuture((Object) null);
-+        this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
-+        this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
-+        this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
-         this.levelHeightAccessor = world;
-         this.lightEngine = lightingProvider;
-         this.onLevelChange = levelUpdateListener;
-@@ -72,6 +76,18 @@
-         this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
+@@ -71,6 +_,18 @@
+         this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()];
      }
  
 +    // CraftBukkit start
@@ -54,64 +32,31 @@
      public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
          return this.tickingChunkFuture;
      }
-@@ -85,8 +101,8 @@
-     }
- 
-     @Nullable
--    public LevelChunk getTickingChunk() {
--        return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse((Object) null);
-+    public final LevelChunk getTickingChunk() { // Paper - final for inline
-+        return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
-     }
- 
-     @Nullable
-@@ -138,6 +154,7 @@
+@@ -129,6 +_,7 @@
+         } else {
              boolean flag = this.hasChangedSections;
-             int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
- 
-+            if (i < 0 || i >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
-             if (this.changedBlocksPerSection[i] == null) {
+             int sectionIndex = this.levelHeightAccessor.getSectionIndex(pos.getY());
++            if (sectionIndex < 0 || sectionIndex >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
+             if (this.changedBlocksPerSection[sectionIndex] == null) {
                  this.hasChangedSections = true;
-                 this.changedBlocksPerSection[i] = new ShortOpenHashSet();
-@@ -224,8 +241,11 @@
-                                 ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection);
- 
-                                 this.broadcast(list, packetplayoutmultiblockchange);
-+                                // CraftBukkit start
-+                                List finalList = list;
-                                 packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
--                                    this.broadcastBlockEntityIfNeeded(list, world, blockposition1, iblockdata1);
-+                                    this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
-+                                    // CraftBukkit end
-                                 });
-                             }
-                         }
-@@ -291,7 +311,7 @@
-         this.pendingFullStateConfirmation = completablefuture1;
-         chunkFuture.thenAccept((chunkresult) -> {
-             chunkresult.ifSuccess((chunk) -> {
--                completablefuture1.complete((Object) null);
-+                completablefuture1.complete(null); // CraftBukkit - decompile error
-             });
-         });
-     }
-@@ -301,6 +321,38 @@
-         chunkLoadingManager.onFullChunkStatusChange(this.pos, target);
+                 this.changedBlocksPerSection[sectionIndex] = new ShortOpenHashSet();
+@@ -274,6 +_,38 @@
+         chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus);
      }
  
 +    // CraftBukkit start
 +    // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
 +    // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks
-+    protected void callEventIfUnloading(ChunkMap playerchunkmap) {
++    protected void callEventIfUnloading(ChunkMap chunkMap) {
 +        FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
 +        FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel);
 +        boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
 +        boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
 +        if (oldIsFull && !newIsFull) {
 +            this.getFullChunkFuture().thenAccept((either) -> {
-+                LevelChunk chunk = (LevelChunk) either.orElse(null);
++                LevelChunk chunk = either.orElse(null);
 +                if (chunk != null) {
-+                    playerchunkmap.callbackExecutor.execute(() -> {
++                    chunkMap.callbackExecutor.execute(() -> {
 +                        // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
 +                        // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
 +                        // These actions may however happen deferred, so we manually set the needsSaving flag already here.
@@ -121,26 +66,26 @@
 +                }
 +            }).exceptionally((throwable) -> {
 +                // ensure exceptions are printed, by default this is not the case
-+                MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
++                net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
 +                return null;
 +            });
 +
 +            // Run callback right away if the future was already done
-+            playerchunkmap.callbackExecutor.run();
++            chunkMap.callbackExecutor.run();
 +        }
 +    }
 +    // CraftBukkit end
 +
-     protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) {
-         FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
-         FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
-@@ -309,12 +361,28 @@
- 
-         this.wasAccessibleSinceLastSave |= flag1;
-         if (!flag && flag1) {
+     protected void updateFutures(ChunkMap chunkMap, Executor executor) {
+         FullChunkStatus fullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+         FullChunkStatus fullChunkStatus1 = ChunkLevel.fullStatus(this.ticketLevel);
+@@ -281,12 +_,28 @@
+         boolean isOrAfter1 = fullChunkStatus1.isOrAfter(FullChunkStatus.FULL);
+         this.wasAccessibleSinceLastSave |= isOrAfter1;
+         if (!isOrAfter && isOrAfter1) {
 +            int expectCreateCount = ++this.fullChunkCreateCount; // Paper
-             this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
-             this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
+             this.fullChunkFuture = chunkMap.prepareAccessibleChunk(this);
+             this.scheduleFullChunkPromotion(chunkMap, this.fullChunkFuture, executor, FullChunkStatus.FULL);
 +            // Paper start - cache ticking ready status
 +            this.fullChunkFuture.thenAccept(chunkResult -> {
 +                chunkResult.ifSuccess(chunk -> {
@@ -154,19 +99,19 @@
              this.addSaveDependency(this.fullChunkFuture);
          }
  
-         if (flag && !flag1) {
+         if (isOrAfter && !isOrAfter1) {
 +            // Paper start
 +            if (this.isFullChunkReady) {
 +                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 +            }
 +            // Paper end
-             this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
-             this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+             this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
+             this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
          }
-@@ -325,11 +393,25 @@
-         if (!flag2 && flag3) {
-             this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
-             this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
+@@ -296,11 +_,25 @@
+         if (!isOrAfter2 && isOrAfter3) {
+             this.tickingChunkFuture = chunkMap.prepareTickingChunk(this);
+             this.scheduleFullChunkPromotion(chunkMap, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
 +            // Paper start - cache ticking ready status
 +            this.tickingChunkFuture.thenAccept(chunkResult -> {
 +                chunkResult.ifSuccess(chunk -> {
@@ -179,21 +124,21 @@
              this.addSaveDependency(this.tickingChunkFuture);
          }
  
-         if (flag2 && !flag3) {
--            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+         if (isOrAfter2 && !isOrAfter3) {
+-            this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
 +            // Paper start
 +            if (this.isTickingReady) {
 +                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 +            }
 +            // Paper end
 +            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
-             this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+             this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
          }
  
-@@ -343,11 +425,24 @@
+@@ -313,11 +_,24 @@
  
-             this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
-             this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
+             this.entityTickingChunkFuture = chunkMap.prepareEntityTickingChunk(this);
+             this.scheduleFullChunkPromotion(chunkMap, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
 +            // Paper start - cache ticking ready status
 +            this.entityTickingChunkFuture.thenAccept(chunkResult -> {
 +                chunkResult.ifSuccess(chunk -> {
@@ -205,39 +150,39 @@
              this.addSaveDependency(this.entityTickingChunkFuture);
          }
  
-         if (flag4 && !flag5) {
--            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+         if (isOrAfter4 && !isOrAfter5) {
+-            this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
 +            // Paper start
 +            if (this.isEntityTickingReady) {
 +                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
 +            }
 +            // Paper end
 +            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
-             this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+             this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
          }
  
-@@ -357,6 +452,26 @@
+@@ -327,6 +_,26 @@
  
          this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
          this.oldTicketLevel = this.ticketLevel;
 +        // CraftBukkit start
 +        // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
-+        if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
++        if (!fullChunkStatus.isOrAfter(FullChunkStatus.FULL) && fullChunkStatus1.isOrAfter(FullChunkStatus.FULL)) {
 +            this.getFullChunkFuture().thenAccept((either) -> {
 +                LevelChunk chunk = (LevelChunk) either.orElse(null);
 +                if (chunk != null) {
-+                    chunkLoadingManager.callbackExecutor.execute(() -> {
++                    chunkMap.callbackExecutor.execute(() -> {
 +                        chunk.loadCallback();
 +                    });
 +                }
 +            }).exceptionally((throwable) -> {
 +                // ensure exceptions are printed, by default this is not the case
-+                MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
++                net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
 +                return null;
 +            });
 +
 +            // Run callback right away if the future was already done
-+            chunkLoadingManager.callbackExecutor.run();
++            chunkMap.callbackExecutor.run();
 +        }
 +        // CraftBukkit end
      }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
new file mode 100644
index 0000000000..559aabf756
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
@@ -0,0 +1,375 @@
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -145,6 +_,33 @@
+     public int serverViewDistance;
+     private final WorldGenContext worldGenContext;
+ 
++    // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
++    public final CallbackExecutor callbackExecutor = new CallbackExecutor();
++    public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
++
++        private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
++
++        @Override
++        public void execute(Runnable runnable) {
++            this.queue.add(runnable);
++        }
++
++        @Override
++        public void run() {
++            Runnable task;
++            while ((task = this.queue.poll()) != null) {
++                task.run();
++            }
++        }
++    };
++    // CraftBukkit end
++
++    // Paper start
++    public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
++        return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
++    }
++    // Paper end
++
+     public ChunkMap(
+         ServerLevel level,
+         LevelStorageSource.LevelStorageAccess levelStorageAccess,
+@@ -171,13 +_,19 @@
+         this.level = level;
+         RegistryAccess registryAccess = level.registryAccess();
+         long seed = level.getSeed();
+-        if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
++        // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
++        ChunkGenerator randomGenerator = generator;
++        if (randomGenerator instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator customChunkGenerator) {
++            randomGenerator = customChunkGenerator.getDelegate();
++        }
++        if (randomGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
++        // CraftBukkit end
+             this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), seed);
+         } else {
+             this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), seed);
+         }
+ 
+-        this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed);
++        this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed, level.spigotConfig); // Spigot
+         this.mainThreadExecutor = mainThreadExecutor;
+         ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen");
+         this.progressListener = progressListener;
+@@ -207,6 +_,12 @@
+         this.chunksToEagerlySave.add(chunkPos.toLong());
+     }
+ 
++    // Paper start
++    public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
++        return -1;
++    }
++    // Paper end
++
+     protected ChunkGenerator generator() {
+         return this.worldGenContext.generator();
+     }
+@@ -354,9 +_,9 @@
+                 }
+             );
+         stringBuilder.append("Updating:").append(System.lineSeparator());
+-        this.updatingChunkMap.values().forEach(consumer);
++        ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
+         stringBuilder.append("Visible:").append(System.lineSeparator());
+-        this.visibleChunkMap.values().forEach(consumer);
++        ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
+         CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading");
+         CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading");
+         crashReportCategory.setDetail("Details", details);
+@@ -392,6 +_,9 @@
+                     holder.setTicketLevel(newLevel);
+                 } else {
+                     holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this);
++                    // Paper start
++                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
++                    // Paper end
+                 }
+ 
+                 this.updatingChunkMap.put(chunkPos, holder);
+@@ -420,8 +_,8 @@
+ 
+     protected void saveAllChunks(boolean flush) {
+         if (flush) {
+-            List<ChunkHolder> list = this.visibleChunkMap
+-                .values()
++            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level) // Paper - moonrise
++                //.values() // Paper - moonrise
+                 .stream()
+                 .filter(ChunkHolder::wasAccessibleSinceLastSave)
+                 .peek(ChunkHolder::refreshAccessibility)
+@@ -447,7 +_,7 @@
+             this.nextChunkSaveTime.clear();
+             long millis = Util.getMillis();
+ 
+-            for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) {
++            for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper
+                 this.saveChunkIfNeeded(chunkHolder, millis);
+             }
+         }
+@@ -468,6 +_,7 @@
+     public boolean hasWork() {
+         return this.lightEngine.hasLightWork()
+             || !this.pendingUnloads.isEmpty()
++            || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) // Paper - moonrise
+             || !this.updatingChunkMap.isEmpty()
+             || this.poiManager.hasWork()
+             || !this.toDrop.isEmpty()
+@@ -526,7 +_,11 @@
+                 this.scheduleUnload(chunkPos, chunkHolder);
+             } else {
+                 ChunkAccess latestChunk = chunkHolder.getLatestChunk();
+-                if (this.pendingUnloads.remove(chunkPos, chunkHolder) && latestChunk != null) {
++                // Paper start
++                boolean removed;
++                if ((removed = this.pendingUnloads.remove(chunkPos, chunkHolder)) && latestChunk != null) {
++                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder);
++                    // Paper end
+                     if (latestChunk instanceof LevelChunk levelChunk) {
+                         levelChunk.setLoaded(false);
+                     }
+@@ -540,7 +_,9 @@
+                     this.lightEngine.tryScheduleUpdate();
+                     this.progressListener.onStatusChange(latestChunk.getPos(), null);
+                     this.nextChunkSaveTime.remove(latestChunk.getPos().toLong());
+-                }
++                } else if (removed) { // Paper start
++                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder);
++                } // Paper end
+             }
+         }, this.unloadQueue::add).whenComplete((_void, error) -> {
+             if (error != null) {
+@@ -818,7 +_,7 @@
+         }
+     }
+ 
+-    protected void setServerViewDistance(int viewDistance) {
++    public void setServerViewDistance(int viewDistance) { // Paper - publi
+         int i = Mth.clamp(viewDistance, 2, 32);
+         if (i != this.serverViewDistance) {
+             this.serverViewDistance = i;
+@@ -856,7 +_,7 @@
+     }
+ 
+     public int size() {
+-        return this.visibleChunkMap.size();
++        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
+     }
+ 
+     public net.minecraft.server.level.DistanceManager getDistanceManager() {
+@@ -864,7 +_,7 @@
+     }
+ 
+     protected Iterable<ChunkHolder> getChunks() {
+-        return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
++        return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
+     }
+ 
+     void dumpChunks(Writer writer) throws IOException {
+@@ -888,10 +_,10 @@
+             .build(writer);
+         TickingTracker tickingTracker = this.distanceManager.tickingTracker();
+ 
+-        for (Entry<ChunkHolder> entry : this.visibleChunkMap.long2ObjectEntrySet()) {
+-            long longKey = entry.getLongKey();
++        for (ChunkHolder entry : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper - Moonrise
++            long longKey = entry.pos.toLong(); // Paper - Moonrise
+             ChunkPos chunkPos = new ChunkPos(longKey);
+-            ChunkHolder chunkHolder = entry.getValue();
++            ChunkHolder chunkHolder = entry; // Paper - Moonrise
+             Optional<ChunkAccess> optional = Optional.ofNullable(chunkHolder.getLatestChunk());
+             Optional<LevelChunk> optional1 = optional.flatMap(chunk -> chunk instanceof LevelChunk ? Optional.of((LevelChunk)chunk) : Optional.empty());
+             csvOutput.writeRow(
+@@ -931,11 +_,13 @@
+     }
+ 
+     private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos pos) {
+-        return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk"));
++        return this.read(pos).thenApplyAsync(optional -> optional.map(tag -> upgradeChunkTag(tag, pos)), Util.backgroundExecutor().forName("upgradeChunk")); // CraftBukkit
+     }
+ 
+-    private CompoundTag upgradeChunkTag(CompoundTag tag) {
+-        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer());
++    // CraftBukkit start
++    private CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) {
++        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level);
++    // CraftBukkit end
+     }
+ 
+     void forEachSpawnCandidateChunk(Consumer<ChunkHolder> action) {
+@@ -951,12 +_,34 @@
+     }
+ 
+     public boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) {
+-        return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos);
++        // Spigot start
++        return this.anyPlayerCloseEnoughForSpawning(chunkPos, false);
++    }
++
++    boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos, boolean reducedRange) {
++        return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, reducedRange);
++        // Spigot end
+     }
+ 
+     private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos) {
++        // Spigot start
++        return this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, false);
++    }
++
++    private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) {
++        double blockRange; // Paper - use from event
++        // Spigot end
+         for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-            if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) {
++            // Paper start - PlayerNaturallySpawnCreaturesEvent
++            com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
++            blockRange = 16384.0D;
++            if (reducedRange) {
++                event = serverPlayer.playerNaturallySpawnedEvent;
++                if (event == null || event.isCancelled()) continue;
++                blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
++            }
++            // Paper end - PlayerNaturallySpawnCreaturesEvent
++            if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) {
+                 return true;
+             }
+         }
+@@ -972,7 +_,7 @@
+             Builder<ServerPlayer> builder = ImmutableList.builder();
+ 
+             for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-                if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) {
++                if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, 16384.0D)) { // Spigot
+                     builder.add(serverPlayer);
+                 }
+             }
+@@ -981,12 +_,12 @@
+         }
+     }
+ 
+-    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos) {
++    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos, double range) { // Spigot
+         if (player.isSpectator()) {
+             return false;
+         } else {
+             double d = euclideanDistanceSquared(chunkPos, player);
+-            return d < 16384.0;
++            return d < range; // Spigot
+         }
+     }
+ 
+@@ -1100,9 +_,19 @@
+     }
+ 
+     public void addEntity(Entity entity) {
++        org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
++        // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
++        if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
++            LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
++                + ": " + entity  + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
++            return;
++        }
++        // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
++        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delayadding to tracker until after list packets
+         if (!(entity instanceof EnderDragonPart)) {
+             EntityType<?> type = entity.getType();
+             int i = type.clientTrackingRange() * 16;
++            i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
+             if (i != 0) {
+                 int updateInterval = type.updateInterval();
+                 if (this.entityMap.containsKey(entity.getId())) {
+@@ -1126,6 +_,7 @@
+     }
+ 
+     protected void removeEntity(Entity entity) {
++        org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
+         if (entity instanceof ServerPlayer serverPlayer) {
+             this.updatePlayerStatus(serverPlayer, false);
+ 
+@@ -1230,7 +_,7 @@
+         });
+     }
+ 
+-    class DistanceManager extends net.minecraft.server.level.DistanceManager {
++    public class DistanceManager extends net.minecraft.server.level.DistanceManager { // Paper - public
+         protected DistanceManager(final Executor dispatcher, final Executor mainThreadExecutor) {
+             super(dispatcher, mainThreadExecutor);
+         }
+@@ -1258,10 +_,10 @@
+         final Entity entity;
+         private final int range;
+         SectionPos lastSectionPos;
+-        public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
++        public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
+ 
+         public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) {
+-            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast);
++            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this.seenBy); // CraftBukkit
+             this.entity = entity;
+             this.range = range;
+             this.lastSectionPos = SectionPos.of(entity);
+@@ -1297,24 +_,47 @@
+         }
+ 
+         public void removePlayer(ServerPlayer player) {
++            org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
+             if (this.seenBy.remove(player.connection)) {
+                 this.serverEntity.removePairing(player);
+             }
+         }
+ 
+         public void updatePlayer(ServerPlayer player) {
++            org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+             if (player != this.entity) {
+-                Vec3 vec3 = player.position().subtract(this.entity.position());
++                // Paper start - remove allocation of Vec3D here
++                // Vec3 vec3d = player.position().subtract(this.entity.position());
++                double vec3d_dx = player.getX() - this.entity.getX();
++                double vec3d_dz = player.getZ() - this.entity.getZ();
++                // Paper end - remove allocation of Vec3D here
+                 int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
+                 double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16);
+-                double d1 = vec3.x * vec3.x + vec3.z * vec3.z;
++                double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
+                 double d2 = d * d;
+-                boolean flag = d1 <= d2
+-                    && this.entity.broadcastToPlayer(player)
+-                    && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
++                // Paper start - Configurable entity tracking range by Y
++                boolean flag = d1 <= d2;
++                if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
++                    double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
++                    if (rangeY != -1) {
++                        double vec3d_dy = player.getY() - this.entity.getY();
++                        flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
++                    }
++                }
++                flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
++                // Paper end - Configurable entity tracking range by Y
++                // CraftBukkit start - respect vanish API
++                if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
++                    flag = false;
++                }
++                // CraftBukkit end
+                 if (flag) {
+                     if (this.seenBy.add(player.connection)) {
++                        // Paper start - entity tracking events
++                        if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
+                         this.serverEntity.addPairing(player);
++                        }
++                        // Paper end - entity tracking events
+                     }
+                 } else if (this.seenBy.remove(player.connection)) {
+                     this.serverEntity.removePairing(player);
+@@ -1331,6 +_,7 @@
+ 
+             for (Entity entity : this.entity.getIndirectPassengers()) {
+                 int i1 = entity.getType().clientTrackingRange() * 16;
++                i1 = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i1); // Paper
+                 if (i1 > i) {
+                     i = i1;
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
new file mode 100644
index 0000000000..0626bbca7a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/server/level/DistanceManager.java
++++ b/net/minecraft/server/level/DistanceManager.java
+@@ -107,6 +_,12 @@
+         }
+ 
+         if (!this.chunksToUpdateFutures.isEmpty()) {
++            // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
++            for (final ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
++                chunkHolder.callEventIfUnloading(chunkMap);
++            }
++            // CraftBukkit end - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
++
+             for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
+                 chunkHolder.updateHighestAllowedStatus(chunkMap);
+             }
+@@ -143,7 +_,7 @@
+         }
+     }
+ 
+-    void addTicket(long chunkPos, Ticket<?> ticket) {
++    boolean addTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+         SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
+         int ticketLevelAt = getTicketLevelAt(tickets);
+         Ticket<?> ticket1 = tickets.addOrGet(ticket);
+@@ -151,11 +_,14 @@
+         if (ticket.getTicketLevel() < ticketLevelAt) {
+             this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
+         }
++        return ticket == ticket1; // CraftBukkit
+     }
+ 
+-    void removeTicket(long chunkPos, Ticket<?> ticket) {
++    boolean removeTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+         SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
++        boolean removed = false; // CraftBukkit
+         if (tickets.remove(ticket)) {
++            removed = true; // CraftBukkit
+         }
+ 
+         if (tickets.isEmpty()) {
+@@ -163,6 +_,7 @@
+         }
+ 
+         this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
++        return removed; // CraftBukkit
+     }
+ 
+     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
+@@ -175,17 +_,29 @@
+     }
+ 
+     public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
++        // CraftBukkit start
++        this.addRegionTicketAtDistance(type, pos, distance, value);
++    }
++    public <T> boolean addRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
++        // CraftBukkit end
+         Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+         long packedChunkPos = pos.toLong();
+-        this.addTicket(packedChunkPos, ticket);
++        final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit
+         this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
++        return addded; // CraftBukkit
+     }
+ 
+     public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
++        // CraftBukkit start
++        removeRegionTicketAtDistance(type, pos, distance, value);
++    }
++    public <T> boolean removeRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
++        // CraftBukkit end
+         Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+         long packedChunkPos = pos.toLong();
+-        this.removeTicket(packedChunkPos, ticket);
++        final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit
+         this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
++        return removed; // CraftBukkit
+     }
+ 
+     private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
+@@ -217,8 +_,9 @@
+         ChunkPos chunkPos = sectionPos.chunk();
+         long packedChunkPos = chunkPos.toLong();
+         ObjectSet<ServerPlayer> set = this.playersPerChunk.get(packedChunkPos);
+-        set.remove(player);
+-        if (set.isEmpty()) {
++        if (set == null) return; // CraftBukkit - SPIGOT-6208
++        if (set != null) set.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
++        if (set == null || set.isEmpty()) { // Paper
+             this.playersPerChunk.remove(packedChunkPos);
+             this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
+             this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);
+@@ -299,7 +_,7 @@
+     }
+ 
+     public void removeTicketsOnClosing() {
+-        ImmutableSet<TicketType<?>> set = ImmutableSet.of(TicketType.UNKNOWN);
++        ImmutableSet<TicketType<?>> set = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
+         ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
+ 
+         while (objectIterator.hasNext()) {
+@@ -329,6 +_,26 @@
+     public boolean hasTickets() {
+         return !this.tickets.isEmpty();
+     }
++
++    // CraftBukkit start
++    public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
++        Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
++
++        for (java.util.Iterator<Entry<SortedArraySet<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
++            Entry<SortedArraySet<Ticket<?>>> entry = iterator.next();
++            SortedArraySet<Ticket<?>> tickets = entry.getValue();
++            if (tickets.remove(target)) {
++                // copied from removeTicket
++                this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
++
++                // can't use entry after it's removed
++                if (tickets.isEmpty()) {
++                    iterator.remove();
++                }
++            }
++        }
++    }
++    // CraftBukkit end
+ 
+     class ChunkTicketTracker extends ChunkTracker {
+         private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch
rename to paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
index f7949f17c6..5f558ccc4a 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
-@@ -74,6 +74,13 @@
+@@ -73,6 +_,13 @@
      @Nullable
      @VisibleForDebug
      private NaturalSpawner.SpawnState lastSpawnState;
@@ -12,9 +12,9 @@
 +    long chunkFutureAwaitCounter;
 +    // Paper end
  
-     public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
-         this.level = world;
-@@ -95,6 +102,64 @@
+     public ServerChunkCache(
+         ServerLevel level,
+@@ -121,6 +_,64 @@
          this.clearCache();
      }
  
@@ -79,53 +79,50 @@
      @Override
      public ThreadedLevelLightEngine getLightEngine() {
          return this.lightEngine;
-@@ -138,7 +203,7 @@
-                 if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) {
-                     ChunkAccess ichunkaccess = this.lastChunk[l];
- 
--                    if (ichunkaccess != null || !create) {
-+                    if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
-                         return ichunkaccess;
+@@ -160,7 +_,7 @@
+             for (int i = 0; i < 4; i++) {
+                 if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) {
+                     ChunkAccess chunkAccess = this.lastChunk[i];
+-                    if (chunkAccess != null || !requireChunk) {
++                    if (chunkAccess != null) {  // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
+                         return chunkAccess;
                      }
                  }
-@@ -150,8 +215,9 @@
- 
-             Objects.requireNonNull(completablefuture);
-             chunkproviderserver_b.managedBlock(completablefuture::isDone);
+@@ -169,6 +_,7 @@
+             profilerFiller.incrementCounter("getChunkCacheMiss");
+             CompletableFuture<ChunkResult<ChunkAccess>> chunkFutureMainThread = this.getChunkFutureMainThread(x, z, chunkStatus, requireChunk);
+             this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone);
 +            // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
-             ChunkResult<ChunkAccess> chunkresult = (ChunkResult) completablefuture.join();
--            ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse((Object) null);
-+            ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
- 
-             if (ichunkaccess1 == null && create) {
-                 throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError()));
-@@ -231,7 +297,15 @@
-         int l = ChunkLevel.byStatus(leastStatus);
-         ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
- 
--        if (create) {
+             ChunkResult<ChunkAccess> chunkResult = chunkFutureMainThread.join();
+             ChunkAccess chunkAccess1 = chunkResult.orElse(null);
+             if (chunkAccess1 == null && requireChunk) {
+@@ -240,7 +_,15 @@
+         long packedChunkPos = chunkPos.toLong();
+         int i = ChunkLevel.byStatus(chunkStatus);
+         ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
+-        if (requireChunk) {
 +        // CraftBukkit start - don't add new ticket for currently unloading chunk
 +        boolean currentlyUnloading = false;
-+        if (playerchunk != null) {
-+            FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
-+            FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
++        if (visibleChunkIfPresent != null) {
++            FullChunkStatus oldChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.oldTicketLevel);
++            FullChunkStatus currentChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.getTicketLevel());
 +            currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
 +        }
-+        if (create && !currentlyUnloading) {
-+            // CraftBukkit end
-             this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
-             if (this.chunkAbsent(playerchunk, l)) {
-                 ProfilerFiller gameprofilerfiller = Profiler.get();
-@@ -250,7 +324,7 @@
++        if (requireChunk && !currentlyUnloading) {
++        // CraftBukkit end
+             this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos);
+             if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+                 ProfilerFiller profilerFiller = Profiler.get();
+@@ -260,7 +_,7 @@
      }
  
-     private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
--        return holder == null || holder.getTicketLevel() > maxLevel;
-+        return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks
+     private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) {
+-        return chunkHolder == null || chunkHolder.getTicketLevel() > status;
++        return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks
      }
  
      @Override
-@@ -279,7 +353,7 @@
+@@ -287,7 +_,7 @@
          return this.mainThreadProcessor.pollTask();
      }
  
@@ -133,13 +130,12 @@
 +    public boolean runDistanceManagerUpdates() { // Paper - public
          boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
          boolean flag1 = this.chunkMap.promoteChunkMap();
- 
-@@ -309,18 +383,40 @@
+         this.chunkMap.runGenerationTasks();
+@@ -315,17 +_,38 @@
  
      @Override
      public void close() throws IOException {
 -        this.save(true);
-+        // CraftBukkit start
 +        this.close(true);
 +    }
 +
@@ -168,26 +164,24 @@
 +    // CraftBukkit end
 +
      @Override
-     public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) {
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("purge");
+     public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) {
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("purge");
 -        if (this.level.tickRateManager().runsNormally() || !tickChunks) {
 +        if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot
              this.distanceManager.purgeStaleTickets();
          }
  
-@@ -401,14 +497,22 @@
- 
-         this.lastSpawnState = spawnercreature_d;
+@@ -400,11 +_,19 @@
+         );
+         this.lastSpawnState = spawnState;
          profiler.popPush("spawnAndTick");
--        boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
-+        boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
-         int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
-         List list1;
- 
-         if (flag && (this.spawnEnemies || this.spawnFriendlies)) {
--            boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L;
+-        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
++        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit;
+         int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
+         List<MobCategory> filteredSpawningCategories;
+         if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
+-            boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L;
 +            // Paper start - PlayerNaturallySpawnCreaturesEvent
 +            for (ServerPlayer entityPlayer : this.level.players()) {
 +                int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
@@ -196,60 +190,51 @@
 +                entityPlayer.playerNaturallySpawnedEvent.callEvent();
 +            }
 +            // Paper end - PlayerNaturallySpawnCreaturesEvent
-+            boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
- 
--            list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
-+            list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit
++            boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+             filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag);
          } else {
-             list1 = List.of();
-         }
-@@ -420,7 +524,7 @@
-             ChunkPos chunkcoordintpair = chunk.getPos();
- 
-             chunk.incrementInhabitedTime(timeDelta);
--            if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) {
-+            if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
-                 NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1);
+             filteredSpawningCategories = List.of();
+@@ -413,7 +_,7 @@
+         for (LevelChunk levelChunk : chunks) {
+             ChunkPos pos = levelChunk.getPos();
+             levelChunk.incrementInhabitedTime(timeInhabited);
+-            if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos)) {
++            if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot
+                 NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, filteredSpawningCategories);
              }
  
-@@ -541,10 +645,16 @@
+@@ -526,8 +_,13 @@
  
      @Override
-     public void setSpawnSettings(boolean spawnMonsters) {
--        this.spawnEnemies = spawnMonsters;
+     public void setSpawnSettings(boolean spawnSettings) {
+-        this.spawnEnemies = spawnSettings;
 -        this.spawnFriendlies = this.spawnFriendlies;
 +        // CraftBukkit start
-+        this.setSpawnSettings(spawnMonsters, this.spawnFriendlies);
-     }
- 
-+    public void setSpawnSettings(boolean flag, boolean spawnFriendlies) {
-+        this.spawnEnemies = flag;
++        this.setSpawnSettings(spawnSettings, this.spawnFriendlies);
++    }
++    public void setSpawnSettings(boolean spawnEnemies, boolean spawnFriendlies) {
++        this.spawnEnemies = spawnEnemies;
 +        this.spawnFriendlies = spawnFriendlies;
 +        // CraftBukkit end
-+    }
-+
-     public String getChunkDebugData(ChunkPos pos) {
-         return this.chunkMap.getChunkDebugData(pos);
      }
-@@ -618,14 +728,20 @@
-         }
+ 
+     public String getChunkDebugData(ChunkPos chunkPos) {
+@@ -603,12 +_,18 @@
  
          @Override
--        protected boolean pollTask() {
-+        // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
-+        public boolean pollTask() {
-+        try {
+         public boolean pollTask() {
++            try { // CraftBukkit - process pending Chunk loadCallback() and unloadCallback() after each run task
              if (ServerChunkCache.this.runDistanceManagerUpdates()) {
                  return true;
              } else {
                  ServerChunkCache.this.lightEngine.tryScheduleUpdate();
                  return super.pollTask();
              }
-+        } finally {
-+            ServerChunkCache.this.chunkMap.callbackExecutor.run();
++            // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
++            } finally {
++                ServerChunkCache.this.chunkMap.callbackExecutor.run();
++            }
++            // CraftBukkit end - process pending Chunk loadCallback() and unloadCallback() after each run task
          }
-+        // CraftBukkit end
-+        }
      }
- 
-     private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
+ }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
new file mode 100644
index 0000000000..5f30bfd43d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
@@ -0,0 +1,134 @@
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
+@@ -65,13 +_,17 @@
+     private Vec3 lastSentMovement;
+     private int tickCount;
+     private int teleportDelay;
+-    private List<Entity> lastPassengers = Collections.emptyList();
++    private List<Entity> lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks
+     private boolean wasRiding;
+     private boolean wasOnGround;
+     @Nullable
+     private List<SynchedEntityData.DataValue<?>> trackedDataValues;
+ 
+-    public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer<Packet<?>> broadcast) {
++    // CraftBukkit start
++    private final Set<net.minecraft.server.network.ServerPlayerConnection> trackedPlayers;
++    public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer<Packet<?>> broadcast, final Set<net.minecraft.server.network.ServerPlayerConnection> trackedPlayers) {
++        this.trackedPlayers = trackedPlayers;
++    // CraftBukkit end
+         this.level = level;
+         this.broadcast = broadcast;
+         this.entity = entity;
+@@ -89,7 +_,7 @@
+     public void sendChanges() {
+         List<Entity> passengers = this.entity.getPassengers();
+         if (!passengers.equals(this.lastPassengers)) {
+-            this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
++            this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
+             removedPassengers(passengers, this.lastPassengers)
+                 .forEach(
+                     removedPassenger -> {
+@@ -102,10 +_,10 @@
+             this.lastPassengers = passengers;
+         }
+ 
+-        if (this.entity instanceof ItemFrame itemFrame && this.tickCount % 10 == 0) {
++        if (!this.trackedPlayers.isEmpty() && this.entity instanceof ItemFrame itemFrame /*&& this.tickCount % 10 == 0*/) { // CraftBukkit - moved tickCount below // Paper - Perf: Only tick item frames if players can see it
+             ItemStack item = itemFrame.getItem();
+-            if (item.getItem() instanceof MapItem) {
+-                MapId mapId = item.get(DataComponents.MAP_ID);
++            if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && item.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
++                MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames
+                 MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level);
+                 if (savedData != null) {
+                     for (ServerPlayer serverPlayer : this.level.players()) {
+@@ -141,7 +_,13 @@
+             } else {
+                 this.teleportDelay++;
+                 Vec3 vec3 = this.entity.trackingPosition();
+-                boolean flag1 = this.positionCodec.delta(vec3).lengthSqr() >= 7.6293945E-6F;
++                // Paper start - reduce allocation of Vec3D here
++                Vec3 base = this.positionCodec.base;
++                double vec3_dx = vec3.x - base.x;
++                double vec3_dy = vec3.y - base.y;
++                double vec3_dz = vec3.z - base.z;
++                boolean flag1 = (vec3_dx * vec3_dx + vec3_dy * vec3_dy + vec3_dz * vec3_dz) >= 7.62939453125E-6D;
++                // Paper end - reduce allocation of Vec3D here
+                 Packet<?> packet = null;
+                 boolean flag2 = flag1 || this.tickCount % 60 == 0;
+                 boolean flag3 = false;
+@@ -219,6 +_,25 @@
+ 
+         this.tickCount++;
+         if (this.entity.hurtMarked) {
++            // CraftBukkit start - Create PlayerVelocity event
++            boolean cancelled = false;
++
++            if (this.entity instanceof ServerPlayer) {
++                org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.entity.getBukkitEntity();
++                org.bukkit.util.Vector velocity = player.getVelocity();
++
++                org.bukkit.event.player.PlayerVelocityEvent event = new org.bukkit.event.player.PlayerVelocityEvent(player, velocity.clone());
++                if (!event.callEvent()) {
++                    cancelled = true;
++                } else if (!velocity.equals(event.getVelocity())) {
++                    player.setVelocity(event.getVelocity());
++                }
++            }
++
++            if (cancelled) {
++                return;
++            }
++            // CraftBukkit end
+             this.entity.hurtMarked = false;
+             this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
+         }
+@@ -273,7 +_,10 @@
+ 
+     public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> consumer) {
+         if (this.entity.isRemoved()) {
+-            LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++            // CraftBukkit start - Remove useless error spam, just return
++            // LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++            return;
++            // CraftBukkit end
+         }
+ 
+         Packet<ClientGamePacketListener> addEntityPacket = this.entity.getAddEntityPacket(this);
+@@ -285,6 +_,12 @@
+         boolean flag = this.trackDelta;
+         if (this.entity instanceof LivingEntity) {
+             Collection<AttributeInstance> syncableAttributes = ((LivingEntity)this.entity).getAttributes().getSyncableAttributes();
++
++            // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
++            if (this.entity.getId() == player.getId()) {
++                ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(syncableAttributes, false);
++            }
++            // CraftBukkit end
+             if (!syncableAttributes.isEmpty()) {
+                 consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), syncableAttributes));
+             }
+@@ -309,8 +_,9 @@
+             }
+ 
+             if (!list.isEmpty()) {
+-                consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
++                consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
+             }
++            ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
+         }
+ 
+         if (!this.entity.getPassengers().isEmpty()) {
+@@ -357,6 +_,11 @@
+         if (this.entity instanceof LivingEntity) {
+             Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
+             if (!attributesToSync.isEmpty()) {
++                // CraftBukkit start - Send scaled max health
++                if (this.entity instanceof ServerPlayer) {
++                    ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
++                }
++                // CraftBukkit end
+                 this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
new file mode 100644
index 0000000000..20b894a4b0
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -0,0 +1,1217 @@
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -54,7 +_,6 @@
+ import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
+ import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
+ import net.minecraft.network.protocol.game.ClientboundExplodePacket;
+-import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
+ import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
+ import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
+ import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
+@@ -120,6 +_,7 @@
+ import net.minecraft.world.level.StructureManager;
+ import net.minecraft.world.level.WorldGenLevel;
+ import net.minecraft.world.level.biome.Biome;
++import net.minecraft.world.level.biome.BiomeSource;
+ import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.SnowLayerBlock;
+@@ -145,7 +_,9 @@
+ import net.minecraft.world.level.gameevent.DynamicGameEventListener;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.gameevent.GameEventDispatcher;
++import net.minecraft.world.level.levelgen.FlatLevelSource;
+ import net.minecraft.world.level.levelgen.Heightmap;
++import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
+ import net.minecraft.world.level.levelgen.structure.BoundingBox;
+ import net.minecraft.world.level.levelgen.structure.Structure;
+ import net.minecraft.world.level.levelgen.structure.StructureCheck;
+@@ -161,7 +_,7 @@
+ import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+ import net.minecraft.world.level.storage.LevelStorageSource;
+-import net.minecraft.world.level.storage.ServerLevelData;
++import net.minecraft.world.level.storage.PrimaryLevelData;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.BooleanOp;
+@@ -182,7 +_,7 @@
+     final List<ServerPlayer> players = Lists.newArrayList();
+     public final ServerChunkCache chunkSource;
+     private final MinecraftServer server;
+-    public final ServerLevelData serverLevelData;
++    public final PrimaryLevelData serverLevelData; // CraftBukkit - type
+     private int lastSpawnChunkRadius;
+     final EntityTickList entityTickList = new EntityTickList();
+     public final PersistentEntitySectionManager<Entity> entityManager;
+@@ -209,11 +_,132 @@
+     private final boolean tickTime;
+     private final RandomSequences randomSequences;
+ 
++    // CraftBukkit start
++    public final LevelStorageSource.LevelStorageAccess levelStorageAccess;
++    public final UUID uuid;
++    public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
++    public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
++
++    public LevelChunk getChunkIfLoaded(int x, int z) {
++        return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
++    }
++
++    @Override
++    public ResourceKey<LevelStem> getTypeKey() {
++        return this.levelStorageAccess.dimensionType;
++    }
++
++    // Paper start
++    public final boolean areChunksLoadedForMove(AABB axisalignedbb) {
++        // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
++        // ICollisionAccess methods for VoxelShapes)
++        // be more strict too, add a block (dumb plugins in move events?)
++        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
++        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
++
++        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
++        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
++
++        int minChunkX = minBlockX >> 4;
++        int maxChunkX = maxBlockX >> 4;
++
++        int minChunkZ = minBlockZ >> 4;
++        int maxChunkZ = maxBlockZ >> 4;
++
++        ServerChunkCache chunkProvider = this.getChunkSource();
++
++        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
++            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
++                if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
++                    return false;
++                }
++            }
++        }
++
++        return true;
++    }
++
++    public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority,
++                                             java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
++        if (Thread.currentThread() != this.thread) {
++            this.getChunkSource().mainThreadProcessor.execute(() -> {
++                this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad);
++            });
++            return;
++        }
++        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
++        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
++
++        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
++        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
++
++        int minChunkX = minBlockX >> 4;
++        int minChunkZ = minBlockZ >> 4;
++
++        int maxChunkX = maxBlockX >> 4;
++        int maxChunkZ = maxBlockZ >> 4;
++
++        this.loadChunks(minChunkX, minChunkZ, maxChunkX, maxChunkZ, priority, onLoad);
++    }
++
++    public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ,
++                                 ca.spottedleaf.concurrentutil.util.Priority priority,
++                                 java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
++        List<net.minecraft.world.level.chunk.ChunkAccess> ret = new java.util.ArrayList<>();
++        it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList();
++        ServerChunkCache chunkProvider = this.getChunkSource();
++
++        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
++        int[] loadedChunks = new int[1];
++
++        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
++
++        java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
++            if (chunk != null) {
++                int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel());
++                ret.add(chunk);
++                ticketLevels.add(ticketLevel);
++                chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
++            }
++            if (++loadedChunks[0] == requiredChunks) {
++                try {
++                    onLoad.accept(java.util.Collections.unmodifiableList(ret));
++                } finally {
++                    for (int i = 0, len = ret.size(); i < len; ++i) {
++                        ChunkPos chunkPos = ret.get(i).getPos();
++                        int ticketLevel = ticketLevels.getInt(i);
++
++                        chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
++                        chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
++                    }
++                }
++            }
++        };
++
++        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
++            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
++                ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
++                    this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
++                );
++            }
++        }
++    }
++    // Paper end
++
++    // Paper start - optimise getPlayerByUUID
++    @Nullable
++    @Override
++    public Player getPlayerByUUID(UUID uuid) {
++        final Player player = this.getServer().getPlayerList().getPlayer(uuid);
++        return player != null && player.level() == this ? player : null;
++    }
++    // Paper end - optimise getPlayerByUUID
++
+     public ServerLevel(
+         MinecraftServer server,
+         Executor dispatcher,
+         LevelStorageSource.LevelStorageAccess levelStorageAccess,
+-        ServerLevelData serverLevelData,
++        PrimaryLevelData serverLevelData, // CraftBukkit
+         ResourceKey<Level> dimension,
+         LevelStem levelStem,
+         ChunkProgressListener progressListener,
+@@ -221,14 +_,38 @@
+         long biomeZoomSeed,
+         List<CustomSpawner> customSpawners,
+         boolean tickTime,
+-        @Nullable RandomSequences randomSequences
++        @Nullable RandomSequences randomSequences,
++        org.bukkit.World.Environment env, // CraftBukkit
++        org.bukkit.generator.ChunkGenerator gen, // CraftBukkit
++        org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit
+     ) {
+-        super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates());
++        // CraftBukkit start
++        super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules()))); // Paper - create paper world configs
++        this.pvpMode = server.isPvpAllowed();
++        this.levelStorageAccess = levelStorageAccess;
++        this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
++        // CraftBukkit end
+         this.tickTime = tickTime;
+         this.server = server;
+         this.customSpawners = customSpawners;
+         this.serverLevelData = serverLevelData;
+         ChunkGenerator chunkGenerator = levelStem.generator();
++        // CraftBukkit start
++        this.serverLevelData.setWorld(this);
++
++        if (biomeProvider != null) {
++            BiomeSource worldChunkManager = new org.bukkit.craftbukkit.generator.CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkGenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider
++            if (chunkGenerator instanceof NoiseBasedChunkGenerator cga) {
++                chunkGenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
++            } else if (chunkGenerator instanceof FlatLevelSource cpf) {
++                chunkGenerator = new FlatLevelSource(cpf.settings(), worldChunkManager);
++            }
++        }
++
++        if (gen != null) {
++            chunkGenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkGenerator, gen);
++        }
++        // CraftBukkit end
+         boolean flag = server.forceSynchronousWrites();
+         DataFixer fixerUpper = server.getFixerUpper();
+         EntityPersistentStorage<Entity> entityPersistentStorage = new EntityStorage(
+@@ -250,8 +_,8 @@
+             server.getStructureManager(),
+             dispatcher,
+             chunkGenerator,
+-            server.getPlayerList().getViewDistance(),
+-            server.getPlayerList().getSimulationDistance(),
++            this.spigotConfig.viewDistance, // Spigot
++            this.spigotConfig.simulationDistance, // Spigot
+             flag,
+             progressListener,
+             this.entityManager::updateChunkStatus,
+@@ -272,7 +_,7 @@
+             this.chunkSource.chunkScanner(),
+             this.registryAccess(),
+             server.getStructureManager(),
+-            dimension,
++            getTypeKey(),  // Paper - Fix missing CB diff
+             chunkGenerator,
+             this.chunkSource.randomState(),
+             this,
+@@ -281,7 +_,7 @@
+             fixerUpper
+         );
+         this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck);
+-        if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
++        if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END
+             this.dragonFight = new EndDragonFight(this, seed, server.getWorldData().endDragonFightData());
+         } else {
+             this.dragonFight = null;
+@@ -292,7 +_,15 @@
+         this.randomSequences = Objects.requireNonNullElseGet(
+             randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(seed), "random_sequences")
+         );
+-    }
++        this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
++    }
++
++    // Paper start
++    @Override
++    public boolean hasChunk(int chunkX, int chunkZ) {
++        return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
++    }
++    // Paper end
+ 
+     @Deprecated
+     @VisibleForTesting
+@@ -304,8 +_,8 @@
+         this.serverLevelData.setClearWeatherTime(clearTime);
+         this.serverLevelData.setRainTime(weatherTime);
+         this.serverLevelData.setThunderTime(weatherTime);
+-        this.serverLevelData.setRaining(isRaining);
+-        this.serverLevelData.setThundering(isThundering);
++        this.serverLevelData.setRaining(isRaining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
++        this.serverLevelData.setThundering(isThundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
+     }
+ 
+     @Override
+@@ -332,12 +_,25 @@
+ 
+         int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
+         if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
++            // Paper start - create time skip event - move up calculations
++            final long newDayTime = this.levelData.getDayTime() + 24000L;
++            org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(
++                this.getWorld(),
++                org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP,
++                (newDayTime - newDayTime % 24000L) - this.getDayTime()
++            );
++            // Paper end - create time skip event - move up calculations
+             if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+-                long l = this.levelData.getDayTime() + 24000L;
+-                this.setDayTime(l - l % 24000L);
++                // Paper start - call time skip event if gamerule is enabled
++                // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime
++                // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param
++                if (event.callEvent()) {
++                    this.setDayTime(this.getDayTime() + event.getSkipAmount());
++                }
++                // Paper end - call time skip event if gamerule is enabled
+             }
+ 
+-            this.wakeUpAllPlayers();
++            if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled
+             if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
+                 this.resetWeatherCycle();
+             }
+@@ -352,9 +_,9 @@
+         if (!this.isDebug() && runsNormally) {
+             long l = this.getGameTime();
+             profilerFiller.push("blockTicks");
+-            this.blockTicks.tick(l, 65536, this::tickBlock);
++            this.blockTicks.tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks
+             profilerFiller.popPush("fluidTicks");
+-            this.fluidTicks.tick(l, 65536, this::tickFluid);
++            this.fluidTicks.tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks
+             profilerFiller.pop();
+         }
+ 
+@@ -372,7 +_,7 @@
+ 
+         this.handlingTick = false;
+         profilerFiller.pop();
+-        boolean flag = !this.players.isEmpty() || !this.getForcedChunks().isEmpty();
++        boolean flag = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this
+         if (flag) {
+             this.resetEmptyTime();
+         }
+@@ -385,6 +_,7 @@
+                 profilerFiller.pop();
+             }
+ 
++            org.spigotmc.ActivationRange.activateEntities(this); // Spigot
+             this.entityTickList
+                 .forEach(
+                     entity -> {
+@@ -461,12 +_,12 @@
+         int minBlockZ = pos.getMinBlockZ();
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("thunder");
+-        if (isRaining && this.isThundering() && this.random.nextInt(100000) == 0) {
++        if (!this.paperConfig().environment.disableThunder && isRaining && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
+             BlockPos blockPos = this.findLightningTargetAround(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
+             if (this.isRainingAt(blockPos)) {
+                 DifficultyInstance currentDifficultyAt = this.getCurrentDifficultyAt(blockPos);
+                 boolean flag = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
+-                    && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * 0.01
++                    && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) // Paper - Configurable spawn chances for skeleton horses
+                     && !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
+                 if (flag) {
+                     SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
+@@ -474,7 +_,7 @@
+                         skeletonHorse.setTrap(true);
+                         skeletonHorse.setAge(0);
+                         skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
+-                        this.addFreshEntity(skeletonHorse);
++                        this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
+                     }
+                 }
+ 
+@@ -482,18 +_,20 @@
+                 if (lightningBolt != null) {
+                     lightningBolt.moveTo(Vec3.atBottomCenterOf(blockPos));
+                     lightningBolt.setVisualOnly(flag);
+-                    this.addFreshEntity(lightningBolt);
++                    this.strikeLightning(lightningBolt, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit
+                 }
+             }
+         }
+ 
+         profilerFiller.popPush("iceandsnow");
+ 
++        if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
+         for (int i = 0; i < randomTickSpeed; i++) {
+             if (this.random.nextInt(48) == 0) {
+                 this.tickPrecipitation(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
+             }
+         }
++        } // Paper - Option to disable ice and snow
+ 
+         profilerFiller.popPush("tickBlocks");
+         if (randomTickSpeed > 0) {
+@@ -535,7 +_,7 @@
+         BlockPos blockPos1 = heightmapPos.below();
+         Biome biome = this.getBiome(heightmapPos).value();
+         if (biome.shouldFreeze(this, blockPos1)) {
+-            this.setBlockAndUpdate(blockPos1, Blocks.ICE.defaultBlockState());
++            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockPos1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+         }
+ 
+         if (this.isRaining()) {
+@@ -547,10 +_,10 @@
+                     if (layersValue < Math.min(_int, 8)) {
+                         BlockState blockState1 = blockState.setValue(SnowLayerBlock.LAYERS, Integer.valueOf(layersValue + 1));
+                         Block.pushEntitiesUp(blockState, blockState1, this, heightmapPos);
+-                        this.setBlockAndUpdate(heightmapPos, blockState1);
++                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, blockState1, null); // CraftBukkit
+                     }
+                 } else {
+-                    this.setBlockAndUpdate(heightmapPos, Blocks.SNOW.defaultBlockState());
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
+                 }
+             }
+ 
+@@ -575,6 +_,11 @@
+     }
+ 
+     protected BlockPos findLightningTargetAround(BlockPos pos) {
++        // Paper start - Add methods to find targets for lightning strikes
++        return this.findLightningTargetAround(pos, false);
++    }
++    public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) {
++        // Paper end - Add methods to find targets for lightning strikes
+         BlockPos heightmapPos = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
+         Optional<BlockPos> optional = this.findLightningRod(heightmapPos);
+         if (optional.isPresent()) {
+@@ -582,11 +_,12 @@
+         } else {
+             AABB aabb = AABB.encapsulatingFullBlocks(heightmapPos, heightmapPos.atY(this.getMaxY() + 1)).inflate(3.0);
+             List<LivingEntity> entitiesOfClass = this.getEntitiesOfClass(
+-                LivingEntity.class, aabb, entity -> entity != null && entity.isAlive() && this.canSeeSky(entity.blockPosition())
++                LivingEntity.class, aabb, entity -> entity != null && entity.isAlive() && this.canSeeSky(entity.blockPosition()) && !entity.isSpectator() // Paper - Fix lightning being able to hit spectators (MC-262422)
+             );
+             if (!entitiesOfClass.isEmpty()) {
+                 return entitiesOfClass.get(this.random.nextInt(entitiesOfClass.size())).blockPosition();
+             } else {
++                if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes
+                 if (heightmapPos.getY() == this.getMinY() - 1) {
+                     heightmapPos = heightmapPos.above(2);
+                 }
+@@ -673,8 +_,8 @@
+                 this.serverLevelData.setThunderTime(thunderTime);
+                 this.serverLevelData.setRainTime(rainTime);
+                 this.serverLevelData.setClearWeatherTime(clearWeatherTime);
+-                this.serverLevelData.setThundering(isThundering);
+-                this.serverLevelData.setRaining(isRaining1);
++                this.serverLevelData.setThundering(isThundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
++                this.serverLevelData.setRaining(isRaining1, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
+             }
+ 
+             this.oThunderLevel = this.thunderLevel;
+@@ -695,6 +_,7 @@
+             this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
+         }
+ 
++        /* CraftBukkit start
+         if (this.oRainLevel != this.rainLevel) {
+             this.server
+                 .getPlayerList()
+@@ -717,14 +_,47 @@
+             this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel));
+             this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
+         }
++        */
++        for (int idx = 0; idx < this.players.size(); ++idx) {
++            if (((ServerPlayer) this.players.get(idx)).level() == this) {
++                ((ServerPlayer) this.players.get(idx)).tickWeather();
++            }
++        }
++
++        if (isRaining != this.isRaining()) {
++            // Only send weather packets to those affected
++            for (int idx = 0; idx < this.players.size(); ++idx) {
++                if (((ServerPlayer) this.players.get(idx)).level() == this) {
++                    ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false);
++                }
++            }
++        }
++        for (int idx = 0; idx < this.players.size(); ++idx) {
++            if (((ServerPlayer) this.players.get(idx)).level() == this) {
++                ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
++            }
++        }
++        // CraftBukkit end
+     }
+ 
+     @VisibleForTesting
+     public void resetWeatherCycle() {
+-        this.serverLevelData.setRainTime(0);
+-        this.serverLevelData.setRaining(false);
+-        this.serverLevelData.setThunderTime(0);
+-        this.serverLevelData.setThundering(false);
++        // CraftBukkit start
++        this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
++        // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++        // Not that everyone ever manages to get the whole server to sleep at the same time....
++        if (!this.serverLevelData.isRaining()) {
++            this.serverLevelData.setRainTime(0);
++        }
++        // CraftBukkit end
++        this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
++        // CraftBukkit start
++        // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++        // Not that everyone ever manages to get the whole server to sleep at the same time....
++        if (!this.serverLevelData.isThundering()) {
++            this.serverLevelData.setThunderTime(0);
++        }
++        // CraftBukkit end
+     }
+ 
+     public void resetEmptyTime() {
+@@ -747,12 +_,20 @@
+     }
+ 
+     public void tickNonPassenger(Entity entity) {
++        // Spigot start
++        if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
++            entity.tickCount++;
++            entity.inactiveTick();
++            return;
++        }
++        // Spigot end
+         entity.setOldPosAndRot();
+         ProfilerFiller profilerFiller = Profiler.get();
+         entity.tickCount++;
+         profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
+         profilerFiller.incrementCounter("tickNonPassenger");
+         entity.tick();
++        entity.postTick(); // CraftBukkit
+         profilerFiller.pop();
+ 
+         for (Entity entity1 : entity.getPassengers()) {
+@@ -770,6 +_,7 @@
+             profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
+             profilerFiller.incrementCounter("tickPassenger");
+             passengerEntity.rideTick();
++            passengerEntity.postTick(); // CraftBukkit
+             profilerFiller.pop();
+ 
+             for (Entity entity : passengerEntity.getPassengers()) {
+@@ -786,6 +_,7 @@
+     public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
+         ServerChunkCache chunkSource = this.getChunkSource();
+         if (!skipSave) {
++            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit
+             if (progress != null) {
+                 progress.progressStartNoAbort(Component.translatable("menu.savingLevel"));
+             }
+@@ -802,11 +_,19 @@
+                 this.entityManager.autoSave();
+             }
+         }
++
++        // CraftBukkit start - moved from MinecraftServer.saveChunks
++        ServerLevel worldserver1 = this;
++
++        this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
++        this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
++        this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
++        // CraftBukkit end
+     }
+ 
+     private void saveLevelData(boolean join) {
+         if (this.dragonFight != null) {
+-            this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData());
++            this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
+         }
+ 
+         DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage();
+@@ -871,18 +_,40 @@
+ 
+     @Override
+     public boolean addFreshEntity(Entity entity) {
+-        return this.addEntity(entity);
++        // CraftBukkit start
++        return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++
++    @Override
++    public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++        return this.addEntity(entity, reason);
++        // CraftBukkit end
+     }
+ 
+     public boolean addWithUUID(Entity entity) {
+-        return this.addEntity(entity);
++        // CraftBukkit start
++        return this.addWithUUID(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++
++    public boolean addWithUUID(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++        return this.addEntity(entity, reason);
++        // CraftBukkit end
+     }
+ 
+     public void addDuringTeleport(Entity entity) {
++        // CraftBukkit start
++        // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds,
++        // since it is only an implementation detail, that a new entity is created when
++        // they are traveling between worlds.
++        this.addDuringTeleport(entity, null);
++    }
++
++    public void addDuringTeleport(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++        // CraftBukkit end
+         if (entity instanceof ServerPlayer serverPlayer) {
+             this.addPlayer(serverPlayer);
+         } else {
+-            this.addEntity(entity);
++            this.addEntity(entity, reason); // CraftBukkit
+         }
+     }
+ 
+@@ -905,40 +_,119 @@
+         this.entityManager.addNewEntity(player);
+     }
+ 
+-    private boolean addEntity(Entity entity) {
++    // CraftBukkit start
++    private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++        org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
++        entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process
++        // Paper start - extra debug info
++        if (entity.valid) {
++            MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable());
++            return true;
++        }
++        // Paper end - extra debug info
++        if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason
+         if (entity.isRemoved()) {
+-            LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType()));
++            // LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType())); // CraftBukkit - remove warning
+             return false;
+         } else {
++            if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added
++            // Paper start - capture all item additions to the world
++            if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
++                captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity);
++                return true;
++            }
++            // Paper end - capture all item additions to the world
++            // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world.
++            if (spawnReason != null && !org.bukkit.craftbukkit.event.CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
++                return false;
++            }
++            // CraftBukkit end
++
+             return this.entityManager.addNewEntity(entity);
+         }
+     }
+ 
+     public boolean tryAddFreshEntityWithPassengers(Entity entity) {
++        // CraftBukkit start
++        return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++    }
++
++    public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++        // CraftBukkit end
+         if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) {
+             return false;
+         } else {
+-            this.addFreshEntityWithPassengers(entity);
++            this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
+             return true;
+         }
+     }
+ 
+     public void unload(LevelChunk chunk) {
++        // Spigot Start
++        for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) {
++            if (tileentity instanceof net.minecraft.world.Container) {
++                // Paper start - this area looks like it can load chunks, change the behavior
++                // chests for example can apply physics to the world
++                // so instead we just change the active container and call the event
++                for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) {
++                    ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
++                }
++                // Paper end - this area looks like it can load chunks, change the behavior
++            }
++        }
++        // Spigot End
+         chunk.clearAllBlockEntities();
+         chunk.unregisterTickContainerFromLevel(this);
+     }
+ 
+     public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) {
+-        player.remove(reason);
+-    }
++        player.remove(reason, null); // CraftBukkit - add Bukkit remove cause
++    }
++
++    // CraftBukkit start
++    public boolean strikeLightning(Entity entitylightning) {
++        return this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.UNKNOWN);
++    }
++
++    public boolean strikeLightning(Entity entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause cause) {
++        org.bukkit.event.weather.LightningStrikeEvent lightning = org.bukkit.craftbukkit.event.CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause);
++
++        if (lightning.isCancelled()) {
++            return false;
++        }
++
++        return this.addFreshEntity(entitylightning);
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) {
++        // CraftBukkit start
++        Player breakerPlayer = null;
++        Entity entity = this.getEntity(breakerId);
++        if (entity instanceof Player) breakerPlayer = (Player) entity;
++        // CraftBukkit end
++
++        // Paper start - Add BlockBreakProgressUpdateEvent
++        // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server.
++        // Hence, do not call the event.
++        if (entity != null) {
++            float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f;
++            org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos);
++            new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity())
++                .callEvent();
++        }
++        // Paper end - Add BlockBreakProgressUpdateEvent
+         for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+             if (serverPlayer != null && serverPlayer.level() == this && serverPlayer.getId() != breakerId) {
+                 double d = pos.getX() - serverPlayer.getX();
+                 double d1 = pos.getY() - serverPlayer.getY();
+                 double d2 = pos.getZ() - serverPlayer.getZ();
++                // CraftBukkit start
++                if (breakerPlayer != null && !serverPlayer.getBukkitEntity().canSee(breakerPlayer.getBukkitEntity())) {
++                    continue;
++                }
++                // CraftBukkit end
+                 if (d * d + d1 * d1 + d2 * d2 < 1024.0) {
+                     serverPlayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
+                 }
+@@ -1000,7 +_,7 @@
+     public void levelEvent(@Nullable Player player, int type, BlockPos pos, int data) {
+         this.server
+             .getPlayerList()
+-            .broadcast(player, pos.getX(), pos.getY(), pos.getZ(), 64.0, this.dimension(), new ClientboundLevelEventPacket(type, pos, data, false));
++            .broadcast(player, pos.getX(), pos.getY(), pos.getZ(), 64.0, this.dimension(), new ClientboundLevelEventPacket(type, pos, data, false)); // Paper - diff on change (the 64.0 distance is used as defaults for sound ranges in spigot config for ender dragon, end portal and wither)
+     }
+ 
+     public int getLogicalHeight() {
+@@ -1009,6 +_,11 @@
+ 
+     @Override
+     public void gameEvent(Holder<GameEvent> gameEvent, Vec3 pos, GameEvent.Context context) {
++        // Paper start - Prevent GameEvents being fired from unloaded chunks
++        if (this.getChunkIfLoadedImmediately((Mth.floor(pos.x) >> 4), (Mth.floor(pos.z) >> 4)) == null) {
++            return;
++        }
++        // Paper end - Prevent GameEvents being fired from unloaded chunks
+         this.gameEventDispatcher.post(gameEvent, pos, context);
+     }
+ 
+@@ -1021,17 +_,28 @@
+ 
+         this.getChunkSource().blockChanged(pos);
+         this.pathTypesByPosCache.invalidate(pos);
++        if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
+         VoxelShape collisionShape = oldState.getCollisionShape(this, pos);
+         VoxelShape collisionShape1 = newState.getCollisionShape(this, pos);
+         if (Shapes.joinIsNotEmpty(collisionShape, collisionShape1, BooleanOp.NOT_SAME)) {
+             List<PathNavigation> list = new ObjectArrayList<>();
+ 
++            try { // Paper - catch CME see below why
+             for (Mob mob : this.navigatingMobs) {
+                 PathNavigation navigation = mob.getNavigation();
+                 if (navigation.shouldRecomputePath(pos)) {
+                     list.add(navigation);
+                 }
+             }
++            // Paper start - catch CME see below why
++            } catch (final java.util.ConcurrentModificationException concurrentModificationException) {
++                // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
++                // In this case we just run the update again across all the iterators as the chunk will then be loaded
++                // As this is a relative edge case it is much faster than copying navigators (on either read or write)
++                this.sendBlockUpdated(pos, oldState, newState, flags);
++                return;
++            }
++            // Paper end - catch CME see below why
+ 
+             try {
+                 this.isUpdatingNavigations = true;
+@@ -1043,15 +_,18 @@
+                 this.isUpdatingNavigations = false;
+             }
+         }
++        } // Paper - option to disable pathfinding updates
+     }
+ 
+     @Override
+     public void updateNeighborsAt(BlockPos pos, Block block) {
++        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
+         this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null));
+     }
+ 
+     @Override
+     public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) {
++        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
+         this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation);
+     }
+ 
+@@ -1100,6 +_,42 @@
+         ParticleOptions largeExplosionParticles,
+         Holder<SoundEvent> explosionSound
+     ) {
++        // CraftBukkit start
++        this.explode0(source, damageSource, damageCalculator, x, y, z, radius, fire, explosionInteraction, smallExplosionParticles, largeExplosionParticles, explosionSound);
++    }
++
++    public ServerExplosion explode0(
++        @Nullable Entity source,
++        @Nullable DamageSource damageSource,
++        @Nullable ExplosionDamageCalculator damageCalculator,
++        double x,
++        double y,
++        double z,
++        float radius,
++        boolean fire,
++        Level.ExplosionInteraction explosionInteraction,
++        ParticleOptions smallExplosionParticles,
++        ParticleOptions largeExplosionParticles,
++        Holder<SoundEvent> explosionSound
++    ) {
++        return this.explode0(source, damageSource, damageCalculator, x, y, z, radius, fire, explosionInteraction, smallExplosionParticles, largeExplosionParticles, explosionSound, null);
++    }
++    public ServerExplosion explode0(
++        @Nullable Entity source,
++        @Nullable DamageSource damageSource,
++        @Nullable ExplosionDamageCalculator damageCalculator,
++        double x,
++        double y,
++        double z,
++        float radius,
++        boolean fire,
++        Level.ExplosionInteraction explosionInteraction,
++        ParticleOptions smallExplosionParticles,
++        ParticleOptions largeExplosionParticles,
++        Holder<SoundEvent> explosionSound,
++        java.util.function.Consumer<ServerExplosion> configurator
++    ) {
++        // CraftBukkit end
+         Explosion.BlockInteraction blockInteraction = switch (explosionInteraction) {
+             case NONE -> Explosion.BlockInteraction.KEEP;
+             case BLOCK -> this.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY);
+@@ -1108,10 +_,17 @@
+                 : Explosion.BlockInteraction.KEEP;
+             case TNT -> this.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY);
+             case TRIGGER -> Explosion.BlockInteraction.TRIGGER_BLOCK;
++            case STANDARD -> Explosion.BlockInteraction.DESTROY; // CraftBukkit - handle custom explosion type
+         };
+         Vec3 vec3 = new Vec3(x, y, z);
+         ServerExplosion serverExplosion = new ServerExplosion(this, source, damageSource, damageCalculator, vec3, radius, fire, blockInteraction);
++        if (configurator != null) configurator.accept(serverExplosion);// Paper - Allow explosions to damage source
+         serverExplosion.explode();
++        // CraftBukkit start
++        if (serverExplosion.wasCanceled) {
++            return serverExplosion;
++        }
++        // CraftBukkit end
+         ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles;
+ 
+         for (ServerPlayer serverPlayer : this.players) {
+@@ -1120,6 +_,8 @@
+                 serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound));
+             }
+         }
++
++        return serverExplosion; // CraftBukkit
+     }
+ 
+     private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayGameRule) {
+@@ -1190,7 +_,7 @@
+     public <T extends ParticleOptions> int sendParticles(
+         T type, double posX, double posY, double posZ, int particleCount, double xOffset, double yOffset, double zOffset, double speed
+     ) {
+-        return this.sendParticles(type, false, false, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed);
++        return this.sendParticlesSource(null, type, false, false, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); // CraftBukkit - visibility api support
+     }
+ 
+     public <T extends ParticleOptions> int sendParticles(
+@@ -1206,13 +_,49 @@
+         double zOffset,
+         double speed
+     ) {
++    // CraftBukkit start - visibility api support
++        return this.sendParticlesSource(null, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed);
++    }
++    public <T extends ParticleOptions> int sendParticlesSource(
++        @javax.annotation.Nullable ServerPlayer sender,
++        T type,
++        boolean overrideLimiter,
++        boolean alwaysShow,
++        double posX,
++        double posY,
++        double posZ,
++        int particleCount,
++        double xOffset,
++        double yOffset,
++        double zOffset,
++        double speed
++    ) {
++        return sendParticlesSource(this.players, sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed);
++    }
++    public <T extends ParticleOptions> int sendParticlesSource(
++        List<ServerPlayer> receivers,
++        @javax.annotation.Nullable ServerPlayer sender,
++        T type,
++        boolean overrideLimiter,
++        boolean alwaysShow,
++        double posX,
++        double posY,
++        double posZ,
++        int particleCount,
++        double xOffset,
++        double yOffset,
++        double zOffset,
++        double speed
++    ) {
++    // CraftBukkit end - visibility api support
+         ClientboundLevelParticlesPacket clientboundLevelParticlesPacket = new ClientboundLevelParticlesPacket(
+             type, overrideLimiter, alwaysShow, posX, posY, posZ, (float)xOffset, (float)yOffset, (float)zOffset, (float)speed, particleCount
+         );
+         int i = 0;
+ 
+-        for (int i1 = 0; i1 < this.players.size(); i1++) {
+-            ServerPlayer serverPlayer = this.players.get(i1);
++        for (int i1 = 0; i1 < receivers.size(); i1++) { // Paper - particle API
++            ServerPlayer serverPlayer = receivers.get(i1);  // Paper - particle API
++            if (sender != null && !serverPlayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
+             if (this.sendParticles(serverPlayer, overrideLimiter, posX, posY, posZ, clientboundLevelParticlesPacket)) {
+                 i++;
+             }
+@@ -1280,7 +_,7 @@
+ 
+     @Nullable
+     public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipExistingChunks) {
+-        if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
++        if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
+             return null;
+         } else {
+             Optional<HolderSet.Named<Structure>> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag);
+@@ -1327,11 +_,38 @@
+     @Nullable
+     @Override
+     public MapItemSavedData getMapData(MapId mapId) {
+-        return this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), mapId.key());
++        // Paper start - Call missing map initialize event and set id
++        final DimensionDataStorage storage = this.getServer().overworld().getDataStorage();
++
++        final Optional<net.minecraft.world.level.saveddata.SavedData> cacheEntry = storage.cache.get(mapId.key());
++        if (cacheEntry == null) { // Cache did not contain, try to load and may init
++            final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache
++            if (worldmap != null) { // map was read, init it and return
++                worldmap.id = mapId;
++                new org.bukkit.event.server.MapInitializeEvent(worldmap.mapView).callEvent();
++                return worldmap;
++            }
++
++            return null; // Map does not exist, reading failed.
++        }
++
++        // Cache entry exists, update it with the id ref and return.
++        if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) {
++            mapItemSavedData.id = mapId;
++            return mapItemSavedData;
++        }
++
++        return null;
++        // Paper end - Call missing map initialize event and set id
+     }
+ 
+     @Override
+     public void setMapData(MapId mapId, MapItemSavedData mapData) {
++        // CraftBukkit start
++        mapData.id = mapId;
++        org.bukkit.event.server.MapInitializeEvent event = new org.bukkit.event.server.MapInitializeEvent(mapData.mapView);
++        event.callEvent();
++        // CraftBukkit end
+         this.getServer().overworld().getDataStorage().set(mapId.key(), mapData);
+     }
+ 
+@@ -1344,17 +_,27 @@
+         BlockPos spawnPos = this.levelData.getSpawnPos();
+         float spawnAngle = this.levelData.getSpawnAngle();
+         if (!spawnPos.equals(pos) || spawnAngle != angle) {
++            org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent
+             this.levelData.setSpawn(pos, angle);
++            new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent
+             this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle));
+         }
+ 
+         if (this.lastSpawnChunkRadius > 1) {
+-            this.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(spawnPos), this.lastSpawnChunkRadius, Unit.INSTANCE);
++            // Paper start - allow disabling gamerule limits
++            for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(spawnPos, this.lastSpawnChunkRadius - 2)) {
++                this.getChunkSource().removeTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE);
++            }
++            // Paper end - allow disabling gamerule limits
+         }
+ 
+         int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1;
+         if (i > 1) {
+-            this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), i, Unit.INSTANCE);
++            // Paper start - allow disabling gamerule limits
++            for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(pos, i - 2)) {
++                this.getChunkSource().addTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE);
++            }
++            // Paper end - allow disabling gamerule limits
+         }
+ 
+         this.lastSpawnChunkRadius = i;
+@@ -1403,6 +_,11 @@
+                 DebugPackets.sendPoiRemovedPacket(this, blockPos);
+             }));
+             optional1.ifPresent(poiType -> this.getServer().execute(() -> {
++                // Paper start - Remove stale POIs
++                if (optional.isEmpty() && this.getPoiManager().exists(blockPos, ignored -> true)) {
++                    this.getPoiManager().remove(blockPos);
++                }
++                // Paper end - Remove stale POIs
+                 this.getPoiManager().add(blockPos, (Holder<PoiType>)poiType);
+                 DebugPackets.sendPoiAddedPacket(this, blockPos);
+             }));
+@@ -1543,6 +_,11 @@
+     @Override
+     public void blockUpdated(BlockPos pos, Block block) {
+         if (!this.isDebug()) {
++            // CraftBukkit start
++            if (this.populating) {
++                return;
++            }
++            // CraftBukkit end
+             this.updateNeighborsAt(pos, block);
+         }
+     }
+@@ -1562,12 +_,12 @@
+     }
+ 
+     public boolean isFlat() {
+-        return this.server.getWorldData().isFlatWorld();
++        return this.serverLevelData.isFlatWorld(); // CraftBukkit
+     }
+ 
+     @Override
+     public long getSeed() {
+-        return this.server.getWorldData().worldGenOptions().seed();
++        return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit
+     }
+ 
+     @Nullable
+@@ -1618,6 +_,7 @@
+ 
+     @Override
+     public LevelEntityGetter<Entity> getEntities() {
++        org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
+         return this.entityManager.getEntityGetter();
+     }
+ 
+@@ -1699,6 +_,27 @@
+         return this.serverLevelData.getGameRules();
+     }
+ 
++    // Paper start - respect global sound events gamerule
++    public List<net.minecraft.server.level.ServerPlayer> getPlayersForGlobalSoundGamerule() {
++        return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players();
++    }
++
++    public double getGlobalSoundRangeSquared(java.util.function.Function<org.spigotmc.SpigotWorldConfig, Integer> rangeFunction) {
++        final double range = rangeFunction.apply(this.spigotConfig);
++        return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent
++    }
++    // Paper end - respect global sound events gamerule
++    // Paper start - notify observers even if grow failed
++    public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) {
++        // notify observers if the block state is the same and the Y level equals the original y level (for mega trees)
++        // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the
++        // tree grew or not
++        if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) {
++            this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512);
++        }
++    }
++    // Paper end - notify observers even if grow failed
++
+     @Override
+     public CrashReportCategory fillReportDetails(CrashReport report) {
+         CrashReportCategory crashReportCategory = super.fillReportDetails(report);
+@@ -1723,24 +_,32 @@
+ 
+         @Override
+         public void onTickingStart(Entity entity) {
++            if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking
+             ServerLevel.this.entityTickList.add(entity);
+         }
+ 
+         @Override
+         public void onTickingEnd(Entity entity) {
+             ServerLevel.this.entityTickList.remove(entity);
++            // Paper start - Reset pearls when they stop being ticked
++            if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) {
++                pearl.cachedOwner = null;
++                pearl.ownerUUID = null;
++            }
++            // Paper end - Reset pearls when they stop being ticked
+         }
+ 
+         @Override
+         public void onTrackingStart(Entity entity) {
+-            ServerLevel.this.getChunkSource().addEntity(entity);
++            org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
++            // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
+             if (entity instanceof ServerPlayer serverPlayer) {
+                 ServerLevel.this.players.add(serverPlayer);
+                 ServerLevel.this.updateSleepingPlayerList();
+             }
+ 
+             if (entity instanceof Mob mob) {
+-                if (ServerLevel.this.isUpdatingNavigations) {
++                if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning
+                     String string = "onTrackingStart called during navigation iteration";
+                     Util.logAndPauseIfInIde(
+                         "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")
+@@ -1757,10 +_,61 @@
+             }
+ 
+             entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
++            entity.inWorld = true; // CraftBukkit - Mark entity as in world
++            entity.valid = true; // CraftBukkit
++            ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server
++            // Paper start - Entity origin API
++            if (entity.getOriginVector() == null) {
++                entity.setOrigin(entity.getBukkitEntity().getLocation());
++            }
++            // Default to current world if unknown, gross assumption but entities rarely change world
++            if (entity.getOriginWorld() == null) {
++                entity.setOrigin(entity.getOriginVector().toLocation(getWorld()));
++            }
++            // Paper end - Entity origin API
++            new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid
+         }
+ 
+         @Override
+         public void onTrackingEnd(Entity entity) {
++            org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
++            // Spigot start
++            if ( entity instanceof Player )
++            {
++                com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) ->
++                {
++                    for (Object o : worldData.cache.values() )
++                    {
++                        if ( o instanceof MapItemSavedData )
++                        {
++                            MapItemSavedData map = (MapItemSavedData) o;
++                            map.carriedByPlayers.remove( (Player) entity );
++                            for (
++                                java.util.Iterator<net.minecraft.world.level.saveddata.maps.MapItemSavedData.HoldingPlayer> iter = map.carriedBy.iterator();
++                                iter.hasNext();
++                            ) {
++                                if ( iter.next().player == entity )
++                                {
++                                    iter.remove();
++                                }
++                            }
++                        }
++                    }
++                } );
++            }
++            // Spigot end
++            // Spigot Start
++            if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
++                // Paper start - Fix merchant inventory not closing on entity removal
++                if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
++                    merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
++                }
++                // Paper end - Fix merchant inventory not closing on entity removal
++                for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) {
++                    h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
++                }
++            }
++            // Spigot End
+             ServerLevel.this.getChunkSource().removeEntity(entity);
+             if (entity instanceof ServerPlayer serverPlayer) {
+                 ServerLevel.this.players.remove(serverPlayer);
+@@ -1768,7 +_,7 @@
+             }
+ 
+             if (entity instanceof Mob mob) {
+-                if (ServerLevel.this.isUpdatingNavigations) {
++                if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning
+                     String string = "onTrackingStart called during navigation iteration";
+                     Util.logAndPauseIfInIde(
+                         "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")
+@@ -1785,6 +_,15 @@
+             }
+ 
+             entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
++            // CraftBukkit start
++            entity.valid = false;
++            if (!(entity instanceof ServerPlayer)) {
++                for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players
++                    player.getBukkitEntity().onEntityRemove(entity);
++                }
++            }
++            // CraftBukkit end
++            new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid
+         }
+ 
+         @Override
+@@ -1792,4 +_,12 @@
+             entity.updateDynamicGameEventListener(DynamicGameEventListener::move);
+         }
+     }
++
++    // Paper start - check global player list where appropriate
++    @Override
++    @Nullable
++    public Player getGlobalPlayerByUUID(UUID uuid) {
++        return this.server.getPlayerList().getPlayer(uuid);
++    }
++    // Paper end - check global player list where appropriate
+ }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
new file mode 100644
index 0000000000..afffdcd318
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -0,0 +1,1684 @@
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -135,10 +_,12 @@
+ import net.minecraft.world.entity.projectile.ThrownEnderpearl;
+ import net.minecraft.world.entity.vehicle.AbstractBoat;
+ import net.minecraft.world.entity.vehicle.AbstractMinecart;
++import net.minecraft.world.food.FoodData;
+ import net.minecraft.world.inventory.AbstractContainerMenu;
+ import net.minecraft.world.inventory.ContainerListener;
+ import net.minecraft.world.inventory.ContainerSynchronizer;
+ import net.minecraft.world.inventory.HorseInventoryMenu;
++import net.minecraft.world.inventory.InventoryMenu;
+ import net.minecraft.world.inventory.ResultSlot;
+ import net.minecraft.world.inventory.Slot;
+ import net.minecraft.world.item.Item;
+@@ -158,12 +_,14 @@
+ import net.minecraft.world.level.biome.BiomeManager;
+ import net.minecraft.world.level.block.BedBlock;
+ import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.ChestBlock;
+ import net.minecraft.world.level.block.HorizontalDirectionalBlock;
+ import net.minecraft.world.level.block.RespawnAnchorBlock;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+ import net.minecraft.world.level.block.entity.CommandBlockEntity;
+ import net.minecraft.world.level.block.entity.SignBlockEntity;
+ import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.portal.TeleportTransition;
+ import net.minecraft.world.level.saveddata.maps.MapId;
+@@ -223,7 +_,8 @@
+     private int levitationStartTime;
+     private boolean disconnected;
+     private int requestedViewDistance = 2;
+-    public String language = "en_us";
++    public String language = null; // CraftBukkit - default  // Paper - default to null
++    public java.util.Locale adventure$locale = java.util.Locale.US; // Paper
+     @Nullable
+     private Vec3 startingToFallPosition;
+     @Nullable
+@@ -258,6 +_,13 @@
+             }
+         }
+ 
++        // Paper start - Sync offhand slot in menus
++        @Override
++        public void sendOffHandSlotChange() {
++            ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy()));
++        }
++        // Paper end - Sync offhand slot in menus
++
+         @Override
+         public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) {
+             ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(container.containerId, container.incrementStateId(), slot, itemStack));
+@@ -288,6 +_,31 @@
+             }
+         }
+ 
++        // Paper start - Add PlayerInventorySlotChangeEvent
++        @Override
++        public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) {
++            Slot slot = handler.getSlot(slotId);
++            if (!(slot instanceof ResultSlot)) {
++                if (slot.container == ServerPlayer.this.getInventory()) {
++                    if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) {
++                        CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
++                        return;
++                    }
++                    io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(
++                        ServerPlayer.this.getBukkitEntity(),
++                        slotId,
++                        org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(oldStack),
++                        org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)
++                    );
++                    event.callEvent();
++                    if (event.shouldTriggerAdvancements()) {
++                        CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
++                    }
++                }
++            }
++        }
++        // Paper end - Add PlayerInventorySlotChangeEvent
++
+         @Override
+         public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) {
+         }
+@@ -316,9 +_,43 @@
+         public void sendSystemMessage(Component component) {
+             ServerPlayer.this.sendSystemMessage(component);
+         }
++
++        // CraftBukkit start
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++            return ServerPlayer.this.getBukkitEntity();
++        }
++        // CraftBukkit end
+     };
+     private int containerCounter;
+     public boolean wonGame;
++    private int containerUpdateDelay; // Paper - Configurable container update tick rate
++    public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed
++    public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options
++    // Paper start - cancellable death event
++    public boolean queueHealthUpdatePacket;
++    public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
++    // Paper end - cancellable death event
++    // CraftBukkit start
++    public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
++    public String displayName;
++    public net.kyori.adventure.text.Component adventure$displayName; // Paper
++    public Component listName;
++    public int listOrder = 0;
++    public org.bukkit.Location compassTarget;
++    public int newExp = 0;
++    public int newLevel = 0;
++    public int newTotalExp = 0;
++    public boolean keepLevel = false;
++    public double maxHealthCache;
++    public boolean joining = true;
++    public boolean sentListPacket = false;
++    public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
++    // CraftBukkit end
++    public boolean isRealPlayer; // Paper
++    public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
++    public @Nullable String clientBrandName = null; // Paper - Brand support
++    public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
+ 
+     public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
+         super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
+@@ -328,16 +_,71 @@
+         this.server = server;
+         this.stats = server.getPlayerList().getPlayerStats(this);
+         this.advancements = server.getPlayerList().getPlayerAdvancements(this);
+-        this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F);
+-        this.updateOptions(clientInformation);
++        // this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F);  // Paper - Don't move existing players to world spawn
++        this.updateOptionsNoEvents(clientInformation); // Paper - don't call options events on login
+         this.object = null;
++
++        // CraftBukkit start
++        this.displayName = this.getScoreboardName();
++        this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
++        this.bukkitPickUpLoot = true;
++        this.maxHealthCache = this.getMaxHealth();
++    }
++
++    // Use method to resend items in hands in case of client desync, because the item use got cancelled.
++    // For example, when cancelling the leash event
++    @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all
++    public void resendItemInHands() {
++        this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> {
++            this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem());
++        });
++        this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem());
++    }
++
++    // Yes, this doesn't match Vanilla, but it's the best we can do for now.
++    // If this is an issue, PRs are welcome
++    public final BlockPos getSpawnPoint(ServerLevel worldserver) {
++        BlockPos blockposition = worldserver.getSharedSpawnPos();
++
++        if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) {
++            int i = Math.max(0, this.server.getSpawnRadius(worldserver));
++            int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
++
++            if (j < i) {
++                i = j;
++            }
++
++            if (j <= 1) {
++                i = 1;
++            }
++
++            long k = (long) (i * 2 + 1);
++            long l = k * k;
++            int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
++            int j1 = this.getCoprime(i1);
++            int k1 = RandomSource.create().nextInt(i1);
++
++            for (int l1 = 0; l1 < i1; ++l1) {
++                int i2 = (k1 + j1 * l1) % i1;
++                int j2 = i2 % (i * 2 + 1);
++                int k2 = i2 / (i * 2 + 1);
++                BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
++
++                if (blockposition1 != null) {
++                    return blockposition1;
++                }
++            }
++        }
++
++        return blockposition;
++    // CraftBukkit end
+     }
+ 
+     @Override
+     public BlockPos adjustSpawnLocation(ServerLevel level, BlockPos pos) {
+         AABB aabb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO);
+         BlockPos blockPos = pos;
+-        if (level.dimensionType().hasSkyLight() && level.getServer().getWorldData().getGameType() != GameType.ADVENTURE) {
++        if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
+             int max = Math.max(0, this.server.getSpawnRadius(level));
+             int floor = Mth.floor(level.getWorldBorder().getDistanceToBorder(pos.getX(), pos.getZ()));
+             if (floor < max) {
+@@ -420,11 +_,20 @@
+         if (compound.contains("recipeBook", 10)) {
+             this.recipeBook.fromNbt(compound.getCompound("recipeBook"), key -> this.server.getRecipeManager().byKey(key).isPresent());
+         }
++        this.getBukkitEntity().readExtraData(compound); // CraftBukkit
+ 
+         if (this.isSleeping()) {
+             this.stopSleeping();
+         }
+ 
++        // CraftBukkit start
++        String spawnWorld = compound.getString("SpawnWorld");
++        org.bukkit.craftbukkit.CraftWorld oldWorld = (org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(spawnWorld);
++        if (oldWorld != null) {
++            this.respawnDimension = oldWorld.getHandle().dimension();
++        }
++        // CraftBukkit end
++
+         if (compound.contains("SpawnX", 99) && compound.contains("SpawnY", 99) && compound.contains("SpawnZ", 99)) {
+             this.respawnPosition = new BlockPos(compound.getInt("SpawnX"), compound.getInt("SpawnY"), compound.getInt("SpawnZ"));
+             this.respawnForced = compound.getBoolean("SpawnForced");
+@@ -490,7 +_,18 @@
+     private void saveParentVehicle(CompoundTag tag) {
+         Entity rootVehicle = this.getRootVehicle();
+         Entity vehicle = this.getVehicle();
+-        if (vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger()) {
++        // CraftBukkit start - handle non-persistent vehicles
++        boolean persistVehicle = true;
++        if (vehicle != null) {
++            for (Entity topVehicle = vehicle; topVehicle != null; topVehicle = topVehicle.getVehicle()) {
++                if (!topVehicle.persist) {
++                    persistVehicle = false;
++                    break;
++                }
++            }
++        }
++        if (persistVehicle && vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger() && !rootVehicle.isRemoved()) { // Paper - Ensure valid vehicle status
++            // CraftBukkit end
+             CompoundTag compoundTag = new CompoundTag();
+             CompoundTag compoundTag1 = new CompoundTag();
+             rootVehicle.save(compoundTag1);
+@@ -504,7 +_,7 @@
+         if (tag.isPresent() && tag.get().contains("RootVehicle", 10) && this.level() instanceof ServerLevel serverLevel) {
+             CompoundTag compound = tag.get().getCompound("RootVehicle");
+             Entity entity = EntityType.loadEntityRecursive(
+-                compound.getCompound("Entity"), serverLevel, EntitySpawnReason.LOAD, entity2 -> !serverLevel.addWithUUID(entity2) ? null : entity2
++                compound.getCompound("Entity"), serverLevel, EntitySpawnReason.LOAD, entity2 -> !serverLevel.addWithUUID(entity2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity2 // Paper - Entity#getEntitySpawnReason
+             );
+             if (entity == null) {
+                 return;
+@@ -530,10 +_,10 @@
+ 
+             if (!this.isPassenger()) {
+                 LOGGER.warn("Couldn't reattach entity to player");
+-                entity.discard();
++                entity.discard(null); // CraftBukkit - add Bukkit remove cause
+ 
+                 for (Entity entity1x : entity.getIndirectPassengers()) {
+-                    entity1x.discard();
++                    entity1x.discard(null); // CraftBukkit - add Bukkit remove cause
+                 }
+             }
+         }
+@@ -544,6 +_,7 @@
+             ListTag listTag = new ListTag();
+ 
+             for (ThrownEnderpearl thrownEnderpearl : this.enderPearls) {
++                if (thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) continue; // Paper - Allow using old ender pearl behavior
+                 if (thrownEnderpearl.isRemoved()) {
+                     LOGGER.warn("Trying to save removed ender pearl, skipping");
+                 } else {
+@@ -593,6 +_,29 @@
+         }
+     }
+ 
++    // CraftBukkit start - World fallback code, either respawn location or global spawn
++    public void spawnIn(Level world) {
++        this.setLevel(world);
++        if (world == null) {
++            this.unsetRemoved();
++            Vec3 position = null;
++            if (this.respawnDimension != null) {
++                world = this.server.getLevel(this.respawnDimension);
++                if (world != null && this.getRespawnPosition() != null) {
++                    position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null);
++                }
++            }
++            if (world == null || position == null) {
++                world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getServer().getWorlds().get(0)).getHandle();
++                position = Vec3.atCenterOf(world.getSharedSpawnPos());
++            }
++            this.setLevel(world);
++            this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet
++        }
++        this.gameMode.setLevel((ServerLevel) world);
++    }
++    // CraftBukkit end
++
+     public void setExperiencePoints(int experiencePoints) {
+         float f = this.getXpNeededForNextLevel();
+         float f1 = (f - 1.0F) / f;
+@@ -650,6 +_,11 @@
+ 
+     @Override
+     public void tick() {
++        // CraftBukkit start
++        if (this.joining) {
++            this.joining = false;
++        }
++        // CraftBukkit end
+         this.tickClientLoadTimeout();
+         this.gameMode.tick();
+         this.wardenSpawnTracker.tick();
+@@ -657,9 +_,14 @@
+             this.invulnerableTime--;
+         }
+ 
+-        this.containerMenu.broadcastChanges();
+-        if (!this.containerMenu.stillValid(this)) {
+-            this.closeContainer();
++        // Paper start - Configurable container update tick rate
++        if (--this.containerUpdateDelay <= 0) {
++            this.containerMenu.broadcastChanges();
++            this.containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate;
++        }
++        // Paper end - Configurable container update tick rate
++        if (this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen
++            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
+             this.containerMenu = this.inventoryMenu;
+         }
+ 
+@@ -709,7 +_,7 @@
+ 
+     public void doTick() {
+         try {
+-            if (!this.isSpectator() || !this.touchingUnloadedChunk()) {
++            if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn)
+                 super.tick();
+             }
+ 
+@@ -723,7 +_,7 @@
+             if (this.getHealth() != this.lastSentHealth
+                 || this.lastSentFood != this.foodData.getFoodLevel()
+                 || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
+-                this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
++                this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit
+                 this.lastSentHealth = this.getHealth();
+                 this.lastSentFood = this.foodData.getFoodLevel();
+                 this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F;
+@@ -754,6 +_,12 @@
+                 this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float)this.lastRecordedExperience));
+             }
+ 
++            // CraftBukkit start - Force max health updates
++            if (this.maxHealthCache != this.getMaxHealth()) {
++                this.getBukkitEntity().updateScaledHealth();
++            }
++            // CraftBukkit end
++
+             if (this.experienceLevel != this.lastRecordedLevel) {
+                 this.lastRecordedLevel = this.experienceLevel;
+                 this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float)this.lastRecordedLevel));
+@@ -767,6 +_,21 @@
+             if (this.tickCount % 20 == 0) {
+                 CriteriaTriggers.LOCATION.trigger(this);
+             }
++
++            // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border
++            if (this.oldLevel == -1) {
++                this.oldLevel = this.experienceLevel;
++            }
++
++            if (this.oldLevel != this.experienceLevel) {
++                org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel);
++                this.oldLevel = this.experienceLevel;
++            }
++
++            if (this.getBukkitEntity().hasClientWorldBorder()) {
++                ((org.bukkit.craftbukkit.CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick();
++            }
++            // CraftBukkit end
+         } catch (Throwable var4) {
+             CrashReport crashReport = CrashReport.forThrowable(var4, "Ticking player");
+             CrashReportCategory crashReportCategory = crashReport.addCategory("Player being ticked");
+@@ -791,7 +_,7 @@
+         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) {
+             if (this.tickCount % 20 == 0) {
+                 if (this.getHealth() < this.getMaxHealth()) {
+-                    this.heal(1.0F);
++                    this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit - added regain reason of "REGEN" for filtering purposes.
+                 }
+ 
+                 float saturationLevel = this.foodData.getSaturationLevel();
+@@ -840,15 +_,100 @@
+     }
+ 
+     private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) {
+-        this.getScoreboard().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
+-    }
++        this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
++    }
++
++    // Paper start - PlayerDeathEvent#getItemsToKeep
++    private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList<ItemStack> inv) {
++        List<org.bukkit.inventory.ItemStack> itemsToKeep = event.getItemsToKeep();
++        if (inv == null) {
++            // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot?
++            if (!itemsToKeep.isEmpty()) {
++                for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) {
++                    event.getEntity().getInventory().addItem(itemStack);
++                }
++            }
++
++            return;
++        }
++
++        for (int i = 0; i < inv.size(); ++i) {
++            ItemStack item = inv.get(i);
++            if (EnchantmentHelper.has(item, net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) {
++                inv.set(i, ItemStack.EMPTY);
++                continue;
++            }
++
++            final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack();
++            boolean keep = false;
++            final java.util.Iterator<org.bukkit.inventory.ItemStack> iterator = itemsToKeep.iterator();
++            while (iterator.hasNext()) {
++                final org.bukkit.inventory.ItemStack itemStack = iterator.next();
++                if (bukkitStack.equals(itemStack)) {
++                    iterator.remove();
++                    keep = true;
++                    break;
++                }
++            }
++
++            if (!keep) {
++                inv.set(i, ItemStack.EMPTY);
++            }
++        }
++    }
++    // Paper end - PlayerDeathEvent#getItemsToKeep
+ 
+     @Override
+     public void die(DamageSource cause) {
+-        this.gameEvent(GameEvent.ENTITY_DIE);
+-        boolean _boolean = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
+-        if (_boolean) {
+-            Component deathMessage = this.getCombatTracker().getDeathMessage();
++        // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check
++        boolean _boolean = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); final boolean showDeathMessage = _boolean; // Paper - OBFHELPER
++        // CraftBukkit start - fire PlayerDeathEvent
++        if (this.isRemoved()) {
++            return;
++        }
++        List<DefaultDrop> loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior
++        boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
++        if (!keepInventory) {
++            for (ItemStack item : this.getInventory().getContents()) {
++                if (!item.isEmpty() && !EnchantmentHelper.has(item, net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
++                    loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event)
++                }
++            }
++        }
++        if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false
++            // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule)
++            this.dropFromLootTable(this.serverLevel(), cause, this.lastHurtByPlayerTime > 0);
++            // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove.
++            loot.addAll(this.drops);
++            this.drops.clear(); // SPIGOT-5188: make sure to clear
++        } // Paper - fix player loottables running when mob loot gamerule is false
++
++        Component defaultMessage = this.getCombatTracker().getDeathMessage();
++
++        String deathmessage = defaultMessage.getString();
++        this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel
++        org.bukkit.event.entity.PlayerDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerDeathEvent(this, cause, loot, io.papermc.paper.adventure.PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure
++        // Paper start - cancellable death event
++        if (event.isCancelled()) {
++            // make compatible with plugins that might have already set the health in an event listener
++            if (this.getHealth() <= 0) {
++                this.setHealth((float) event.getReviveHealth());
++            }
++            return;
++        }
++        this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method
++        // Paper end
++
++        // SPIGOT-943 - only call if they have an inventory open
++        if (this.containerMenu != this.inventoryMenu) {
++            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason
++        }
++
++        net.kyori.adventure.text.Component apiDeathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure
++
++        if (apiDeathMessage != null && apiDeathMessage != net.kyori.adventure.text.Component.empty() && showDeathMessage) { // Paper - Adventure // TODO: allow plugins to override?
++            Component deathMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(apiDeathMessage); // Paper - Adventure
++
+             this.connection
+                 .send(
+                     new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage),
+@@ -882,11 +_,22 @@
+             this.tellNeutralMobsThatIDied();
+         }
+ 
+-        if (!this.isSpectator()) {
+-            this.dropAllDeathLoot(this.serverLevel(), cause);
++        // SPIGOT-5478 must be called manually now
++        if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), cause.getEntity()); // Paper - tie to event
++        // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory.
++        if (!event.getKeepInventory()) {
++            // Paper start - PlayerDeathEvent#getItemsToKeep
++            for (NonNullList<ItemStack> inv : this.getInventory().compartments) {
++                processKeep(event, inv);
++            }
++            processKeep(event, null);
++            // Paper end - PlayerDeathEvent#getItemsToKeep
+         }
+ 
+-        this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
++        this.setCamera(this); // Remove spectated target
++        // CraftBukkit end
++
++        this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead
+         LivingEntity killCredit = this.getKillCredit();
+         if (killCredit != null) {
+             this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType()));
+@@ -919,10 +_,10 @@
+     public void awardKillScore(Entity entity, DamageSource damageSource) {
+         if (entity != this) {
+             super.awardKillScore(entity, damageSource);
+-            this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
++            this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead
+             if (entity instanceof Player) {
+                 this.awardStat(Stats.PLAYER_KILLS);
+-                this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
++                this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead
+             } else {
+                 this.awardStat(Stats.MOB_KILLS);
+             }
+@@ -938,7 +_,7 @@
+         if (playersTeam != null) {
+             int id = playersTeam.getColor().getId();
+             if (id >= 0 && id < crtieria.length) {
+-                this.getScoreboard().forAllObjectives(crtieria[id], scoreHolder, ScoreAccess::increment);
++                this.level().getCraftServer().getScoreboardManager().forAllObjectives(crtieria[id], scoreHolder, ScoreAccess::increment); // CraftBukkit - Get our scores instead
+             }
+         }
+     }
+@@ -949,9 +_,20 @@
+             return false;
+         } else {
+             Entity entity = damageSource.getEntity();
+-            return !(entity instanceof Player player && !this.canHarmPlayer(player))
++            if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false.
++             !(entity instanceof Player player && !this.canHarmPlayer(player))
+                 && !(entity instanceof AbstractArrow abstractArrow && abstractArrow.getOwner() instanceof Player player1 && !this.canHarmPlayer(player1))
+-                && super.hurtServer(level, damageSource, amount);
++            )) return false; // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false.
++            // Paper start - cancellable death events
++            this.queueHealthUpdatePacket = true;
++            boolean damaged = super.hurtServer(level, damageSource, amount);
++            this.queueHealthUpdatePacket = false;
++            if (this.queuedHealthUpdatePacket != null) {
++                this.connection.send(this.queuedHealthUpdatePacket);
++                this.queuedHealthUpdatePacket = null;
++            }
++            return damaged;
++            // Paper end - cancellable death events
+         }
+     }
+ 
+@@ -961,10 +_,15 @@
+     }
+ 
+     private boolean isPvpAllowed() {
+-        return this.server.isPvpAllowed();
++        return this.level().pvpMode;  // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
+     }
+ 
+-    public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean useCharge, TeleportTransition.PostTeleportTransition postTeleportTransition) {
++    // CraftBukkit start
++    public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean useCharge, TeleportTransition.PostTeleportTransition postTeleportTransition, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason respawnReason) {
++        TeleportTransition teleportTransition;
++        boolean isBedSpawn = false;
++        boolean isAnchorSpawn = false;
++        // CraftBukkit end
+         BlockPos respawnPosition = this.getRespawnPosition();
+         float respawnAngle = this.getRespawnAngle();
+         boolean isRespawnForced = this.isRespawnForced();
+@@ -973,13 +_,66 @@
+             Optional<ServerPlayer.RespawnPosAngle> optional = findRespawnAndUseSpawnBlock(level, respawnPosition, respawnAngle, isRespawnForced, useCharge);
+             if (optional.isPresent()) {
+                 ServerPlayer.RespawnPosAngle respawnPosAngle = optional.get();
+-                return new TeleportTransition(level, respawnPosAngle.position(), Vec3.ZERO, respawnPosAngle.yaw(), 0.0F, postTeleportTransition);
++                // CraftBukkit start
++                isBedSpawn = respawnPosAngle.isBedSpawn();
++                isAnchorSpawn = respawnPosAngle.isAnchorSpawn();
++                teleportTransition = new TeleportTransition(level, respawnPosAngle.position(), Vec3.ZERO, respawnPosAngle.yaw(), 0.0F, postTeleportTransition);
++                // CraftBukkit end
+             } else {
+-                return TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postTeleportTransition);
++                teleportTransition = TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postTeleportTransition); // CraftBukkit
+             }
+         } else {
+-            return new TeleportTransition(this.server.overworld(), this, postTeleportTransition);
+-        }
++            teleportTransition = new TeleportTransition(this.server.overworld(), this, postTeleportTransition);  // CraftBukkit
++        }
++        // CraftBukkit start
++        if (respawnReason == null) {
++            return teleportTransition;
++        }
++
++        org.bukkit.entity.Player respawnPlayer = this.getBukkitEntity();
++        org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(
++            teleportTransition.position(),
++            teleportTransition.newLevel().getWorld(),
++            teleportTransition.yRot(),
++            teleportTransition.xRot()
++        );
++
++        // Paper start - respawn flags
++        com.google.common.collect.ImmutableSet.Builder<org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag> builder = com.google.common.collect.ImmutableSet.builder();
++        if (respawnReason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) {
++            builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL);
++        }
++        org.bukkit.event.player.PlayerRespawnEvent respawnEvent = new org.bukkit.event.player.PlayerRespawnEvent(
++            respawnPlayer,
++            location,
++            isBedSpawn,
++            isAnchorSpawn,
++            respawnReason,
++            builder
++        );
++        // Paper end - respawn flags
++        this.level().getCraftServer().getPluginManager().callEvent(respawnEvent);
++        // Spigot Start
++        if (this.connection.isDisconnected()) {
++            return null;
++        }
++        // Spigot End
++
++        location = respawnEvent.getRespawnLocation();
++
++        return new TeleportTransition(
++            ((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle(),
++            org.bukkit.craftbukkit.util.CraftLocation.toVec3D(location),
++            teleportTransition.deltaMovement(),
++            location.getYaw(),
++            location.getPitch(),
++            teleportTransition.missingRespawnBlock(),
++            teleportTransition.asPassenger(),
++            teleportTransition.relatives(),
++            teleportTransition.postTeleportTransition(),
++            teleportTransition.cause()
++        );
++        // CraftBukkit end
+     }
+ 
+     public static Optional<ServerPlayer.RespawnPosAngle> findRespawnAndUseSpawnBlock(
+@@ -993,10 +_,10 @@
+                 level.setBlock(pos, blockState.setValue(RespawnAnchorBlock.CHARGE, Integer.valueOf(blockState.getValue(RespawnAnchorBlock.CHARGE) - 1)), 3);
+             }
+ 
+-            return optional.map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos));
++            return optional.map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos, false, true)); // CraftBukkit
+         } else if (block instanceof BedBlock && BedBlock.canSetSpawn(level)) {
+             return BedBlock.findStandUpPosition(EntityType.PLAYER, level, pos, blockState.getValue(BedBlock.FACING), angle)
+-                .map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos));
++                .map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos, true, false)); // CraftBukkit
+         } else if (!forced) {
+             return Optional.empty();
+         } else {
+@@ -1004,7 +_,7 @@
+             BlockState blockState1 = level.getBlockState(pos.above());
+             boolean isPossibleToRespawnInThis1 = blockState1.getBlock().isPossibleToRespawnInThis(blockState1);
+             return isPossibleToRespawnInThis && isPossibleToRespawnInThis1
+-                ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle))
++                ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle, false, false))  // CraftBukkit
+                 : Optional.empty();
+         }
+     }
+@@ -1022,6 +_,7 @@
+     @Nullable
+     @Override
+     public ServerPlayer teleport(TeleportTransition teleportTransition) {
++        if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154
+         if (this.isRemoved()) {
+             return null;
+         } else {
+@@ -1031,17 +_,52 @@
+ 
+             ServerLevel level = teleportTransition.newLevel();
+             ServerLevel serverLevel = this.serverLevel();
+-            ResourceKey<Level> resourceKey = serverLevel.dimension();
++            // CraftBukkit start
++            ResourceKey<LevelStem> resourceKey = serverLevel.getTypeKey();
++
++            org.bukkit.Location enter = this.getBukkitEntity().getLocation();
++            PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
++            org.bukkit.Location exit = /* (worldserver == null) ? null : // Paper - always non-null */org.bukkit.craftbukkit.util.CraftLocation.toBukkit(absolutePosition.position(), level.getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
++            org.bukkit.event.player.PlayerTeleportEvent tpEvent = new org.bukkit.event.player.PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTransition.cause());
++            // Paper start - gateway-specific teleport event
++            if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
++                tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity));
++            }
++            // Paper end - gateway-specific teleport event
++            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(tpEvent);
++            org.bukkit.Location newExit = tpEvent.getTo();
++            if (tpEvent.isCancelled() || newExit == null) {
++                return null;
++            }
++            if (!newExit.equals(exit)) {
++                level = ((org.bukkit.craftbukkit.CraftWorld) newExit.getWorld()).getHandle();
++                teleportTransition = new TeleportTransition(
++                    level,
++                    org.bukkit.craftbukkit.util.CraftLocation.toVec3D(newExit),
++                    Vec3.ZERO,
++                    newExit.getYaw(),
++                    newExit.getPitch(),
++                    teleportTransition.missingRespawnBlock(),
++                    teleportTransition.asPassenger(),
++                    Set.of(),
++                    teleportTransition.postTeleportTransition(),
++                    teleportTransition.cause());
++            }
++            // CraftBukkit end
+             if (!teleportTransition.asPassenger()) {
+                 this.stopRiding();
+             }
+ 
+-            if (level.dimension() == resourceKey) {
+-                this.connection.teleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
++            // CraftBukkit start
++            if (level != null && level.dimension() == serverLevel.dimension()) {
++                this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
++                // CraftBukkit end
+                 this.connection.resetPosition();
+                 teleportTransition.postTeleportTransition().onTransition(this);
+                 return this;
+             } else {
++                // CraftBukkit start
++                /*
+                 this.isChangingDimension = true;
+                 LevelData levelData = level.getLevelData();
+                 this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(level), (byte)3));
+@@ -1050,16 +_,30 @@
+                 playerList.sendPlayerPermissionLevel(this);
+                 serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+                 this.unsetRemoved();
++                */
++                // CraftBukkit end
+                 ProfilerFiller profilerFiller = Profiler.get();
+                 profilerFiller.push("moving");
+-                if (resourceKey == Level.OVERWORLD && level.dimension() == Level.NETHER) {
++                if (level != null && resourceKey == LevelStem.OVERWORLD && level.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event
+                     this.enteredNetherPosition = this.position();
+                 }
+ 
+                 profilerFiller.pop();
+                 profilerFiller.push("placing");
++                // CraftBukkit start
++                this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds
++                LevelData worlddata = level.getLevelData();
++
++                this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(level), (byte) 3));
++                this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
++                PlayerList playerList = this.server.getPlayerList();
++
++                playerList.sendPlayerPermissionLevel(this);
++                serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++                this.unsetRemoved();
++                // CraftBukkit end
+                 this.setServerLevel(level);
+-                this.connection.teleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
++                this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event
+                 this.connection.resetPosition();
+                 level.addDuringTeleport(this);
+                 profilerFiller.pop();
+@@ -1073,10 +_,40 @@
+                 this.lastSentExp = -1;
+                 this.lastSentHealth = -1.0F;
+                 this.lastSentFood = -1;
++
++
++                // CraftBukkit start
++                org.bukkit.event.player.PlayerChangedWorldEvent changeEvent = new org.bukkit.event.player.PlayerChangedWorldEvent(this.getBukkitEntity(), serverLevel.getWorld());
++                this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
++                // CraftBukkit end
++                // Paper start - Reset shield blocking on dimension change
++                if (this.isBlocking()) {
++                    this.stopUsingItem();
++                }
++                // Paper end - Reset shield blocking on dimension change
+                 return this;
+             }
+         }
+     }
++
++    // CraftBukkit start
++    @Override
++    public org.bukkit.craftbukkit.event.CraftPortalEvent callPortalEvent(
++        Entity entity,
++        org.bukkit.Location exit,
++        org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause,
++        int searchRadius,
++        int creationRadius
++    ) {
++        org.bukkit.Location enter = this.getBukkitEntity().getLocation();
++        org.bukkit.event.player.PlayerPortalEvent event = new org.bukkit.event.player.PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius);
++        org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event);
++        if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) {
++            return null;
++        }
++        return new org.bukkit.craftbukkit.event.CraftPortalEvent(event);
++    }
++    // CraftBukkit end
+ 
+     @Override
+     public void forceSetRotation(float yRot, float xRot) {
+@@ -1086,12 +_,26 @@
+     public void triggerDimensionChangeTriggers(ServerLevel level) {
+         ResourceKey<Level> resourceKey = level.dimension();
+         ResourceKey<Level> resourceKey1 = this.level().dimension();
+-        CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1);
+-        if (resourceKey == Level.NETHER && resourceKey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++        // CraftBukkit start
++        ResourceKey<Level> maindimensionkey = org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(level);
++        ResourceKey<Level> maindimensionkey1 = org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(this.level());
++        // Paper start - Add option for strict advancement dimension checks
++        if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) {
++            maindimensionkey = resourceKey;
++            maindimensionkey1 = resourceKey1;
++        }
++        // Paper end - Add option for strict advancement dimension checks
++        CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1);
++        if (maindimensionkey != resourceKey || maindimensionkey1 != resourceKey1) {
++            CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1);
++        }
++
++        if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++            // CraftBukkit end
+             CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition);
+         }
+ 
+-        if (resourceKey1 != Level.NETHER) {
++        if (maindimensionkey1 != Level.NETHER) { // CraftBukkit
+             this.enteredNetherPosition = null;
+         }
+     }
+@@ -1110,16 +_,21 @@
+     @Override
+     public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at) {
+         Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
++    // CraftBukkit start - moved bed result checks from below into separate method
++        return getBedResult(at, direction);
++    }
++    private Either<Player.BedSleepingProblem, Unit> getBedResult(BlockPos at, Direction direction) {
++    // CraftBukkit end - moved bed result checks from below into separate method
+         if (this.isSleeping() || !this.isAlive()) {
+             return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
+-        } else if (!this.level().dimensionType().natural()) {
++        } else if (!this.level().dimensionType().natural() && !this.level().dimensionType().bedWorks()) { // CraftBukkit - moved bed result checks from below into separate method
+             return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
+         } else if (!this.bedInRange(at, direction)) {
+             return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
+         } else if (this.bedBlocked(at, direction)) {
+             return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+         } else {
+-            this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true);
++            this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent
+             if (this.level().isDay()) {
+                 return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
+             } else {
+@@ -1138,7 +_,34 @@
+                     }
+                 }
+ 
+-                Either<Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at).ifRight(unit -> {
++    // CraftBukkit start
++                return Either.right(Unit.INSTANCE);
++            }
++        }
++    }
++
++    @Override
++    public Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at, boolean force) {
++        Direction enumdirection = (Direction) this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
++        Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> bedResult = this.getBedResult(at, enumdirection);
++
++        if (bedResult.left().orElse(null) == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) {
++            return bedResult; // return immediately if the result is not bypassable by plugins
++        }
++
++        if (force) {
++            bedResult = Either.right(Unit.INSTANCE);
++        }
++
++        bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, at, bedResult);
++        if (bedResult.left().isPresent()) {
++            return bedResult;
++        }
++
++        {
++            {
++                Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at, force).ifRight((unit) -> {
++    // CraftBukkit end
+                     this.awardStat(Stats.SLEEP_IN_BED);
+                     CriteriaTriggers.SLEPT_IN_BED.trigger(this);
+                 });
+@@ -1174,13 +_,31 @@
+ 
+     @Override
+     public void stopSleepInBed(boolean wakeImmediately, boolean updateLevelForSleepingPlayers) {
++        if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one!
++        // CraftBukkit start - fire PlayerBedLeaveEvent
++        org.bukkit.craftbukkit.entity.CraftPlayer player = this.getBukkitEntity();
++        BlockPos bedPosition = this.getSleepingPos().orElse(null);
++
++        org.bukkit.block.Block bed;
++        if (bedPosition != null) {
++            bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ());
++        } else {
++            bed = this.level().getWorld().getBlockAt(player.getLocation());
++        }
++
++        org.bukkit.event.player.PlayerBedLeaveEvent event = new org.bukkit.event.player.PlayerBedLeaveEvent(player, bed, true);
++        this.level().getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return;
++        }
++        // CraftBukkit end
+         if (this.isSleeping()) {
+             this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2));
+         }
+ 
+         super.stopSleepInBed(wakeImmediately, updateLevelForSleepingPlayers);
+         if (this.connection != null) {
+-            this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
++            this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.EXIT_BED); // CraftBukkit
+         }
+     }
+ 
+@@ -1192,9 +_,9 @@
+ 
+     @Override
+     public boolean isInvulnerableTo(ServerLevel level, DamageSource damageSource) {
+-        return super.isInvulnerableTo(level, damageSource)
++        return (super.isInvulnerableTo(level, damageSource) // Paper - disable player cramming;
+             || this.isChangingDimension() && !damageSource.is(DamageTypes.ENDER_PEARL)
+-            || !this.hasClientLoaded();
++            || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && damageSource.is(DamageTypes.CRAMMING)); // Paper - disable player cramming;
+     }
+ 
+     @Override
+@@ -1237,8 +_,9 @@
+         this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText));
+     }
+ 
+-    public void nextContainerCounter() {
++    public int nextContainerCounter() { // CraftBukkit - void -> int
+         this.containerCounter = this.containerCounter % 100 + 1;
++        return this.containerCounter; // CraftBukkit
+     }
+ 
+     @Override
+@@ -1246,12 +_,43 @@
+         if (menu == null) {
+             return OptionalInt.empty();
+         } else {
++            // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...)
++            /*
+             if (this.containerMenu != this.inventoryMenu) {
+                 this.closeContainer();
+             }
++            */
++            // CraftBukkit end
+ 
+             this.nextContainerCounter();
+             AbstractContainerMenu abstractContainerMenu = menu.createMenu(this.containerCounter, this.getInventory(), this);
++            Component title = null; // Paper - Add titleOverride to InventoryOpenEvent
++            // CraftBukkit start - Inventory open hook
++            if (abstractContainerMenu != null) {
++                abstractContainerMenu.setTitle(menu.getDisplayName());
++
++                boolean cancelled = false;
++                // Paper start - Add titleOverride to InventoryOpenEvent
++                final com.mojang.datafixers.util.Pair<net.kyori.adventure.text.Component, AbstractContainerMenu> result = org.bukkit.craftbukkit.event.CraftEventFactory.callInventoryOpenEventWithTitle(this, abstractContainerMenu, cancelled);
++                abstractContainerMenu = result.getSecond();
++                title = io.papermc.paper.adventure.PaperAdventure.asVanilla(result.getFirst());
++                // Paper end - Add titleOverride to InventoryOpenEvent
++                if (abstractContainerMenu == null && !cancelled) { // Let pre-cancelled events fall through
++                    // SPIGOT-5263 - close chest if cancelled
++                    if (menu instanceof Container) {
++                        ((Container) menu).stopOpen(this);
++                    } else if (menu instanceof ChestBlock.DoubleInventory) {
++                        // SPIGOT-5355 - double chests too :(
++                        ((ChestBlock.DoubleInventory) menu).inventorylargechest.stopOpen(this);
++                        // Paper start - Fix InventoryOpenEvent cancellation
++                    } else if (!this.enderChestInventory.isActiveChest(null)) {
++                        this.enderChestInventory.stopOpen(this);
++                        // Paper end - Fix InventoryOpenEvent cancellation
++                    }
++                    return OptionalInt.empty();
++                }
++            }
++            // CraftBukkit end
+             if (abstractContainerMenu == null) {
+                 if (this.isSpectator()) {
+                     this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true);
+@@ -1259,10 +_,14 @@
+ 
+                 return OptionalInt.empty();
+             } else {
++                // CraftBukkit start
++                this.containerMenu = abstractContainerMenu; // Moved up
++                if (!this.isImmobile())
+                 this.connection
+-                    .send(new ClientboundOpenScreenPacket(abstractContainerMenu.containerId, abstractContainerMenu.getType(), menu.getDisplayName()));
++                    .send(new net.minecraft.network.protocol.game.ClientboundOpenScreenPacket(abstractContainerMenu.containerId, abstractContainerMenu.getType(), java.util.Objects.requireNonNullElseGet(title, abstractContainerMenu::getTitle))); // Paper - Add titleOverride to InventoryOpenEven
++                // CraftBukkit end
+                 this.initMenu(abstractContainerMenu);
+-                this.containerMenu = abstractContainerMenu;
++                // CraftBukkit - moved up
+                 return OptionalInt.of(this.containerCounter);
+             }
+         }
+@@ -1275,14 +_,25 @@
+ 
+     @Override
+     public void openHorseInventory(AbstractHorse horse, Container inventory) {
+-        if (this.containerMenu != this.inventoryMenu) {
+-            this.closeContainer();
+-        }
+-
++        // CraftBukkit start - Inventory open hook
+         this.nextContainerCounter();
++        AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns());
++        container.setTitle(horse.getDisplayName());
++        container = org.bukkit.craftbukkit.event.CraftEventFactory.callInventoryOpenEvent(this, container);
++
++        if (container == null) {
++            inventory.stopOpen(this);
++            return;
++        }
++        // CraftBukkit end
++        if (this.containerMenu != this.inventoryMenu) {
++            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason
++        }
++
++        // this.nextContainerCounter(); // CraftBukkit - moved up
+         int inventoryColumns = horse.getInventoryColumns();
+         this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventoryColumns, horse.getId()));
+-        this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, inventoryColumns);
++        this.containerMenu = container; // CraftBukkit
+         this.initMenu(this.containerMenu);
+     }
+ 
+@@ -1304,9 +_,28 @@
+ 
+     @Override
+     public void closeContainer() {
++        // Paper start - Inventory close reason
++        this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN);
++    }
++    @Override
++    public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
++        // Paper end - Inventory close reason
+         this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
+         this.doCloseContainer();
+     }
++    // Paper start - special close for unloaded inventory
++    @Override
++    public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++        // copied from above
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
++        // Paper end
++        // copied from below
++        this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
++        this.containerMenu = this.inventoryMenu;
++        // do not run close logic
++    }
++    // Paper end - special close for unloaded inventory
+ 
+     @Override
+     public void doCloseContainer() {
+@@ -1330,19 +_,19 @@
+                 int rounded = Math.round((float)Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F);
+                 if (rounded > 0) {
+                     this.awardStat(Stats.SWIM_ONE_CM, rounded);
+-                    this.causeFoodExhaustion(0.01F * rounded * 0.01F);
++                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot
+                 }
+             } else if (this.isEyeInFluid(FluidTags.WATER)) {
+                 int rounded = Math.round((float)Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F);
+                 if (rounded > 0) {
+                     this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, rounded);
+-                    this.causeFoodExhaustion(0.01F * rounded * 0.01F);
++                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot
+                 }
+             } else if (this.isInWater()) {
+                 int rounded = Math.round((float)Math.sqrt(dx * dx + dz * dz) * 100.0F);
+                 if (rounded > 0) {
+                     this.awardStat(Stats.WALK_ON_WATER_ONE_CM, rounded);
+-                    this.causeFoodExhaustion(0.01F * rounded * 0.01F);
++                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot
+                 }
+             } else if (this.onClimbable()) {
+                 if (dy > 0.0) {
+@@ -1353,13 +_,13 @@
+                 if (rounded > 0) {
+                     if (this.isSprinting()) {
+                         this.awardStat(Stats.SPRINT_ONE_CM, rounded);
+-                        this.causeFoodExhaustion(0.1F * rounded * 0.01F);
++                        this.causeFoodExhaustion(this.level().spigotConfig.sprintMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot
+                     } else if (this.isCrouching()) {
+                         this.awardStat(Stats.CROUCH_ONE_CM, rounded);
+-                        this.causeFoodExhaustion(0.0F * rounded * 0.01F);
++                        this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent // Spigot
+                     } else {
+                         this.awardStat(Stats.WALK_ONE_CM, rounded);
+-                        this.causeFoodExhaustion(0.0F * rounded * 0.01F);
++                        this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent // Spigot
+                     }
+                 }
+             } else if (this.isFallFlying()) {
+@@ -1399,13 +_,13 @@
+     @Override
+     public void awardStat(Stat<?> stat, int amount) {
+         this.stats.increment(this, stat, amount);
+-        this.getScoreboard().forAllObjectives(stat, this, score -> score.add(amount));
++        this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, score -> score.add(amount)); // CraftBukkit - Get our scores instead
+     }
+ 
+     @Override
+     public void resetStat(Stat<?> stat) {
+         this.stats.setValue(this, stat, 0);
+-        this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset);
++        this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead
+     }
+ 
+     @Override
+@@ -1436,9 +_,9 @@
+         super.jumpFromGround();
+         this.awardStat(Stats.JUMP);
+         if (this.isSprinting()) {
+-            this.causeFoodExhaustion(0.2F);
++            this.causeFoodExhaustion(this.level().spigotConfig.jumpSprintExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
+         } else {
+-            this.causeFoodExhaustion(0.05F);
++            this.causeFoodExhaustion(this.level().spigotConfig.jumpWalkExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
+         }
+     }
+ 
+@@ -1451,6 +_,13 @@
+     public void disconnect() {
+         this.disconnected = true;
+         this.ejectPassengers();
++
++        // Paper start - Workaround vehicle not tracking the passenger disconnection dismount
++        if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) {
++            this.stopRiding();
++        }
++        // Paper end - Workaround vehicle not tracking the passenger disconnection dismount
++
+         if (this.isSleeping()) {
+             this.stopSleepInBed(true, false);
+         }
+@@ -1462,6 +_,7 @@
+ 
+     public void resetSentInfo() {
+         this.lastSentHealth = -1.0E8F;
++        this.lastSentExp = -1; // CraftBukkit - Added to reset
+     }
+ 
+     @Override
+@@ -1496,12 +_,12 @@
+         this.onUpdateAbilities();
+         if (keepEverything) {
+             this.getAttributes().assignBaseValues(that.getAttributes());
+-            this.getAttributes().assignPermanentModifiers(that.getAttributes());
++            // this.getAttributes().assignPermanentModifiers(that.getAttributes()); // CraftBukkit
+             this.setHealth(that.getHealth());
+             this.foodData = that.foodData;
+ 
+             for (MobEffectInstance mobEffectInstance : that.getActiveEffects()) {
+-                this.addEffect(new MobEffectInstance(mobEffectInstance));
++                // this.addEffect(new MobEffectInstance(mobEffectInstance));  // CraftBukkit
+             }
+ 
+             this.getInventory().replaceWith(that.getInventory());
+@@ -1512,7 +_,7 @@
+             this.portalProcess = that.portalProcess;
+         } else {
+             this.getAttributes().assignBaseValues(that.getAttributes());
+-            this.setHealth(this.getMaxHealth());
++            // this.setHealth(this.getMaxHealth()); // CraftBukkit
+             if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || that.isSpectator()) {
+                 this.getInventory().replaceWith(that.getInventory());
+                 this.experienceLevel = that.experienceLevel;
+@@ -1528,7 +_,7 @@
+         this.lastSentExp = -1;
+         this.lastSentHealth = -1.0F;
+         this.lastSentFood = -1;
+-        this.recipeBook.copyOverData(that.recipeBook);
++        // this.recipeBook.copyOverData(that.recipeBook); // CraftBukkit
+         this.seenCredits = that.seenCredits;
+         this.enteredNetherPosition = that.enteredNetherPosition;
+         this.chunkTrackingView = that.chunkTrackingView;
+@@ -1581,7 +_,7 @@
+     }
+ 
+     @Override
+-    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
++    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit
+         if (this.isSleeping()) {
+             this.stopSleepInBed(true, true);
+         }
+@@ -1590,7 +_,7 @@
+             this.setCamera(this);
+         }
+ 
+-        boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera);
++        boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, cause);  // CraftBukkit
+         if (flag) {
+             this.setYHeadRot(relativeMovements.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw);
+         }
+@@ -1627,9 +_,17 @@
+     }
+ 
+     public boolean setGameMode(GameType gameMode) {
++        // Paper start - Expand PlayerGameModeChangeEvent
++        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
++        return event == null ? false : event.isCancelled();
++    }
++    @Nullable
++    public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) {
+         boolean isSpectator = this.isSpectator();
+-        if (!this.gameMode.changeGameModeForPlayer(gameMode)) {
+-            return false;
++        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message);
++        if (event == null || event.isCancelled()) {
++            return null;
++        // Paper end - Expand PlayerGameModeChangeEvent
+         } else {
+             this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId()));
+             if (gameMode == GameType.SPECTATOR) {
+@@ -1645,7 +_,7 @@
+ 
+             this.onUpdateAbilities();
+             this.updateEffectVisibility();
+-            return true;
++            return event; // Paper - Expand PlayerGameModeChangeEvent
+         }
+     }
+ 
+@@ -1705,8 +_,13 @@
+     }
+ 
+     public void sendChatMessage(OutgoingChatMessage message, boolean filtered, ChatType.Bound boundType) {
++        // Paper start
++        this.sendChatMessage(message, filtered, boundType, null);
++    }
++    public void sendChatMessage(OutgoingChatMessage message, boolean filtered, ChatType.Bound boundType, @Nullable Component unsigned) {
++        // Paper end
+         if (this.acceptsChatMessages()) {
+-            message.sendToPlayer(this, filtered, boundType);
++            message.sendToPlayer(this, filtered, boundType, unsigned);  // Paper
+         }
+     }
+ 
+@@ -1717,7 +_,42 @@
+     }
+ 
+     public void updateOptions(ClientInformation clientInformation) {
++        // Paper start - settings event
++        new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> {
++            map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientInformation.language());
++            map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientInformation.viewDistance());
++            map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientInformation.chatVisibility().name()));
++            map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientInformation.chatColors());
++            map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientInformation.modelCustomisation()));
++            map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientInformation.mainHand() == HumanoidArm.LEFT ? org.bukkit.inventory.MainHand.LEFT : org.bukkit.inventory.MainHand.RIGHT);
++            map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientInformation.textFilteringEnabled());
++            map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientInformation.allowsListing());
++            map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientInformation.particleStatus().name()));
++        })).callEvent();
++        // Paper end - settings event
++        // CraftBukkit start
++        if (this.getMainArm() != clientInformation.mainHand()) {
++            org.bukkit.event.player.PlayerChangedMainHandEvent event = new org.bukkit.event.player.PlayerChangedMainHandEvent(
++                this.getBukkitEntity(),
++                this.getMainArm() == HumanoidArm.LEFT ? org.bukkit.inventory.MainHand.LEFT : org.bukkit.inventory.MainHand.RIGHT
++            );
++            this.server.server.getPluginManager().callEvent(event);
++        }
++        if (this.language == null || !this.language.equals(clientInformation.language())) { // Paper
++            org.bukkit.event.player.PlayerLocaleChangeEvent event = new org.bukkit.event.player.PlayerLocaleChangeEvent(
++                this.getBukkitEntity(),
++                clientInformation.language()
++            );
++            this.server.server.getPluginManager().callEvent(event);
++        }
++        // CraftBukkit end
++        // Paper start - don't call options events on login
++        this.updateOptionsNoEvents(clientInformation);
++    }
++    public void updateOptionsNoEvents(ClientInformation clientInformation) {
++        // Paper end
+         this.language = clientInformation.language();
++        this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper
+         this.requestedViewDistance = clientInformation.viewDistance();
+         this.chatVisibility = clientInformation.chatVisibility();
+         this.canChatColor = clientInformation.chatColors();
+@@ -1803,8 +_,23 @@
+         Entity camera = this.getCamera();
+         this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate);
+         if (camera != this.camera) {
++            // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity
++            if (this.camera == this) {
++                com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), camera.getBukkitEntity());
++                if (!playerStopSpectatingEntityEvent.callEvent()) {
++                    this.camera = camera; // rollback camera entity again
++                    return;
++                }
++            } else {
++                com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), camera.getBukkitEntity(), entityToSpectate.getBukkitEntity());
++                if (!playerStartSpectatingEntityEvent.callEvent()) {
++                    this.camera = camera; // rollback camera entity again
++                    return;
++                }
++            }
++            // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity
+             if (this.camera.level() instanceof ServerLevel serverLevel) {
+-                this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false);
++                this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
+             }
+ 
+             if (entityToSpectate != null) {
+@@ -1838,11 +_,11 @@
+ 
+     @Nullable
+     public Component getTabListDisplayName() {
+-        return null;
++        return this.listName; // CraftBukkit
+     }
+ 
+     public int getTabListOrder() {
+-        return 0;
++        return this.listOrder; // CraftBukkit
+     }
+ 
+     @Override
+@@ -1884,11 +_,56 @@
+         this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false);
+     }
+ 
++    @Deprecated // Paper - Add PlayerSetSpawnEvent
+     public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage) {
++        // Paper start - Add PlayerSetSpawnEvent
++        this.setRespawnPosition(dimension, position, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN);
++    }
++    @Deprecated
++    public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause cause) {
++        return this.setRespawnPosition(dimension, position, angle, forced, sendMessage, cause == org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESET ?
++            com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name()));
++    }
++    public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) {
++        org.bukkit.Location spawnLoc = null;
++        boolean willNotify = false;
+         if (position != null) {
+             boolean flag = position.equals(this.respawnPosition) && dimension.equals(this.respawnDimension);
+-            if (sendMessage && !flag) {
+-                this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn"));
++            spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), position);
++            spawnLoc.setYaw(angle);
++            willNotify = sendMessage && !flag;
++        }
++        org.bukkit.event.player.PlayerSpawnChangeEvent dumbEvent = new org.bukkit.event.player.PlayerSpawnChangeEvent(
++            this.getBukkitEntity(),
++            spawnLoc,
++            forced,
++            cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN
++                ? org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESET
++                : org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.valueOf(cause.name())
++        );
++        dumbEvent.callEvent();
++
++        com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(
++            this.getBukkitEntity(),
++            cause,
++            dumbEvent.getNewSpawn(),
++            dumbEvent.isForced(),
++            willNotify,
++            willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null
++        );
++        event.setCancelled(dumbEvent.isCancelled());
++        if (!event.callEvent()) {
++            return false;
++        }
++        if (event.getLocation() != null) {
++            dimension = event.getLocation().getWorld() != null ? ((org.bukkit.craftbukkit.CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension;
++            position = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation());
++            angle = event.getLocation().getYaw();
++            forced = event.isForced();
++            // Paper end - Add PlayerSetSpawnEvent
++
++            if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent
++                this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent
+             }
+ 
+             this.respawnPosition = position;
+@@ -1901,6 +_,8 @@
+             this.respawnAngle = 0.0F;
+             this.respawnForced = false;
+         }
++
++        return true; // Paper - Add PlayerSetSpawnEvent
+     }
+ 
+     public SectionPos getLastSectionPos() {
+@@ -1930,16 +_,41 @@
+     }
+ 
+     @Override
+-    public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean traceItem) {
++    public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean traceItem, boolean callEvent) { // CraftBukkit - SPIGOT-2942: Add boolean to call event
+         ItemEntity itemEntity = this.createItemStackToDrop(droppedItem, dropAround, traceItem);
+         if (itemEntity == null) {
+             return null;
+         } else {
++            // CraftBukkit start - fire PlayerDropItemEvent
++            if (callEvent) {
++                org.bukkit.entity.Player player = this.getBukkitEntity();
++                org.bukkit.entity.Item drop = (org.bukkit.entity.Item) itemEntity.getBukkitEntity();
++
++                org.bukkit.event.player.PlayerDropItemEvent event = new org.bukkit.event.player.PlayerDropItemEvent(player, drop);
++                this.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand();
++                    if (traceItem && (cur == null || cur.getAmount() == 0)) {
++                        // The complete stack was dropped
++                        player.getInventory().setItemInHand(drop.getItemStack());
++                    } else if (traceItem && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) {
++                        // Only one item is dropped
++                        cur.setAmount(cur.getAmount() + 1);
++                        player.getInventory().setItemInHand(cur);
++                    } else {
++                        // Fallback
++                        player.getInventory().addItem(drop.getItemStack());
++                    }
++                    return null;
++                }
++            }
++            // CraftBukkit end
+             this.level().addFreshEntity(itemEntity);
+             ItemStack item = itemEntity.getItem();
+             if (traceItem) {
+                 if (!item.isEmpty()) {
+-                    this.awardStat(Stats.ITEM_DROPPED.get(item.getItem()), droppedItem.getCount());
++                    this.awardStat(Stats.ITEM_DROPPED.get(item.getItem()), item.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item
+                 }
+ 
+                 this.awardStat(Stats.DROP);
+@@ -1955,6 +_,11 @@
+             return null;
+         } else {
+             double d = this.getEyeY() - 0.3F;
++            // Paper start
++            ItemStack tmp = droppedItem.copy();
++            tmp.setCount(0);
++            droppedItem = tmp;
++            // Paper end
+             ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), d, this.getZ(), droppedItem);
+             itemEntity.setPickUpDelay(40);
+             if (includeThrowerName) {
+@@ -2008,6 +_,16 @@
+     }
+ 
+     public void loadGameTypes(@Nullable CompoundTag tag) {
++        // Paper start - Expand PlayerGameModeChangeEvent
++        if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(tag, "playerGameType")) {
++            if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) {
++                this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE);
++            } else {
++                this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(tag,"playerGameType"), ServerPlayer.readPlayerMode(tag, "previousPlayerGameType"));
++            }
++            return;
++        }
++        // Paper end - Expand PlayerGameModeChangeEvent
+         this.gameMode
+             .setGameModeForPlayer(this.calculateGameModeForNewPlayer(readPlayerMode(tag, "playerGameType")), readPlayerMode(tag, "previousPlayerGameType"));
+     }
+@@ -2108,8 +_,14 @@
+ 
+     @Override
+     public void stopRiding() {
++        // Paper start - Force entity dismount during teleportation
++        this.stopRiding(false);
++    }
++    @Override
++    public void stopRiding(boolean suppressCancellation) {
++        // Paper end - Force entity dismount during teleportation
+         Entity vehicle = this.getVehicle();
+-        super.stopRiding();
++        super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation
+         if (vehicle instanceof LivingEntity livingEntity) {
+             for (MobEffectInstance mobEffectInstance : livingEntity.getActiveEffects()) {
+                 this.connection.send(new ClientboundRemoveMobEffectPacket(vehicle.getId(), mobEffectInstance.getEffect()));
+@@ -2204,13 +_,15 @@
+     }
+ 
+     public static long placeEnderPearlTicket(ServerLevel level, ChunkPos pos) {
+-        level.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, pos, 2, pos);
++        if (!level.paperConfig().misc.legacyEnderPearlBehavior) level.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, pos, 2, pos); // Paper - Allow using old ender pearl behavior
+         return TicketType.ENDER_PEARL.timeout();
+     }
+ 
+-    public record RespawnPosAngle(Vec3 position, float yaw) {
+-        public static ServerPlayer.RespawnPosAngle of(Vec3 position, BlockPos towardsPos) {
+-            return new ServerPlayer.RespawnPosAngle(position, calculateLookAtYaw(position, towardsPos));
++    // CraftBukkit start
++    public record RespawnPosAngle(Vec3 position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn) {
++        public static ServerPlayer.RespawnPosAngle of(Vec3 position, BlockPos towardsPos, boolean isBedSpawn, boolean isAnchorSpawn) {
++            return new ServerPlayer.RespawnPosAngle(position, calculateLookAtYaw(position, towardsPos), isBedSpawn, isAnchorSpawn);
++    // CraftBukkit end
+         }
+ 
+         private static float calculateLookAtYaw(Vec3 position, BlockPos towardsPos) {
+@@ -2218,4 +_,147 @@
+             return (float)Mth.wrapDegrees(Mth.atan2(vec3.z, vec3.x) * 180.0F / (float)Math.PI - 90.0);
+         }
+     }
++
++    // CraftBukkit start - Add per-player time and weather.
++    public long timeOffset = 0;
++    public boolean relativeTime = true;
++
++    public long getPlayerTime() {
++        if (this.relativeTime) {
++            // Adds timeOffset to the current server time.
++            return this.level().getDayTime() + this.timeOffset;
++        } else {
++            // Adds timeOffset to the beginning of this day.
++            return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset;
++        }
++    }
++
++    public org.bukkit.WeatherType weather = null;
++
++    public org.bukkit.WeatherType getPlayerWeather() {
++        return this.weather;
++    }
++
++    public void setPlayerWeather(org.bukkit.WeatherType type, boolean plugin) {
++        if (!plugin && this.weather != null) {
++            return;
++        }
++
++        if (plugin) {
++            this.weather = type;
++        }
++
++        if (type == org.bukkit.WeatherType.DOWNFALL) {
++            this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0));
++        } else {
++            this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0));
++        }
++    }
++
++    private float pluginRainPosition;
++    private float pluginRainPositionPrevious;
++
++    public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) {
++        if (this.weather == null) {
++            // Vanilla
++            if (oldRain != newRain) {
++                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain));
++            }
++        } else {
++            // Plugin
++            if (this.pluginRainPositionPrevious != this.pluginRainPosition) {
++                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.pluginRainPosition));
++            }
++        }
++
++        if (oldThunder != newThunder) {
++            if (this.weather == org.bukkit.WeatherType.DOWNFALL || this.weather == null) {
++                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder));
++            } else {
++                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0));
++            }
++        }
++    }
++
++    public void tickWeather() {
++        if (this.weather == null) return;
++
++        this.pluginRainPositionPrevious = this.pluginRainPosition;
++        if (this.weather == org.bukkit.WeatherType.DOWNFALL) {
++            this.pluginRainPosition += 0.01;
++        } else {
++            this.pluginRainPosition -= 0.01;
++        }
++
++        this.pluginRainPosition = Mth.clamp(this.pluginRainPosition, 0.0F, 1.0F);
++    }
++
++    public void resetPlayerWeather() {
++        this.weather = null;
++        this.setPlayerWeather(this.level().getLevelData().isRaining() ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR, false);
++    }
++
++    @Override
++    public String toString() {
++        return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")";
++    }
++
++    // SPIGOT-1903, MC-98153
++    public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) {
++        this.moveTo(x, y, z, yaw, pitch);
++        this.connection.resetPosition();
++    }
++
++    @Override
++    public boolean isImmobile() {
++        return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs
++    }
++
++    @Override
++    public net.minecraft.world.scores.Scoreboard getScoreboard() {
++        return this.getBukkitEntity().getScoreboard().getHandle();
++    }
++
++    public void reset() {
++        float exp = 0;
++
++        if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++            exp = this.experienceProgress;
++            this.newTotalExp = this.totalExperience;
++            this.newLevel = this.experienceLevel;
++        }
++
++        this.setHealth(this.getMaxHealth());
++        this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset
++        this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn
++        this.setRemainingFireTicks(0);
++        this.fallDistance = 0;
++        this.foodData = new FoodData();
++        this.experienceLevel = this.newLevel;
++        this.totalExperience = this.newTotalExp;
++        this.experienceProgress = 0;
++        this.deathTime = 0;
++        this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent
++        this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
++        this.effectsDirty = true;
++        this.containerMenu = this.inventoryMenu;
++        this.lastHurtByPlayer = null;
++        this.lastHurtByMob = null;
++        this.combatTracker = new net.minecraft.world.damagesource.CombatTracker(this);
++        this.lastSentExp = -1;
++        if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++            this.experienceProgress = exp;
++        } else {
++            this.giveExperiencePoints(this.newExp);
++        }
++        this.keepLevel = false;
++        this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death
++        this.skipDropExperience = false; // CraftBukkit - SPIGOT-7462: Reset experience drop skip, so that further deaths drop xp
++    }
++
++    @Override
++    public org.bukkit.craftbukkit.entity.CraftPlayer getBukkitEntity() {
++        return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity();
++    }
++    // CraftBukkit end
+ }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch
rename to paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index aba7ebeae1..eac24793a9 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -13,6 +13,7 @@
+@@ -13,6 +_,7 @@
  import net.minecraft.world.InteractionResult;
  import net.minecraft.world.MenuProvider;
  import net.minecraft.world.entity.EquipmentSlot;
@@ -8,75 +8,51 @@
  import net.minecraft.world.item.ItemStack;
  import net.minecraft.world.item.context.UseOnContext;
  import net.minecraft.world.item.enchantment.EnchantmentHelper;
-@@ -20,12 +21,30 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.GameMasterBlock;
-+import net.minecraft.world.level.block.TrapDoorBlock;
- import net.minecraft.world.level.block.entity.BlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.CakeBlock;
-+import net.minecraft.world.level.block.DoorBlock;
-+import org.bukkit.GameMode;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.event.block.BlockBreakEvent;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.event.Event;
-+import org.bukkit.event.block.Action;
-+import org.bukkit.event.player.PlayerGameModeChangeEvent;
-+import org.bukkit.event.player.PlayerInteractEvent;
-+// CraftBukkit end
-+
- public class ServerPlayerGameMode {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -42,6 +61,8 @@
-     private BlockPos delayedDestroyPos;
+@@ -41,6 +_,8 @@
+     private BlockPos delayedDestroyPos = BlockPos.ZERO;
      private int delayedTickStart;
-     private int lastSentState;
+     private int lastSentState = -1;
 +    public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction
 +    public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction
  
      public ServerPlayerGameMode(ServerPlayer player) {
-         this.gameModeForPlayer = GameType.DEFAULT_MODE;
-@@ -53,18 +74,32 @@
+         this.player = player;
+@@ -48,21 +_,39 @@
      }
  
-     public boolean changeGameModeForPlayer(GameType gameMode) {
+     public boolean changeGameModeForPlayer(GameType gameModeForPlayer) {
 +        // Paper start - Expand PlayerGameModeChangeEvent
-+        PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
++        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameModeForPlayer, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
 +        return event != null && event.isCancelled();
 +    }
 +    @Nullable
-+    public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
++    public org.bukkit.event.player.PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause playerGameModeChangeCause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
 +        // Paper end - Expand PlayerGameModeChangeEvent
-         if (gameMode == this.gameModeForPlayer) {
+         if (gameModeForPlayer == this.gameModeForPlayer) {
 -            return false;
 +            return null; // Paper - Expand PlayerGameModeChangeEvent
          } else {
--            this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer);
+-            this.setGameModeForPlayer(gameModeForPlayer, this.previousGameModeForPlayer);
 +            // CraftBukkit start
-+            PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper
-+            this.level.getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
++            org.bukkit.event.player.PlayerGameModeChangeEvent event = new org.bukkit.event.player.PlayerGameModeChangeEvent(
++                this.player.getBukkitEntity(),
++                org.bukkit.GameMode.getByValue(gameMode.getId()),
++                playerGameModeChangeCause, // Paper
++                cancelMessage
++            );
++            if (!event.callEvent()) {
 +                return event; // Paper - Expand PlayerGameModeChangeEvent
 +            }
 +            // CraftBukkit end
 +            this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571
              this.player.onUpdateAbilities();
--            this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
-+            this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
+             this.player
+                 .server
+                 .getPlayerList()
+-                .broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
++                .broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
              this.level.updateSleepingPlayerList();
-             if (gameMode == GameType.CREATIVE) {
+             if (gameModeForPlayer == GameType.CREATIVE) {
                  this.player.resetCurrentImpulseContext();
              }
  
@@ -85,61 +61,61 @@
          }
      }
  
-@@ -92,12 +127,12 @@
+@@ -90,10 +_,10 @@
      }
  
      public void tick() {
--        ++this.gameTicks;
-+        this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
-         BlockState iblockdata;
- 
+-        this.gameTicks++;
++        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit;
          if (this.hasDelayedDestroy) {
--            iblockdata = this.level.getBlockState(this.delayedDestroyPos);
--            if (iblockdata.isAir()) {
-+            iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
-+            if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks
+-            BlockState blockState = this.level.getBlockState(this.delayedDestroyPos);
+-            if (blockState.isAir()) {
++            BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
++            if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
                  this.hasDelayedDestroy = false;
              } else {
-                 float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
-@@ -108,7 +143,13 @@
+                 float f = this.incrementDestroyProgress(blockState, this.delayedDestroyPos, this.delayedTickStart);
+@@ -103,7 +_,13 @@
                  }
              }
          } else if (this.isDestroyingBlock) {
--            iblockdata = this.level.getBlockState(this.destroyPos);
+-            BlockState blockState = this.level.getBlockState(this.destroyPos);
 +            // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead
-+            iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos);
-+            if (iblockdata == null) {
++            BlockState blockState = this.level.getBlockStateIfLoaded(this.destroyPos);
++            if (blockState == null) {
 +                this.isDestroyingBlock = false;
 +                return;
 +            }
 +            // Paper end - Don't allow digging into unloaded chunks
-             if (iblockdata.isAir()) {
+             if (blockState.isAir()) {
                  this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
                  this.lastSentState = -1;
-@@ -137,6 +178,7 @@
+@@ -131,6 +_,7 @@
  
-     public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
-         if (!this.player.canInteractWithBlock(pos, 1.0D)) {
+     public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction face, int maxBuildHeight, int sequence) {
+         if (!this.player.canInteractWithBlock(pos, 1.0)) {
 +            if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away
              this.debugLogging(pos, false, sequence, "too far");
-         } else if (pos.getY() > worldHeight) {
+         } else if (pos.getY() > maxBuildHeight) {
              this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
-@@ -146,16 +188,40 @@
- 
+@@ -138,16 +_,40 @@
+         } else {
              if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
                  if (!this.level.mayInteract(this.player, pos)) {
 +                    // CraftBukkit start - fire PlayerInteractEvent
-+                    CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
++                    org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
                      this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
                      this.debugLogging(pos, false, sequence, "may not interact");
+-                    return;
+-                }
 +                    // Update any tile entity data for this block
 +                    capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
 +                    // CraftBukkit end
-                     return;
-                 }
- 
++                    return;
++                }
++
 +                // CraftBukkit start
-+                PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
++                org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
 +                if (event.isCancelled()) {
 +                    // Let the client know the block still exists
 +                    // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
@@ -148,7 +124,7 @@
 +                    return;
 +                }
 +                // CraftBukkit end
-+
+ 
                  if (this.isCreative()) {
                      this.destroyAndAck(pos, sequence, "creative destroy");
                      return;
@@ -156,7 +132,7 @@
  
 +                // Spigot start - handle debug stick left click for non-creative
 +                if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK)
-+                        && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) {
++                    && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) {
 +                    // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
 +                    return;
 +                }
@@ -165,13 +141,13 @@
                  if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
                      this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
                      this.debugLogging(pos, false, sequence, "block action restricted");
-@@ -166,7 +232,21 @@
+@@ -157,7 +_,21 @@
+                 this.destroyProgressStart = this.gameTicks;
                  float f = 1.0F;
- 
-                 iblockdata = this.level.getBlockState(pos);
--                if (!iblockdata.isAir()) {
+                 BlockState blockState = this.level.getBlockState(pos);
+-                if (!blockState.isAir()) {
 +                // CraftBukkit start - Swings at air do *NOT* exist.
-+                if (event.useInteractedBlock() == Event.Result.DENY) {
++                if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) {
 +                    // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
 +                    // Paper start - Don't resync blocks
 +                    //BlockState data = this.level.getBlockState(pos);
@@ -184,22 +160,22 @@
 +                    //    this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
 +                    //}
 +                    // Paper end - Don't resync blocks
-+                } else if (!iblockdata.isAir()) {
-                     EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> {
-                         this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND);
-                     });
-@@ -174,6 +254,26 @@
-                     f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos);
++                } else if (!blockState.isAir()) {
+                     EnchantmentHelper.onHitBlock(
+                         this.level,
+                         this.player.getMainHandItem(),
+@@ -172,6 +_,26 @@
+                     f = blockState.getDestroyProgress(this.player, this.player.level(), pos);
                  }
  
-+                if (event.useItemInHand() == Event.Result.DENY) {
++                if (event.useItemInHand() == org.bukkit.event.Event.Result.DENY) {
 +                    // If we 'insta destroyed' then the client needs to be informed.
 +                    if (f > 1.0f) {
 +                        // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
 +                    }
 +                    return;
 +                }
-+                org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent
++                org.bukkit.event.block.BlockDamageEvent blockEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageEvent(this.player, pos, face, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent
 +
 +                if (blockEvent.isCancelled()) {
 +                    // Let the client know the block still exists
@@ -212,15 +188,15 @@
 +                }
 +                // CraftBukkit end
 +
-                 if (!iblockdata.isAir() && f >= 1.0F) {
+                 if (!blockState.isAir() && f >= 1.0F) {
                      this.destroyAndAck(pos, sequence, "insta mine");
                  } else {
-@@ -217,14 +317,18 @@
+@@ -212,14 +_,18 @@
                  this.debugLogging(pos, true, sequence, "stopped destroying");
              } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
                  this.isDestroyingBlock = false;
 -                if (!Objects.equals(this.destroyPos, pos)) {
--                    ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
+-                    LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
 -                    this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
 -                    this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
 +                if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper
@@ -234,31 +210,30 @@
                  this.level.destroyBlockProgress(this.player.getId(), pos, -1);
                  this.debugLogging(pos, true, sequence, "aborted destroying");
 +
-+                CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
++                org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
              }
- 
          }
-@@ -242,19 +346,82 @@
+     }
+@@ -235,36 +_,125 @@
  
      public boolean destroyBlock(BlockPos pos) {
-         BlockState iblockdata = this.level.getBlockState(pos);
+         BlockState blockState = this.level.getBlockState(pos);
+-        if (!this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) {
 +        // CraftBukkit start - fire BlockBreakEvent
-+        org.bukkit.block.Block bblock = CraftBlock.at(this.level, pos);
-+        BlockBreakEvent event = null;
- 
--        if (!this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) {
++        org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, pos);
++        org.bukkit.event.block.BlockBreakEvent event = null;
 +        if (this.player instanceof ServerPlayer) {
 +            // Sword + Creative mode pre-cancel
-+            boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player);
++            boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player);
 +
 +            // Tell client the block is gone immediately then process events
 +            // Don't tell the client if its a creative sword break because its not broken!
 +            if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block
-+                ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState());
++                ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState());
 +                this.player.connection.send(packet);
 +            }
 +
-+            event = new BlockBreakEvent(bblock, this.player.getBukkitEntity());
++            event = new org.bukkit.event.block.BlockBreakEvent(bblock, this.player.getBukkitEntity());
 +
 +            // Sword + Creative mode pre-cancel
 +            event.setCancelled(isSwordNoBreak);
@@ -291,40 +266,38 @@
 +
 +                // Update any tile entity data for this block
 +                if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
-+                BlockEntity tileentity = this.level.getBlockEntity(pos);
-+                if (tileentity != null) {
-+                    this.player.connection.send(tileentity.getUpdatePacket());
-+                }
++                    BlockEntity tileentity = this.level.getBlockEntity(pos);
++                    if (tileentity != null) {
++                        this.player.connection.send(tileentity.getUpdatePacket());
++                    }
 +                } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction
 +                return false;
 +            }
 +        }
 +        // CraftBukkit end
 +
-+        if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false
++        if (false && !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) { // CraftBukkit - false
              return false;
          } else {
-+            iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
-+            if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
-             BlockEntity tileentity = this.level.getBlockEntity(pos);
-             Block block = iblockdata.getBlock();
- 
++            blockState = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
++            if (blockState.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
+             BlockEntity blockEntity = this.level.getBlockEntity(pos);
+             Block block = blockState.getBlock();
 -            if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
 +            if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
-                 this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3);
+                 this.level.sendBlockUpdated(pos, blockState, blockState, 3);
                  return false;
              } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
                  return false;
              } else {
 +                // CraftBukkit start
 +                org.bukkit.block.BlockState state = bblock.getState();
-+                this.level.captureDrops = new ArrayList<>();
++                this.level.captureDrops = new java.util.ArrayList<>();
 +                // CraftBukkit end
-                 BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
+                 BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player);
                  boolean flag = this.level.removeBlock(pos, false);
- 
-@@ -262,20 +429,46 @@
-                     block.destroy(this.level, pos, iblockdata1);
+                 if (flag) {
+                     block.destroy(this.level, pos, blockState1);
                  }
  
 +                ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place
@@ -333,22 +306,24 @@
 -                    return true;
 +                    // return true; // CraftBukkit
                  } else {
-                     ItemStack itemstack = this.player.getMainHandItem();
-                     ItemStack itemstack1 = itemstack.copy();
-                     boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
-+                    mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
-+                    isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
- 
-                     itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
--                    if (flag && flag1) {
--                        block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1);
-+                    if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
-+                        block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion
-                     }
- 
+                     ItemStack mainHandItem = this.player.getMainHandItem();
+                     ItemStack itemStack = mainHandItem.copy();
+                     boolean hasCorrectToolForDrops = this.player.hasCorrectToolForDrops(blockState1);
++                    mainHandStack = itemStack; // Paper - Trigger bee_nest_destroyed trigger in the correct place
++                    isCorrectTool = hasCorrectToolForDrops; // Paper - Trigger bee_nest_destroyed trigger in the correct place
+                     mainHandItem.mineBlock(this.level, blockState1, pos, this.player);
+-                    if (flag && hasCorrectToolForDrops) {
+-                        block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack);
+-                    }
+-
 -                    return true;
+-                }
++                    if (flag && hasCorrectToolForDrops/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
++                        block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion
++                    }
++
 +                    // return true; // CraftBukkit
-                 }
++                }
 +                // CraftBukkit start
 +                java.util.List<net.minecraft.world.entity.item.ItemEntity> itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world
 +                this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff
@@ -359,12 +334,12 @@
 +
 +                // Drop event experience
 +                if (flag && event != null) {
-+                    iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
++                    blockState.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
 +                }
 +                // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy)
 +                if (mainHandStack != null) {
-+                    if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above
-+                        CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount());
++                    if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && blockEntity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above
++                        CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, blockState, mainHandStack, beehiveBlockEntity.getOccupantCount());
 +                    }
 +                }
 +                // Paper end - Trigger bee_nest_destroyed trigger in the correct place
@@ -374,7 +349,7 @@
              }
          }
      }
-@@ -321,17 +514,63 @@
+@@ -307,15 +_,61 @@
          }
      }
  
@@ -384,40 +359,38 @@
 +    public BlockPos interactPosition;
 +    public InteractionHand interactHand;
 +    public ItemStack interactItemStack;
-     public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
-         BlockPos blockposition = hitResult.getBlockPos();
-         BlockState iblockdata = world.getBlockState(blockposition);
+     public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+         BlockPos blockPos = hitResult.getBlockPos();
+         BlockState blockState = level.getBlockState(blockPos);
 +        boolean cancelledBlock = false;
 +        boolean cancelledItem = false; // Paper - correctly handle items on cooldown
- 
-         if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) {
+         if (!blockState.getBlock().isEnabled(level.enabledFeatures())) {
              return InteractionResult.FAIL;
          } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
-             MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
-+            cancelledBlock = !(itileinventory instanceof MenuProvider);
+             MenuProvider menuProvider = blockState.getMenuProvider(level, blockPos);
+-            if (menuProvider != null) {
+-                player.openMenu(menuProvider);
++            cancelledBlock = !(menuProvider instanceof MenuProvider);
 +        }
- 
--            if (itileinventory != null) {
--                player.openMenu(itileinventory);
++
 +        if (player.getCooldowns().isOnCooldown(stack)) {
 +            cancelledItem = true; // Paper - correctly handle items on cooldown
 +        }
-+
-+        PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown
++        org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(player, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, blockPos, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown
 +        this.firedInteract = true;
-+        this.interactResult = event.useItemInHand() == Event.Result.DENY;
-+        this.interactPosition = blockposition.immutable();
++        this.interactResult = event.useItemInHand() == org.bukkit.event.Event.Result.DENY;
++        this.interactPosition = blockPos.immutable();
 +        this.interactHand = hand;
 +        this.interactItemStack = stack.copy();
 +
-+        if (event.useInteractedBlock() == Event.Result.DENY) {
++        if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) {
 +            // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
-+            if (iblockdata.getBlock() instanceof DoorBlock) {
++            if (blockState.getBlock() instanceof net.minecraft.world.level.block.DoorBlock) {
 +                // Paper start - Don't resync blocks
 +                // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
 +                // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below()));
 +                // Paper end - Don't resync blocks
-+            } else if (iblockdata.getBlock() instanceof CakeBlock) {
++            } else if (blockState.getBlock() instanceof net.minecraft.world.level.block.CakeBlock) {
 +                player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
 +            } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) {
 +                // send a correcting update to the client, as it already placed the upper half of the bisected item
@@ -425,33 +398,33 @@
 +
 +                // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc)
 +                //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks
-+            // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method
-+            } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) {
++                // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method
++            } else if (blockState.is(net.minecraft.world.level.block.Blocks.JIGSAW) || blockState.is(net.minecraft.world.level.block.Blocks.STRUCTURE_BLOCK) || blockState.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) {
 +                player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId));
 +            }
 +            // Paper end - extend Player Interact cancellation
 +            player.getBukkitEntity().updateInventory(); // SPIGOT-2867
 +            this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
-+            return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
++            return (event.useItemInHand() != org.bukkit.event.Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
 +        } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
-+            MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition);
++            MenuProvider itileinventory = blockState.getMenuProvider(level, blockPos);
 +
 +            if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
                  return InteractionResult.CONSUME;
              } else {
                  return InteractionResult.PASS;
-@@ -359,7 +598,7 @@
+@@ -340,7 +_,7 @@
                  }
              }
  
 -            if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) {
 +            if (!stack.isEmpty() && !this.interactResult) { // add !interactResult SPIGOT-764
-                 UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult);
- 
+                 UseOnContext useOnContext = new UseOnContext(player, hand, hitResult);
+                 InteractionResult interactionResult1;
                  if (this.isCreative()) {
-@@ -377,6 +616,11 @@
+@@ -357,6 +_,11 @@
  
-                 return enuminteractionresult;
+                 return interactionResult1;
              } else {
 +                // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response
 +                if (this.interactResult && this.interactResult != cancelledItem) {
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch
similarity index 63%
rename from paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch
rename to paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch
index ca9484bbeb..12796a0ffe 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch
@@ -1,20 +1,20 @@
 --- a/net/minecraft/server/level/TicketType.java
 +++ b/net/minecraft/server/level/TicketType.java
-@@ -7,6 +7,7 @@
+@@ -7,6 +_,7 @@
  import net.minecraft.world.level.ChunkPos;
  
  public class TicketType<T> {
 +    public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
- 
      private final String name;
      private final Comparator<T> comparator;
-@@ -22,6 +23,9 @@
-     public static final TicketType<BlockPos> PORTAL = TicketType.create("portal", Vec3i::compareTo, 300);
-     public static final TicketType<ChunkPos> ENDER_PEARL = TicketType.create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40);
-     public static final TicketType<ChunkPos> UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
+     public long timeout;
+@@ -17,6 +_,9 @@
+     public static final TicketType<BlockPos> PORTAL = create("portal", Vec3i::compareTo, 300);
+     public static final TicketType<ChunkPos> ENDER_PEARL = create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40);
+     public static final TicketType<ChunkPos> UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
 +    public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
 +    public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
 +    public static final TicketType<Integer> POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type
  
-     public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
-         return new TicketType<>(name, argumentComparator, 0L);
+     public static <T> TicketType<T> create(String name, Comparator<T> comparator) {
+         return new TicketType<>(name, comparator, 0L);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch
similarity index 58%
rename from paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch
rename to paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch
index 415887c78f..6daf126e10 100644
--- a/paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch
@@ -1,11 +1,9 @@
 --- a/net/minecraft/server/level/WorldGenRegion.java
 +++ b/net/minecraft/server/level/WorldGenRegion.java
-@@ -167,7 +167,27 @@
-         int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
+@@ -151,6 +_,26 @@
+         return chessboardDistance < this.generatingStep.directDependencies().size();
+     }
  
-         return k < this.generatingStep.directDependencies().size();
-+    }
-+
 +    // Paper start - if loaded util
 +    @Nullable
 +    @Override
@@ -23,36 +21,43 @@
 +    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
 +        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
 +        return chunk == null ? null : chunk.getFluidState(blockposition);
-     }
++    }
 +    // Paper end
- 
++
      @Override
      public BlockState getBlockState(BlockPos pos) {
-@@ -217,7 +237,8 @@
-         if (iblockdata.isAir()) {
+         return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos);
+@@ -198,7 +_,8 @@
+         if (blockState.isAir()) {
              return false;
          } else {
--            if (drop) {
-+            if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens
+-            if (dropBlock) {
++            if (dropBlock) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens
 +            if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
-                 BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
- 
-                 Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY);
-@@ -264,6 +285,7 @@
+                 BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+                 Block.dropResources(blockState, this.level, pos, blockEntity, entity, ItemStack.EMPTY);
+             }
+@@ -242,6 +_,7 @@
          }
      }
  
 +    private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls
      @Override
      public boolean ensureCanWrite(BlockPos pos) {
-         int i = SectionPos.blockToSectionCoord(pos.getX());
-@@ -283,7 +305,15 @@
+         int sectionPosX = SectionPos.blockToSectionCoord(pos.getX());
+@@ -259,6 +_,8 @@
  
              return true;
          } else {
 +            // Paper start - Buffer OOB setBlock calls
 +            if (!hasSetFarWarned) {
-             Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
+             Util.logAndPauseIfInIde(
+                 "Detected setBlock in a far chunk ["
+                     + sectionPosX
+@@ -270,6 +_,12 @@
+                     + this.generatingStep.targetStatus()
+                     + (this.currentlyGenerating == null ? "" : ", currently generating: " + this.currentlyGenerating.get())
+             );
 +                hasSetFarWarned = true;
 +                if (this.getServer() != null && this.getServer().isDebugging()) {
 +                    io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call");
@@ -62,17 +67,17 @@
              return false;
          }
      }
-@@ -294,7 +324,7 @@
+@@ -280,7 +_,7 @@
              return false;
          } else {
-             ChunkAccess ichunkaccess = this.getChunk(pos);
--            BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false);
-+            BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper
- 
-             if (iblockdata1 != null) {
-                 this.level.onBlockStateChange(pos, iblockdata1, state);
-@@ -310,6 +340,17 @@
-                         ichunkaccess.removeBlockEntity(pos);
+             ChunkAccess chunk = this.getChunk(pos);
+-            BlockState blockState = chunk.setBlockState(pos, state, false);
++            BlockState blockState = chunk.setBlockState(pos, state, false); final BlockState previousBlockState = blockState; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper
+             if (blockState != null) {
+                 this.level.onBlockStateChange(pos, blockState, state);
+             }
+@@ -294,6 +_,17 @@
+                         chunk.removeBlockEntity(pos);
                      }
                  } else {
 +                    // Paper start - Clear block entity before setting up a DUMMY block entity
@@ -83,13 +88,13 @@
 +                    // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750)
 +                    // This logic is *also* found in LevelChunk#setBlockState.
 +                    if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) {
-+                        ichunkaccess.removeBlockEntity(pos);
++                        chunk.removeBlockEntity(pos);
 +                    }
 +                    // Paper end - Clear block entity before setting up a DUMMY block entity
-                     CompoundTag nbttagcompound = new CompoundTag();
- 
-                     nbttagcompound.putInt("x", pos.getX());
-@@ -336,6 +377,13 @@
+                     CompoundTag compoundTag = new CompoundTag();
+                     compoundTag.putInt("x", pos.getX());
+                     compoundTag.putInt("y", pos.getY());
+@@ -319,6 +_,13 @@
  
      @Override
      public boolean addFreshEntity(Entity entity) {
@@ -100,6 +105,6 @@
 +    @Override
 +    public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
 +        // CraftBukkit end
-         int i = SectionPos.blockToSectionCoord(entity.getBlockX());
-         int j = SectionPos.blockToSectionCoord(entity.getBlockZ());
- 
+         int sectionPosX = SectionPos.blockToSectionCoord(entity.getBlockX());
+         int sectionPosZ = SectionPos.blockToSectionCoord(entity.getBlockZ());
+         this.getChunk(sectionPosX, sectionPosZ).addEntity(entity);
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch
deleted file mode 100644
index 9a5c5895b8..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch
+++ /dev/null
@@ -1,433 +0,0 @@
---- a/net/minecraft/server/level/ChunkMap.java
-+++ b/net/minecraft/server/level/ChunkMap.java
-@@ -104,6 +104,10 @@
- import org.apache.commons.lang3.mutable.MutableBoolean;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
-+// CraftBukkit end
-+
- public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
- 
-     private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
-@@ -149,6 +153,33 @@
-     public int serverViewDistance;
-     private final WorldGenContext worldGenContext;
- 
-+    // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
-+    public final CallbackExecutor callbackExecutor = new CallbackExecutor();
-+    public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
-+
-+        private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
-+
-+        @Override
-+        public void execute(Runnable runnable) {
-+            this.queue.add(runnable);
-+        }
-+
-+        @Override
-+        public void run() {
-+            Runnable task;
-+            while ((task = this.queue.poll()) != null) {
-+                task.run();
-+            }
-+        }
-+    };
-+    // CraftBukkit end
-+
-+    // Paper start
-+    public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
-+        return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
-+    }
-+    // Paper end
-+
-     public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
-         super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
-         this.visibleChunkMap = this.updatingChunkMap.clone();
-@@ -170,13 +201,19 @@
-         RegistryAccess iregistrycustom = world.registryAccess();
-         long j = world.getSeed();
- 
--        if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
-+        // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
-+        ChunkGenerator randomGenerator = chunkGenerator;
-+        if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) {
-+            randomGenerator = customChunkGenerator.getDelegate();
-+        }
-+        if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
-+            // CraftBukkit end
-             this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
-         } else {
-             this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
-         }
- 
--        this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j);
-+        this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot
-         this.mainThreadExecutor = mainThreadExecutor;
-         ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen");
- 
-@@ -198,6 +235,12 @@
-         this.chunksToEagerlySave.add(pos.toLong());
-     }
- 
-+    // Paper start
-+    public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
-+        return -1;
-+    }
-+    // Paper end
-+
-     protected ChunkGenerator generator() {
-         return this.worldGenContext.generator();
-     }
-@@ -325,7 +368,7 @@
-                         throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
-                     }
- 
--                    ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null);
-+                    ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
- 
-                     if (ichunkaccess == null) {
-                         return ChunkMap.UNLOADED_CHUNK_LIST_RESULT;
-@@ -354,9 +397,9 @@
-         };
- 
-         stringbuilder.append("Updating:").append(System.lineSeparator());
--        this.updatingChunkMap.values().forEach(consumer);
-+        ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
-         stringbuilder.append("Visible:").append(System.lineSeparator());
--        this.visibleChunkMap.values().forEach(consumer);
-+        ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
-         CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
-         CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
- 
-@@ -398,6 +441,9 @@
-                     holder.setTicketLevel(level);
-                 } else {
-                     holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this);
-+                    // Paper start
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
-+                    // Paper end
-                 }
- 
-                 this.updatingChunkMap.put(pos, holder);
-@@ -427,7 +473,7 @@
- 
-     protected void saveAllChunks(boolean flush) {
-         if (flush) {
--            List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
-+            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
-             MutableBoolean mutableboolean = new MutableBoolean();
- 
-             do {
-@@ -453,7 +499,7 @@
-         } else {
-             this.nextChunkSaveTime.clear();
-             long i = Util.getMillis();
--            ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
-+            Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
- 
-             while (objectiterator.hasNext()) {
-                 ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
-@@ -478,7 +524,7 @@
-     }
- 
-     public boolean hasWork() {
--        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
-+        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
-     }
- 
-     private void processUnloads(BooleanSupplier shouldKeepTicking) {
-@@ -537,8 +583,11 @@
-                 this.scheduleUnload(pos, chunk);
-             } else {
-                 ChunkAccess ichunkaccess = chunk.getLatestChunk();
--
--                if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) {
-+                // Paper start
-+                boolean removed;
-+                if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) {
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
-+                    // Paper end
-                     LevelChunk chunk1;
- 
-                     if (ichunkaccess instanceof LevelChunk) {
-@@ -556,7 +605,9 @@
-                     this.lightEngine.tryScheduleUpdate();
-                     this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
-                     this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong());
--                }
-+                } else if (removed) { // Paper start
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
-+                } // Paper end
- 
-             }
-         };
-@@ -905,7 +956,7 @@
-         }
-     }
- 
--    protected void setServerViewDistance(int watchDistance) {
-+    public void setServerViewDistance(int watchDistance) { // Paper - public
-         int j = Mth.clamp(watchDistance, 2, 32);
- 
-         if (j != this.serverViewDistance) {
-@@ -922,7 +973,7 @@
- 
-     }
- 
--    int getPlayerViewDistance(ServerPlayer player) {
-+    public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
-         return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
-     }
- 
-@@ -951,7 +1002,7 @@
-     }
- 
-     public int size() {
--        return this.visibleChunkMap.size();
-+        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
-     }
- 
-     public DistanceManager getDistanceManager() {
-@@ -959,25 +1010,26 @@
-     }
- 
-     protected Iterable<ChunkHolder> getChunks() {
--        return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
-+        return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
-     }
- 
-     void dumpChunks(Writer writer) throws IOException {
-         CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
-         TickingTracker tickingtracker = this.distanceManager.tickingTracker();
--        ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
-+        Iterator<ChunkHolder> objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
- 
-         while (objectbidirectionaliterator.hasNext()) {
--            Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
--            long i = entry.getLongKey();
-+            ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
-+            long i = playerchunk.pos.toLong(); // Paper
-             ChunkPos chunkcoordintpair = new ChunkPos(i);
--            ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
-+            // Paper - move up
-             Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
-             Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
-                 return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
-             });
- 
--            csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
-+            // CraftBukkit - decompile error
-+            csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
-                 return chunk.getBlockEntities().size();
-             }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
-                 return chunk.getBlockTicks().count();
-@@ -990,7 +1042,7 @@
- 
-     private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
-         try {
--            ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow((Object) null);
-+            ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error
- 
-             return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed";
-         } catch (CompletionException completionexception) {
-@@ -1002,12 +1054,14 @@
- 
-     private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
-         return this.read(chunkPos).thenApplyAsync((optional) -> {
--            return optional.map(this::upgradeChunkTag);
-+            return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
-         }, Util.backgroundExecutor().forName("upgradeChunk"));
-     }
- 
--    private CompoundTag upgradeChunkTag(CompoundTag nbt) {
--        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer());
-+    // CraftBukkit start
-+    private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
-+        return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
-+        // CraftBukkit end
-     }
- 
-     void forEachSpawnCandidateChunk(Consumer<ChunkHolder> callback) {
-@@ -1025,10 +1079,23 @@
-     }
- 
-     public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
--        return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos);
-+        // Spigot start
-+        return this.anyPlayerCloseEnoughForSpawning(pos, false);
-     }
- 
-+    boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
-+        return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange);
-+        // Spigot end
-+    }
-+
-     private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) {
-+        // Spigot start
-+        return this.anyPlayerCloseEnoughForSpawningInternal(pos, false);
-+    }
-+
-+    private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
-+        double blockRange; // Paper - use from event
-+        // Spigot end
-         Iterator iterator = this.playerMap.getAllPlayers().iterator();
- 
-         ServerPlayer entityplayer;
-@@ -1039,7 +1106,16 @@
-             }
- 
-             entityplayer = (ServerPlayer) iterator.next();
--        } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos));
-+            // Paper start - PlayerNaturallySpawnCreaturesEvent
-+            com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
-+            blockRange = 16384.0D;
-+            if (reducedRange) {
-+                event = entityplayer.playerNaturallySpawnedEvent;
-+                if (event == null || event.isCancelled()) continue;
-+                blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
-+            }
-+            // Paper end - PlayerNaturallySpawnCreaturesEvent
-+        } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
- 
-         return true;
-     }
-@@ -1056,7 +1132,7 @@
-             while (iterator.hasNext()) {
-                 ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
--                if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) {
-+                if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
-                     builder.add(entityplayer);
-                 }
-             }
-@@ -1065,13 +1141,13 @@
-         }
-     }
- 
--    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
--        if (player.isSpectator()) {
-+    private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
-+        if (entityplayer.isSpectator()) {
-             return false;
-         } else {
--            double d0 = ChunkMap.euclideanDistanceSquared(pos, player);
-+            double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
- 
--            return d0 < 16384.0D;
-+            return d0 < range; // Spigot
-         }
-     }
- 
-@@ -1215,9 +1291,19 @@
-     }
- 
-     public void addEntity(Entity entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
-+        // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
-+        if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
-+            LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
-+                + ": " + entity  + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
-+            return;
-+        }
-+        // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
-+        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
-         if (!(entity instanceof EnderDragonPart)) {
-             EntityType<?> entitytypes = entity.getType();
-             int i = entitytypes.clientTrackingRange() * 16;
-+            i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
- 
-             if (i != 0) {
-                 int j = entitytypes.updateInterval();
-@@ -1250,6 +1336,7 @@
-     }
- 
-     protected void removeEntity(Entity entity) {
-+        org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
-         if (entity instanceof ServerPlayer entityplayer) {
-             this.updatePlayerStatus(entityplayer, false);
-             ObjectIterator objectiterator = this.entityMap.values().iterator();
-@@ -1391,7 +1478,7 @@
-         });
-     }
- 
--    private class ChunkDistanceManager extends DistanceManager {
-+    public class ChunkDistanceManager extends DistanceManager { // Paper - public
- 
-         protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
-             super(workerExecutor, mainThreadExecutor);
-@@ -1421,10 +1508,10 @@
-         final Entity entity;
-         private final int range;
-         SectionPos lastSectionPos;
--        public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
-+        public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
- 
-         public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
--            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast);
-+            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
-             this.entity = entity;
-             this.range = i;
-             this.lastSectionPos = SectionPos.of((EntityAccess) entity);
-@@ -1469,6 +1556,7 @@
-         }
- 
-         public void removePlayer(ServerPlayer player) {
-+            org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
-             if (this.seenBy.remove(player.connection)) {
-                 this.serverEntity.removePairing(player);
-             }
-@@ -1476,17 +1564,41 @@
-         }
- 
-         public void updatePlayer(ServerPlayer player) {
-+            org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
-             if (player != this.entity) {
--                Vec3 vec3d = player.position().subtract(this.entity.position());
-+                // Paper start - remove allocation of Vec3D here
-+                // Vec3 vec3d = player.position().subtract(this.entity.position());
-+                double vec3d_dx = player.getX() - this.entity.getX();
-+                double vec3d_dz = player.getZ() - this.entity.getZ();
-+                // Paper end - remove allocation of Vec3D here
-                 int i = ChunkMap.this.getPlayerViewDistance(player);
-                 double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
--                double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z;
-+                double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
-                 double d2 = d0 * d0;
--                boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
-+                // Paper start - Configurable entity tracking range by Y
-+                boolean flag = d1 <= d2;
-+                if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
-+                    double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
-+                    if (rangeY != -1) {
-+                        double vec3d_dy = player.getY() - this.entity.getY();
-+                        flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
-+                    }
-+                }
-+                flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
-+                // Paper end - Configurable entity tracking range by Y
- 
-+                // CraftBukkit start - respect vanish API
-+                if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits
-+                    flag = false;
-+                }
-+                // CraftBukkit end
-                 if (flag) {
-                     if (this.seenBy.add(player.connection)) {
-+                        // Paper start - entity tracking events
-+                        if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) {
-                         this.serverEntity.addPairing(player);
-+                        }
-+                        // Paper end - entity tracking events
-                     }
-                 } else if (this.seenBy.remove(player.connection)) {
-                     this.serverEntity.removePairing(player);
-@@ -1506,6 +1618,7 @@
-             while (iterator.hasNext()) {
-                 Entity entity = (Entity) iterator.next();
-                 int j = entity.getType().clientTrackingRange() * 16;
-+                j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
- 
-                 if (j > i) {
-                     i = j;
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch
deleted file mode 100644
index 60e1437870..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch
+++ /dev/null
@@ -1,152 +0,0 @@
---- a/net/minecraft/server/level/DistanceManager.java
-+++ b/net/minecraft/server/level/DistanceManager.java
-@@ -117,8 +117,17 @@
- 
-             ChunkHolder playerchunk;
- 
-+            // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
-             while (iterator.hasNext()) {
-                 playerchunk = (ChunkHolder) iterator.next();
-+                playerchunk.callEventIfUnloading(chunkLoadingManager);
-+            }
-+
-+            iterator = this.chunksToUpdateFutures.iterator();
-+            // CraftBukkit end
-+
-+            while (iterator.hasNext()) {
-+                playerchunk = (ChunkHolder) iterator.next();
-                 playerchunk.updateHighestAllowedStatus(chunkLoadingManager);
-             }
- 
-@@ -165,30 +174,33 @@
-         }
-     }
- 
--    void addTicket(long position, Ticket<?> ticket) {
--        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(position);
-+    boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-+        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
-         int j = DistanceManager.getTicketLevelAt(arraysetsorted);
-         Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
- 
-         ticket1.setCreatedTick(this.ticketTickCounter);
-         if (ticket.getTicketLevel() < j) {
--            this.ticketTracker.update(position, ticket.getTicketLevel(), true);
-+            this.ticketTracker.update(i, ticket.getTicketLevel(), true);
-         }
- 
-+        return ticket == ticket1; // CraftBukkit
-     }
- 
--    void removeTicket(long pos, Ticket<?> ticket) {
--        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(pos);
-+    boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-+        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
- 
-+        boolean removed = false; // CraftBukkit
-         if (arraysetsorted.remove(ticket)) {
--            ;
-+            removed = true; // CraftBukkit
-         }
- 
-         if (arraysetsorted.isEmpty()) {
--            this.tickets.remove(pos);
-+            this.tickets.remove(i);
-         }
- 
--        this.ticketTracker.update(pos, DistanceManager.getTicketLevelAt(arraysetsorted), false);
-+        this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
-+        return removed; // CraftBukkit
-     }
- 
-     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T argument) {
-@@ -202,19 +214,33 @@
-     }
- 
-     public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
--        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
--        long j = pos.toLong();
-+        // CraftBukkit start
-+        this.addRegionTicketAtDistance(type, pos, radius, argument);
-+    }
- 
--        this.addTicket(j, ticket);
-+    public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
-+        // CraftBukkit end
-+        Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
-+        long j = chunkcoordintpair.toLong();
-+
-+        boolean added = this.addTicket(j, ticket); // CraftBukkit
-         this.tickingTicketsTracker.addTicket(j, ticket);
-+        return added; // CraftBukkit
-     }
- 
-     public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
--        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument);
--        long j = pos.toLong();
-+        // CraftBukkit start
-+        this.removeRegionTicketAtDistance(type, pos, radius, argument);
-+    }
- 
--        this.removeTicket(j, ticket);
-+    public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
-+        // CraftBukkit end
-+        Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
-+        long j = chunkcoordintpair.toLong();
-+
-+        boolean removed = this.removeTicket(j, ticket); // CraftBukkit
-         this.tickingTicketsTracker.removeTicket(j, ticket);
-+        return removed; // CraftBukkit
-     }
- 
-     private SortedArraySet<Ticket<?>> getTickets(long position) {
-@@ -253,9 +279,10 @@
-         ChunkPos chunkcoordintpair = pos.chunk();
-         long i = chunkcoordintpair.toLong();
-         ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
-+        if (objectset == null) return; // CraftBukkit - SPIGOT-6208
- 
--        objectset.remove(player);
--        if (objectset.isEmpty()) {
-+        if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
-+        if (objectset == null || objectset.isEmpty()) { // Paper
-             this.playersPerChunk.remove(i);
-             this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
-             this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
-@@ -358,7 +385,7 @@
-     }
- 
-     public void removeTicketsOnClosing() {
--        ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN);
-+        ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
-         ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
- 
-         while (objectiterator.hasNext()) {
-@@ -389,7 +416,27 @@
- 
-     public boolean hasTickets() {
-         return !this.tickets.isEmpty();
-+    }
-+
-+    // CraftBukkit start
-+    public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
-+        Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
-+
-+        for (java.util.Iterator<Entry<SortedArraySet<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
-+            Entry<SortedArraySet<Ticket<?>>> entry = iterator.next();
-+            SortedArraySet<Ticket<?>> tickets = entry.getValue();
-+            if (tickets.remove(target)) {
-+                // copied from removeTicket
-+                this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
-+
-+                // can't use entry after it's removed
-+                if (tickets.isEmpty()) {
-+                    iterator.remove();
-+                }
-+            }
-+        }
-     }
-+    // CraftBukkit end
- 
-     private class ChunkTicketTracker extends ChunkTracker {
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch
deleted file mode 100644
index f458422c50..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch
+++ /dev/null
@@ -1,179 +0,0 @@
---- a/net/minecraft/server/level/ServerEntity.java
-+++ b/net/minecraft/server/level/ServerEntity.java
-@@ -31,7 +31,6 @@
- import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
- import net.minecraft.network.protocol.game.VecDeltaCodec;
- import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EquipmentSlot;
- import net.minecraft.world.entity.Leashable;
-@@ -50,6 +49,13 @@
- import net.minecraft.world.phys.Vec3;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import net.minecraft.server.network.ServerPlayerConnection;
-+import net.minecraft.util.Mth;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.player.PlayerVelocityEvent;
-+// CraftBukkit end
-+
- public class ServerEntity {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-@@ -69,18 +75,22 @@
-     private Vec3 lastSentMovement;
-     private int tickCount;
-     private int teleportDelay;
--    private List<Entity> lastPassengers = Collections.emptyList();
-+    private List<Entity> lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks
-     private boolean wasRiding;
-     private boolean wasOnGround;
-     @Nullable
-     private List<SynchedEntityData.DataValue<?>> trackedDataValues;
-+    // CraftBukkit start
-+    private final Set<ServerPlayerConnection> trackedPlayers;
- 
--    public ServerEntity(ServerLevel world, Entity entity, int tickInterval, boolean alwaysUpdateVelocity, Consumer<Packet<?>> receiver) {
--        this.level = world;
--        this.broadcast = receiver;
-+    public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
-+        this.trackedPlayers = trackedPlayers;
-+        // CraftBukkit end
-+        this.level = worldserver;
-+        this.broadcast = consumer;
-         this.entity = entity;
--        this.updateInterval = tickInterval;
--        this.trackDelta = alwaysUpdateVelocity;
-+        this.updateInterval = i;
-+        this.trackDelta = flag;
-         this.positionCodec.setBase(entity.trackingPosition());
-         this.lastSentMovement = entity.getDeltaMovement();
-         this.lastSentYRot = Mth.packDegrees(entity.getYRot());
-@@ -94,7 +104,7 @@
-         List<Entity> list = this.entity.getPassengers();
- 
-         if (!list.equals(this.lastPassengers)) {
--            this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
-+            this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
-             ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> {
-                 if (entity instanceof ServerPlayer entityplayer) {
-                     entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
-@@ -106,19 +116,19 @@
- 
-         Entity entity = this.entity;
- 
--        if (entity instanceof ItemFrame entityitemframe) {
--            if (this.tickCount % 10 == 0) {
-+        if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it
-+            if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
-                 ItemStack itemstack = entityitemframe.getItem();
- 
--                if (itemstack.getItem() instanceof MapItem) {
--                    MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID);
-+                if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
-+                    MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames
-                     MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level);
- 
-                     if (worldmap != null) {
--                        Iterator iterator = this.level.players().iterator();
-+                        Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
- 
-                         while (iterator.hasNext()) {
--                            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-+                            ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
- 
-                             worldmap.tickCarriedBy(entityplayer, itemstack);
-                             Packet<?> packet = worldmap.getUpdatePacket(mapid, entityplayer);
-@@ -168,7 +178,13 @@
- 
-                     ++this.teleportDelay;
-                     Vec3 vec3d = this.entity.trackingPosition();
--                    boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D;
-+                    // Paper start - reduce allocation of Vec3D here
-+                    Vec3 base = this.positionCodec.base;
-+                    double vec3d_dx = vec3d.x - base.x;
-+                    double vec3d_dy = vec3d.y - base.y;
-+                    double vec3d_dz = vec3d.z - base.z;
-+                    boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D;
-+                    // Paper end - reduce allocation of Vec3D here
-                     Packet<?> packet1 = null;
-                     boolean flag2 = flag1 || this.tickCount % 60 == 0;
-                     boolean flag3 = false;
-@@ -248,6 +264,27 @@
- 
-         ++this.tickCount;
-         if (this.entity.hurtMarked) {
-+            // CraftBukkit start - Create PlayerVelocity event
-+            boolean cancelled = false;
-+
-+            if (this.entity instanceof ServerPlayer) {
-+                Player player = (Player) this.entity.getBukkitEntity();
-+                org.bukkit.util.Vector velocity = player.getVelocity();
-+
-+                PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
-+                this.entity.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    cancelled = true;
-+                } else if (!velocity.equals(event.getVelocity())) {
-+                    player.setVelocity(event.getVelocity());
-+                }
-+            }
-+
-+            if (cancelled) {
-+                return;
-+            }
-+            // CraftBukkit end
-             this.entity.hurtMarked = false;
-             this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
-         }
-@@ -298,7 +335,10 @@
- 
-     public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> sender) {
-         if (this.entity.isRemoved()) {
--            ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
-+            // CraftBukkit start - Remove useless error spam, just return
-+            // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
-+            return;
-+            // CraftBukkit end
-         }
- 
-         Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket(this);
-@@ -313,6 +353,12 @@
-         if (this.entity instanceof LivingEntity) {
-             Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
- 
-+            // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
-+            if (this.entity.getId() == player.getId()) {
-+                ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false);
-+            }
-+            // CraftBukkit end
-+
-             if (!collection.isEmpty()) {
-                 sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
-             }
-@@ -342,8 +388,9 @@
-             }
- 
-             if (!list.isEmpty()) {
--                sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
-+                sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
-             }
-+            ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
-         }
- 
-         if (!this.entity.getPassengers().isEmpty()) {
-@@ -396,6 +443,11 @@
-             Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync();
- 
-             if (!set.isEmpty()) {
-+                // CraftBukkit start - Send scaled max health
-+                if (this.entity instanceof ServerPlayer) {
-+                    ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
-+                }
-+                // CraftBukkit end
-                 this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
-             }
- 
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch
deleted file mode 100644
index d9915e3d7d..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch
+++ /dev/null
@@ -1,1261 +0,0 @@
---- a/net/minecraft/server/level/ServerLevel.java
-+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -58,7 +58,6 @@
- import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
- import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
- import net.minecraft.network.protocol.game.ClientboundExplodePacket;
--import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
- import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
- import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
- import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
-@@ -124,6 +123,7 @@
- import net.minecraft.world.level.StructureManager;
- import net.minecraft.world.level.WorldGenLevel;
- import net.minecraft.world.level.biome.Biome;
-+import net.minecraft.world.level.biome.BiomeSource;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.SnowLayerBlock;
-@@ -149,7 +149,9 @@
- import net.minecraft.world.level.gameevent.DynamicGameEventListener;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.gameevent.GameEventDispatcher;
-+import net.minecraft.world.level.levelgen.FlatLevelSource;
- import net.minecraft.world.level.levelgen.Heightmap;
-+import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
- import net.minecraft.world.level.levelgen.structure.BoundingBox;
- import net.minecraft.world.level.levelgen.structure.Structure;
- import net.minecraft.world.level.levelgen.structure.StructureCheck;
-@@ -165,7 +167,7 @@
- import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- import net.minecraft.world.level.storage.LevelStorageSource;
--import net.minecraft.world.level.storage.ServerLevelData;
-+import net.minecraft.world.level.storage.PrimaryLevelData;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.BooleanOp;
-@@ -173,6 +175,16 @@
- import net.minecraft.world.phys.shapes.VoxelShape;
- import net.minecraft.world.ticks.LevelTicks;
- import org.slf4j.Logger;
-+import org.bukkit.Bukkit;
-+import org.bukkit.WeatherType;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.generator.CustomWorldChunkManager;
-+import org.bukkit.craftbukkit.util.WorldUUID;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
-+import org.bukkit.event.server.MapInitializeEvent;
-+import org.bukkit.event.weather.LightningStrikeEvent;
-+import org.bukkit.event.world.TimeSkipEvent;
-+// CraftBukkit end
- 
- public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel {
- 
-@@ -187,7 +199,7 @@
-     final List<ServerPlayer> players = Lists.newArrayList();
-     public final ServerChunkCache chunkSource;
-     private final MinecraftServer server;
--    public final ServerLevelData serverLevelData;
-+    public final PrimaryLevelData serverLevelData; // CraftBukkit - type
-     private int lastSpawnChunkRadius;
-     final EntityTickList entityTickList = new EntityTickList();
-     public final PersistentEntitySectionManager<Entity> entityManager;
-@@ -214,54 +226,204 @@
-     private final boolean tickTime;
-     private final RandomSequences randomSequences;
- 
--    public ServerLevel(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey<Level> worldKey, LevelStem dimensionOptions, ChunkProgressListener worldGenerationProgressListener, boolean debugWorld, long seed, List<CustomSpawner> spawners, boolean shouldTickTime, @Nullable RandomSequences randomSequencesState) {
--        super(properties, worldKey, server.registryAccess(), dimensionOptions.type(), false, debugWorld, seed, server.getMaxChainedNeighborUpdates());
--        this.tickTime = shouldTickTime;
--        this.server = server;
--        this.customSpawners = spawners;
--        this.serverLevelData = properties;
--        ChunkGenerator chunkgenerator = dimensionOptions.generator();
--        boolean flag2 = server.forceSynchronousWrites();
--        DataFixer datafixer = server.getFixerUpper();
--        EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(session.getLevelId(), worldKey, "entities"), session.getDimensionPath(worldKey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, server);
-+    // CraftBukkit start
-+    public final LevelStorageSource.LevelStorageAccess convertable;
-+    public final UUID uuid;
-+    public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
-+    public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
-+
-+    public LevelChunk getChunkIfLoaded(int x, int z) {
-+        return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
-+    }
-+
-+    @Override
-+    public ResourceKey<LevelStem> getTypeKey() {
-+        return this.convertable.dimensionType;
-+    }
-+
-+    // Paper start
-+    public final boolean areChunksLoadedForMove(AABB axisalignedbb) {
-+        // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override
-+        // ICollisionAccess methods for VoxelShapes)
-+        // be more strict too, add a block (dumb plugins in move events?)
-+        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
-+        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
-+
-+        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
-+        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
-+
-+        int minChunkX = minBlockX >> 4;
-+        int maxChunkX = maxBlockX >> 4;
-+
-+        int minChunkZ = minBlockZ >> 4;
-+        int maxChunkZ = maxBlockZ >> 4;
-+
-+        ServerChunkCache chunkProvider = this.getChunkSource();
-+
-+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
-+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
-+                if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) {
-+                    return false;
-+                }
-+            }
-+        }
-+
-+        return true;
-+    }
-+
-+    public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority,
-+                                             java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
-+        if (Thread.currentThread() != this.thread) {
-+            this.getChunkSource().mainThreadProcessor.execute(() -> {
-+                this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad);
-+            });
-+            return;
-+        }
-+        int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
-+        int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
-+
-+        int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3;
-+        int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3;
-+
-+        int minChunkX = minBlockX >> 4;
-+        int minChunkZ = minBlockZ >> 4;
-+
-+        int maxChunkX = maxBlockX >> 4;
-+        int maxChunkZ = maxBlockZ >> 4;
-+
-+        this.loadChunks(minChunkX, minChunkZ, maxChunkX, maxChunkZ, priority, onLoad);
-+    }
-+
-+    public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ,
-+                                 ca.spottedleaf.concurrentutil.util.Priority priority,
-+                                 java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
-+        List<net.minecraft.world.level.chunk.ChunkAccess> ret = new java.util.ArrayList<>();
-+        it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList();
-+        ServerChunkCache chunkProvider = this.getChunkSource();
- 
-+        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
-+        int[] loadedChunks = new int[1];
-+
-+        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
-+
-+        java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
-+            if (chunk != null) {
-+                int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel());
-+                ret.add(chunk);
-+                ticketLevels.add(ticketLevel);
-+                chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
-+            }
-+            if (++loadedChunks[0] == requiredChunks) {
-+                try {
-+                    onLoad.accept(java.util.Collections.unmodifiableList(ret));
-+                } finally {
-+                    for (int i = 0, len = ret.size(); i < len; ++i) {
-+                        ChunkPos chunkPos = ret.get(i).getPos();
-+                        int ticketLevel = ticketLevels.getInt(i);
-+
-+                        chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
-+                        chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier);
-+                    }
-+                }
-+            }
-+        };
-+
-+        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
-+            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
-+                ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
-+                    this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
-+                );
-+            }
-+        }
-+    }
-+    // Paper end
-+
-+    // Paper start - optimise getPlayerByUUID
-+    @Nullable
-+    @Override
-+    public Player getPlayerByUUID(UUID uuid) {
-+        final Player player = this.getServer().getPlayerList().getPlayer(uuid);
-+        return player != null && player.level() == this ? player : null;
-+    }
-+    // Paper end - optimise getPlayerByUUID
-+
-+    // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
-+    public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
-+        super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs
-+        this.pvpMode = minecraftserver.isPvpAllowed();
-+        this.convertable = convertable_conversionsession;
-+        this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
-+        // CraftBukkit end
-+        this.tickTime = flag1;
-+        this.server = minecraftserver;
-+        this.customSpawners = list;
-+        this.serverLevelData = iworlddataserver;
-+        ChunkGenerator chunkgenerator = worlddimension.generator();
-+        // CraftBukkit start
-+        this.serverLevelData.setWorld(this);
-+
-+        if (biomeProvider != null) {
-+            BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider
-+            if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
-+                chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
-+            } else if (chunkgenerator instanceof FlatLevelSource cpf) {
-+                chunkgenerator = new FlatLevelSource(cpf.settings(), worldChunkManager);
-+            }
-+        }
-+
-+        if (gen != null) {
-+            chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen);
-+        }
-+        // CraftBukkit end
-+        boolean flag2 = minecraftserver.forceSynchronousWrites();
-+        DataFixer datafixer = minecraftserver.getFixerUpper();
-+        EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver);
-+
-         this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage);
--        StructureTemplateManager structuretemplatemanager = server.getStructureManager();
--        int j = server.getPlayerList().getViewDistance();
--        int k = server.getPlayerList().getSimulationDistance();
-+        StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
-+        int j = this.spigotConfig.viewDistance; // Spigot
-+        int k = this.spigotConfig.simulationDistance; // Spigot
-         PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
- 
-         Objects.requireNonNull(this.entityManager);
--        this.chunkSource = new ServerChunkCache(this, session, datafixer, structuretemplatemanager, workerExecutor, chunkgenerator, j, k, flag2, worldGenerationProgressListener, persistententitysectionmanager::updateChunkStatus, () -> {
--            return server.overworld().getDataStorage();
-+        this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> {
-+            return minecraftserver.overworld().getDataStorage();
-         });
-         this.chunkSource.getGeneratorState().ensureStructuresGenerated();
-         this.portalForcer = new PortalForcer(this);
-         this.updateSkyBrightness();
-         this.prepareWeather();
--        this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize());
-+        this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize());
-         this.raids = (Raids) this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration()));
--        if (!server.isSingleplayer()) {
--            properties.setGameType(server.getDefaultGameType());
-+        if (!minecraftserver.isSingleplayer()) {
-+            iworlddataserver.setGameType(minecraftserver.getDefaultGameType());
-         }
- 
--        long l = server.getWorldData().worldGenOptions().seed();
-+        long l = minecraftserver.getWorldData().worldGenOptions().seed();
- 
--        this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), server.getStructureManager(), worldKey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer);
--        this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck);
--        if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
--            this.dragonFight = new EndDragonFight(this, l, server.getWorldData().endDragonFightData());
-+        this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), this.getTypeKey(), chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); // Paper - Fix missing CB diff
-+        this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), this.structureCheck); // CraftBukkit
-+        if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END
-+            this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit
-         } else {
-             this.dragonFight = null;
-         }
- 
-         this.sleepStatus = new SleepStatus();
-         this.gameEventDispatcher = new GameEventDispatcher(this);
--        this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomSequencesState, () -> {
-+        this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
-             return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
-         });
-+        this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
-     }
- 
-+    // Paper start
-+    @Override
-+    public boolean hasChunk(int chunkX, int chunkZ) {
-+        return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null;
-+    }
-+    // Paper end
-+
-     /** @deprecated */
-     @Deprecated
-     @VisibleForTesting
-@@ -273,8 +435,8 @@
-         this.serverLevelData.setClearWeatherTime(clearDuration);
-         this.serverLevelData.setRainTime(rainDuration);
-         this.serverLevelData.setThunderTime(rainDuration);
--        this.serverLevelData.setRaining(raining);
--        this.serverLevelData.setThundering(thundering);
-+        this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
-+        this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
-     }
- 
-     @Override
-@@ -305,12 +467,20 @@
-         long j;
- 
-         if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
-+            // CraftBukkit start
-+            j = this.levelData.getDayTime() + 24000L;
-+            TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime());
-             if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
--                j = this.levelData.getDayTime() + 24000L;
--                this.setDayTime(j - j % 24000L);
-+                this.getCraftServer().getPluginManager().callEvent(event);
-+                if (!event.isCancelled()) {
-+                    this.setDayTime(this.getDayTime() + event.getSkipAmount());
-+                }
-             }
- 
--            this.wakeUpAllPlayers();
-+            if (!event.isCancelled()) {
-+                this.wakeUpAllPlayers();
-+            }
-+            // CraftBukkit end
-             if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
-                 this.resetWeatherCycle();
-             }
-@@ -325,9 +495,9 @@
-         if (!this.isDebug() && flag) {
-             j = this.getGameTime();
-             gameprofilerfiller.push("blockTicks");
--            this.blockTicks.tick(j, 65536, this::tickBlock);
-+            this.blockTicks.tick(j, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks
-             gameprofilerfiller.popPush("fluidTicks");
--            this.fluidTicks.tick(j, 65536, this::tickFluid);
-+            this.fluidTicks.tick(j, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks
-             gameprofilerfiller.pop();
-         }
- 
-@@ -345,7 +515,7 @@
- 
-         this.handlingTick = false;
-         gameprofilerfiller.pop();
--        boolean flag1 = !this.players.isEmpty() || !this.getForcedChunks().isEmpty();
-+        boolean flag1 = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this
- 
-         if (flag1) {
-             this.resetEmptyTime();
-@@ -359,6 +529,7 @@
-                 gameprofilerfiller.pop();
-             }
- 
-+            org.spigotmc.ActivationRange.activateEntities(this); // Spigot
-             this.entityTickList.forEach((entity) -> {
-                 if (!entity.isRemoved()) {
-                     if (!tickratemanager.isEntityFrozen(entity)) {
-@@ -429,7 +600,7 @@
- 
-     private void wakeUpAllPlayers() {
-         this.sleepStatus.removeAllSleepers();
--        ((List) this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> {
-+        (this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { // CraftBukkit - decompile error
-             entityplayer.stopSleepInBed(false, false);
-         });
-     }
-@@ -442,12 +613,12 @@
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("thunder");
--        if (flag && this.isThundering() && this.random.nextInt(100000) == 0) {
-+        if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
-             BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
- 
-             if (this.isRainingAt(blockposition)) {
-                 DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
--                boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD);
-+                boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses
- 
-                 if (flag1) {
-                     SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
-@@ -456,7 +627,7 @@
-                         entityhorseskeleton.setTrap(true);
-                         entityhorseskeleton.setAge(0);
-                         entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
--                        this.addFreshEntity(entityhorseskeleton);
-+                        this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
-                     }
-                 }
- 
-@@ -465,18 +636,20 @@
-                 if (entitylightning != null) {
-                     entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
-                     entitylightning.setVisualOnly(flag1);
--                    this.addFreshEntity(entitylightning);
-+                    this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit
-                 }
-             }
-         }
- 
-         gameprofilerfiller.popPush("iceandsnow");
- 
-+        if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
-         for (int l = 0; l < randomTickSpeed; ++l) {
-             if (this.random.nextInt(48) == 0) {
-                 this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15));
-             }
-         }
-+        } // Paper - Option to disable ice and snow
- 
-         gameprofilerfiller.popPush("tickBlocks");
-         if (randomTickSpeed > 0) {
-@@ -521,7 +694,7 @@
-         Biome biomebase = (Biome) this.getBiome(blockposition1).value();
- 
-         if (biomebase.shouldFreeze(this, blockposition2)) {
--            this.setBlockAndUpdate(blockposition2, Blocks.ICE.defaultBlockState());
-+            org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
-         }
- 
-         if (this.isRaining()) {
-@@ -537,10 +710,10 @@
-                         BlockState iblockdata1 = (BlockState) iblockdata.setValue(SnowLayerBlock.LAYERS, j + 1);
- 
-                         Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1);
--                        this.setBlockAndUpdate(blockposition1, iblockdata1);
-+                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit
-                     }
-                 } else {
--                    this.setBlockAndUpdate(blockposition1, Blocks.SNOW.defaultBlockState());
-+                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
-                 }
-             }
- 
-@@ -568,6 +741,11 @@
-     }
- 
-     protected BlockPos findLightningTargetAround(BlockPos pos) {
-+        // Paper start - Add methods to find targets for lightning strikes
-+        return this.findLightningTargetAround(pos, false);
-+    }
-+    public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) {
-+        // Paper end - Add methods to find targets for lightning strikes
-         BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
-         Optional<BlockPos> optional = this.findLightningRod(blockposition1);
- 
-@@ -576,12 +754,13 @@
-         } else {
-             AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, blockposition1.atY(this.getMaxY() + 1)).inflate(3.0D);
-             List<LivingEntity> list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> {
--                return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition());
-+                return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422)
-             });
- 
-             if (!list.isEmpty()) {
-                 return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition();
-             } else {
-+                if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes
-                 if (blockposition1.getY() == this.getMinY() - 1) {
-                     blockposition1 = blockposition1.above(2);
-                 }
-@@ -679,8 +858,8 @@
-                 this.serverLevelData.setThunderTime(j);
-                 this.serverLevelData.setRainTime(k);
-                 this.serverLevelData.setClearWeatherTime(i);
--                this.serverLevelData.setThundering(flag1);
--                this.serverLevelData.setRaining(flag2);
-+                this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
-+                this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
-             }
- 
-             this.oThunderLevel = this.thunderLevel;
-@@ -701,33 +880,67 @@
-             this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
-         }
- 
-+        /* CraftBukkit start
-         if (this.oRainLevel != this.rainLevel) {
--            this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension());
-+            this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension());
-         }
- 
-         if (this.oThunderLevel != this.thunderLevel) {
--            this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension());
-+            this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension());
-         }
- 
-         if (flag != this.isRaining()) {
-             if (flag) {
--                this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0.0F));
--            } else {
--                this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
-+                this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.STOP_RAINING, 0.0F));
-+            } else {
-+                this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F));
-             }
- 
--            this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel));
--            this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
-+            this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel));
-+            this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel));
-         }
-+        // */
-+        for (int idx = 0; idx < this.players.size(); ++idx) {
-+            if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                ((ServerPlayer) this.players.get(idx)).tickWeather();
-+            }
-+        }
- 
-+        if (flag != this.isRaining()) {
-+            // Only send weather packets to those affected
-+            for (int idx = 0; idx < this.players.size(); ++idx) {
-+                if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                    ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false);
-+                }
-+            }
-+        }
-+        for (int idx = 0; idx < this.players.size(); ++idx) {
-+            if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
-+            }
-+        }
-+        // CraftBukkit end
-+
-     }
- 
-     @VisibleForTesting
-     public void resetWeatherCycle() {
--        this.serverLevelData.setRainTime(0);
--        this.serverLevelData.setRaining(false);
--        this.serverLevelData.setThunderTime(0);
--        this.serverLevelData.setThundering(false);
-+        // CraftBukkit start
-+        this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
-+        // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
-+        // Not that everyone ever manages to get the whole server to sleep at the same time....
-+        if (!this.serverLevelData.isRaining()) {
-+            this.serverLevelData.setRainTime(0);
-+        }
-+        // CraftBukkit end
-+        this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
-+        // CraftBukkit start
-+        // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
-+        // Not that everyone ever manages to get the whole server to sleep at the same time....
-+        if (!this.serverLevelData.isThundering()) {
-+            this.serverLevelData.setThunderTime(0);
-+        }
-+        // CraftBukkit end
-     }
- 
-     public void resetEmptyTime() {
-@@ -754,6 +967,13 @@
-     }
- 
-     public void tickNonPassenger(Entity entity) {
-+        // Spigot start
-+        if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
-+            entity.tickCount++;
-+            entity.inactiveTick();
-+            return;
-+        }
-+        // Spigot end
-         entity.setOldPosAndRot();
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-@@ -763,6 +983,7 @@
-         });
-         gameprofilerfiller.incrementCounter("tickNonPassenger");
-         entity.tick();
-+        entity.postTick(); // CraftBukkit
-         gameprofilerfiller.pop();
-         Iterator iterator = entity.getPassengers().iterator();
- 
-@@ -786,6 +1007,7 @@
-                 });
-                 gameprofilerfiller.incrementCounter("tickPassenger");
-                 passenger.rideTick();
-+                passenger.postTick(); // CraftBukkit
-                 gameprofilerfiller.pop();
-                 Iterator iterator = passenger.getPassengers().iterator();
- 
-@@ -810,6 +1032,7 @@
-         ServerChunkCache chunkproviderserver = this.getChunkSource();
- 
-         if (!savingDisabled) {
-+            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit
-             if (progressListener != null) {
-                 progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
-             }
-@@ -827,11 +1050,19 @@
-             }
- 
-         }
-+
-+        // CraftBukkit start - moved from MinecraftServer.saveChunks
-+        ServerLevel worldserver1 = this;
-+
-+        this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
-+        this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
-+        this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
-+        // CraftBukkit end
-     }
- 
-     private void saveLevelData(boolean flush) {
-         if (this.dragonFight != null) {
--            this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData());
-+            this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
-         }
- 
-         DimensionDataStorage worldpersistentdata = this.getChunkSource().getDataStorage();
-@@ -903,18 +1134,40 @@
- 
-     @Override
-     public boolean addFreshEntity(Entity entity) {
--        return this.addEntity(entity);
-+        // CraftBukkit start
-+        return this.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
-     }
- 
-+    @Override
-+    public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
-+        return this.addEntity(entity, reason);
-+        // CraftBukkit end
-+    }
-+
-     public boolean addWithUUID(Entity entity) {
--        return this.addEntity(entity);
-+        // CraftBukkit start
-+        return this.addWithUUID(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
-+
-+    public boolean addWithUUID(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
-+        return this.addEntity(entity, reason);
-+        // CraftBukkit end
-     }
- 
-     public void addDuringTeleport(Entity entity) {
-+        // CraftBukkit start
-+        // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds,
-+        // since it is only an implementation detail, that a new entity is created when
-+        // they are traveling between worlds.
-+        this.addDuringTeleport(entity, null);
-+    }
-+
-+    public void addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
-+        // CraftBukkit end
-         if (entity instanceof ServerPlayer entityplayer) {
-             this.addPlayer(entityplayer);
-         } else {
--            this.addEntity(entity);
-+            this.addEntity(entity, reason); // CraftBukkit
-         }
- 
-     }
-@@ -939,41 +1192,116 @@
-         this.entityManager.addNewEntity(player);
-     }
- 
--    private boolean addEntity(Entity entity) {
-+    // CraftBukkit start
-+    private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
-+        org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
-+        entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process
-+        // Paper start - extra debug info
-+        if (entity.valid) {
-+            MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable());
-+            return true;
-+        }
-+        // Paper end - extra debug info
-+        if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason
-         if (entity.isRemoved()) {
--            ServerLevel.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType()));
-+            // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit
-             return false;
-         } else {
-+            if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added
-+            // Paper start - capture all item additions to the world
-+            if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
-+                captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity);
-+                return true;
-+            }
-+            // Paper end - capture all item additions to the world
-+            // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world.
-+            if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
-+                return false;
-+            }
-+            // CraftBukkit end
-+
-             return this.entityManager.addNewEntity(entity);
-         }
-     }
- 
-     public boolean tryAddFreshEntityWithPassengers(Entity entity) {
--        Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID);
-+        // CraftBukkit start
-+        return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
-+
-+    public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
-+        // CraftBukkit end
-+        Stream<UUID> stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error
-         PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
- 
-         Objects.requireNonNull(this.entityManager);
-         if (stream.anyMatch(persistententitysectionmanager::isLoaded)) {
-             return false;
-         } else {
--            this.addFreshEntityWithPassengers(entity);
-+            this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
-             return true;
-         }
-     }
- 
-     public void unload(LevelChunk chunk) {
-+        // Spigot Start
-+        for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) {
-+            if (tileentity instanceof net.minecraft.world.Container) {
-+                // Paper start - this area looks like it can load chunks, change the behavior
-+                // chests for example can apply physics to the world
-+                // so instead we just change the active container and call the event
-+                for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) {
-+                    ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
-+                }
-+                // Paper end - this area looks like it can load chunks, change the behavior
-+            }
-+        }
-+        // Spigot End
-         chunk.clearAllBlockEntities();
-         chunk.unregisterTickContainerFromLevel(this);
-     }
- 
-     public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) {
--        player.remove(reason);
-+        player.remove(reason, null); // CraftBukkit - add Bukkit remove cause
-     }
- 
-+    // CraftBukkit start
-+    public boolean strikeLightning(Entity entitylightning) {
-+        return this.strikeLightning(entitylightning, LightningStrikeEvent.Cause.UNKNOWN);
-+    }
-+
-+    public boolean strikeLightning(Entity entitylightning, LightningStrikeEvent.Cause cause) {
-+        LightningStrikeEvent lightning = CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause);
-+
-+        if (lightning.isCancelled()) {
-+            return false;
-+        }
-+
-+        return this.addFreshEntity(entitylightning);
-+    }
-+    // CraftBukkit end
-+
-     @Override
-     public void destroyBlockProgress(int entityId, BlockPos pos, int progress) {
-         Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
- 
-+        // CraftBukkit start
-+        Player entityhuman = null;
-+        Entity entity = this.getEntity(entityId);
-+        if (entity instanceof Player) entityhuman = (Player) entity;
-+        // CraftBukkit end
-+
-+        // Paper start - Add BlockBreakProgressUpdateEvent
-+        // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server.
-+        // Hence, do not call the event.
-+        if (entity != null) {
-+            float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f;
-+            org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos);
-+            new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity())
-+                .callEvent();
-+        }
-+        // Paper end - Add BlockBreakProgressUpdateEvent
-+
-         while (iterator.hasNext()) {
-             ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- 
-@@ -982,6 +1310,12 @@
-                 double d1 = (double) pos.getY() - entityplayer.getY();
-                 double d2 = (double) pos.getZ() - entityplayer.getZ();
- 
-+                // CraftBukkit start
-+                if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
-+                    continue;
-+                }
-+                // CraftBukkit end
-+
-                 if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
-                     entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress));
-                 }
-@@ -1030,7 +1364,7 @@
- 
-     @Override
-     public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) {
--        this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false));
-+        this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false)); // Paper - diff on change (the 64.0 distance is used as defaults for sound ranges in spigot config for ender dragon, end portal and wither)
-     }
- 
-     public int getLogicalHeight() {
-@@ -1039,6 +1373,11 @@
- 
-     @Override
-     public void gameEvent(Holder<GameEvent> event, Vec3 emitterPos, GameEvent.Context emitter) {
-+        // Paper start - Prevent GameEvents being fired from unloaded chunks
-+        if (this.getChunkIfLoadedImmediately((Mth.floor(emitterPos.x) >> 4), (Mth.floor(emitterPos.z) >> 4)) == null) {
-+            return;
-+        }
-+        // Paper end - Prevent GameEvents being fired from unloaded chunks
-         this.gameEventDispatcher.post(event, emitterPos, emitter);
-     }
- 
-@@ -1052,6 +1391,7 @@
- 
-         this.getChunkSource().blockChanged(pos);
-         this.pathTypesByPosCache.invalidate(pos);
-+        if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
-         VoxelShape voxelshape = oldState.getCollisionShape(this, pos);
-         VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
- 
-@@ -1060,7 +1400,18 @@
-             Iterator iterator = this.navigatingMobs.iterator();
- 
-             while (iterator.hasNext()) {
--                Mob entityinsentient = (Mob) iterator.next();
-+                // CraftBukkit start - fix SPIGOT-6362
-+                Mob entityinsentient;
-+                try {
-+                    entityinsentient = (Mob) iterator.next();
-+                } catch (java.util.ConcurrentModificationException ex) {
-+                    // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
-+                    // In this case we just run the update again across all the iterators as the chunk will then be loaded
-+                    // As this is a relative edge case it is much faster than copying navigators (on either read or write)
-+                    this.sendBlockUpdated(pos, oldState, newState, flags);
-+                    return;
-+                }
-+                // CraftBukkit end
-                 PathNavigation navigationabstract = entityinsentient.getNavigation();
- 
-                 if (navigationabstract.shouldRecomputePath(pos)) {
-@@ -1082,15 +1433,18 @@
-             }
- 
-         }
-+        } // Paper - option to disable pathfinding updates
-     }
- 
-     @Override
-     public void updateNeighborsAt(BlockPos pos, Block block) {
-+        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
-         this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, (Direction) null, (Direction) null));
-     }
- 
-     @Override
-     public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) {
-+        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
-         this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null, orientation);
-     }
- 
-@@ -1126,9 +1480,20 @@
- 
-     @Override
-     public void explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions smallParticle, ParticleOptions largeParticle, Holder<SoundEvent> soundEvent) {
-+        // CraftBukkit start
-+        this.explode0(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, smallParticle, largeParticle, soundEvent);
-+    }
-+
-+    public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder<SoundEvent> holder) {
-+    // Paper start - Allow explosions to damage source
-+        return this.explode0(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, particleparam, particleparam1, holder, null);
-+    }
-+    public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder<SoundEvent> holder, java.util.function.Consumer<ServerExplosion> configurator) {
-+    // Paper end - Allow explosions to damage source
-+        // CraftBukkit end
-         Explosion.BlockInteraction explosion_effect;
- 
--        switch (explosionSourceType) {
-+        switch (world_a) {
-             case NONE:
-                 explosion_effect = Explosion.BlockInteraction.KEEP;
-                 break;
-@@ -1144,16 +1509,27 @@
-             case TRIGGER:
-                 explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK;
-                 break;
-+            // CraftBukkit start - handle custom explosion type
-+            case STANDARD:
-+                explosion_effect = Explosion.BlockInteraction.DESTROY;
-+                break;
-+            // CraftBukkit end
-             default:
-                 throw new MatchException((String) null, (Throwable) null);
-         }
- 
-         Explosion.BlockInteraction explosion_effect1 = explosion_effect;
--        Vec3 vec3d = new Vec3(x, y, z);
--        ServerExplosion serverexplosion = new ServerExplosion(this, entity, damageSource, behavior, vec3d, power, createFire, explosion_effect1);
-+        Vec3 vec3d = new Vec3(d0, d1, d2);
-+        ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1);
-+        if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source
- 
-         serverexplosion.explode();
--        ParticleOptions particleparam2 = serverexplosion.isSmall() ? smallParticle : largeParticle;
-+        // CraftBukkit start
-+        if (serverexplosion.wasCanceled) {
-+            return serverexplosion;
-+        }
-+        // CraftBukkit end
-+        ParticleOptions particleparam2 = serverexplosion.isSmall() ? particleparam : particleparam1;
-         Iterator iterator = this.players.iterator();
- 
-         while (iterator.hasNext()) {
-@@ -1162,10 +1538,11 @@
-             if (entityplayer.distanceToSqr(vec3d) < 4096.0D) {
-                 Optional<Vec3> optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer));
- 
--                entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, soundEvent));
-+                entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, holder));
-             }
-         }
- 
-+        return serverexplosion; // CraftBukkit
-     }
- 
-     private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayRule) {
-@@ -1226,17 +1603,29 @@
-     }
- 
-     public <T extends ParticleOptions> int sendParticles(T parameters, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) {
--        return this.sendParticles(parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed);
-+        return this.sendParticlesSource(null, parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support
-     }
- 
-     public <T extends ParticleOptions> int sendParticles(T parameters, boolean force, boolean important, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) {
--        ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(parameters, force, important, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) speed, count);
--        int j = 0;
-+        return this.sendParticlesSource(null, parameters, force, important, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support
-+    }
- 
--        for (int k = 0; k < this.players.size(); ++k) {
--            ServerPlayer entityplayer = (ServerPlayer) this.players.get(k);
-+    // CraftBukkit start - visibility api support
-+    public <T extends ParticleOptions> int sendParticlesSource(ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) {
-+        // Paper start - Particle API
-+        return this.sendParticlesSource(this.players, sender, t0, flag, flag1, d0, d1, d2, i, d3, d4, d5, d6);
-+    }
-+    public <T extends ParticleOptions> int sendParticlesSource(List<ServerPlayer> receivers, @Nullable ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) {
-+        // Paper end - Particle API
-+        // CraftBukkit end
-+        ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, flag, flag1, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i);
-+        int j = 0;
- 
--            if (this.sendParticles(entityplayer, force, x, y, z, packetplayoutworldparticles)) {
-+        for (Player entityhuman : receivers) { // Paper - Particle API
-+            ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API
-+            if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
-+
-+            if (this.sendParticles(entityplayer, flag, d0, d1, d2, packetplayoutworldparticles)) {
-                 ++j;
-             }
-         }
-@@ -1292,7 +1681,7 @@
- 
-     @Nullable
-     public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) {
--        if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
-+        if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
-             return null;
-         } else {
-             Optional<HolderSet.Named<Structure>> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag);
-@@ -1334,11 +1723,38 @@
-     @Nullable
-     @Override
-     public MapItemSavedData getMapData(MapId id) {
--        return (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key());
-+        // Paper start - Call missing map initialize event and set id
-+        final DimensionDataStorage storage = this.getServer().overworld().getDataStorage();
-+
-+        final Optional<net.minecraft.world.level.saveddata.SavedData> cacheEntry = storage.cache.get(id.key());
-+        if (cacheEntry == null) { // Cache did not contain, try to load and may init
-+            final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), id.key()); // get populates the cache
-+            if (worldmap != null) { // map was read, init it and return
-+                worldmap.id = id;
-+                new MapInitializeEvent(worldmap.mapView).callEvent();
-+                return worldmap;
-+            }
-+
-+            return null; // Map does not exist, reading failed.
-+        }
-+
-+        // Cache entry exists, update it with the id ref and return.
-+        if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) {
-+            mapItemSavedData.id = id;
-+            return mapItemSavedData;
-+        }
-+
-+        return null;
-+        // Paper end - Call missing map initialize event and set id
-     }
- 
-     @Override
-     public void setMapData(MapId id, MapItemSavedData state) {
-+        // CraftBukkit start
-+        state.id = id;
-+        MapInitializeEvent event = new MapInitializeEvent(state.mapView);
-+        Bukkit.getServer().getPluginManager().callEvent(event);
-+        // CraftBukkit end
-         this.getServer().overworld().getDataStorage().set(id.key(), state);
-     }
- 
-@@ -1352,18 +1768,28 @@
-         float f1 = this.levelData.getSpawnAngle();
- 
-         if (!blockposition1.equals(pos) || f1 != angle) {
-+            org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent
-             this.levelData.setSpawn(pos, angle);
-+            new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent
-             this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle));
-         }
- 
-         if (this.lastSpawnChunkRadius > 1) {
--            this.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(blockposition1), this.lastSpawnChunkRadius, Unit.INSTANCE);
-+            // Paper start - allow disabling gamerule limits
-+            for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(blockposition1, this.lastSpawnChunkRadius - 2)) {
-+                this.getChunkSource().removeTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE);
-+            }
-+            // Paper end - allow disabling gamerule limits
-         }
- 
-         int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1;
- 
-         if (i > 1) {
--            this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), i, Unit.INSTANCE);
-+            // Paper start - allow disabling gamerule limits
-+            for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(pos, i - 2)) {
-+                this.getChunkSource().addTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE);
-+            }
-+            // Paper end - allow disabling gamerule limits
-         }
- 
-         this.lastSpawnChunkRadius = i;
-@@ -1419,6 +1845,11 @@
-             });
-             optional1.ifPresent((holder) -> {
-                 this.getServer().execute(() -> {
-+                    // Paper start - Remove stale POIs
-+                    if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) {
-+                        this.getPoiManager().remove(blockposition1);
-+                    }
-+                    // Paper end - Remove stale POIs
-                     this.getPoiManager().add(blockposition1, holder);
-                     DebugPackets.sendPoiAddedPacket(this, blockposition1);
-                 });
-@@ -1649,6 +2080,11 @@
-     @Override
-     public void blockUpdated(BlockPos pos, Block block) {
-         if (!this.isDebug()) {
-+            // CraftBukkit start
-+            if (this.populating) {
-+                return;
-+            }
-+            // CraftBukkit end
-             this.updateNeighborsAt(pos, block);
-         }
- 
-@@ -1668,12 +2104,12 @@
-     }
- 
-     public boolean isFlat() {
--        return this.server.getWorldData().isFlatWorld();
-+        return this.serverLevelData.isFlatWorld(); // CraftBukkit
-     }
- 
-     @Override
-     public long getSeed() {
--        return this.server.getWorldData().worldGenOptions().seed();
-+        return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit
-     }
- 
-     @Nullable
-@@ -1696,7 +2132,7 @@
-     private static <T> String getTypeCount(Iterable<T> items, Function<T, String> classifier) {
-         try {
-             Object2IntOpenHashMap<String> object2intopenhashmap = new Object2IntOpenHashMap();
--            Iterator iterator = items.iterator();
-+            Iterator<T> iterator = items.iterator(); // CraftBukkit - decompile error
- 
-             while (iterator.hasNext()) {
-                 T t0 = iterator.next();
-@@ -1705,7 +2141,7 @@
-                 object2intopenhashmap.addTo(s, 1);
-             }
- 
--            return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry::getIntValue).reversed()).limit(5L).map((entry) -> {
-+            return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry<String>::getIntValue).reversed()).limit(5L).map((entry) -> { // CraftBukkit - decompile error
-                 String s1 = (String) entry.getKey();
- 
-                 return s1 + ":" + entry.getIntValue();
-@@ -1717,6 +2153,7 @@
- 
-     @Override
-     public LevelEntityGetter<Entity> getEntities() {
-+        org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
-         return this.entityManager.getEntityGetter();
-     }
- 
-@@ -1800,7 +2237,28 @@
- 
-     public GameRules getGameRules() {
-         return this.serverLevelData.getGameRules();
-+    }
-+
-+    // Paper start - respect global sound events gamerule
-+    public List<net.minecraft.server.level.ServerPlayer> getPlayersForGlobalSoundGamerule() {
-+        return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players();
-+    }
-+
-+    public double getGlobalSoundRangeSquared(java.util.function.Function<org.spigotmc.SpigotWorldConfig, Integer> rangeFunction) {
-+        final double range = rangeFunction.apply(this.spigotConfig);
-+        return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent
-+    }
-+    // Paper end - respect global sound events gamerule
-+    // Paper start - notify observers even if grow failed
-+    public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) {
-+        // notify observers if the block state is the same and the Y level equals the original y level (for mega trees)
-+        // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the
-+        // tree grew or not
-+        if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) {
-+            this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512);
-+        }
-     }
-+    // Paper end - notify observers even if grow failed
- 
-     @Override
-     public CrashReportCategory fillReportDetails(CrashReport report) {
-@@ -1828,22 +2286,30 @@
-         }
- 
-         public void onTickingStart(Entity entity) {
-+            if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking
-             ServerLevel.this.entityTickList.add(entity);
-         }
- 
-         public void onTickingEnd(Entity entity) {
-             ServerLevel.this.entityTickList.remove(entity);
-+            // Paper start - Reset pearls when they stop being ticked
-+            if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) {
-+                pearl.cachedOwner = null;
-+                pearl.ownerUUID = null;
-+            }
-+            // Paper end - Reset pearls when they stop being ticked
-         }
- 
-         public void onTrackingStart(Entity entity) {
--            ServerLevel.this.getChunkSource().addEntity(entity);
-+            org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
-+            // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
-             if (entity instanceof ServerPlayer entityplayer) {
-                 ServerLevel.this.players.add(entityplayer);
-                 ServerLevel.this.updateSleepingPlayerList();
-             }
- 
-             if (entity instanceof Mob entityinsentient) {
--                if (ServerLevel.this.isUpdatingNavigations) {
-+                if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning
-                     String s = "onTrackingStart called during navigation iteration";
- 
-                     Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration"));
-@@ -1864,9 +2330,58 @@
-             }
- 
-             entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
-+            entity.inWorld = true; // CraftBukkit - Mark entity as in world
-+            entity.valid = true; // CraftBukkit
-+            ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server
-+            // Paper start - Entity origin API
-+            if (entity.getOriginVector() == null) {
-+                entity.setOrigin(entity.getBukkitEntity().getLocation());
-+            }
-+            // Default to current world if unknown, gross assumption but entities rarely change world
-+            if (entity.getOriginWorld() == null) {
-+                entity.setOrigin(entity.getOriginVector().toLocation(getWorld()));
-+            }
-+            // Paper end - Entity origin API
-+            new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid
-         }
- 
-         public void onTrackingEnd(Entity entity) {
-+            org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
-+            // Spigot start
-+            if ( entity instanceof Player )
-+            {
-+                com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) ->
-+                {
-+                    for (Object o : worldData.cache.values() )
-+                    {
-+                        if ( o instanceof MapItemSavedData )
-+                        {
-+                            MapItemSavedData map = (MapItemSavedData) o;
-+                            map.carriedByPlayers.remove( (Player) entity );
-+                            for ( Iterator<MapItemSavedData.HoldingPlayer> iter = (Iterator<MapItemSavedData.HoldingPlayer>) map.carriedBy.iterator(); iter.hasNext(); )
-+                            {
-+                                if ( iter.next().player == entity )
-+                                {
-+                                    iter.remove();
-+                                }
-+                            }
-+                        }
-+                    }
-+                } );
-+            }
-+            // Spigot end
-+            // Spigot Start
-+            if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
-+                // Paper start - Fix merchant inventory not closing on entity removal
-+                if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
-+                    merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
-+                }
-+                // Paper end - Fix merchant inventory not closing on entity removal
-+                for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) {
-+                    h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
-+                }
-+            }
-+            // Spigot End
-             ServerLevel.this.getChunkSource().removeEntity(entity);
-             if (entity instanceof ServerPlayer entityplayer) {
-                 ServerLevel.this.players.remove(entityplayer);
-@@ -1874,7 +2389,7 @@
-             }
- 
-             if (entity instanceof Mob entityinsentient) {
--                if (ServerLevel.this.isUpdatingNavigations) {
-+                if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning
-                     String s = "onTrackingStart called during navigation iteration";
- 
-                     Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration"));
-@@ -1895,10 +2410,27 @@
-             }
- 
-             entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
-+            // CraftBukkit start
-+            entity.valid = false;
-+            if (!(entity instanceof ServerPlayer)) {
-+                for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players
-+                    player.getBukkitEntity().onEntityRemove(entity);
-+                }
-+            }
-+            // CraftBukkit end
-+            new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid
-         }
- 
-         public void onSectionChange(Entity entity) {
-             entity.updateDynamicGameEventListener(DynamicGameEventListener::move);
-         }
-     }
-+
-+    // Paper start - check global player list where appropriate
-+    @Override
-+    @Nullable
-+    public Player getGlobalPlayerByUUID(UUID uuid) {
-+        return this.server.getPlayerList().getPlayer(uuid);
-+    }
-+    // Paper end - check global player list where appropriate
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch
deleted file mode 100644
index 9daf2af366..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch
+++ /dev/null
@@ -1,1891 +0,0 @@
---- a/net/minecraft/server/level/ServerPlayer.java
-+++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -103,10 +103,6 @@
- import net.minecraft.util.Unit;
- import net.minecraft.util.profiling.Profiler;
- import net.minecraft.util.profiling.ProfilerFiller;
--import net.minecraft.world.Container;
--import net.minecraft.world.Difficulty;
--import net.minecraft.world.InteractionHand;
--import net.minecraft.world.MenuProvider;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.damagesource.DamageTypes;
- import net.minecraft.world.effect.MobEffectInstance;
-@@ -135,15 +131,16 @@
- import net.minecraft.world.entity.player.ChatVisiblity;
- import net.minecraft.world.entity.player.Input;
- import net.minecraft.world.entity.player.Inventory;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.projectile.AbstractArrow;
- import net.minecraft.world.entity.projectile.ThrownEnderpearl;
- import net.minecraft.world.entity.vehicle.AbstractBoat;
- import net.minecraft.world.entity.vehicle.AbstractMinecart;
-+import net.minecraft.world.food.FoodData;
- import net.minecraft.world.inventory.AbstractContainerMenu;
- import net.minecraft.world.inventory.ContainerListener;
- import net.minecraft.world.inventory.ContainerSynchronizer;
- import net.minecraft.world.inventory.HorseInventoryMenu;
-+import net.minecraft.world.inventory.InventoryMenu;
- import net.minecraft.world.inventory.ResultSlot;
- import net.minecraft.world.inventory.Slot;
- import net.minecraft.world.item.Item;
-@@ -154,8 +151,6 @@
- import net.minecraft.world.item.WrittenBookItem;
- import net.minecraft.world.item.crafting.Recipe;
- import net.minecraft.world.item.crafting.RecipeHolder;
--import net.minecraft.world.item.enchantment.EnchantmentHelper;
--import net.minecraft.world.item.trading.MerchantOffers;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.GameType;
-@@ -163,12 +158,14 @@
- import net.minecraft.world.level.biome.BiomeManager;
- import net.minecraft.world.level.block.BedBlock;
- import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.ChestBlock;
- import net.minecraft.world.level.block.HorizontalDirectionalBlock;
- import net.minecraft.world.level.block.RespawnAnchorBlock;
- import net.minecraft.world.level.block.entity.BlockEntity;
- import net.minecraft.world.level.block.entity.CommandBlockEntity;
- import net.minecraft.world.level.block.entity.SignBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.level.saveddata.maps.MapId;
-@@ -179,11 +176,48 @@
- import net.minecraft.world.scores.PlayerTeam;
- import net.minecraft.world.scores.ScoreAccess;
- import net.minecraft.world.scores.ScoreHolder;
-+import org.slf4j.Logger;
-+import net.minecraft.world.Container;
-+import net.minecraft.world.Difficulty;
-+import net.minecraft.world.InteractionHand;
-+import net.minecraft.world.MenuProvider;
-+// CraftBukkit start
-+import net.minecraft.world.damagesource.CombatTracker;
-+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
-+import net.minecraft.world.item.enchantment.EnchantmentHelper;
-+import net.minecraft.world.item.trading.MerchantOffers;
-+import net.minecraft.world.scores.Scoreboard;
- import net.minecraft.world.scores.Team;
- import net.minecraft.world.scores.criteria.ObjectiveCriteria;
--import org.slf4j.Logger;
-+import io.papermc.paper.adventure.PaperAdventure; // Paper
-+import org.bukkit.Bukkit;
-+import org.bukkit.Location;
-+import org.bukkit.WeatherType;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.CraftWorldBorder;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.event.CraftPortalEvent;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.craftbukkit.util.CraftDimensionUtil;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.entity.EntityExhaustionEvent;
-+import org.bukkit.event.player.PlayerBedLeaveEvent;
-+import org.bukkit.event.player.PlayerChangedMainHandEvent;
-+import org.bukkit.event.player.PlayerChangedWorldEvent;
-+import org.bukkit.event.player.PlayerDropItemEvent;
-+import org.bukkit.event.player.PlayerLocaleChangeEvent;
-+import org.bukkit.event.player.PlayerPortalEvent;
-+import org.bukkit.event.player.PlayerRespawnEvent;
-+import org.bukkit.event.player.PlayerSpawnChangeEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
-+import org.bukkit.inventory.MainHand;
-+// CraftBukkit end
- 
--public class ServerPlayer extends Player {
-+public class ServerPlayer extends net.minecraft.world.entity.player.Player {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
-     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
-@@ -225,7 +259,8 @@
-     private int levitationStartTime;
-     private boolean disconnected;
-     private int requestedViewDistance;
--    public String language;
-+    public String language = null; // CraftBukkit - default  // Paper - default to null
-+    public java.util.Locale adventure$locale = java.util.Locale.US; // Paper
-     @Nullable
-     private Vec3 startingToFallPosition;
-     @Nullable
-@@ -258,7 +293,35 @@
-     private final CommandSource commandSource;
-     private int containerCounter;
-     public boolean wonGame;
-+    private int containerUpdateDelay; // Paper - Configurable container update tick rate
-+    public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed
-+    public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options
-+    // Paper start - cancellable death event
-+    public boolean queueHealthUpdatePacket;
-+    public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
-+    // Paper end - cancellable death event
- 
-+    // CraftBukkit start
-+    public CraftPlayer.TransferCookieConnection transferCookieConnection;
-+    public String displayName;
-+    public net.kyori.adventure.text.Component adventure$displayName; // Paper
-+    public Component listName;
-+    public int listOrder = 0;
-+    public org.bukkit.Location compassTarget;
-+    public int newExp = 0;
-+    public int newLevel = 0;
-+    public int newTotalExp = 0;
-+    public boolean keepLevel = false;
-+    public double maxHealthCache;
-+    public boolean joining = true;
-+    public boolean sentListPacket = false;
-+    public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
-+    // CraftBukkit end
-+    public boolean isRealPlayer; // Paper
-+    public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
-+    public @Nullable String clientBrandName = null; // Paper - Brand support
-+    public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
-+
-     public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
-         super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
-         this.chatVisibility = ChatVisiblity.FULL;
-@@ -266,7 +329,7 @@
-         this.canChatColor = true;
-         this.lastActionTime = Util.getMillis();
-         this.requestedViewDistance = 2;
--        this.language = "en_us";
-+        this.language =  null; // Paper - default to null
-         this.lastSectionPos = SectionPos.of(0, 0, 0);
-         this.chunkTrackingView = ChunkTrackingView.EMPTY;
-         this.respawnDimension = Level.OVERWORLD;
-@@ -285,7 +348,14 @@
- 
-             }
- 
-+            // Paper start - Sync offhand slot in menus
-             @Override
-+            public void sendOffHandSlotChange() {
-+                ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy()));
-+            }
-+            // Paper end - Sync offhand slot in menus
-+
-+            @Override
-             public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) {
-                 ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack));
-             }
-@@ -316,6 +386,25 @@
- 
-                 }
-             }
-+            // Paper start - Add PlayerInventorySlotChangeEvent
-+            @Override
-+            public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) {
-+                Slot slot = handler.getSlot(slotId);
-+                if (!(slot instanceof ResultSlot)) {
-+                    if (slot.container == ServerPlayer.this.getInventory()) {
-+                        if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) {
-+                            CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
-+                            return;
-+                        }
-+                        io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack));
-+                        event.callEvent();
-+                        if (event.shouldTriggerAdvancements()) {
-+                            CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
-+                        }
-+                    }
-+                }
-+            }
-+            // Paper end - Add PlayerInventorySlotChangeEvent
- 
-             @Override
-             public void dataChanged(AbstractContainerMenu handler, int property, int value) {}
-@@ -340,6 +429,13 @@
-             public void sendSystemMessage(Component message) {
-                 ServerPlayer.this.sendSystemMessage(message);
-             }
-+
-+            // CraftBukkit start
-+            @Override
-+            public CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+                return ServerPlayer.this.getBukkitEntity();
-+            }
-+            // CraftBukkit end
-         };
-         this.textFilter = server.createTextFilterForPlayer(this);
-         this.gameMode = server.createGameModeForPlayer(this);
-@@ -349,17 +445,72 @@
-         this.server = server;
-         this.stats = server.getPlayerList().getPlayerStats(this);
-         this.advancements = server.getPlayerList().getPlayerAdvancements(this);
--        this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F);
--        this.updateOptions(clientOptions);
-+        // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn
-+        this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login
-         this.object = null;
-+
-+        // CraftBukkit start
-+        this.displayName = this.getScoreboardName();
-+        this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
-+        this.bukkitPickUpLoot = true;
-+        this.maxHealthCache = this.getMaxHealth();
-+    }
-+
-+    // Use method to resend items in hands in case of client desync, because the item use got cancelled.
-+    // For example, when cancelling the leash event
-+    @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all
-+    public void resendItemInHands() {
-+        this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> {
-+            this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem());
-+        });
-+        this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem());
-+    }
-+
-+    // Yes, this doesn't match Vanilla, but it's the best we can do for now.
-+    // If this is an issue, PRs are welcome
-+    public final BlockPos getSpawnPoint(ServerLevel worldserver) {
-+        BlockPos blockposition = worldserver.getSharedSpawnPos();
-+
-+        if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) {
-+            int i = Math.max(0, this.server.getSpawnRadius(worldserver));
-+            int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
-+
-+            if (j < i) {
-+                i = j;
-+            }
-+
-+            if (j <= 1) {
-+                i = 1;
-+            }
-+
-+            long k = (long) (i * 2 + 1);
-+            long l = k * k;
-+            int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
-+            int j1 = this.getCoprime(i1);
-+            int k1 = RandomSource.create().nextInt(i1);
-+
-+            for (int l1 = 0; l1 < i1; ++l1) {
-+                int i2 = (k1 + j1 * l1) % i1;
-+                int j2 = i2 % (i * 2 + 1);
-+                int k2 = i2 / (i * 2 + 1);
-+                BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
-+
-+                if (blockposition1 != null) {
-+                    return blockposition1;
-+                }
-+            }
-+        }
-+
-+        return blockposition;
-     }
-+    // CraftBukkit end
- 
-     @Override
-     public BlockPos adjustSpawnLocation(ServerLevel world, BlockPos basePos) {
-         AABB axisalignedbb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO);
-         BlockPos blockposition1 = basePos;
- 
--        if (world.dimensionType().hasSkyLight() && world.getServer().getWorldData().getGameType() != GameType.ADVENTURE) {
-+        if (world.dimensionType().hasSkyLight() && world.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
-             int i = Math.max(0, this.server.getSpawnRadius(world));
-             int j = Mth.floor(world.getWorldBorder().getDistanceToBorder((double) basePos.getX(), (double) basePos.getZ()));
- 
-@@ -395,14 +546,20 @@
- 
-                     Objects.requireNonNull(basePos);
-                     crashreportsystemdetails.setDetail("Origin", basePos::toString);
-+                    // CraftBukkit start - decompile error
-+                    int finalI = i;
-                     crashreportsystemdetails.setDetail("Radius", () -> {
--                        return Integer.toString(i);
-+                        return Integer.toString(finalI);
-+                        // CraftBukkit end
-                     });
-                     crashreportsystemdetails.setDetail("Candidate", () -> {
-                         return "[" + l2 + "," + i3 + "]";
-                     });
-+                    // CraftBukkit start - decompile error
-+                    int finalL1 = l1;
-                     crashreportsystemdetails.setDetail("Progress", () -> {
--                        return "" + l1 + " out of " + i1;
-+                        return "" + finalL1 + " out of " + i1;
-+                        // CraftBukkit end
-                     });
-                     throw new ReportedException(crashreport);
-                 }
-@@ -440,7 +597,7 @@
-             dataresult = WardenSpawnTracker.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("warden_spawn_tracker")));
-             logger = ServerPlayer.LOGGER;
-             Objects.requireNonNull(logger);
--            dataresult.resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> {
-+            ((DataResult<WardenSpawnTracker>) dataresult).resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> {
-                 this.wardenSpawnTracker = wardenspawntracker;
-             });
-         }
-@@ -457,17 +614,26 @@
-                 return this.server.getRecipeManager().byKey(resourcekey).isPresent();
-             });
-         }
-+        this.getBukkitEntity().readExtraData(nbt); // CraftBukkit
- 
-         if (this.isSleeping()) {
-             this.stopSleeping();
-         }
- 
-+        // CraftBukkit start
-+        String spawnWorld = nbt.getString("SpawnWorld");
-+        CraftWorld oldWorld = (CraftWorld) Bukkit.getWorld(spawnWorld);
-+        if (oldWorld != null) {
-+            this.respawnDimension = oldWorld.getHandle().dimension();
-+        }
-+        // CraftBukkit end
-+
-         if (nbt.contains("SpawnX", 99) && nbt.contains("SpawnY", 99) && nbt.contains("SpawnZ", 99)) {
-             this.respawnPosition = new BlockPos(nbt.getInt("SpawnX"), nbt.getInt("SpawnY"), nbt.getInt("SpawnZ"));
-             this.respawnForced = nbt.getBoolean("SpawnForced");
-             this.respawnAngle = nbt.getFloat("SpawnAngle");
-             if (nbt.contains("SpawnDimension")) {
--                DataResult dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension"));
-+                DataResult<ResourceKey<Level>> dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension")); // CraftBukkit - decompile error
-                 Logger logger1 = ServerPlayer.LOGGER;
- 
-                 Objects.requireNonNull(logger1);
-@@ -482,7 +648,7 @@
-             dataresult = BlockPos.CODEC.parse(NbtOps.INSTANCE, nbtbase);
-             logger = ServerPlayer.LOGGER;
-             Objects.requireNonNull(logger);
--            dataresult.resultOrPartial(logger::error).ifPresent((blockposition) -> {
-+            ((DataResult<BlockPos>) dataresult).resultOrPartial(logger::error).ifPresent((blockposition) -> { // CraftBukkit - decompile error
-                 this.raidOmenPosition = blockposition;
-             });
-         }
-@@ -492,7 +658,7 @@
-     @Override
-     public void addAdditionalSaveData(CompoundTag nbt) {
-         super.addAdditionalSaveData(nbt);
--        DataResult dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker);
-+        DataResult<Tag> dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); // CraftBukkit - decompile error
-         Logger logger = ServerPlayer.LOGGER;
- 
-         Objects.requireNonNull(logger);
-@@ -526,6 +692,7 @@
-                 nbt.put("SpawnDimension", nbtbase);
-             });
-         }
-+        this.getBukkitEntity().setExtraData(nbt); // CraftBukkit
- 
-         nbt.putBoolean("spawn_extra_particles_on_fall", this.spawnExtraParticlesOnFall);
-         if (this.raidOmenPosition != null) {
-@@ -544,7 +711,20 @@
-         Entity entity = this.getRootVehicle();
-         Entity entity1 = this.getVehicle();
- 
--        if (entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) {
-+        // CraftBukkit start - handle non-persistent vehicles
-+        boolean persistVehicle = true;
-+        if (entity1 != null) {
-+            Entity vehicle;
-+            for (vehicle = entity1; vehicle != null; vehicle = vehicle.getVehicle()) {
-+                if (!vehicle.persist) {
-+                    persistVehicle = false;
-+                    break;
-+                }
-+            }
-+        }
-+
-+        if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status
-+            // CraftBukkit end
-             CompoundTag nbttagcompound1 = new CompoundTag();
-             CompoundTag nbttagcompound2 = new CompoundTag();
- 
-@@ -564,7 +744,7 @@
-                 ServerLevel worldserver = (ServerLevel) world;
-                 CompoundTag nbttagcompound = ((CompoundTag) nbt.get()).getCompound("RootVehicle");
-                 Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver, EntitySpawnReason.LOAD, (entity1) -> {
--                    return !worldserver.addWithUUID(entity1) ? null : entity1;
-+                    return !worldserver.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // CraftBukkit - decompile error // Paper - Entity#getEntitySpawnReason
-                 });
- 
-                 if (entity == null) {
-@@ -598,12 +778,12 @@
- 
-                 if (!this.isPassenger()) {
-                     ServerPlayer.LOGGER.warn("Couldn't reattach entity to player");
--                    entity.discard();
-+                    entity.discard(null); // CraftBukkit - add Bukkit remove cause
-                     iterator = entity.getIndirectPassengers().iterator();
- 
-                     while (iterator.hasNext()) {
-                         entity1 = (Entity) iterator.next();
--                        entity1.discard();
-+                        entity1.discard(null); // CraftBukkit - add Bukkit remove cause
-                     }
-                 }
-             }
-@@ -618,6 +798,7 @@
- 
-             while (iterator.hasNext()) {
-                 ThrownEnderpearl entityenderpearl = (ThrownEnderpearl) iterator.next();
-+                if (entityenderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) continue; // Paper - Allow using old ender pearl behavior
- 
-                 if (entityenderpearl.isRemoved()) {
-                     ServerPlayer.LOGGER.warn("Trying to save removed ender pearl, skipping");
-@@ -625,7 +806,7 @@
-                     CompoundTag nbttagcompound1 = new CompoundTag();
- 
-                     entityenderpearl.save(nbttagcompound1);
--                    DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location());
-+                    DataResult<Tag> dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location()); // CraftBukkit - decompile error
-                     Logger logger = ServerPlayer.LOGGER;
- 
-                     Objects.requireNonNull(logger);
-@@ -651,7 +832,7 @@
-                 nbttaglist.forEach((nbtbase1) -> {
-                     if (nbtbase1 instanceof CompoundTag nbttagcompound) {
-                         if (nbttagcompound.contains("ender_pearl_dimension")) {
--                            DataResult dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension"));
-+                            DataResult<ResourceKey<Level>> dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension")); // CraftBukkit - decompile error
-                             Logger logger = ServerPlayer.LOGGER;
- 
-                             Objects.requireNonNull(logger);
-@@ -684,7 +865,30 @@
-             }
-         }
- 
-+    }
-+
-+    // CraftBukkit start - World fallback code, either respawn location or global spawn
-+    public void spawnIn(Level world) {
-+        this.setLevel(world);
-+        if (world == null) {
-+            this.unsetRemoved();
-+            Vec3 position = null;
-+            if (this.respawnDimension != null) {
-+                world = this.server.getLevel(this.respawnDimension);
-+                if (world != null && this.getRespawnPosition() != null) {
-+                    position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null);
-+                }
-+            }
-+            if (world == null || position == null) {
-+                world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
-+                position = Vec3.atCenterOf(world.getSharedSpawnPos());
-+            }
-+            this.setLevel(world);
-+            this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet
-+        }
-+        this.gameMode.setLevel((ServerLevel) world);
-     }
-+    // CraftBukkit end
- 
-     public void setExperiencePoints(int points) {
-         float f = (float) this.getXpNeededForNextLevel();
-@@ -744,6 +948,11 @@
- 
-     @Override
-     public void tick() {
-+        // CraftBukkit start
-+        if (this.joining) {
-+            this.joining = false;
-+        }
-+        // CraftBukkit end
-         this.tickClientLoadTimeout();
-         this.gameMode.tick();
-         this.wardenSpawnTracker.tick();
-@@ -751,9 +960,13 @@
-             --this.invulnerableTime;
-         }
- 
--        this.containerMenu.broadcastChanges();
--        if (!this.containerMenu.stillValid(this)) {
--            this.closeContainer();
-+        if (--this.containerUpdateDelay <= 0) {
-+            this.containerMenu.broadcastChanges();
-+            this.containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate;
-+        }
-+        // Paper end - Configurable container update tick rate
-+        if (this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen
-+            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
-             this.containerMenu = this.inventoryMenu;
-         }
- 
-@@ -807,7 +1020,7 @@
- 
-     public void doTick() {
-         try {
--            if (!this.isSpectator() || !this.touchingUnloadedChunk()) {
-+            if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn)
-                 super.tick();
-             }
- 
-@@ -820,7 +1033,7 @@
-             }
- 
-             if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
--                this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
-+                this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit
-                 this.lastSentHealth = this.getHealth();
-                 this.lastSentFood = this.foodData.getFoodLevel();
-                 this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F;
-@@ -851,6 +1064,12 @@
-                 this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float) this.lastRecordedExperience));
-             }
- 
-+            // CraftBukkit start - Force max health updates
-+            if (this.maxHealthCache != this.getMaxHealth()) {
-+                this.getBukkitEntity().updateScaledHealth();
-+            }
-+            // CraftBukkit end
-+
-             if (this.experienceLevel != this.lastRecordedLevel) {
-                 this.lastRecordedLevel = this.experienceLevel;
-                 this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float) this.lastRecordedLevel));
-@@ -865,6 +1084,20 @@
-                 CriteriaTriggers.LOCATION.trigger(this);
-             }
- 
-+            // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border
-+            if (this.oldLevel == -1) {
-+                this.oldLevel = this.experienceLevel;
-+            }
-+
-+            if (this.oldLevel != this.experienceLevel) {
-+                CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel);
-+                this.oldLevel = this.experienceLevel;
-+            }
-+
-+            if (this.getBukkitEntity().hasClientWorldBorder()) {
-+                ((CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick();
-+            }
-+            // CraftBukkit end
-         } catch (Throwable throwable) {
-             CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking player");
-             CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Player being ticked");
-@@ -893,7 +1126,7 @@
-         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) {
-             if (this.tickCount % 20 == 0) {
-                 if (this.getHealth() < this.getMaxHealth()) {
--                    this.heal(1.0F);
-+                    this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit - added regain reason of "REGEN" for filtering purposes.
-                 }
- 
-                 float f = this.foodData.getSaturationLevel();
-@@ -946,19 +1179,105 @@
-     }
- 
-     private void updateScoreForCriteria(ObjectiveCriteria criterion, int score) {
--        this.getScoreboard().forAllObjectives(criterion, this, (scoreaccess) -> {
-+        // CraftBukkit - Use our scores instead
-+        this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterion, this, (scoreaccess) -> {
-             scoreaccess.set(score);
-         });
-     }
- 
-+    // Paper start - PlayerDeathEvent#getItemsToKeep
-+    private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList<ItemStack> inv) {
-+        List<org.bukkit.inventory.ItemStack> itemsToKeep = event.getItemsToKeep();
-+        if (inv == null) {
-+            // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot?
-+            if (!itemsToKeep.isEmpty()) {
-+                for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) {
-+                    event.getEntity().getInventory().addItem(itemStack);
-+                }
-+            }
-+
-+            return;
-+        }
-+
-+        for (int i = 0; i < inv.size(); ++i) {
-+            ItemStack item = inv.get(i);
-+            if (EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) {
-+                inv.set(i, ItemStack.EMPTY);
-+                continue;
-+            }
-+
-+            final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack();
-+            boolean keep = false;
-+            final Iterator<org.bukkit.inventory.ItemStack> iterator = itemsToKeep.iterator();
-+            while (iterator.hasNext()) {
-+                final org.bukkit.inventory.ItemStack itemStack = iterator.next();
-+                if (bukkitStack.equals(itemStack)) {
-+                    iterator.remove();
-+                    keep = true;
-+                    break;
-+                }
-+            }
-+
-+            if (!keep) {
-+                inv.set(i, ItemStack.EMPTY);
-+            }
-+        }
-+    }
-+    // Paper end - PlayerDeathEvent#getItemsToKeep
-+
-     @Override
-     public void die(DamageSource damageSource) {
--        this.gameEvent(GameEvent.ENTITY_DIE);
-+        // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check
-         boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
-+        // CraftBukkit start - fire PlayerDeathEvent
-+        if (this.isRemoved()) {
-+            return;
-+        }
-+        List<DefaultDrop> loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior
-+        boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
- 
--        if (flag) {
--            Component ichatbasecomponent = this.getCombatTracker().getDeathMessage();
-+        if (!keepInventory) {
-+            for (ItemStack item : this.getInventory().getContents()) {
-+                if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) {
-+                    loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event)
-+                }
-+            }
-+        }
-+        if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false
-+        // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule)
-+        this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0);
-+        // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove.
- 
-+        loot.addAll(this.drops);
-+        this.drops.clear(); // SPIGOT-5188: make sure to clear
-+        } // Paper - fix player loottables running when mob loot gamerule is false
-+
-+        Component defaultMessage = this.getCombatTracker().getDeathMessage();
-+
-+        String deathmessage = defaultMessage.getString();
-+        this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel
-+        org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, damageSource, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure
-+        // Paper start - cancellable death event
-+        if (event.isCancelled()) {
-+            // make compatible with plugins that might have already set the health in an event listener
-+            if (this.getHealth() <= 0) {
-+                this.setHealth((float) event.getReviveHealth());
-+            }
-+            return;
-+        }
-+        this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method
-+        // Paper end
-+
-+        // SPIGOT-943 - only call if they have an inventory open
-+        if (this.containerMenu != this.inventoryMenu) {
-+            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason
-+        }
-+
-+        net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure
-+
-+        if (deathMessage != null && deathMessage != net.kyori.adventure.text.Component.empty() && flag) { // Paper - Adventure // TODO: allow plugins to override?
-+            Component ichatbasecomponent = PaperAdventure.asVanilla(deathMessage); // Paper - Adventure
-+
-             this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), ichatbasecomponent), PacketSendListener.exceptionallySend(() -> {
-                 boolean flag1 = true;
-                 String s = ichatbasecomponent.getString(256);
-@@ -988,12 +1307,23 @@
-         if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_FORGIVE_DEAD_PLAYERS)) {
-             this.tellNeutralMobsThatIDied();
-         }
--
--        if (!this.isSpectator()) {
--            this.dropAllDeathLoot(this.serverLevel(), damageSource);
-+        // SPIGOT-5478 must be called manually now
-+        if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), damageSource.getEntity()); // Paper - tie to event
-+        // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory.
-+        if (!event.getKeepInventory()) {
-+            // Paper start - PlayerDeathEvent#getItemsToKeep
-+            for (NonNullList<ItemStack> inv : this.getInventory().compartments) {
-+                processKeep(event, inv);
-+            }
-+            processKeep(event, null);
-+            // Paper end - PlayerDeathEvent#getItemsToKeep
-         }
- 
--        this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
-+        this.setCamera(this); // Remove spectated target
-+        // CraftBukkit end
-+
-+        // CraftBukkit - Get our scores instead
-+        this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
-         LivingEntity entityliving = this.getKillCredit();
- 
-         if (entityliving != null) {
-@@ -1028,10 +1358,12 @@
-     public void awardKillScore(Entity entityKilled, DamageSource damageSource) {
-         if (entityKilled != this) {
-             super.awardKillScore(entityKilled, damageSource);
--            this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
--            if (entityKilled instanceof Player) {
-+            // CraftBukkit - Get our scores instead
-+            this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
-+            if (entityKilled instanceof net.minecraft.world.entity.player.Player) {
-                 this.awardStat(Stats.PLAYER_KILLS);
--                this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
-+                // CraftBukkit - Get our scores instead
-+                this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
-             } else {
-                 this.awardStat(Stats.MOB_KILLS);
-             }
-@@ -1049,7 +1381,8 @@
-             int i = scoreboardteam.getColor().getId();
- 
-             if (i >= 0 && i < criterions.length) {
--                this.getScoreboard().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment);
-+                // CraftBukkit - Get our scores instead
-+                this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment);
-             }
-         }
- 
-@@ -1062,8 +1395,8 @@
-         } else {
-             Entity entity = source.getEntity();
- 
--            if (entity instanceof Player) {
--                Player entityhuman = (Player) entity;
-+            if (entity instanceof net.minecraft.world.entity.player.Player) {
-+                net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity;
- 
-                 if (!this.canHarmPlayer(entityhuman)) {
-                     return false;
-@@ -1074,8 +1407,8 @@
-                 AbstractArrow entityarrow = (AbstractArrow) entity;
-                 Entity entity1 = entityarrow.getOwner();
- 
--                if (entity1 instanceof Player) {
--                    Player entityhuman1 = (Player) entity1;
-+                if (entity1 instanceof net.minecraft.world.entity.player.Player) {
-+                    net.minecraft.world.entity.player.Player entityhuman1 = (net.minecraft.world.entity.player.Player) entity1;
- 
-                     if (!this.canHarmPlayer(entityhuman1)) {
-                         return false;
-@@ -1083,38 +1416,84 @@
-                 }
-             }
- 
--            return super.hurtServer(world, source, amount);
-+            // Paper start - cancellable death events
-+            // return super.hurtServer(world, source, amount);
-+            this.queueHealthUpdatePacket = true;
-+            boolean damaged = super.hurtServer(world, source, amount);
-+            this.queueHealthUpdatePacket = false;
-+            if (this.queuedHealthUpdatePacket != null) {
-+                this.connection.send(this.queuedHealthUpdatePacket);
-+                this.queuedHealthUpdatePacket = null;
-+            }
-+            return damaged;
-+            // Paper end - cancellable death events
-         }
-     }
- 
-     @Override
--    public boolean canHarmPlayer(Player player) {
-+    public boolean canHarmPlayer(net.minecraft.world.entity.player.Player player) {
-         return !this.isPvpAllowed() ? false : super.canHarmPlayer(player);
-     }
- 
-     private boolean isPvpAllowed() {
--        return this.server.isPvpAllowed();
-+        // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
-+        return this.level().pvpMode;
-     }
- 
--    public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean alive, TeleportTransition.PostTeleportTransition postDimensionTransition) {
-+    // CraftBukkit start
-+    public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean flag, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerRespawnEvent.RespawnReason reason) {
-+        TeleportTransition teleportTransition;
-+        boolean isBedSpawn = false;
-+        boolean isAnchorSpawn = false;
-+        // CraftBukkit end
-         BlockPos blockposition = this.getRespawnPosition();
-         float f = this.getRespawnAngle();
-         boolean flag1 = this.isRespawnForced();
-         ServerLevel worldserver = this.server.getLevel(this.getRespawnDimension());
- 
-         if (worldserver != null && blockposition != null) {
--            Optional<ServerPlayer.RespawnPosAngle> optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, alive);
-+            Optional<ServerPlayer.RespawnPosAngle> optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, flag);
- 
-             if (optional.isPresent()) {
-                 ServerPlayer.RespawnPosAngle entityplayer_respawnposangle = (ServerPlayer.RespawnPosAngle) optional.get();
- 
--                return new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, postDimensionTransition);
-+                // CraftBukkit start
-+                isBedSpawn = entityplayer_respawnposangle.isBedSpawn();
-+                isAnchorSpawn = entityplayer_respawnposangle.isAnchorSpawn();
-+                teleportTransition = new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, teleporttransition_a);
-+                // CraftBukkit end
-             } else {
--                return TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postDimensionTransition);
-+                teleportTransition = TeleportTransition.missingRespawnBlock(this.server.overworld(), this, teleporttransition_a); // CraftBukkit
-             }
-         } else {
--            return new TeleportTransition(this.server.overworld(), this, postDimensionTransition);
-+            teleportTransition = new TeleportTransition(this.server.overworld(), this, teleporttransition_a); // CraftBukkit
-         }
-+        // CraftBukkit start
-+        if (reason == null) {
-+            return teleportTransition;
-+        }
-+
-+        Player respawnPlayer = this.getBukkitEntity();
-+        Location location = CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot());
-+
-+        // Paper start - respawn flags
-+        com.google.common.collect.ImmutableSet.Builder<org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag> builder = com.google.common.collect.ImmutableSet.builder();
-+        if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) {
-+            builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL);
-+        }
-+        PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder);
-+        // Paper end - respawn flags
-+        this.level().getCraftServer().getPluginManager().callEvent(respawnEvent);
-+        // Spigot Start
-+        if (this.connection.isDisconnected()) {
-+            return null;
-+        }
-+        // Spigot End
-+
-+        location = respawnEvent.getRespawnLocation();
-+
-+        return new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), teleportTransition.deltaMovement(), location.getYaw(), location.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), teleportTransition.relatives(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
-+        // CraftBukkit end
-     }
- 
-     public static Optional<ServerPlayer.RespawnPosAngle> findRespawnAndUseSpawnBlock(ServerLevel world, BlockPos pos, float spawnAngle, boolean spawnForced, boolean alive) {
-@@ -1129,11 +1508,11 @@
-             }
- 
-             return optional.map((vec3d) -> {
--                return ServerPlayer.RespawnPosAngle.of(vec3d, pos);
-+                return ServerPlayer.RespawnPosAngle.of(vec3d, pos, false, true); // CraftBukkit
-             });
-         } else if (block instanceof BedBlock && BedBlock.canSetSpawn(world)) {
-             return BedBlock.findStandUpPosition(EntityType.PLAYER, world, pos, (Direction) iblockdata.getValue(BedBlock.FACING), spawnAngle).map((vec3d) -> {
--                return ServerPlayer.RespawnPosAngle.of(vec3d, pos);
-+                return ServerPlayer.RespawnPosAngle.of(vec3d, pos, true, false); // CraftBukkit
-             });
-         } else if (!spawnForced) {
-             return Optional.empty();
-@@ -1142,7 +1521,7 @@
-             BlockState iblockdata1 = world.getBlockState(pos.above());
-             boolean flag3 = iblockdata1.getBlock().isPossibleToRespawnInThis(iblockdata1);
- 
--            return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle)) : Optional.empty();
-+            return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle, false, false)) : Optional.empty(); // CraftBukkit
-         }
-     }
- 
-@@ -1160,6 +1539,7 @@
-     @Nullable
-     @Override
-     public ServerPlayer teleport(TeleportTransition teleportTarget) {
-+        if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154
-         if (this.isRemoved()) {
-             return null;
-         } else {
-@@ -1169,39 +1549,78 @@
- 
-             ServerLevel worldserver = teleportTarget.newLevel();
-             ServerLevel worldserver1 = this.serverLevel();
--            ResourceKey<Level> resourcekey = worldserver1.dimension();
-+            // CraftBukkit start
-+            ResourceKey<LevelStem> resourcekey = worldserver1.getTypeKey();
- 
-+            Location enter = this.getBukkitEntity().getLocation();
-+            PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
-+            Location exit = /* (worldserver == null) ? null : // Paper - always non-null */CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
-+            PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause());
-+            // Paper start - gateway-specific teleport event
-+            if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
-+                tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity));
-+            }
-+            // Paper end - gateway-specific teleport event
-+            Bukkit.getServer().getPluginManager().callEvent(tpEvent);
-+            Location newExit = tpEvent.getTo();
-+            if (tpEvent.isCancelled() || newExit == null) {
-+                return null;
-+            }
-+            if (!newExit.equals(exit)) {
-+                worldserver = ((CraftWorld) newExit.getWorld()).getHandle();
-+                teleportTarget = new TeleportTransition(worldserver, CraftLocation.toVec3D(newExit), Vec3.ZERO, newExit.getYaw(), newExit.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause());
-+            }
-+            // CraftBukkit end
-+
-             if (!teleportTarget.asPassenger()) {
-                 this.stopRiding();
-             }
- 
--            if (worldserver.dimension() == resourcekey) {
--                this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
-+            // CraftBukkit start
-+            if (worldserver != null && worldserver.dimension() == worldserver1.dimension()) {
-+                this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
-+                // CraftBukkit end
-                 this.connection.resetPosition();
-                 teleportTarget.postTeleportTransition().onTransition(this);
-                 return this;
-             } else {
-+                // CraftBukkit start
-+                /*
-                 this.isChangingDimension = true;
--                LevelData worlddata = worldserver.getLevelData();
-+                WorldData worlddata = worldserver.getLevelData();
- 
--                this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3));
--                this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-+                this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3));
-+                this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-                 PlayerList playerlist = this.server.getPlayerList();
- 
-                 playerlist.sendPlayerPermissionLevel(this);
-                 worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
-                 this.unsetRemoved();
-+                */
-+                // CraftBukkit end
-                 ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-                 gameprofilerfiller.push("moving");
--                if (resourcekey == Level.OVERWORLD && worldserver.dimension() == Level.NETHER) {
-+                if (worldserver != null && resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event
-                     this.enteredNetherPosition = this.position();
-                 }
- 
-                 gameprofilerfiller.pop();
-                 gameprofilerfiller.push("placing");
-+                // CraftBukkit start
-+                this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds
-+                LevelData worlddata = worldserver.getLevelData();
-+
-+                this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3));
-+                this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-+                PlayerList playerlist = this.server.getPlayerList();
-+
-+                playerlist.sendPlayerPermissionLevel(this);
-+                worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
-+                this.unsetRemoved();
-+                // CraftBukkit end
-                 this.setServerLevel(worldserver);
--                this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
-+                this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event
-                 this.connection.resetPosition();
-                 worldserver.addDuringTeleport(this);
-                 gameprofilerfiller.pop();
-@@ -1215,10 +1634,33 @@
-                 this.lastSentExp = -1;
-                 this.lastSentHealth = -1.0F;
-                 this.lastSentFood = -1;
-+
-+                // CraftBukkit start
-+                PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
-+                this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
-+                // CraftBukkit end
-+                // Paper start - Reset shield blocking on dimension change
-+                if (this.isBlocking()) {
-+                    this.stopUsingItem();
-+                }
-+                // Paper end - Reset shield blocking on dimension change
-                 return this;
-             }
-+        }
-+    }
-+
-+    // CraftBukkit start
-+    @Override
-+    public CraftPortalEvent callPortalEvent(Entity entity, Location exit, TeleportCause cause, int searchRadius, int creationRadius) {
-+        Location enter = this.getBukkitEntity().getLocation();
-+        PlayerPortalEvent event = new PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius);
-+        Bukkit.getServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) {
-+            return null;
-         }
-+        return new CraftPortalEvent(event);
-     }
-+    // CraftBukkit end
- 
-     @Override
-     public void forceSetRotation(float yaw, float pitch) {
-@@ -1228,13 +1670,27 @@
-     public void triggerDimensionChangeTriggers(ServerLevel origin) {
-         ResourceKey<Level> resourcekey = origin.dimension();
-         ResourceKey<Level> resourcekey1 = this.level().dimension();
-+        // CraftBukkit start
-+        ResourceKey<Level> maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin);
-+        ResourceKey<Level> maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level());
- 
--        CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1);
--        if (resourcekey == Level.NETHER && resourcekey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
-+        // Paper start - Add option for strict advancement dimension checks
-+        if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) {
-+            maindimensionkey = resourcekey;
-+            maindimensionkey1 = resourcekey1;
-+        }
-+        // Paper end - Add option for strict advancement dimension checks
-+        CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1);
-+        if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) {
-+            CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1);
-+        }
-+
-+        if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
-+            // CraftBukkit end
-             CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition);
-         }
- 
--        if (resourcekey1 != Level.NETHER) {
-+        if (maindimensionkey1 != Level.NETHER) { // CraftBukkit
-             this.enteredNetherPosition = null;
-         }
- 
-@@ -1251,36 +1707,63 @@
-         this.containerMenu.broadcastChanges();
-     }
- 
--    @Override
--    public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos pos) {
--        Direction enumdirection = (Direction) this.level().getBlockState(pos).getValue(HorizontalDirectionalBlock.FACING);
--
-+    // CraftBukkit start - moved bed result checks from below into separate method
-+    private Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> getBedResult(BlockPos blockposition, Direction enumdirection) {
-         if (!this.isSleeping() && this.isAlive()) {
--            if (!this.level().dimensionType().natural()) {
--                return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
--            } else if (!this.bedInRange(pos, enumdirection)) {
--                return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
--            } else if (this.bedBlocked(pos, enumdirection)) {
--                return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
-+            if (!this.level().dimensionType().natural() || !this.level().dimensionType().bedWorks()) {
-+                return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
-+            } else if (!this.bedInRange(blockposition, enumdirection)) {
-+                return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY);
-+            } else if (this.bedBlocked(blockposition, enumdirection)) {
-+                return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED);
-             } else {
--                this.setRespawnPosition(this.level().dimension(), pos, this.getYRot(), false, true);
-+                this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent
-                 if (this.level().isDay()) {
--                    return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
-+                    return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
-                 } else {
-                     if (!this.isCreative()) {
-                         double d0 = 8.0D;
-                         double d1 = 5.0D;
--                        Vec3 vec3d = Vec3.atBottomCenterOf(pos);
-+                        Vec3 vec3d = Vec3.atBottomCenterOf(blockposition);
-                         List<Monster> list = this.level().getEntitiesOfClass(Monster.class, new AABB(vec3d.x() - 8.0D, vec3d.y() - 5.0D, vec3d.z() - 8.0D, vec3d.x() + 8.0D, vec3d.y() + 5.0D, vec3d.z() + 8.0D), (entitymonster) -> {
-                             return entitymonster.isPreventingPlayerRest(this.serverLevel(), this);
-                         });
- 
-                         if (!list.isEmpty()) {
--                            return Either.left(Player.BedSleepingProblem.NOT_SAFE);
-+                            return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE);
-                         }
-                     }
- 
--                    Either<Player.BedSleepingProblem, Unit> either = super.startSleepInBed(pos).ifRight((unit) -> {
-+                    return Either.right(Unit.INSTANCE);
-+                }
-+            }
-+        } else {
-+            return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM);
-+        }
-+    }
-+
-+    @Override
-+    public Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockposition, boolean force) {
-+        Direction enumdirection = (Direction) this.level().getBlockState(blockposition).getValue(HorizontalDirectionalBlock.FACING);
-+        Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> bedResult = this.getBedResult(blockposition, enumdirection);
-+
-+        if (bedResult.left().orElse(null) == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) {
-+            return bedResult; // return immediately if the result is not bypassable by plugins
-+        }
-+
-+        if (force) {
-+            bedResult = Either.right(Unit.INSTANCE);
-+        }
-+
-+        bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, blockposition, bedResult);
-+        if (bedResult.left().isPresent()) {
-+            return bedResult;
-+        }
-+
-+        {
-+            {
-+                {
-+                    Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> either = super.startSleepInBed(blockposition, force).ifRight((unit) -> {
-                         this.awardStat(Stats.SLEEP_IN_BED);
-                         CriteriaTriggers.SLEPT_IN_BED.trigger(this);
-                     });
-@@ -1293,9 +1776,8 @@
-                     return either;
-                 }
-             }
--        } else {
--            return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
-         }
-+        // CraftBukkit end
-     }
- 
-     @Override
-@@ -1322,13 +1804,31 @@
- 
-     @Override
-     public void stopSleepInBed(boolean skipSleepTimer, boolean updateSleepingPlayers) {
-+        if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one!
-+        // CraftBukkit start - fire PlayerBedLeaveEvent
-+        CraftPlayer player = this.getBukkitEntity();
-+        BlockPos bedPosition = this.getSleepingPos().orElse(null);
-+
-+        org.bukkit.block.Block bed;
-+        if (bedPosition != null) {
-+            bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ());
-+        } else {
-+            bed = this.level().getWorld().getBlockAt(player.getLocation());
-+        }
-+
-+        PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed, true);
-+        this.level().getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return;
-+        }
-+        // CraftBukkit end
-         if (this.isSleeping()) {
-             this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2));
-         }
- 
-         super.stopSleepInBed(skipSleepTimer, updateSleepingPlayers);
-         if (this.connection != null) {
--            this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
-+            this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), TeleportCause.EXIT_BED); // CraftBukkit
-         }
- 
-     }
-@@ -1341,7 +1841,7 @@
- 
-     @Override
-     public boolean isInvulnerableTo(ServerLevel world, DamageSource source) {
--        return super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded();
-+        return (super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && source.is(DamageTypes.CRAMMING)); // Paper - disable player cramming
-     }
- 
-     @Override
-@@ -1387,8 +1887,9 @@
-         this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front));
-     }
- 
--    public void nextContainerCounter() {
-+    public int nextContainerCounter() { // CraftBukkit - void -> int
-         this.containerCounter = this.containerCounter % 100 + 1;
-+        return this.containerCounter; // CraftBukkit
-     }
- 
-     @Override
-@@ -1396,13 +1897,44 @@
-         if (factory == null) {
-             return OptionalInt.empty();
-         } else {
-+            // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...)
-+            /*
-             if (this.containerMenu != this.inventoryMenu) {
-                 this.closeContainer();
-             }
-+            */
-+            // CraftBukkit end
- 
-             this.nextContainerCounter();
-             AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this);
- 
-+            Component title = null; // Paper - Add titleOverride to InventoryOpenEvent
-+            // CraftBukkit start - Inventory open hook
-+            if (container != null) {
-+                container.setTitle(factory.getDisplayName());
-+
-+                boolean cancelled = false;
-+                // Paper start - Add titleOverride to InventoryOpenEvent
-+                final com.mojang.datafixers.util.Pair<net.kyori.adventure.text.Component, AbstractContainerMenu> result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled);
-+                container = result.getSecond();
-+                title = PaperAdventure.asVanilla(result.getFirst());
-+                // Paper end - Add titleOverride to InventoryOpenEvent
-+                if (container == null && !cancelled) { // Let pre-cancelled events fall through
-+                    // SPIGOT-5263 - close chest if cancelled
-+                    if (factory instanceof Container) {
-+                        ((Container) factory).stopOpen(this);
-+                    } else if (factory instanceof ChestBlock.DoubleInventory) {
-+                        // SPIGOT-5355 - double chests too :(
-+                        ((ChestBlock.DoubleInventory) factory).inventorylargechest.stopOpen(this);
-+                        // Paper start - Fix InventoryOpenEvent cancellation
-+                    } else if (!this.enderChestInventory.isActiveChest(null)) {
-+                        this.enderChestInventory.stopOpen(this);
-+                        // Paper end - Fix InventoryOpenEvent cancellation
-+                    }
-+                    return OptionalInt.empty();
-+                }
-+            }
-+            // CraftBukkit end
-             if (container == null) {
-                 if (this.isSpectator()) {
-                     this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true);
-@@ -1410,9 +1942,11 @@
- 
-                 return OptionalInt.empty();
-             } else {
--                this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), factory.getDisplayName()));
--                this.initMenu(container);
-+                // CraftBukkit start
-                 this.containerMenu = container;
-+                if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent
-+                // CraftBukkit end
-+                this.initMenu(container);
-                 return OptionalInt.of(this.containerCounter);
-             }
-         }
-@@ -1425,15 +1959,26 @@
- 
-     @Override
-     public void openHorseInventory(AbstractHorse horse, Container inventory) {
-+        // CraftBukkit start - Inventory open hook
-+        this.nextContainerCounter();
-+        AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns());
-+        container.setTitle(horse.getDisplayName());
-+        container = CraftEventFactory.callInventoryOpenEvent(this, container);
-+
-+        if (container == null) {
-+            inventory.stopOpen(this);
-+            return;
-+        }
-+        // CraftBukkit end
-         if (this.containerMenu != this.inventoryMenu) {
--            this.closeContainer();
-+            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason
-         }
- 
--        this.nextContainerCounter();
-+        // this.nextContainerCounter(); // CraftBukkit - moved up
-         int i = horse.getInventoryColumns();
- 
-         this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, i, horse.getId()));
--        this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, i);
-+        this.containerMenu = container; // CraftBukkit
-         this.initMenu(this.containerMenu);
-     }
- 
-@@ -1456,9 +2001,28 @@
- 
-     @Override
-     public void closeContainer() {
-+        // Paper start - Inventory close reason
-+        this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN);
-+    }
-+    @Override
-+    public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
-+        CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
-+        // Paper end - Inventory close reason
-         this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
-         this.doCloseContainer();
-     }
-+    // Paper start - special close for unloaded inventory
-+    @Override
-+    public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
-+        // copied from above
-+        CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
-+        // Paper end
-+        // copied from below
-+        this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
-+        this.containerMenu = this.inventoryMenu;
-+        // do not run close logic
-+    }
-+    // Paper end - special close for unloaded inventory
- 
-     @Override
-     public void doCloseContainer() {
-@@ -1485,19 +2049,19 @@
-                 i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F);
-                 if (i > 0) {
-                     this.awardStat(Stats.SWIM_ONE_CM, i);
--                    this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
-+                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot
-                 }
-             } else if (this.isEyeInFluid(FluidTags.WATER)) {
-                 i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F);
-                 if (i > 0) {
-                     this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i);
--                    this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
-+                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot
-                 }
-             } else if (this.isInWater()) {
-                 i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaZ * deltaZ) * 100.0F);
-                 if (i > 0) {
-                     this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i);
--                    this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
-+                    this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot
-                 }
-             } else if (this.onClimbable()) {
-                 if (deltaY > 0.0D) {
-@@ -1508,13 +2072,13 @@
-                 if (i > 0) {
-                     if (this.isSprinting()) {
-                         this.awardStat(Stats.SPRINT_ONE_CM, i);
--                        this.causeFoodExhaustion(0.1F * (float) i * 0.01F);
-+                        this.causeFoodExhaustion(this.level().spigotConfig.sprintMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot
-                     } else if (this.isCrouching()) {
-                         this.awardStat(Stats.CROUCH_ONE_CM, i);
--                        this.causeFoodExhaustion(0.0F * (float) i * 0.01F);
-+                        this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent // Spigot
-                     } else {
-                         this.awardStat(Stats.WALK_ONE_CM, i);
--                        this.causeFoodExhaustion(0.0F * (float) i * 0.01F);
-+                        this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent // Spigot
-                     }
-                 }
-             } else if (this.isFallFlying()) {
-@@ -1557,7 +2121,7 @@
-     @Override
-     public void awardStat(Stat<?> stat, int amount) {
-         this.stats.increment(this, stat, amount);
--        this.getScoreboard().forAllObjectives(stat, this, (scoreaccess) -> {
-+        this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, (scoreaccess) -> {
-             scoreaccess.add(amount);
-         });
-     }
-@@ -1565,7 +2129,7 @@
-     @Override
-     public void resetStat(Stat<?> stat) {
-         this.stats.setValue(this, stat, 0);
--        this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset);
-+        this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead
-     }
- 
-     @Override
-@@ -1597,9 +2161,9 @@
-         super.jumpFromGround();
-         this.awardStat(Stats.JUMP);
-         if (this.isSprinting()) {
--            this.causeFoodExhaustion(0.2F);
-+            this.causeFoodExhaustion(this.level().spigotConfig.jumpSprintExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
-         } else {
--            this.causeFoodExhaustion(0.05F);
-+            this.causeFoodExhaustion(this.level().spigotConfig.jumpWalkExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value
-         }
- 
-     }
-@@ -1613,6 +2177,13 @@
-     public void disconnect() {
-         this.disconnected = true;
-         this.ejectPassengers();
-+
-+        // Paper start - Workaround vehicle not tracking the passenger disconnection dismount
-+        if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) {
-+            this.stopRiding();
-+        }
-+        // Paper end - Workaround vehicle not tracking the passenger disconnection dismount
-+
-         if (this.isSleeping()) {
-             this.stopSleepInBed(true, false);
-         }
-@@ -1625,6 +2196,7 @@
- 
-     public void resetSentInfo() {
-         this.lastSentHealth = -1.0E8F;
-+        this.lastSentExp = -1; // CraftBukkit - Added to reset
-     }
- 
-     @Override
-@@ -1661,7 +2233,7 @@
-         this.onUpdateAbilities();
-         if (alive) {
-             this.getAttributes().assignBaseValues(oldPlayer.getAttributes());
--            this.getAttributes().assignPermanentModifiers(oldPlayer.getAttributes());
-+            // this.getAttributes().assignPermanentModifiers(entityplayer.getAttributes()); // CraftBukkit
-             this.setHealth(oldPlayer.getHealth());
-             this.foodData = oldPlayer.foodData;
-             Iterator iterator = oldPlayer.getActiveEffects().iterator();
-@@ -1669,7 +2241,7 @@
-             while (iterator.hasNext()) {
-                 MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
- 
--                this.addEffect(new MobEffectInstance(mobeffect));
-+                // this.addEffect(new MobEffect(mobeffect)); // CraftBukkit
-             }
- 
-             this.getInventory().replaceWith(oldPlayer.getInventory());
-@@ -1680,7 +2252,7 @@
-             this.portalProcess = oldPlayer.portalProcess;
-         } else {
-             this.getAttributes().assignBaseValues(oldPlayer.getAttributes());
--            this.setHealth(this.getMaxHealth());
-+            // this.setHealth(this.getMaxHealth()); // CraftBukkit
-             if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || oldPlayer.isSpectator()) {
-                 this.getInventory().replaceWith(oldPlayer.getInventory());
-                 this.experienceLevel = oldPlayer.experienceLevel;
-@@ -1696,7 +2268,7 @@
-         this.lastSentExp = -1;
-         this.lastSentHealth = -1.0F;
-         this.lastSentFood = -1;
--        this.recipeBook.copyOverData(oldPlayer.recipeBook);
-+        // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit
-         this.seenCredits = oldPlayer.seenCredits;
-         this.enteredNetherPosition = oldPlayer.enteredNetherPosition;
-         this.chunkTrackingView = oldPlayer.chunkTrackingView;
-@@ -1752,19 +2324,19 @@
-     }
- 
-     @Override
--    public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) {
-+    public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<Relative> set, float f, float f1, boolean flag, TeleportCause cause) { // CraftBukkit
-         if (this.isSleeping()) {
-             this.stopSleepInBed(true, true);
-         }
- 
--        if (resetCamera) {
-+        if (flag) {
-             this.setCamera(this);
-         }
- 
--        boolean flag1 = super.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera);
-+        boolean flag1 = super.teleportTo(worldserver, d0, d1, d2, set, f, f1, flag, cause); // CraftBukkit
- 
-         if (flag1) {
--            this.setYHeadRot(flags.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw);
-+            this.setYHeadRot(set.contains(Relative.Y_ROT) ? this.getYHeadRot() + f : f);
-         }
- 
-         return flag1;
-@@ -1799,10 +2371,18 @@
-     }
- 
-     public boolean setGameMode(GameType gameMode) {
-+        // Paper start - Expand PlayerGameModeChangeEvent
-+        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
-+        return event == null ? false : event.isCancelled();
-+    }
-+    @Nullable
-+    public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) {
-         boolean flag = this.isSpectator();
- 
--        if (!this.gameMode.changeGameModeForPlayer(gameMode)) {
--            return false;
-+        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message);
-+        if (event == null || event.isCancelled()) {
-+            return null;
-+            // Paper end - Expand PlayerGameModeChangeEvent
-         } else {
-             this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId()));
-             if (gameMode == GameType.SPECTATOR) {
-@@ -1818,7 +2398,7 @@
- 
-             this.onUpdateAbilities();
-             this.updateEffectVisibility();
--            return true;
-+            return event; // Paper - Expand PlayerGameModeChangeEvent
-         }
-     }
- 
-@@ -1861,8 +2441,13 @@
-     }
- 
-     public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params) {
-+        // Paper start
-+        this.sendChatMessage(message, filterMaskEnabled, params, null);
-+    }
-+    public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params, @Nullable Component unsigned) {
-+        // Paper end
-         if (this.acceptsChatMessages()) {
--            message.sendToPlayer(this, filterMaskEnabled, params);
-+            message.sendToPlayer(this, filterMaskEnabled, params, unsigned); // Paper
-         }
- 
-     }
-@@ -1878,7 +2463,36 @@
-     }
- 
-     public void updateOptions(ClientInformation clientOptions) {
-+        // Paper start - settings event
-+        new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> {
-+            map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientOptions.language());
-+            map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientOptions.viewDistance());
-+            map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()));
-+            map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientOptions.chatColors());
-+            map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()));
-+            map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
-+            map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientOptions.textFilteringEnabled());
-+            map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientOptions.allowsListing());
-+            map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientOptions.particleStatus().name()));
-+        })).callEvent();
-+        // Paper end - settings event
-+        // CraftBukkit start
-+        if (this.getMainArm() != clientOptions.mainHand()) {
-+            PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
-+            this.server.server.getPluginManager().callEvent(event);
-+        }
-+        if (this.language == null || !this.language.equals(clientOptions.language())) { // Paper
-+            PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(this.getBukkitEntity(), clientOptions.language());
-+            this.server.server.getPluginManager().callEvent(event);
-+        }
-+        // CraftBukkit end
-+        // Paper start - don't call options events on login
-+        this.updateOptionsNoEvents(clientOptions);
-+    }
-+    public void updateOptionsNoEvents(ClientInformation clientOptions) {
-+        // Paper end
-         this.language = clientOptions.language();
-+        this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper
-         this.requestedViewDistance = clientOptions.viewDistance();
-         this.chatVisibility = clientOptions.chatVisibility();
-         this.canChatColor = clientOptions.chatColors();
-@@ -1957,12 +2571,27 @@
- 
-         this.camera = (Entity) (entity == null ? this : entity);
-         if (entity1 != this.camera) {
-+            // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity
-+            if (this.camera == this) {
-+                com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity());
-+                if (!playerStopSpectatingEntityEvent.callEvent()) {
-+                    this.camera = entity1; // rollback camera entity again
-+                    return;
-+                }
-+            } else {
-+                com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity());
-+                if (!playerStartSpectatingEntityEvent.callEvent()) {
-+                    this.camera = entity1; // rollback camera entity again
-+                    return;
-+                }
-+            }
-+            // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity
-             Level world = this.camera.level();
- 
-             if (world instanceof ServerLevel) {
-                 ServerLevel worldserver = (ServerLevel) world;
- 
--                this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false);
-+                this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, TeleportCause.SPECTATE); // CraftBukkit
-             }
- 
-             if (entity != null) {
-@@ -1999,11 +2628,11 @@
- 
-     @Nullable
-     public Component getTabListDisplayName() {
--        return null;
-+        return this.listName; // CraftBukkit
-     }
- 
-     public int getTabListOrder() {
--        return 0;
-+        return this.listOrder; // CraftBukkit
-     }
- 
-     @Override
-@@ -2045,12 +2674,44 @@
-         this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false);
-     }
- 
-+    @Deprecated // Paper - Add PlayerSetSpawnEvent
-     public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) {
-+        // Paper start - Add PlayerSetSpawnEvent
-+        this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN);
-+    }
-+    @Deprecated
-+    public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) {
-+        return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ?
-+            com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name()));
-+    }
-+    public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) {
-+        Location spawnLoc = null;
-+        boolean willNotify = false;
-         if (pos != null) {
-             boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension);
-+            spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos);
-+            spawnLoc.setYaw(angle);
-+            willNotify = sendMessage && !flag2;
-+        }
- 
--            if (sendMessage && !flag2) {
--                this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn"));
-+        PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced,
-+            cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name()));
-+        dumbEvent.callEvent();
-+
-+        com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null);
-+        event.setCancelled(dumbEvent.isCancelled());
-+        if (!event.callEvent()) {
-+            return false;
-+        }
-+        if (event.getLocation() != null) {
-+            dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension;
-+            pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation());
-+            angle = event.getLocation().getYaw();
-+            forced = event.isForced();
-+            // Paper end - Add PlayerSetSpawnEvent
-+
-+            if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent
-+                this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent
-             }
- 
-             this.respawnPosition = pos;
-@@ -2064,6 +2725,7 @@
-             this.respawnForced = false;
-         }
- 
-+        return true; // Paper - Add PlayerSetSpawnEvent
-     }
- 
-     public SectionPos getLastSectionPos() {
-@@ -2088,18 +2750,44 @@
-     }
- 
-     @Override
--    public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) {
--        ItemEntity entityitem = this.createItemStackToDrop(stack, throwRandomly, retainOwnership);
-+    public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) { // CraftBukkit - SPIGOT-2942: Add boolean to call event
-+        ItemEntity entityitem = this.createItemStackToDrop(itemstack, flag, flag1);
- 
-         if (entityitem == null) {
-             return null;
-         } else {
-+            // CraftBukkit start - fire PlayerDropItemEvent
-+            if (callEvent) {
-+                Player player = (Player) this.getBukkitEntity();
-+                org.bukkit.entity.Item drop = (org.bukkit.entity.Item) entityitem.getBukkitEntity();
-+
-+                PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop);
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
-+                    org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand();
-+                    if (flag1 && (cur == null || cur.getAmount() == 0)) {
-+                        // The complete stack was dropped
-+                        player.getInventory().setItemInHand(drop.getItemStack());
-+                    } else if (flag1 && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) {
-+                        // Only one item is dropped
-+                        cur.setAmount(cur.getAmount() + 1);
-+                        player.getInventory().setItemInHand(cur);
-+                    } else {
-+                        // Fallback
-+                        player.getInventory().addItem(drop.getItemStack());
-+                    }
-+                    return null;
-+                }
-+            }
-+            // CraftBukkit end
-+
-             this.level().addFreshEntity(entityitem);
-             ItemStack itemstack1 = entityitem.getItem();
- 
--            if (retainOwnership) {
-+            if (flag1) {
-                 if (!itemstack1.isEmpty()) {
--                    this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount());
-+                    this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item
-                 }
- 
-                 this.awardStat(Stats.DROP);
-@@ -2115,6 +2803,11 @@
-             return null;
-         } else {
-             double d0 = this.getEyeY() - 0.30000001192092896D;
-+            // Paper start
-+            ItemStack tmp = stack.copy();
-+            stack.setCount(0);
-+            stack = tmp;
-+            // Paper end
-             ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), stack);
- 
-             entityitem.setPickUpDelay(40);
-@@ -2166,6 +2859,16 @@
-     }
- 
-     public void loadGameTypes(@Nullable CompoundTag nbt) {
-+        // Paper start - Expand PlayerGameModeChangeEvent
-+        if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) {
-+            if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) {
-+                this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE);
-+            } else {
-+                this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType"));
-+            }
-+            return;
-+        }
-+        // Paper end - Expand PlayerGameModeChangeEvent
-         this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType"));
-     }
- 
-@@ -2275,9 +2978,15 @@
- 
-     @Override
-     public void stopRiding() {
-+        // Paper start - Force entity dismount during teleportation
-+        this.stopRiding(false);
-+    }
-+    @Override
-+    public void stopRiding(boolean suppressCancellation) {
-+        // Paper end - Force entity dismount during teleportation
-         Entity entity = this.getVehicle();
- 
--        super.stopRiding();
-+        super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation
-         if (entity instanceof LivingEntity entityliving) {
-             Iterator iterator = entityliving.getActiveEffects().iterator();
- 
-@@ -2371,20 +3080,165 @@
-     }
- 
-     public static long placeEnderPearlTicket(ServerLevel world, ChunkPos chunkPos) {
--        world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos);
-+        if (!world.paperConfig().misc.legacyEnderPearlBehavior) world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos); // Paper - Allow using old ender pearl behavior
-         return TicketType.ENDER_PEARL.timeout();
-     }
- 
--    public static record RespawnPosAngle(Vec3 position, float yaw) {
-+    // CraftBukkit start
-+    public static record RespawnPosAngle(Vec3 position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn) {
- 
--        public static ServerPlayer.RespawnPosAngle of(Vec3 respawnPos, BlockPos currentPos) {
--            return new ServerPlayer.RespawnPosAngle(respawnPos, calculateLookAtYaw(respawnPos, currentPos));
-+        public static ServerPlayer.RespawnPosAngle of(Vec3 vec3d, BlockPos blockposition, boolean isBedSpawn, boolean isAnchorSpawn) {
-+            return new ServerPlayer.RespawnPosAngle(vec3d, calculateLookAtYaw(vec3d, blockposition), isBedSpawn, isAnchorSpawn);
-+            // CraftBukkit end
-         }
- 
-         private static float calculateLookAtYaw(Vec3 respawnPos, BlockPos currentPos) {
-             Vec3 vec3d1 = Vec3.atBottomCenterOf(currentPos).subtract(respawnPos).normalize();
- 
-             return (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D);
-+        }
-+    }
-+
-+    // CraftBukkit start - Add per-player time and weather.
-+    public long timeOffset = 0;
-+    public boolean relativeTime = true;
-+
-+    public long getPlayerTime() {
-+        if (this.relativeTime) {
-+            // Adds timeOffset to the current server time.
-+            return this.level().getDayTime() + this.timeOffset;
-+        } else {
-+            // Adds timeOffset to the beginning of this day.
-+            return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset;
-         }
-     }
-+
-+    public WeatherType weather = null;
-+
-+    public WeatherType getPlayerWeather() {
-+        return this.weather;
-+    }
-+
-+    public void setPlayerWeather(WeatherType type, boolean plugin) {
-+        if (!plugin && this.weather != null) {
-+            return;
-+        }
-+
-+        if (plugin) {
-+            this.weather = type;
-+        }
-+
-+        if (type == WeatherType.DOWNFALL) {
-+            this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0));
-+        } else {
-+            this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0));
-+        }
-+    }
-+
-+    private float pluginRainPosition;
-+    private float pluginRainPositionPrevious;
-+
-+    public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) {
-+        if (this.weather == null) {
-+            // Vanilla
-+            if (oldRain != newRain) {
-+                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain));
-+            }
-+        } else {
-+            // Plugin
-+            if (this.pluginRainPositionPrevious != this.pluginRainPosition) {
-+                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.pluginRainPosition));
-+            }
-+        }
-+
-+        if (oldThunder != newThunder) {
-+            if (this.weather == WeatherType.DOWNFALL || this.weather == null) {
-+                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder));
-+            } else {
-+                this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0));
-+            }
-+        }
-+    }
-+
-+    public void tickWeather() {
-+        if (this.weather == null) return;
-+
-+        this.pluginRainPositionPrevious = this.pluginRainPosition;
-+        if (this.weather == WeatherType.DOWNFALL) {
-+            this.pluginRainPosition += 0.01;
-+        } else {
-+            this.pluginRainPosition -= 0.01;
-+        }
-+
-+        this.pluginRainPosition = Mth.clamp(this.pluginRainPosition, 0.0F, 1.0F);
-+    }
-+
-+    public void resetPlayerWeather() {
-+        this.weather = null;
-+        this.setPlayerWeather(this.level().getLevelData().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false);
-+    }
-+
-+    @Override
-+    public String toString() {
-+        return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")";
-+    }
-+
-+    // SPIGOT-1903, MC-98153
-+    public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) {
-+        this.moveTo(x, y, z, yaw, pitch);
-+        this.connection.resetPosition();
-+    }
-+
-+    @Override
-+    public boolean isImmobile() {
-+        return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs
-+    }
-+
-+    @Override
-+    public Scoreboard getScoreboard() {
-+        return this.getBukkitEntity().getScoreboard().getHandle();
-+    }
-+
-+    public void reset() {
-+        float exp = 0;
-+
-+        if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
-+            exp = this.experienceProgress;
-+            this.newTotalExp = this.totalExperience;
-+            this.newLevel = this.experienceLevel;
-+        }
-+
-+        this.setHealth(this.getMaxHealth());
-+        this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset
-+        this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn
-+        this.setRemainingFireTicks(0);
-+        this.fallDistance = 0;
-+        this.foodData = new FoodData();
-+        this.experienceLevel = this.newLevel;
-+        this.totalExperience = this.newTotalExp;
-+        this.experienceProgress = 0;
-+        this.deathTime = 0;
-+        this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent
-+        this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
-+        this.effectsDirty = true;
-+        this.containerMenu = this.inventoryMenu;
-+        this.lastHurtByPlayer = null;
-+        this.lastHurtByMob = null;
-+        this.combatTracker = new CombatTracker(this);
-+        this.lastSentExp = -1;
-+        if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
-+            this.experienceProgress = exp;
-+        } else {
-+            this.giveExperiencePoints(this.newExp);
-+        }
-+        this.keepLevel = false;
-+        this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death
-+        this.skipDropExperience = false; // CraftBukkit - SPIGOT-7462: Reset experience drop skip, so that further deaths drop xp
-+    }
-+
-+    @Override
-+    public CraftPlayer getBukkitEntity() {
-+        return (CraftPlayer) super.getBukkitEntity();
-+    }
-+    // CraftBukkit end
- }
diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java
index 8cf720f085..109e569b7b 100644
--- a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java
+++ b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java
@@ -275,7 +275,7 @@ public abstract class Configurations<G, W> {
     }
 
     public Path getWorldConfigFile(ServerLevel level) {
-        return level.convertable.levelDirectory.path().resolve(this.worldConfigFileName);
+        return level.levelStorageAccess.levelDirectory.path().resolve(this.worldConfigFileName);
     }
 
     public static class ContextMap {
diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
index c5644d8d64..098ab351de 100644
--- a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
+++ b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
@@ -327,7 +327,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
     }
 
     private static ContextMap createWorldContextMap(ServerLevel level) {
-        return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules());
+        return createWorldContextMap(level.levelStorageAccess.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules());
     }
 
     public static ContextMap createWorldContextMap(final Path dir, final String levelName, final ResourceLocation worldKey, final SpigotWorldConfig spigotConfig, final RegistryAccess registryAccess, final GameRules gameRules) {
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 5b64111bc8..2cdfb3ac17 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1494,7 +1494,7 @@ public final class CraftServer implements Server {
 
             handle.getChunkSource().close(save);
             handle.entityManager.close(save); // SPIGOT-6722: close entityManager
-            handle.convertable.close();
+            handle.levelStorageAccess.close();
         } catch (Exception ex) {
             this.getLogger().log(Level.SEVERE, null, ex);
         }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index ca62105a0f..619b71eac6 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -6,7 +6,6 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.mojang.datafixers.util.Pair;
 import io.papermc.paper.FeatureHooks;
-import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import java.io.File;
@@ -21,7 +20,6 @@ import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -32,7 +30,6 @@ import net.minecraft.core.particles.ParticleTypes;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.Tag;
-import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
 import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
 import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
 import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
@@ -77,7 +74,6 @@ import org.bukkit.Chunk;
 import org.bukkit.ChunkSnapshot;
 import org.bukkit.Difficulty;
 import org.bukkit.Effect;
-import org.bukkit.FeatureFlag;
 import org.bukkit.FluidCollisionMode;
 import org.bukkit.GameRule;
 import org.bukkit.Instrument;
@@ -130,7 +126,6 @@ import org.bukkit.entity.TippedArrow;
 import org.bukkit.entity.Trident;
 import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
 import org.bukkit.event.weather.LightningStrikeEvent;
-import org.bukkit.event.world.SpawnChangeEvent;
 import org.bukkit.event.world.TimeSkipEvent;
 import org.bukkit.generator.BiomeProvider;
 import org.bukkit.generator.BlockPopulator;
@@ -1631,7 +1626,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public File getWorldFolder() {
-        return this.world.convertable.getLevelPath(LevelResource.ROOT).toFile().getParentFile();
+        return this.world.levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile().getParentFile();
     }
 
     @Override

From 172a80cb66833874304ea586aedc5a23964be089 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 22:35:35 -0800
Subject: [PATCH 145/285] Somehow a bunch of items were missed

---
 .../dedicated/DedicatedServer.java.patch      |  2 +-
 .../server/level/ChunkMap.java.patch          |  2 +-
 .../server/level/ServerChunkCache.java.patch  |  6 +-
 .../net/minecraft/world/entity/Mob.java.patch |  6 +-
 .../world/entity/animal/Cat.java.patch        |  2 +-
 .../world/entity/animal/Cow.java.patch        |  2 +-
 .../animal/armadillo/Armadillo.java.patch     |  5 +-
 .../entity/animal/camel/Camel.java.patch      |  2 +-
 .../world/entity/monster/Bogged.java.patch    |  3 +-
 .../world/entity/monster/Monster.java.patch   |  2 +-
 .../world/item/ArmorStandItem.java.patch      | 15 +++
 .../minecraft/world/item/AxeItem.java.patch   | 14 +++
 .../minecraft/world/item/BoatItem.java.patch  | 42 +++++++++
 .../world/item/BoneMealItem.java.patch        | 30 ++++++
 .../world/item/BucketItem.java.patch          | 92 +++++++++++++++++++
 .../world/item/CrossbowItem.java.patch        | 47 ++++++++++
 .../minecraft/world/item/DyeItem.java.patch   | 20 ++++
 .../minecraft/world/item/EggItem.java.patch   | 45 +++++++++
 .../world/item/EndCrystalItem.java.patch      | 30 ++++++
 .../world/item/EnderEyeItem.java.patch        | 56 +++++++++++
 .../world/item/EnderpearlItem.java.patch      | 48 ++++++++++
 .../world/level/BaseSpawner.java.patch        |  9 +-
 .../block/entity/BeaconBlockEntity.java.patch | 12 ++-
 .../entity/JukeboxBlockEntity.java.patch      |  2 +-
 .../PersistentEntitySectionManager.java.patch |  6 +-
 25 files changed, 475 insertions(+), 25 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 1fd2cae77f..6608a2eae4 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -205,7 +205,7 @@
  
          try {
 -            this.getConnection().startTcpServerListener(inetAddress, this.getPort());
-+            this.getConnection().bind(bindAddress); // Paper - Unix domain socket support
++            this.getConnection().startTcpServerListener(bindAddress); // Paper - Unix domain socket support
          } catch (IOException var10) {
              LOGGER.warn("**** FAILED TO BIND TO PORT!");
              LOGGER.warn("The exception was: {}", var10.toString());
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
index 559aabf756..6ce77e18f7 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
@@ -196,7 +196,7 @@
 -        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer());
 +    // CraftBukkit start
 +    private CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) {
-+        return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level);
++        return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level);
 +    // CraftBukkit end
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
index 5f558ccc4a..7d5ee65f78 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -172,7 +172,7 @@
              this.distanceManager.purgeStaleTickets();
          }
  
-@@ -400,11 +_,19 @@
+@@ -400,12 +_,20 @@
          );
          this.lastSpawnState = spawnState;
          profiler.popPush("spawnAndTick");
@@ -182,6 +182,7 @@
          List<MobCategory> filteredSpawningCategories;
          if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
 -            boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L;
+-            filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag);
 +            // Paper start - PlayerNaturallySpawnCreaturesEvent
 +            for (ServerPlayer entityPlayer : this.level.players()) {
 +                int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
@@ -191,9 +192,10 @@
 +            }
 +            // Paper end - PlayerNaturallySpawnCreaturesEvent
 +            boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
-             filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag);
++            filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit
          } else {
              filteredSpawningCategories = List.of();
+         }
 @@ -413,7 +_,7 @@
          for (LevelChunk levelChunk : chunks) {
              ChunkPos pos = levelChunk.getPos();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
index 3670fec552..6e9bf95ddf 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Mob.java.patch
@@ -217,7 +217,7 @@
 +            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
 -            Entity nearestPlayer = this.level().getNearestPlayer(this, -1.0);
-+            Entity nearestPlayer = this.level().getNearestPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
++            Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
              if (nearestPlayer != null) {
 -                double d = nearestPlayer.distanceToSqr(this);
 -                int despawnDistance = this.getType().getCategory().getDespawnDistance();
@@ -320,7 +320,7 @@
                      this.setItemSlot(equipmentSlot, ItemStack.EMPTY);
 +                    // Paper start
 +                    } else {
-+                        this.clearedEquipmentSlots.add(enumitemslot);
++                        this.clearedEquipmentSlots.add(equipmentSlot);
 +                    }
 +                    // Paper end
                  }
@@ -338,7 +338,7 @@
 +        EntityType<T> entityType, ConversionParams conversionParams, EntitySpawnReason spawnReason, ConversionParams.AfterConversion<T> afterConversion, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason creatureSpawnReason
 +    ) {
 +    // Paper start - entity zap event - allow cancellation of conversion post creation
-+        return this.convertTo(entityType, conversionParams, spawnReason, e -> { afterConversion.finalizeConversion(e); return true; }, transformReason, spawnReason);
++        return this.convertTo(entityType, conversionParams, spawnReason, e -> { afterConversion.finalizeConversion(e); return true; }, transformReason, creatureSpawnReason);
 +    }
 +    @Nullable
 +    public <T extends Mob> T convertTo(
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
index 01deda0a89..f475fefe0a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cat.java.patch
@@ -14,7 +14,7 @@
                      DyeColor dyeColor = dyeItem.getDyeColor();
                      if (dyeColor != this.getCollarColor()) {
 +                        // Paper start - Add EntityDyeEvent and CollarColorable interface
-+                        final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
++                        final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) dyeColor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
 +                        if (!event.callEvent()) return InteractionResult.FAIL;
 +                        dyeColor = DyeColor.byId(event.getColor().getWoolData());
 +                        // Paper end - Add EntityDyeEvent and CollarColorable interface
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
index c8bd9cb88e..a5059e85d7 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Cow.java.patch
@@ -5,7 +5,7 @@
          ItemStack itemInHand = player.getItemInHand(hand);
          if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
 +            // CraftBukkit start - Got milk?
-+            org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
++            org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemInHand, Items.MILK_BUCKET, hand);
 +            if (event.isCancelled()) {
 +                player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
 +                return InteractionResult.PASS;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
index 95199daa53..28a059aba2 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/armadillo/Armadillo.java.patch
@@ -13,19 +13,20 @@
  
              this.scuteTime = this.pickNextScuteDropTime();
          }
-@@ -283,7 +_,11 @@
+@@ -283,8 +_,11 @@
      }
  
      @Override
 -    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
+-        super.actuallyHurt(level, damageSource, amount);
 +    // CraftBukkit start - void -> boolean
 +    public boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) {
 +        boolean damageResult = super.actuallyHurt(level, damageSource, amount, event);
 +        if (!damageResult) return false;
 +        // CraftBukkit end
-         super.actuallyHurt(level, damageSource, amount);
          if (!this.isNoAi() && !this.isDeadOrDying()) {
              if (damageSource.getEntity() instanceof LivingEntity) {
+                 this.getBrain().setMemoryWithExpiry(MemoryModuleType.DANGER_DETECTED_RECENTLY, true, 80L);
 @@ -295,6 +_,7 @@
                  this.rollOut();
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
index 2ca2c4bbf8..2b76781cbe 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/camel/Camel.java.patch
@@ -11,7 +11,7 @@
              boolean flag1 = this.isTamed() && this.getAge() == 0 && this.canFallInLove();
              if (flag1) {
 -                this.setInLove(player);
-+                this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying
++                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
              }
  
              boolean isBaby = this.isBaby();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
index a9624e7f90..2098a78455 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
@@ -43,7 +43,8 @@
 +    public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears, java.util.List<ItemStack> drops) {
 +    // Paper end - custom shear drops
          level.playSound(null, this, SoundEvents.BOGGED_SHEAR, soundSource, 1.0F, 1.0F);
-         this.spawnShearedMushrooms(level, shears);
+-        this.spawnShearedMushrooms(level, shears);
++        this.spawnShearedMushrooms(level, shears, drops); // Paper - custom shear drops
          this.setSheared(true);
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
index 842c4f8588..68ded0ff66 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Monster.java.patch
@@ -5,7 +5,7 @@
          } else {
              DimensionType dimensionType = level.dimensionType();
 -            int i = dimensionType.monsterSpawnBlockLightLimit();
-+            int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning
++            int i = level.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning
              if (i < 15 && level.getBrightness(LightLayer.BLOCK, pos) > i) {
                  return false;
              } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch
new file mode 100644
index 0000000000..34157daf71
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/ArmorStandItem.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/item/ArmorStandItem.java
++++ b/net/minecraft/world/item/ArmorStandItem.java
+@@ -45,6 +_,12 @@
+ 
+                     float f = Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
+                     armorStand.moveTo(armorStand.getX(), armorStand.getY(), armorStand.getZ(), f, 0.0F);
++                    // CraftBukkit start
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, armorStand).isCancelled()) {
++                        if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                        return InteractionResult.FAIL;
++                    }
++                    // CraftBukkit end
+                     serverLevel.addFreshEntityWithPassengers(armorStand);
+                     level.playSound(
+                         null, armorStand.getX(), armorStand.getY(), armorStand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F
diff --git a/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch
new file mode 100644
index 0000000000..f39b785ebf
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/AxeItem.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/item/AxeItem.java
++++ b/net/minecraft/world/item/AxeItem.java
+@@ -67,6 +_,11 @@
+                 return InteractionResult.PASS;
+             } else {
+                 ItemStack itemInHand = context.getItemInHand();
++                // Paper start - EntityChangeBlockEvent
++                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, clickedPos, optional.get())) {
++                    return InteractionResult.PASS;
++                }
++                // Paper end
+                 if (player instanceof ServerPlayer) {
+                     CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand);
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch
new file mode 100644
index 0000000000..44050142b3
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/BoatItem.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/item/BoatItem.java
++++ b/net/minecraft/world/item/BoatItem.java
+@@ -30,7 +_,7 @@
+     @Override
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+-        HitResult playerPovHitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.ANY);
++        net.minecraft.world.phys.BlockHitResult playerPovHitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.ANY); // Paper
+         if (playerPovHitResult.getType() == HitResult.Type.MISS) {
+             return InteractionResult.PASS;
+         } else {
+@@ -51,6 +_,13 @@
+             }
+ 
+             if (playerPovHitResult.getType() == HitResult.Type.BLOCK) {
++                // CraftBukkit start - Boat placement
++                org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(player, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, playerPovHitResult.getBlockPos(), playerPovHitResult.getDirection(), itemInHand, false, hand, playerPovHitResult.getLocation());
++
++                if (event.isCancelled()) {
++                    return InteractionResult.PASS;
++                }
++                // CraftBukkit end
+                 AbstractBoat boat = this.getBoat(level, playerPovHitResult, itemInHand, player);
+                 if (boat == null) {
+                     return InteractionResult.FAIL;
+@@ -60,7 +_,15 @@
+                         return InteractionResult.FAIL;
+                     } else {
+                         if (!level.isClientSide) {
+-                            level.addFreshEntity(boat);
++                            // CraftBukkit start
++                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, playerPovHitResult.getBlockPos(), player.getDirection(), player, boat, hand).isCancelled()) {
++                                return InteractionResult.FAIL;
++                            }
++
++                            if (!level.addFreshEntity(boat)) {
++                                return InteractionResult.PASS;
++                            }
++                            // CraftBukkit end
+                             level.gameEvent(player, GameEvent.ENTITY_PLACE, playerPovHitResult.getLocation());
+                             itemInHand.consume(1, player);
+                         }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch
new file mode 100644
index 0000000000..a7ffb1e580
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/BoneMealItem.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/item/BoneMealItem.java
++++ b/net/minecraft/world/item/BoneMealItem.java
+@@ -33,12 +_,17 @@
+ 
+     @Override
+     public InteractionResult useOn(UseOnContext context) {
++        // CraftBukkit start - extract bonemeal application logic to separate, static method
++        return BoneMealItem.applyBonemeal(context);
++    }
++    public static InteractionResult applyBonemeal(UseOnContext context) {
++        // CraftBukkit end
+         Level level = context.getLevel();
+         BlockPos clickedPos = context.getClickedPos();
+         BlockPos blockPos = clickedPos.relative(context.getClickedFace());
+         if (growCrop(context.getItemInHand(), level, clickedPos)) {
+             if (!level.isClientSide) {
+-                context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
++                if (context.getPlayer() != null) context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
+                 level.levelEvent(1505, clickedPos, 15);
+             }
+ 
+@@ -48,7 +_,7 @@
+             boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace());
+             if (isFaceSturdy && growWaterPlant(context.getItemInHand(), level, blockPos, context.getClickedFace())) {
+                 if (!level.isClientSide) {
+-                    context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
++                    if (context.getPlayer() != null) context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
+                     level.levelEvent(1505, blockPos, 15);
+                 }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch
new file mode 100644
index 0000000000..6a14409bf4
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/BucketItem.java.patch
@@ -0,0 +1,92 @@
+--- a/net/minecraft/world/item/BucketItem.java
++++ b/net/minecraft/world/item/BucketItem.java
+@@ -29,6 +_,7 @@
+ import net.minecraft.world.phys.HitResult;
+ 
+ public class BucketItem extends Item implements DispensibleContainerItem {
++    private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
+     public final Fluid content;
+ 
+     public BucketItem(Fluid content, Item.Properties properties) {
+@@ -55,12 +_,23 @@
+             } else if (this.content == Fluids.EMPTY) {
+                 BlockState blockState = level.getBlockState(blockPos);
+                 if (blockState.getBlock() instanceof BucketPickup bucketPickup) {
++                    // CraftBukkit start
++                    ItemStack dummyFluid = bucketPickup.pickupBlock(player, org.bukkit.craftbukkit.util.DummyGeneratorAccess.INSTANCE, blockPos, blockState);
++                    if (dummyFluid.isEmpty()) return InteractionResult.FAIL; // Don't fire event if the bucket won't be filled.
++                    org.bukkit.event.player.PlayerBucketFillEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketFillEvent((net.minecraft.server.level.ServerLevel) level, player, blockPos, blockPos, playerPovHitResult.getDirection(), itemInHand, dummyFluid.getItem(), hand);
++
++                    if (event.isCancelled()) {
++                        // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(level, blockPos)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks
++                        ((ServerPlayer) player).getBukkitEntity().updateInventory(); // SPIGOT-4541
++                        return InteractionResult.FAIL;
++                    }
++                    // CraftBukkit end
+                     ItemStack itemStack = bucketPickup.pickupBlock(player, level, blockPos, blockState);
+                     if (!itemStack.isEmpty()) {
+                         player.awardStat(Stats.ITEM_USED.get(this));
+                         bucketPickup.getPickupSound().ifPresent(soundEvent -> player.playSound(soundEvent, 1.0F, 1.0F));
+                         level.gameEvent(player, GameEvent.FLUID_PICKUP, blockPos);
+-                        ItemStack itemStack1 = ItemUtils.createFilledResult(itemInHand, player, itemStack);
++                        ItemStack itemStack1 = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+                         if (!level.isClientSide) {
+                             CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, itemStack);
+                         }
+@@ -73,7 +_,7 @@
+             } else {
+                 BlockState blockState = level.getBlockState(blockPos);
+                 BlockPos blockPos2 = blockState.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockPos : blockPos1;
+-                if (this.emptyContents(player, level, blockPos2, playerPovHitResult)) {
++                if (this.emptyContents(player, level, blockPos2, playerPovHitResult, playerPovHitResult.getDirection(), blockPos, itemInHand, hand)) { // CraftBukkit
+                     this.checkExtraContent(player, level, itemInHand, blockPos2);
+                     if (player instanceof ServerPlayer) {
+                         CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, blockPos2, itemInHand);
+@@ -90,6 +_,13 @@
+     }
+ 
+     public static ItemStack getEmptySuccessItem(ItemStack bucketStack, Player player) {
++        // Paper start - Fix PlayerBucketEmptyEvent result itemstack
++        if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) {
++            ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent;
++            itemLeftInHandAfterPlayerBucketEmptyEvent = null;
++            return itemInHand;
++        }
++        // Paper end - Fix PlayerBucketEmptyEvent result itemstack
+         return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : bucketStack;
+     }
+ 
+@@ -99,6 +_,12 @@
+ 
+     @Override
+     public boolean emptyContents(@Nullable Player player, Level level, BlockPos pos, @Nullable BlockHitResult result) {
++        // CraftBukkit start
++        return this.emptyContents(player, level, pos, result, null, null, null, InteractionHand.MAIN_HAND);
++    }
++
++    public boolean emptyContents(@Nullable Player player, Level level, BlockPos pos, @Nullable BlockHitResult result, Direction enumdirection, BlockPos clicked, ItemStack itemstack, InteractionHand enumhand) {
++        // CraftBukkit end
+         if (!(this.content instanceof FlowingFluid flowingFluid)) {
+             return false;
+         } else {
+@@ -109,8 +_,19 @@
+                 || canBeReplaced
+                 || block instanceof LiquidBlockContainer liquidBlockContainer
+                     && liquidBlockContainer.canPlaceLiquid(player, level, pos, blockState, this.content);
++            // CraftBukkit start
++            if (flag && player != null) {
++                org.bukkit.event.player.PlayerBucketEmptyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBucketEmptyEvent((net.minecraft.server.level.ServerLevel) level, player, pos, clicked, enumdirection, itemstack, enumhand);
++                if (event.isCancelled()) {
++                    // ((ServerPlayer) player).connection.send(new ClientboundBlockUpdatePacket(level, pos)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks
++                    ((ServerPlayer) player).getBukkitEntity().updateInventory(); // SPIGOT-4541
++                    return false;
++                }
++                itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(org.bukkit.craftbukkit.inventory.CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack
++            }
++            // CraftBukkit end
+             if (!flag) {
+-                return result != null && this.emptyContents(player, level, result.getBlockPos().relative(result.getDirection()), null);
++                return result != null && this.emptyContents(player, level, result.getBlockPos().relative(result.getDirection()), null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
+             } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
+                 int x = pos.getX();
+                 int y = pos.getY();
diff --git a/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch
new file mode 100644
index 0000000000..2f24e0047f
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/CrossbowItem.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/item/CrossbowItem.java
++++ b/net/minecraft/world/item/CrossbowItem.java
+@@ -90,7 +_,14 @@
+     public boolean releaseUsing(ItemStack stack, Level level, LivingEntity entity, int timeLeft) {
+         int i = this.getUseDuration(stack, entity) - timeLeft;
+         float powerForTime = getPowerForTime(i, stack, entity);
+-        if (powerForTime >= 1.0F && !isCharged(stack) && tryLoadProjectiles(entity, stack)) {
++        // Paper start - Add EntityLoadCrossbowEvent
++        if (powerForTime >= 1.0F && !isCharged(stack)) {
++            final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(entity.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(entity.getUsedItemHand()));
++            if (!event.callEvent() || !tryLoadProjectiles(entity, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) {
++                if (entity instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote();
++                return false;
++            }
++            // Paper end - Add EntityLoadCrossbowEvent
+             CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack);
+             chargingSounds.end()
+                 .ifPresent(
+@@ -111,8 +_,14 @@
+         }
+     }
+ 
++    @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent
+     private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbowStack) {
+-        List<ItemStack> list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter);
++        // Paper start - Add EntityLoadCrossbowEvent
++        return CrossbowItem.tryLoadProjectiles(shooter, crossbowStack, true);
++    }
++    private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbowStack, boolean consume) {
++        List<ItemStack> list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter, consume);
++        // Paper end - Add EntityLoadCrossbowEvent
+         if (!list.isEmpty()) {
+             crossbowStack.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list));
+             return true;
+@@ -164,7 +_,11 @@
+     @Override
+     protected Projectile createProjectile(Level level, LivingEntity shooter, ItemStack weapon, ItemStack ammo, boolean isCrit) {
+         if (ammo.is(Items.FIREWORK_ROCKET)) {
+-            return new FireworkRocketEntity(level, ammo, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true);
++            // Paper start
++            FireworkRocketEntity entity =  new FireworkRocketEntity(level, ammo, shooter, shooter.getX(), shooter.getEyeY() - 0.15F, shooter.getZ(), true);
++            entity.spawningEntity = shooter.getUUID(); // Paper
++            return entity;
++            // Paper end
+         } else {
+             Projectile projectile = super.createProjectile(level, shooter, weapon, ammo, isCrit);
+             if (projectile instanceof AbstractArrow abstractArrow) {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch
new file mode 100644
index 0000000000..b19c70d742
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/DyeItem.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/DyeItem.java
++++ b/net/minecraft/world/item/DyeItem.java
+@@ -28,6 +_,17 @@
+             sheep.level().playSound(player, sheep, SoundEvents.DYE_USE, SoundSource.PLAYERS, 1.0F, 1.0F);
+             if (!player.level().isClientSide) {
+                 sheep.setColor(this.dyeColor);
++                // CraftBukkit start
++                byte bColor = (byte) this.dyeColor.getId();
++                org.bukkit.event.entity.SheepDyeWoolEvent event = new org.bukkit.event.entity.SheepDyeWoolEvent((org.bukkit.entity.Sheep) sheep.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData(bColor), (org.bukkit.entity.Player) player.getBukkitEntity());
++                sheep.level().getCraftServer().getPluginManager().callEvent(event);
++
++                if (event.isCancelled()) {
++                    return InteractionResult.PASS;
++                }
++
++                sheep.setColor(DyeColor.byId((byte) event.getColor().getWoolData()));
++                // CraftBukkit end
+                 stack.shrink(1);
+             }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
new file mode 100644
index 0000000000..59f5e151a8
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/item/EggItem.java
++++ b/net/minecraft/world/item/EggItem.java
+@@ -23,6 +_,28 @@
+     @Override
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
++        if (level instanceof ServerLevel serverLevel) {
++            // CraftBukkit start
++            // Paper start - PlayerLaunchProjectileEvent
++            final Projectile.Delayed<ThrownEgg> thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F);
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity());
++            if (event.callEvent() && thrownEgg.attemptSpawn()) {
++                if (event.shouldConsume()) {
++                    itemInHand.consume(1, player);
++                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++                level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++                player.awardStat(Stats.ITEM_USED.get(this));
++            } else {
++                // Paper end - PlayerLaunchProjectileEvent
++                if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++                return InteractionResult.FAIL;
++            }
++            // CraftBukkit end
++        }
+         level.playSound(
+             null,
+             player.getX(),
+@@ -33,12 +_,7 @@
+             0.5F,
+             0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+         );
+-        if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, PROJECTILE_SHOOT_POWER, 1.0F);
+-        }
+-
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
++        // Paper - PlayerLaunchProjectileEvent - moved up
+         return InteractionResult.SUCCESS;
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch
new file mode 100644
index 0000000000..f6474104df
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/EndCrystalItem.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/item/EndCrystalItem.java
++++ b/net/minecraft/world/item/EndCrystalItem.java
+@@ -27,7 +_,7 @@
+         if (!blockState.is(Blocks.OBSIDIAN) && !blockState.is(Blocks.BEDROCK)) {
+             return InteractionResult.FAIL;
+         } else {
+-            BlockPos blockPos = clickedPos.above();
++            BlockPos blockPos = clickedPos.above(); final BlockPos aboveBlockPosition = blockPos; // Paper - OBFHELPER
+             if (!level.isEmptyBlock(blockPos)) {
+                 return InteractionResult.FAIL;
+             } else {
+@@ -41,11 +_,17 @@
+                     if (level instanceof ServerLevel) {
+                         EndCrystal endCrystal = new EndCrystal(level, d + 0.5, d1, d2 + 0.5);
+                         endCrystal.setShowBottom(false);
++                        // CraftBukkit start
++                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, endCrystal).isCancelled()) {
++                            if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                            return InteractionResult.FAIL;
++                        }
++                        // CraftBukkit end
+                         level.addFreshEntity(endCrystal);
+                         level.gameEvent(context.getPlayer(), GameEvent.ENTITY_PLACE, blockPos);
+                         EndDragonFight dragonFight = ((ServerLevel)level).getDragonFight();
+                         if (dragonFight != null) {
+-                            dragonFight.tryRespawn();
++                            dragonFight.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup
+                         }
+                     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
new file mode 100644
index 0000000000..b2667052ad
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
@@ -0,0 +1,56 @@
+--- a/net/minecraft/world/item/EnderEyeItem.java
++++ b/net/minecraft/world/item/EnderEyeItem.java
+@@ -42,6 +_,11 @@
+             return InteractionResult.SUCCESS;
+         } else {
+             BlockState blockState1 = blockState.setValue(EndPortalFrameBlock.HAS_EYE, Boolean.valueOf(true));
++            // Paper start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), clickedPos, blockState1)) {
++                return InteractionResult.PASS;
++            }
++            // Paper end
+             Block.pushEntitiesUp(blockState, blockState1, level, clickedPos);
+             level.setBlock(clickedPos, blockState1, 2);
+             level.updateNeighbourForOutputSignal(clickedPos, Blocks.END_PORTAL_FRAME);
+@@ -57,7 +_,27 @@
+                     }
+                 }
+ 
+-                level.globalLevelEvent(1038, blockPos.offset(1, 0, 1), 0);
++                // CraftBukkit start - Use relative location for far away sounds
++                // level.globalLevelEvent(1038, blockPos.offset(1, 0, 1), 0);
++                int viewDistance = level.getCraftServer().getViewDistance() * 16;
++                BlockPos soundPos = blockPos.offset(1, 0, 1);
++                final net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) level; // Paper - respect global sound events gamerule - ensured by isClientSide check above
++                for (ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
++                    double deltaX = soundPos.getX() - player.getX();
++                    double deltaZ = soundPos.getZ() - player.getZ();
++                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++                    final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.endPortalSoundRadius); // Paper - respect global sound events gamerule
++                    if (!serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule
++                    if (distanceSquared > viewDistance * viewDistance) {
++                        double deltaLength = Math.sqrt(distanceSquared);
++                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
++                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, new BlockPos((int) relativeX, (int) soundPos.getY(), (int) relativeZ), 0, true));
++                    } else {
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, soundPos, 0, true));
++                    }
++                }
++                // CraftBukkit end
+             }
+ 
+             return InteractionResult.SUCCESS;
+@@ -87,7 +_,11 @@
+                 eyeOfEnder.setItem(itemInHand);
+                 eyeOfEnder.signalTo(blockPos);
+                 level.gameEvent(GameEvent.PROJECTILE_SHOOT, eyeOfEnder.position(), GameEvent.Context.of(player));
+-                level.addFreshEntity(eyeOfEnder);
++                // CraftBukkit start
++                if (!level.addFreshEntity(eyeOfEnder)) {
++                    return InteractionResult.FAIL;
++                }
++                // CraftBukkit end
+                 if (player instanceof ServerPlayer serverPlayer) {
+                     CriteriaTriggers.USED_ENDER_EYE.trigger(serverPlayer, blockPos);
+                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch
new file mode 100644
index 0000000000..5eccf6e90b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/item/EnderpearlItem.java
++++ b/net/minecraft/world/item/EnderpearlItem.java
+@@ -21,22 +_,32 @@
+     @Override
+     public InteractionResult use(Level level, Player player, InteractionHand hand) {
+         ItemStack itemInHand = player.getItemInHand(hand);
+-        level.playSound(
+-            null,
+-            player.getX(),
+-            player.getY(),
+-            player.getZ(),
+-            SoundEvents.ENDER_PEARL_THROW,
+-            SoundSource.NEUTRAL,
+-            0.5F,
+-            0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+-        );
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, PROJECTILE_SHOOT_POWER, 1.0F);
++            // CraftBukkit start
++            // Paper start - PlayerLaunchProjectileEvent
++            final Projectile.Delayed<ThrownEnderpearl> thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, serverLevel, itemInHand, player, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F);
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity());
++            if (event.callEvent() && thrownEnderpearl.attemptSpawn()) {
++                if (event.shouldConsume()) {
++                    itemInHand.consume(1, player);
++                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++
++                serverLevel.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (serverLevel.getRandom().nextFloat() * 0.4F + 0.8F));
++                player.awardStat(Stats.ITEM_USED.get(this));
++            } else {
++            // Paper end - PlayerLaunchProjectileEvent
++                if (player instanceof net.minecraft.server.level.ServerPlayer) {
++                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                }
++                return InteractionResult.FAIL;
++            }
+         }
++        level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++        // CraftBukkit end
+ 
+-        player.awardStat(Stats.ITEM_USED.get(this));
+-        itemInHand.consume(1, player);
++        // Paper - PlayerLaunchProjectileEvent - moved up
+         return InteractionResult.SUCCESS;
+     }
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
index b806243653..78ae4f91d8 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/BaseSpawner.java
 +++ b/net/minecraft/world/level/BaseSpawner.java
-@@ -44,9 +_,11 @@
+@@ -44,13 +_,15 @@
      public int maxNearbyEntities = 6;
      public int requiredPlayerRange = 16;
      public int spawnRange = 4;
@@ -12,6 +12,11 @@
      }
  
      public boolean isNearPlayer(Level level, BlockPos pos) {
+-        return level.hasNearbyAlivePlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange);
++        return level.hasNearbyAlivePlayerThatAffectsSpawning(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, this.requiredPlayerRange); // Paper - Affects Spawning API
+     }
+ 
+     public void clientTick(Level level, BlockPos pos) {
 @@ -73,13 +_,19 @@
      }
  
@@ -72,7 +77,7 @@
 +                            if (mob.level().spigotConfig.nerfSpawnerMobs) {
 +                                mob.aware = false;
 +                            }
-+                                                        // Spigot End
++                            // Spigot End
                          }
  
 -                        if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
index acc3c3c614..7fb16a71ce 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
@@ -1,28 +1,30 @@
 --- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
-@@ -106,6 +_,51 @@
+@@ -106,6 +_,53 @@
              return 3;
          }
      };
 +    // CraftBukkit start - add fields and methods
++    @Nullable
 +    public org.bukkit.potion.PotionEffect getPrimaryEffect() {
 +        return (this.primaryPower != null)
 +            ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
 +                this.primaryPower,
-+                BeaconBlockEntity.getLevel(this.levels),
-+                BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
++                BeaconBlockEntity.computeEffectDuration(this.levels),
++                BeaconBlockEntity.computeEffectAmplifier(this.levels, this.primaryPower, this.secondaryPower),
 +                true,
 +                true
 +            ))
 +            : null;
 +    }
 +
++    @Nullable
 +    public org.bukkit.potion.PotionEffect getSecondaryEffect() {
 +        return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower))
 +            ? org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(new MobEffectInstance(
 +                this.secondaryPower,
-+                BeaconBlockEntity.getLevel(this.levels),
-+                BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower),
++                BeaconBlockEntity.computeEffectDuration(this.levels),
++                BeaconBlockEntity.computeEffectAmplifier(this.levels, this.primaryPower, this.secondaryPower),
 +                true,
 +                true
 +            ))
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index 0789c1b70b..3051c3b415 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -12,7 +12,7 @@
 +
 +    @Override
 +    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
-+        return Collections.singletonList(this.item);
++        return java.util.Collections.singletonList(this.item);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
index 2fce5aff6c..0d57d512ea 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
@@ -34,7 +34,7 @@
 +        // I don't want to know why this is a generic type.
 +        Entity entityCasted = (Entity)entity;
 +        boolean wasRemoved = entityCasted.isRemoved();
-+        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true);
++        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, worldGenSpawned, true);
 +        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
 +            // removed by callback
 +            return false;
@@ -107,7 +107,7 @@
                  this.requestChunkLoad(chunkPosValue);
                  return false;
              } else {
-+                if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
++                if (callEvent) org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesUnloadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, new ChunkPos(chunkPosValue), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
                  this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), list));
                  list.forEach(entityAction);
                  return true;
@@ -149,7 +149,7 @@
              this.chunkLoadStatuses.put(chunkEntities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
 +            // CraftBukkit start - call entity load event
 +            List<Entity> entities = this.getEntities(chunkEntities.getPos());
-+            org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities);
++            org.bukkit.craftbukkit.event.CraftEventFactory.callEntitiesLoadEvent(((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level, chunkEntities.getPos(), entities);
 +            // CraftBukkit end
          }
      }

From 42cecc53c15e92b3a4107c1d018f0ef7a8f675b9 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 14 Dec 2024 22:43:34 -0800
Subject: [PATCH 146/285] fix more compile issues

---
 .../net/minecraft/world/level/NaturalSpawner.java.patch    | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
index f58012a2a3..5f188bd666 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -28,7 +28,7 @@
                      BlockPos blockPos = entity.blockPosition();
                      chunkGetter.query(ChunkPos.asLong(blockPos), chunk -> {
                          MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
-@@ -96,17 +_,36 @@
+@@ -96,17 +_,37 @@
          return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
      }
  
@@ -68,8 +68,9 @@
 +            if ((spawnFriendlies || !enumcreaturetype.isFriendly())
 +                && (spawnEnemies || enumcreaturetype.isFriendly())
 +                && (spawnPassives || !enumcreaturetype.isPersistent())
-+                && spawnState.canSpawnForCategoryGlobal(enumcreaturetype)) {
++                && spawnState.canSpawnForCategoryGlobal(enumcreaturetype, limit)) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
 +                list.add(enumcreaturetype);
++                // CraftBukkit end
              }
          }
  
@@ -96,7 +97,7 @@
          int y = pos.getY();
 -        BlockState blockState = chunk.getBlockState(pos);
 -        if (!blockState.isRedstoneConductor(chunk, pos)) {
-+        BlockState blockState = chunk.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
++        BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn
 +        if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn
              BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
              int i = 0;

From 171fb2ec07720e293ece067eb209aa1f5b2e2663 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 09:49:45 +0100
Subject: [PATCH 147/285] Move FeatureHooks to patch

---
 .../io/papermc/paper/FeatureHooks.java.patch  | 75 +++++++++++++++++++
 .../entity/animal/horse/Llama.java.patch      |  2 +-
 .../java/io/papermc/paper/FeatureHooks.java   | 72 ------------------
 3 files changed, 76 insertions(+), 73 deletions(-)
 create mode 100644 paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
 delete mode 100644 paper-server/src/main/java/io/papermc/paper/FeatureHooks.java

diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
new file mode 100644
index 0000000000..af8d9a6358
--- /dev/null
+++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
@@ -0,0 +1,75 @@
+--- /dev/null
++++ b/io/papermc/paper/FeatureHooks.java
+@@ -1,0 +_,72 @@
++package io.papermc.paper;
++
++import io.papermc.paper.command.PaperSubcommand;
++import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
++import it.unimi.dsi.fastutil.longs.LongSet;
++import it.unimi.dsi.fastutil.longs.LongSets;
++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
++import it.unimi.dsi.fastutil.objects.ObjectSet;
++import it.unimi.dsi.fastutil.objects.ObjectSets;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
++import net.minecraft.core.Registry;
++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.biome.Biome;
++import net.minecraft.world.level.block.Block;
++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.chunk.LevelChunkSection;
++import net.minecraft.world.level.chunk.PalettedContainer;
++import org.bukkit.Chunk;
++import org.bukkit.World;
++
++public final class FeatureHooks {
++
++    public static void initChunkTaskScheduler(final boolean useParallelGen) {
++    }
++
++    public static void registerPaperCommands(final Map<Set<String>, PaperSubcommand> commands) {
++    }
++
++    public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
++        return new LevelChunkSection(biomeRegistry);
++    }
++
++    public static void sendChunkRefreshPackets(final List<ServerPlayer> playersInRange, final LevelChunk chunk) {
++        final ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, chunk.level.getLightEngine(), null, null);
++        for (final ServerPlayer player : playersInRange) {
++            if (player.connection == null) continue;
++
++            player.connection.send(refreshPacket);
++        }
++    }
++
++    public static PalettedContainer<BlockState> emptyPalettedBlockContainer() {
++        return new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
++    }
++
++    public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
++        final LongSet keys = new LongOpenHashSet();
++        player.getChunkTrackingView().forEach(pos -> keys.add(pos.longKey));
++        return LongSets.unmodifiable(keys);
++    }
++
++    public static Set<Chunk> getSentChunks(final ServerPlayer player) {
++        final ObjectSet<Chunk> chunks = new ObjectOpenHashSet<>();
++        final World world = player.serverLevel().getWorld();
++        player.getChunkTrackingView().forEach(pos -> {
++            final org.bukkit.Chunk chunk = world.getChunkAt(pos.longKey);
++            chunks.add(chunk);
++        });
++        return ObjectSets.unmodifiable(chunks);
++    }
++
++    public static boolean isChunkSent(final ServerPlayer player, final long chunkKey) {
++        return player.getChunkTrackingView().contains(new ChunkPos(chunkKey));
++    }
++}
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
index 7de40d1411..86290f1699 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/Llama.java.patch
@@ -30,7 +30,7 @@
              if (this.isTamed() && this.getAge() == 0 && this.canFallInLove()) {
                  flag = true;
 -                this.setInLove(player);
-+                this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying
++                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
              }
          }
  
diff --git a/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java b/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java
deleted file mode 100644
index cfd47bcdcd..0000000000
--- a/paper-server/src/main/java/io/papermc/paper/FeatureHooks.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package io.papermc.paper;
-
-import io.papermc.paper.command.PaperSubcommand;
-import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
-import it.unimi.dsi.fastutil.longs.LongSet;
-import it.unimi.dsi.fastutil.longs.LongSets;
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
-import it.unimi.dsi.fastutil.objects.ObjectSet;
-import it.unimi.dsi.fastutil.objects.ObjectSets;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import net.minecraft.core.Registry;
-import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.biome.Biome;
-import net.minecraft.world.level.block.Block;
-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.chunk.LevelChunkSection;
-import net.minecraft.world.level.chunk.PalettedContainer;
-import org.bukkit.Chunk;
-import org.bukkit.World;
-
-public final class FeatureHooks {
-
-    public static void initChunkTaskScheduler(final boolean useParallelGen) {
-    }
-
-    public static void registerPaperCommands(final Map<Set<String>, PaperSubcommand> commands) {
-    }
-
-    public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
-        return new LevelChunkSection(biomeRegistry);
-    }
-
-    public static void sendChunkRefreshPackets(final List<ServerPlayer> playersInRange, final LevelChunk chunk) {
-        final ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, chunk.level.getLightEngine(), null, null);
-        for (final ServerPlayer player : playersInRange) {
-            if (player.connection == null) continue;
-
-            player.connection.send(refreshPacket);
-        }
-    }
-
-    public static PalettedContainer<BlockState> emptyPalettedBlockContainer() {
-        return new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
-    }
-
-    public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
-        final LongSet keys = new LongOpenHashSet();
-        player.getChunkTrackingView().forEach(pos -> keys.add(pos.longKey));
-        return LongSets.unmodifiable(keys);
-    }
-
-    public static Set<Chunk> getSentChunks(final ServerPlayer player) {
-        final ObjectSet<Chunk> chunks = new ObjectOpenHashSet<>();
-        final World world = player.serverLevel().getWorld();
-        player.getChunkTrackingView().forEach(pos -> {
-            final org.bukkit.Chunk chunk = world.getChunkAt(pos.longKey);
-            chunks.add(chunk);
-        });
-        return ObjectSets.unmodifiable(chunks);
-    }
-
-    public static boolean isChunkSent(final ServerPlayer player, final long chunkKey) {
-        return player.getChunkTrackingView().contains(new ChunkPos(chunkKey));
-    }
-}

From 4c723932f6c2a6f3c1947ba0363d344c6c1e3036 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 12:13:09 +0100
Subject: [PATCH 148/285] Small fixups

---
 .../selector/EntitySelectorParser.java.patch  | 11 +++++---
 .../chat/SignedMessageChain.java.patch        |  9 +++----
 .../dedicated/DedicatedServer.java.patch      | 27 ++++++++-----------
 3 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
index 33d39ea636..7274630b68 100644
--- a/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
@@ -1,14 +1,17 @@
 --- a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
 +++ b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
-@@ -122,7 +_,7 @@
+@@ -122,6 +_,11 @@
      }
  
      public static <S> boolean allowSelectors(S suggestionProvider) {
--        return suggestionProvider instanceof SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2);
-+        return suggestionProvider instanceof net.minecraft.commands.CommandSourceStack stack ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") : suggestionProvider instanceof net.minecraft.commands.SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2); // Paper - Fix EntityArgument permissions
++        // Paper start - Fix EntityArgument permissions
++        if (suggestionProvider instanceof net.minecraft.commands.CommandSourceStack stack) {
++            return stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector");
++        }
++        // Paper end - Fix EntityArgument permissions
+         return suggestionProvider instanceof SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2);
      }
  
-     public EntitySelector getSelector() {
 @@ -198,8 +_,10 @@
          };
      }
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
index 39e328fbef..440e3748c5 100644
--- a/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/SignedMessageChain.java.patch
@@ -22,16 +22,15 @@
          static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat");
  
 -        public DecodeException(Component component) {
--            super(component);
 +        // Paper start
 +        public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause;
-+        public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) {
-+            super(message);
++        public DecodeException(Component component, org.bukkit.event.player.PlayerKickEvent.Cause event) {
+             super(component);
 +            this.kickCause = event;
 +        }
 +        // Paper end
-+        public DecodeException(Component message) {
-+            this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper
++        public DecodeException(Component component) {
++            this(component, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 6608a2eae4..def9b71a6f 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -13,25 +13,20 @@
      @Nullable
      private RconThread rconThread;
      public DedicatedServerSettings settings;
-@@ -80,19 +_,12 @@
-     private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker;
+@@ -81,6 +_,7 @@
      public ServerLinks serverLinks;
  
--    public DedicatedServer(
--        Thread serverThread,
--        LevelStorageSource.LevelStorageAccess storageSource,
--        PackRepository packRepository,
--        WorldStem worldStem,
--        DedicatedServerSettings settings,
--        DataFixer fixerUpper,
--        Services services,
--        ChunkProgressListenerFactory progressListenerFactory
--    ) {
+     public DedicatedServer(
++        joptsimple.OptionSet options, net.minecraft.server.WorldLoader.DataLoadContext worldLoader, // CraftBukkit - Signature changed
+         Thread serverThread,
+         LevelStorageSource.LevelStorageAccess storageSource,
+         PackRepository packRepository,
+@@ -90,9 +_,9 @@
+         Services services,
+         ChunkProgressListenerFactory progressListenerFactory
+     ) {
 -        super(serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory);
-+    // CraftBukkit start - Signature changed
-+    public DedicatedServer(joptsimple.OptionSet options, net.minecraft.server.WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings settings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
-+        super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
-+        // CraftBukkit end
++        super(options, worldLoader, serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory); // CraftBukkit - Signature changed
          this.settings = settings;
 -        this.rconConsoleSource = new RconConsoleSource(this);
 +        //this.rconConsoleSource = new RconConsoleSource(this); // CraftBukkit - remove field

From df778ff55d27b80b0bf853be645d3c77f410da13 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 12:41:29 +0100
Subject: [PATCH 149/285] Update first feature patch

---
 ...ldBounds-and-getBlockState-for-inlin.patch | 73 +++++++++----------
 1 file changed, 36 insertions(+), 37 deletions(-)

diff --git a/feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
index bfa353b5dd..51f8cb9c22 100644
--- a/feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
+++ b/feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
@@ -11,11 +11,11 @@ Replace all calls to the new place to the unnecessary forward.
 
 Optimize getType and getBlockData to manually inline and optimize the calls
 
-diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/core/Vec3i.java
-+++ b/src/main/java/net/minecraft/core/Vec3i.java
-@@ -0,0 +0,0 @@ public class Vec3i implements Comparable<Vec3i> {
+diff --git a/net/minecraft/core/Vec3i.java b/net/minecraft/core/Vec3i.java
+index 03e2178430849d26c9826517e34ad069c94fc00a..11555ce7159ca6c8ddfe9691f86d3720c07cb086 100644
+--- a/net/minecraft/core/Vec3i.java
++++ b/net/minecraft/core/Vec3i.java
+@@ -28,6 +28,12 @@ public class Vec3i implements Comparable<Vec3i> {
          );
      }
  
@@ -28,36 +28,36 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public Vec3i(int x, int y, int z) {
          this.x = x;
          this.y = y;
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index da264246f9d1909980c281248e64a522806b36f4..d3a22634c5e76cde78011aa6d40f67370763f83e 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -351,7 +351,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      // Paper end
  
      public boolean isInWorldBounds(BlockPos pos) {
--        return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos);
+-        return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
 +        return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - use better/optimized check
      }
  
      public static boolean isInSpawnableBounds(BlockPos pos) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
+index f68f3f5e8ef39a0dc371e75110227a39791c04c8..12d9b532e466ec4e46920d409b5f1b3ae60b80f8 100644
+--- a/net/minecraft/world/level/chunk/ChunkAccess.java
++++ b/net/minecraft/world/level/chunk/ChunkAccess.java
+@@ -130,6 +130,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
          return GameEventListenerRegistry.NOOP;
      }
  
 +    public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper
      @Nullable
-     public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean moved);
+     public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving);
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-@@ -0,0 +0,0 @@ public class ImposterProtoChunk extends ProtoChunk {
+diff --git a/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+index 6c43e5e685871289968db8171342fd84189edcba..e7c0f4da8508fbca467326f475668d66454d7b77 100644
+--- a/net/minecraft/world/level/chunk/ImposterProtoChunk.java
++++ b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+@@ -56,6 +56,12 @@ public class ImposterProtoChunk extends ProtoChunk {
      public BlockState getBlockState(BlockPos pos) {
          return this.wrapped.getBlockState(pos);
      }
@@ -70,30 +70,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
      public FluidState getFluidState(BlockPos pos) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -0,0 +0,0 @@ public class ProtoChunk extends ChunkAccess {
+diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java
+index e359b5f694210f05e5675a995dbfc1a95cec76db..8c333d7f390d823a7c7f303e2f444f52ec16f799 100644
+--- a/net/minecraft/world/level/chunk/ProtoChunk.java
++++ b/net/minecraft/world/level/chunk/ProtoChunk.java
+@@ -99,12 +99,18 @@ public class ProtoChunk extends ChunkAccess {
  
      @Override
      public BlockState getBlockState(BlockPos pos) {
--        int i = pos.getY();
--        if (this.isOutsideBuildHeight(i)) {
+-        int y = pos.getY();
 +        // Paper start
 +        return getBlockState(pos.getX(), pos.getY(), pos.getZ());
 +    }
 +    public BlockState getBlockState(final int x, final int y, final int z) {
-+        if (this.isOutsideBuildHeight(y)) {
++        // Paper end
+         if (this.isOutsideBuildHeight(y)) {
              return Blocks.VOID_AIR.defaultBlockState();
          } else {
--            LevelChunkSection levelChunkSection = this.getSection(this.getSectionIndex(i));
--            return levelChunkSection.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : levelChunkSection.getBlockState(pos.getX() & 15, i & 15, pos.getZ() & 15);
-+            LevelChunkSection levelChunkSection = this.getSections()[this.getSectionIndex(y)];
-+            return levelChunkSection.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : levelChunkSection.getBlockState(x & 15, y & 15, z & 15);
+-            LevelChunkSection section = this.getSection(this.getSectionIndex(y));
+-            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(pos.getX() & 15, y & 15, pos.getZ() & 15);
++            // Paper start
++            LevelChunkSection section = this.getSections()[this.getSectionIndex(y)];
++            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(x & 15, y & 15, z & 15);
++            // Paper end
          }
      }
-+    // Paper end
  
-     @Override
-     public FluidState getFluidState(BlockPos pos) {

From e99a9b5e4a507e0f695561d445ad545c504dbcab Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 13:05:35 +0100
Subject: [PATCH 150/285] Remove cb null check

---
 .../minecraft/server/level/DistanceManager.java.patch | 11 +++++++----
 .../server/level/ServerChunkCache.java.patch          | 10 +++++-----
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
index 0626bbca7a..f2b1b6a10a 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
@@ -78,15 +78,18 @@
      }
  
      private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
-@@ -217,8 +_,9 @@
+@@ -217,8 +_,12 @@
          ChunkPos chunkPos = sectionPos.chunk();
          long packedChunkPos = chunkPos.toLong();
          ObjectSet<ServerPlayer> set = this.playersPerChunk.get(packedChunkPos);
 -        set.remove(player);
 -        if (set.isEmpty()) {
-+        if (set == null) return; // CraftBukkit - SPIGOT-6208
-+        if (set != null) set.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
-+        if (set == null || set.isEmpty()) { // Paper
++        // Paper start - some state corruption happens here, don't crash, clean up gracefully
++        if (set != null) {
++            set.remove(player);
++        }
++        if (set == null || set.isEmpty()) {
++            // Paper end - some state corruption happens here, don't crash, clean up gracefully
              this.playersPerChunk.remove(packedChunkPos);
              this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
              this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
index 7d5ee65f78..725c75ec09 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -131,11 +131,12 @@
          boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
          boolean flag1 = this.chunkMap.promoteChunkMap();
          this.chunkMap.runGenerationTasks();
-@@ -315,17 +_,38 @@
+@@ -315,17 +_,39 @@
  
      @Override
      public void close() throws IOException {
 -        this.save(true);
++        // CraftBukkit start
 +        this.close(true);
 +    }
 +
@@ -209,13 +210,12 @@
  
      @Override
      public void setSpawnSettings(boolean spawnSettings) {
--        this.spawnEnemies = spawnSettings;
--        this.spawnFriendlies = this.spawnFriendlies;
 +        // CraftBukkit start
 +        this.setSpawnSettings(spawnSettings, this.spawnFriendlies);
 +    }
-+    public void setSpawnSettings(boolean spawnEnemies, boolean spawnFriendlies) {
-+        this.spawnEnemies = spawnEnemies;
++    public void setSpawnSettings(boolean spawnSettings, boolean spawnFriendlies) {
+         this.spawnEnemies = spawnSettings;
+-        this.spawnFriendlies = this.spawnFriendlies;
 +        this.spawnFriendlies = spawnFriendlies;
 +        // CraftBukkit end
      }

From b69631ba217650d2dfa70da535923e0e9d52f770 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 14:29:01 +0100
Subject: [PATCH 151/285] Readd dropped ServerScoreboard hunk

---
 .../server/ServerScoreboard.java.patch        | 27 +++++++++++++++----
 .../ai/behavior/InteractWithDoor.java.patch   | 15 +++++------
 2 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
index 32654cff45..5b73435f3a 100644
--- a/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerScoreboard.java.patch
@@ -47,10 +47,27 @@
              } else {
                  this.startTrackingObjective(objective);
              }
-@@ -114,14 +_,42 @@
-         }
-     }
- 
+@@ -104,24 +_,50 @@
+     @Override
+     public boolean addPlayerToTeam(String playerName, PlayerTeam team) {
+         if (super.addPlayerToTeam(playerName, team)) {
+-            this.server
+-                .getPlayerList()
+-                .broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, playerName, ClientboundSetPlayerTeamPacket.Action.ADD));
+-            this.setDirty();
+-            return true;
+-        } else {
+-            return false;
+-        }
+-    }
++            this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, playerName, ClientboundSetPlayerTeamPacket.Action.ADD)); // CraftBukkit
++            this.setDirty();
++            return true;
++        } else {
++            return false;
++        }
++    }
++
 +    // Paper start - Multiple Entries with Scoreboards
 +    public boolean addPlayersToTeam(java.util.Collection<String> players, PlayerTeam team) {
 +        boolean anyAdded = false;
@@ -69,7 +86,7 @@
 +        }
 +    }
 +    // Paper end - Multiple Entries with Scoreboards
-+
+ 
      @Override
      public void removePlayerFromTeam(String username, PlayerTeam playerTeam) {
          super.removePlayerFromTeam(username, playerTeam);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
index a5139c9b02..8f8cc15760 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
@@ -1,16 +1,15 @@
 --- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
 +++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-@@ -58,6 +_,13 @@
+@@ -58,6 +_,12 @@
                              if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
                                  DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
                                  if (!doorBlock.isOpen(blockState)) {
-+                                // CraftBukkit start - entities opening doors
-+                                org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), blockPos));
-+                                    entity.level().getCraftServer().getPluginManager().callEvent(event);
-+                                if (event.isCancelled()) {
-+                                    return false;
-+                                }
-+                                // CraftBukkit end
++                                    // CraftBukkit start - entities opening doors
++                                    org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), blockPos));
++                                    if (!event.callEvent()) {
++                                        return false;
++                                    }
++                                    // CraftBukkit end
                                      doorBlock.setOpen(entity, level, blockState, blockPos, true);
                                  }
  

From eec5ec406ea23f6569de471b62b4f813776cda0d Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 14:59:04 +0100
Subject: [PATCH 152/285] Fix parrot hurtServer call, revert a bunch of
 comments

---
 .../world/entity/animal/Parrot.java.patch     | 12 +++---
 .../world/entity/animal/Turtle.java.patch     |  4 +-
 .../world/entity/animal/Wolf.java.patch       |  2 +-
 .../boss/enderdragon/EndCrystal.java.patch    |  8 ++--
 .../boss/enderdragon/EnderDragon.java.patch   | 40 +++++++++----------
 .../DragonSittingFlamingPhase.java.patch      |  6 +--
 .../phases/EnderDragonPhaseManager.java.patch |  4 +-
 7 files changed, 39 insertions(+), 37 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
index 554ab3d969..863d2bcb91 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Parrot.java.patch
@@ -29,16 +29,18 @@
      }
  
      @Override
-@@ -387,10 +_,11 @@
-     @Override
-     public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
+@@ -389,8 +_,13 @@
          if (this.isInvulnerableTo(level, damageSource)) {
-+            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit
              return false;
          } else {
++            // CraftBukkit start
++            if (!super.hurtServer(level, damageSource, amount)) {
++                return false;
++            }
              this.setOrderedToSit(false);
 -            return super.hurtServer(level, damageSource, amount);
-+            return true; // CraftBukkit
++            return true;
++            // CraftBukkit
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
index 6a7f48e29a..741323b5b1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Turtle.java.patch
@@ -59,7 +59,7 @@
 +                    int eggCount = this.turtle.random.nextInt(4) + 1;
 +                    com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount);
 +                    if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) {
-+                    // Paper end
++                    // Paper end - Turtle API
                      Level level = this.turtle.level();
                      level.playSound(null, blockPos, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + level.random.nextFloat() * 0.2F);
                      BlockPos blockPos1 = this.blockPos.above();
@@ -69,7 +69,7 @@
 +                        .setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper
                      level.setBlock(blockPos1, blockState, 3);
                      level.gameEvent(GameEvent.BLOCK_PLACE, blockPos1, GameEvent.Context.of(this.turtle, blockState));
-+                    } // Paper
++                    } // CraftBukkit
                      this.turtle.setHasEgg(false);
                      this.turtle.setLayingEgg(false);
                      this.turtle.setInLoveTime(600);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
index f162736896..f783d4c772 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
@@ -7,7 +7,7 @@
 +            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit
              this.setOrderedToSit(false);
 -            return super.hurtServer(level, damageSource, amount);
-+            return true; // CraftBUkkit
++            return true; // CraftBukkit
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
index d69e9ebf10..f318988a05 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
@@ -55,15 +55,15 @@
          } else {
              if (!this.isRemoved()) {
 -                this.remove(Entity.RemovalReason.KILLED);
-+                // Paper start - All non-living entities need this
++                // CraftBukkit start - All non-living entities need this
 +                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false)) {
 +                    return false;
 +                }
-+                // Paper end
++                // CraftBukkit end
                  if (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
                      DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.getEntity()) : null;
 -                    level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK);
-+                    // Paper start
++                    // CraftBukkit start
 +                    org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
 +                    if (event.isCancelled()) {
 +                        return false;
@@ -73,7 +73,7 @@
 +                    level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
 +                } else {
 +                    this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
-+                    // Paper end
++                    // CraftBukkit end
                  }
  
                  this.onDestroyedBy(level, damageSource);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
index 95f6a4e90e..beddc1e044 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -34,7 +34,7 @@
 +    public void setPodium(@Nullable BlockPos blockPos) {
 +        this.podium = blockPos;
 +    }
-+    // Paper end
++    // Paper end - Allow changing the EnderDragon podium
 +
      @Override
      public boolean isFlapping() {
@@ -44,7 +44,7 @@
  
                      Vec3 flyTargetLocation = currentPhase.getFlyTargetLocation();
 -                    if (flyTargetLocation != null) {
-+                    if (flyTargetLocation != null && currentPhase.getPhase() != EnderDragonPhase.HOVERING) { // Paper - Don't move when hovering
++                    if (flyTargetLocation != null && currentPhase.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering
                          double d = flyTargetLocation.x - this.getX();
                          double d1 = flyTargetLocation.y - this.getY();
                          double d2 = flyTargetLocation.z - this.getZ();
@@ -53,14 +53,14 @@
                  this.nearestCrystal = null;
              } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
 -                this.setHealth(this.getHealth() + 1.0F);
-+                // Paper start
++                // CraftBukkit start
 +                org.bukkit.event.entity.EntityRegainHealthEvent event = new org.bukkit.event.entity.EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
 +                this.level().getCraftServer().getPluginManager().callEvent(event);
 +
 +                if (!event.isCancelled()) {
 +                    this.setHealth((float) (this.getHealth() + event.getAmount()));
 +                }
-+                // Paper end
++                // CraftBukkit end
              }
          }
  
@@ -86,11 +86,11 @@
                      if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) {
                          if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) {
 -                            flag1 = level.removeBlock(blockPos, false) || flag1;
-+                            // Paper start - Add blocks to list rather than destroying them
++                            // CraftBukkit start - Add blocks to list rather than destroying them
 +                            //flag1 = level.removeBlock(blockPos, false) || flag1;
 +                            flag1 = true;
 +                            destroyedBlocks.add(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos));
-+                            // Paper end
++                            // CraftBukkit end
                          } else {
                              flag = true;
                          }
@@ -98,7 +98,7 @@
              }
          }
  
-+        // Paper start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
++        // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
 +        // SPIGOT-4882: don't fire event if nothing hit
 +        if (!flag1) {
 +            return flag;
@@ -144,7 +144,7 @@
 +                this.level().removeBlock(blockposition, false);
 +            }
 +        }
-+        // Paper end
++        // CraftBukkit end
 +
          if (flag1) {
              BlockPos blockPos1 = new BlockPos(
@@ -161,7 +161,7 @@
 +            this.silentDeath = false; // Reset to default if event was cancelled
 +            return;
 +        }
-+        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
++        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
 +        // Paper end - Fire entity death event
          this.gameEvent(GameEvent.ENTITY_DIE);
          if (this.dragonFight != null) {
@@ -170,7 +170,7 @@
              this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + f, this.getY() + 2.0 + f1, this.getZ() + f2, 0.0, 0.0, 0.0);
          }
  
-+        // Paper start - SPIGOT-2420: Moved up to #getExpReward method
++        // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method
 +        /*
          int i = 500;
          if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
@@ -178,18 +178,18 @@
          }
 +         */
 +        int i = this.expToDrop;
-+        // Paper end
++        // CraftBukkit end
  
          if (this.level() instanceof ServerLevel serverLevel) {
 -            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
 -                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F));
-+            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) {  // Paper - SPIGOT-2420: Already checked for the game rule when calculating the xp
++            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) {  // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
 +                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
              }
  
              if (this.dragonDeathTime == 1 && !this.isSilent()) {
 -                serverLevel.globalLevelEvent(1028, this.blockPosition(), 0);
-+                // Paper start - Use relative location for far away sounds
++                // CraftBukkit start - Use relative location for far away sounds
 +                // serverLevel.globalLevelEvent(1028, this.blockPosition(), 0);
 +                int viewDistance = serverLevel.getCraftServer().getViewDistance() * 16;
 +                for (net.minecraft.server.level.ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
@@ -207,7 +207,7 @@
 +                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
 +                    }
 +                }
-+                // Paper end
++                // CraftBukkit end
              }
          }
  
@@ -226,7 +226,7 @@
              }
  
 -            this.remove(Entity.RemovalReason.KILLED);
-+            this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
++            this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
              this.gameEvent(GameEvent.ENTITY_DIE);
          }
      }
@@ -234,7 +234,7 @@
          super.addAdditionalSaveData(compound);
          compound.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
          compound.putInt("DragonDeathTime", this.dragonDeathTime);
-+        compound.putInt("Bukkit.expToDrop", this.expToDrop); // Paper - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
++        compound.putInt("Bukkit.expToDrop", this.expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
      }
  
      @Override
@@ -243,11 +243,11 @@
              this.dragonDeathTime = compound.getInt("DragonDeathTime");
          }
 +
-+        // Paper start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
++        // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
 +        if (compound.contains("Bukkit.expToDrop")) {
 +            this.expToDrop = compound.getInt("Bukkit.expToDrop");
 +        }
-+        // Paper end
++        // CraftBukkit end
      }
  
      @Override
@@ -265,7 +265,7 @@
          return 1.0F;
      }
 +
-+    // Paper start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
++    // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
 +    @Override
 +    public int getExpReward(ServerLevel worldserver, Entity entity) {
 +        // CraftBukkit - Moved from #tickDeath method
@@ -278,6 +278,6 @@
 +
 +        return flag ? short0 : 0;
 +    }
-+    // Paper end
++    // CraftBukkit end
 +
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
index f6723920d3..61de7efb59 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch
@@ -6,11 +6,11 @@
              this.flame.addEffect(new MobEffectInstance(MobEffects.HARM));
 +            if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events
              level.addFreshEntity(this.flame);
-+            // Paper start
++            // Paper start - EnderDragon Events
 +            } else {
 +                this.end();
 +            }
-+            // Paper end
++            // Paper end - EnderDragon Events
          }
      }
  
@@ -19,7 +19,7 @@
      public void end() {
          if (this.flame != null) {
 -            this.flame.discard();
-+            this.flame.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // Paper- add Bukkit remove cause
++            this.flame.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
              this.flame = null;
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
index 8f1877cd14..a99fa7b9c1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
@@ -4,7 +4,7 @@
                  this.currentPhase.end();
              }
  
-+            // Paper start - Call EnderDragonChangePhaseEvent
++            // CraftBukkit start - Call EnderDragonChangePhaseEvent
 +            org.bukkit.event.entity.EnderDragonChangePhaseEvent event = new org.bukkit.event.entity.EnderDragonChangePhaseEvent(
 +                (org.bukkit.craftbukkit.entity.CraftEnderDragon) this.dragon.getBukkitEntity(),
 +                (this.currentPhase == null) ? null : org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()),
@@ -15,7 +15,7 @@
 +                return;
 +            }
 +            phase = org.bukkit.craftbukkit.entity.CraftEnderDragon.getMinecraftPhase(event.getNewPhase());
-+            // Paper end
++            // CraftBukkit end
 +
              this.currentPhase = this.getPhase((EnderDragonPhase<DragonPhaseInstance>)phase);
              if (!this.dragon.level().isClientSide) {

From 9346db284445325f06bf3a0933c8f14e400dc16d Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 15:26:23 +0100
Subject: [PATCH 153/285] readd blockitem diff

---
 .../minecraft/world/item/BlockItem.java.patch | 107 ++++++++++++++++--
 1 file changed, 96 insertions(+), 11 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
index 1814a4c1bb..dddf4b93f2 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/BlockItem.java.patch
@@ -1,13 +1,98 @@
 --- a/net/minecraft/world/item/BlockItem.java
 +++ b/net/minecraft/world/item/BlockItem.java
-@@ -9,9 +_,9 @@
- import net.minecraft.core.registries.Registries;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
--import net.minecraft.sounds.SoundSource;
- import net.minecraft.world.InteractionResult;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
+@@ -30,6 +_,11 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.CollisionContext;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.event.block.BlockCanBuildEvent;
++// CraftBukkit end
+ 
+ public class BlockItem extends Item {
+     @Deprecated
+@@ -59,6 +_,14 @@
+                 return InteractionResult.FAIL;
+             } else {
+                 BlockState placementState = this.getPlacementState(blockPlaceContext);
++                // CraftBukkit start - special case for handling block placement with water lilies and snow buckets
++                org.bukkit.block.BlockState bukkitState = null;
++                if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) {
++                    bukkitState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockPlaceContext.getLevel(), blockPlaceContext.getClickedPos());
++                }
++                final org.bukkit.block.BlockState oldBukkitState = bukkitState != null ? bukkitState : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockPlaceContext.getLevel(), blockPlaceContext.getClickedPos()); // Paper - Reset placed block on exception
++                // CraftBukkit end
++
+                 if (placementState == null) {
+                     return InteractionResult.FAIL;
+                 } else if (!this.placeBlock(blockPlaceContext, placementState)) {
+@@ -71,15 +_,40 @@
+                     BlockState blockState = level.getBlockState(clickedPos);
+                     if (blockState.is(placementState.getBlock())) {
+                         blockState = this.updateBlockStateFromTag(clickedPos, level, itemInHand, blockState);
++                        // Paper start - Reset placed block on exception
++                        try {
+                         this.updateCustomBlockEntityTag(clickedPos, level, player, itemInHand, blockState);
+                         updateBlockEntityComponents(level, clickedPos, itemInHand);
++                        } catch (Exception ex) {
++                            oldBukkitState.update(true, false);
++                            if (player instanceof ServerPlayer serverPlayer) {
++                                org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), ex);
++                                serverPlayer.getBukkitEntity().kickPlayer("Packet processing error");
++                                return InteractionResult.FAIL;
++                            }
++                            throw ex; // Rethrow exception if not placed by a player
++                        }
++                        // Paper end - Reset placed block on exception
+                         blockState.getBlock().setPlacedBy(level, clickedPos, blockState, player, itemInHand);
++                        // CraftBukkit start
++                        if (bukkitState != null) {
++                            org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent((net.minecraft.server.level.ServerLevel) level, player, blockPlaceContext.getHand(), bukkitState, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
++                            if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++                                bukkitState.update(true, false);
++
++                                // Paper - if the event is called here, the inventory should be updated
++                                player.containerMenu.sendAllDataToRemote(); // SPIGOT-4541
++                                return InteractionResult.FAIL;
++                            }
++                        }
++                        // CraftBukkit end
+                         if (player instanceof ServerPlayer) {
+                             CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand);
+                         }
+                     }
+ 
+                     SoundType soundType = blockState.getSoundType();
++                    if (player == null) // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker)
+                     level.playSound(
+                         player,
+                         clickedPos,
+@@ -140,8 +_,16 @@
+     protected boolean canPlace(BlockPlaceContext context, BlockState state) {
+         Player player = context.getPlayer();
+         CollisionContext collisionContext = player == null ? CollisionContext.empty() : CollisionContext.of(player);
+-        return (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos()))
+-            && context.getLevel().isUnobstructed(state, context.getClickedPos(), collisionContext);
++        // CraftBukkit start
++        Level world = context.getLevel(); // Paper - Cancel hit for vanished players
++        boolean canBuild = (!this.mustSurvive() || state.canSurvive(world, context.getClickedPos())) && world.checkEntityCollision(state, player, collisionContext, context.getClickedPos(), true); // Paper - Cancel hit for vanished players
++        org.bukkit.entity.Player bukkitPlayer = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
++
++        BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, context.getClickedPos()), bukkitPlayer, CraftBlockData.fromData(state), canBuild, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
++        world.getCraftServer().getPluginManager().callEvent(event);
++
++        return event.isBuildable();
++        // CraftBukkit end
+     }
+ 
+     protected boolean mustSurvive() {
+@@ -170,7 +_,7 @@
+                         return false;
+                     }
+ 
+-                    if (!type.onlyOpCanSetNbt() || player != null && player.canUseGameMasterBlocks()) {
++                    if (!type.onlyOpCanSetNbt() || player != null && (player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place")))) { // Spigot - add permission
+                         return customData.loadInto(blockEntity, level.registryAccess());
+                     }
+ 

From ac9ac5e7eadf8a7da8bc2b7abf8ddadd30f335df Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 15:55:13 +0100
Subject: [PATCH 154/285] readd debugstick check

---
 .../world/item/DebugStickItem.java.patch      |  9 +++
 .../minecraft/world/item/EggItem.java.patch   | 48 +++++++------
 .../world/item/EnderEyeItem.java.patch        |  4 +-
 .../world/item/EnderpearlItem.java.patch      | 22 +++---
 .../item/ExperienceBottleItem.java.patch      | 10 ++-
 .../world/item/FireworkRocketItem.java.patch  | 12 ++--
 .../world/item/HangingEntityItem.java.patch   |  4 +-
 .../minecraft/world/item/ItemStack.java.patch | 71 +++++++------------
 .../world/item/MinecartItem.java.patch        |  2 +-
 .../world/item/NameTagItem.java.patch         |  5 +-
 .../world/item/SnowballItem.java.patch        | 17 +++--
 .../world/item/ThrowablePotionItem.java.patch | 12 ++--
 .../world/item/TridentItem.java.patch         |  8 +--
 13 files changed, 111 insertions(+), 113 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
index be32bd3b62..993126f590 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
@@ -5,3 +5,12 @@
  package net.minecraft.world.item;
  
  import java.util.Collection;
+@@ -51,7 +_,7 @@
+     public boolean handleInteraction(
+         Player player, BlockState stateClicked, LevelAccessor accessor, BlockPos pos, boolean shouldCycleState, ItemStack debugStack
+     ) {
+-        if (!player.canUseGameMasterBlocks()) {
++        if (!player.canUseGameMasterBlocks() && !(player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.debugstick")) && !player.getBukkitEntity().hasPermission("minecraft.debugstick.always")) { // Spigot
+             return false;
+         } else {
+             Holder<Block> blockHolder = stateClicked.getBlockHolder();
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
index 59f5e151a8..c1b9e1c9dd 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/EggItem.java.patch
@@ -1,41 +1,49 @@
 --- a/net/minecraft/world/item/EggItem.java
 +++ b/net/minecraft/world/item/EggItem.java
-@@ -23,6 +_,28 @@
+@@ -23,22 +_,36 @@
      @Override
      public InteractionResult use(Level level, Player player, InteractionHand hand) {
          ItemStack itemInHand = player.getItemInHand(hand);
-+        if (level instanceof ServerLevel serverLevel) {
+-        level.playSound(
+-            null,
+-            player.getX(),
+-            player.getY(),
+-            player.getZ(),
+-            SoundEvents.EGG_THROW,
+-            SoundSource.PLAYERS,
+-            0.5F,
+-            0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+-        );
+         if (level instanceof ServerLevel serverLevel) {
+-            Projectile.spawnProjectileFromRotation(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, PROJECTILE_SHOOT_POWER, 1.0F);
 +            // CraftBukkit start
 +            // Paper start - PlayerLaunchProjectileEvent
 +            final Projectile.Delayed<ThrownEgg> thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F);
-+            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity());
++            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity());
 +            if (event.callEvent() && thrownEgg.attemptSpawn()) {
 +                if (event.shouldConsume()) {
 +                    itemInHand.consume(1, player);
-+                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                } else {
++                    player.containerMenu.sendAllDataToRemote();
 +                }
-+                level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++                level.playSound(
++                    null,
++                    player.getX(),
++                    player.getY(),
++                    player.getZ(),
++                    SoundEvents.EGG_THROW,
++                    SoundSource.PLAYERS,
++                    0.5F,
++                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
++                );
 +                player.awardStat(Stats.ITEM_USED.get(this));
 +            } else {
 +                // Paper end - PlayerLaunchProjectileEvent
-+                if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
-+                }
++                player.containerMenu.sendAllDataToRemote();
 +                return InteractionResult.FAIL;
 +            }
 +            // CraftBukkit end
-+        }
-         level.playSound(
-             null,
-             player.getX(),
-@@ -33,12 +_,7 @@
-             0.5F,
-             0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
-         );
--        if (level instanceof ServerLevel serverLevel) {
--            Projectile.spawnProjectileFromRotation(ThrownEgg::new, serverLevel, itemInHand, player, 0.0F, PROJECTILE_SHOOT_POWER, 1.0F);
--        }
+         }
 -
 -        player.awardStat(Stats.ITEM_USED.get(this));
 -        itemInHand.consume(1, player);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
index b2667052ad..1145ec35de 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/EnderEyeItem.java.patch
@@ -32,9 +32,9 @@
 +                        double deltaLength = Math.sqrt(distanceSquared);
 +                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
 +                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, new BlockPos((int) relativeX, (int) soundPos.getY(), (int) relativeZ), 0, true));
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_END_PORTAL_SPAWN, new BlockPos((int) relativeX, (int) soundPos.getY(), (int) relativeZ), 0, true));
 +                    } else {
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1038, soundPos, 0, true));
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_END_PORTAL_SPAWN, soundPos, 0, true));
 +                    }
 +                }
 +                // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch
index 5eccf6e90b..0b1cbebad3 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/EnderpearlItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/EnderpearlItem.java
 +++ b/net/minecraft/world/item/EnderpearlItem.java
-@@ -21,22 +_,32 @@
+@@ -21,22 +_,38 @@
      @Override
      public InteractionResult use(Level level, Player player, InteractionHand hand) {
          ItemStack itemInHand = player.getItemInHand(hand);
@@ -23,21 +23,27 @@
 +            if (event.callEvent() && thrownEnderpearl.attemptSpawn()) {
 +                if (event.shouldConsume()) {
 +                    itemInHand.consume(1, player);
-+                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                } else {
++                    player.containerMenu.sendAllDataToRemote();
 +                }
 +
-+                serverLevel.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (serverLevel.getRandom().nextFloat() * 0.4F + 0.8F));
++                level.playSound(
++                    null,
++                    player.getX(),
++                    player.getY(),
++                    player.getZ(),
++                    SoundEvents.ENDER_PEARL_THROW,
++                    SoundSource.NEUTRAL,
++                    0.5F,
++                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
++                );
 +                player.awardStat(Stats.ITEM_USED.get(this));
 +            } else {
 +            // Paper end - PlayerLaunchProjectileEvent
-+                if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
-+                }
++                player.containerMenu.sendAllDataToRemote();
 +                return InteractionResult.FAIL;
 +            }
          }
-+        level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
 +        // CraftBukkit end
  
 -        player.awardStat(Stats.ITEM_USED.get(this));
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
index 51cfc0c1ed..9ec3ea678d 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ExperienceBottleItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/ExperienceBottleItem.java
 +++ b/net/minecraft/world/item/ExperienceBottleItem.java
-@@ -21,22 +_,38 @@
+@@ -21,22 +_,36 @@
      @Override
      public InteractionResult use(Level level, Player player, InteractionHand hand) {
          ItemStack itemInHand = player.getItemInHand(hand);
@@ -23,8 +23,8 @@
 +            if (event.callEvent() && thrownExperienceBottle.attemptSpawn()) {
 +                if (event.shouldConsume()) {
 +                    itemInHand.consume(1, player);
-+                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                } else {
++                    player.containerMenu.sendAllDataToRemote();
 +                }
 +
 +                level.playSound(
@@ -38,9 +38,7 @@
 +                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
 +                );
 +            } else {
-+                if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
-+                }
++                player.containerMenu.sendAllDataToRemote();
 +                return InteractionResult.FAIL;
 +            }
 +            // Paper end - PlayerLaunchProjectileEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
index a707470352..d733c3578d 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
@@ -21,12 +21,12 @@
 +            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) fireworkRocketEntity.projectile().getBukkitEntity());
 +            if (!event.callEvent() || !fireworkRocketEntity.attemptSpawn()) return InteractionResult.PASS;
 +            if (event.shouldConsume() && !context.getPlayer().hasInfiniteMaterials()) itemInHand.shrink(1);
-+            else if (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ((net.minecraft.server.level.ServerPlayer) context.getPlayer()).getBukkitEntity().updateInventory();
++            else context.getPlayer().containerMenu.sendAllDataToRemote();
 +            // Paper end - PlayerLaunchProjectileEvent
          }
  
          return InteractionResult.SUCCESS;
-@@ -56,9 +_,19 @@
+@@ -56,9 +_,21 @@
          if (player.isFallFlying()) {
              ItemStack itemInHand = player.getItemInHand(hand);
              if (level instanceof ServerLevel serverLevel) {
@@ -40,11 +40,13 @@
 +                    player.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below
 +                    if (event.shouldConsume() && !player.hasInfiniteMaterials()) {
 +                        itemInHand.shrink(1); // Moved up from below
-+                    } else ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                    } else {
++                        player.containerMenu.sendAllDataToRemote();
++                    }
 +                } else {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                    player.containerMenu.sendAllDataToRemote();
 +                }
-+                // Moved up consume/stat
++                // Moved up consume and changed consume to shrink
 +                // Paper end - PlayerElytraBoostEvent
              }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
index f5c2f3dbb7..ae35e0397c 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/HangingEntityItem.java.patch
@@ -5,8 +5,8 @@
              if (hangingEntity.survives()) {
                  if (!level.isClientSide) {
 +                    // CraftBukkit start - fire HangingPlaceEvent
-+                    org.bukkit.entity.Player who = (context.getPlayer() == null) ? null : (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity();
-+                    org.bukkit.block.Block blockClicked = level.getWorld().getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ());
++                    org.bukkit.entity.Player who = player == null ? null : (org.bukkit.entity.Player) player.getBukkitEntity();
++                    org.bukkit.block.Block blockClicked = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos);
 +                    org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(clickedFace);
 +                    org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand());
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
index 24e6257081..4675b2f09b 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
@@ -1,26 +1,5 @@
 --- a/net/minecraft/world/item/ItemStack.java
 +++ b/net/minecraft/world/item/ItemStack.java
-@@ -22,6 +_,7 @@
- import net.minecraft.ChatFormatting;
- import net.minecraft.advancements.CriteriaTriggers;
- import net.minecraft.core.BlockPos;
-+import net.minecraft.core.Direction;
- import net.minecraft.core.Holder;
- import net.minecraft.core.HolderLookup;
- import net.minecraft.core.HolderSet;
-@@ -45,10 +_,12 @@
- import net.minecraft.network.chat.MutableComponent;
- import net.minecraft.network.codec.ByteBufCodecs;
- import net.minecraft.network.codec.StreamCodec;
-+import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
- import net.minecraft.resources.RegistryOps;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
-+import net.minecraft.sounds.SoundSource;
- import net.minecraft.stats.Stats;
- import net.minecraft.tags.TagKey;
- import net.minecraft.util.ExtraCodecs;
 @@ -136,18 +_,35 @@
              } else {
                  Holder<Item> holder = ITEM_STREAM_CODEC.decode(buffer);
@@ -59,14 +38,13 @@
              }
          }
      };
-@@ -365,10 +_,180 @@
+@@ -365,10 +_,178 @@
              return InteractionResult.PASS;
          } else {
              Item item = this.getItem();
 -            InteractionResult interactionResult = item.useOn(context);
-+            // InteractionResult interactionResult = item.useOn(context);
 +            // CraftBukkit start - handle all block place event logic here
-+            DataComponentPatch oldData = this.components.asPatch();
++            DataComponentPatch previousPatch = this.components.asPatch();
 +            int oldCount = this.getCount();
 +            ServerLevel serverLevel = (ServerLevel) context.getLevel();
 +
@@ -83,11 +61,11 @@
 +            } finally {
 +                serverLevel.captureBlockStates = false;
 +            }
-+            DataComponentPatch newData = this.components.asPatch();
++            DataComponentPatch newPatch = this.components.asPatch();
 +            int newCount = this.getCount();
 +            this.setCount(oldCount);
-+            this.restorePatch(oldData);
-+            if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && serverLevel.capturedBlockStates.size() > 0) {
++            this.restorePatch(previousPatch);
++            if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) {
 +                serverLevel.captureTreeGeneration = false;
 +                org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld());
 +                org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
@@ -107,8 +85,8 @@
 +
 +                if (!fertilizeEvent.isCancelled()) {
 +                    // Change the stack to its new contents if it hasn't been tampered with.
-+                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
-+                        this.restorePatch(newData);
++                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) {
++                        this.restorePatch(newPatch);
 +                        this.setCount(newCount);
 +                    }
 +                    for (org.bukkit.craftbukkit.block.CraftBlockState blockstate : blocks) {
@@ -125,15 +103,14 @@
 +            serverLevel.captureTreeGeneration = false;
              if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) {
 -                player.awardStat(Stats.ITEM_USED.get(item));
-+                // player.awardStat(Stats.ITEM_USED.get(item));
-+                InteractionHand enumhand = context.getHand();
++                InteractionHand hand = context.getHand();
 +                org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
 +                List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
 +                serverLevel.capturedBlockStates.clear();
 +                if (blocks.size() > 1) {
-+                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, enumhand, blocks, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
++                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
 +                } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
-+                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, enumhand, blocks.get(0), clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
++                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos.getX(), clickedPos.getY(), clickedPos.getZ());
 +                }
 +
 +                if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
@@ -143,11 +120,11 @@
 +                    serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot
 +                    // revert back all captured blocks
 +                    serverLevel.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
-+                serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
++                    serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
 +                    for (org.bukkit.block.BlockState blockstate : blocks) {
 +                        blockstate.update(true, false);
 +                    }
-+                serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
++                    serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
 +                    serverLevel.preventPoiUpdated = false;
 +
 +                    // Brute force all possible updates
@@ -160,8 +137,8 @@
 +                    SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
 +                } else {
 +                    // Change the stack to its new contents if it hasn't been tampered with.
-+                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), oldData)) {
-+                        this.restorePatch(newData);
++                    if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) {
++                        this.restorePatch(newPatch);
 +                        this.setCount(newCount);
 +                    }
 +
@@ -172,14 +149,14 @@
 +                    for (org.bukkit.block.BlockState blockstate : blocks) {
 +                        int updateFlag = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getFlag();
 +                        net.minecraft.world.level.block.state.BlockState oldBlock = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getHandle();
-+                        BlockPos newblockposition = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition();
-+                        net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newblockposition);
++                        BlockPos newPos = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition();
++                        net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newPos);
 +
 +                        if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically
-+                            block.onPlace(serverLevel, newblockposition, oldBlock, true, context);
++                            block.onPlace(serverLevel, newPos, oldBlock, true, context);
 +                        }
 +
-+                        serverLevel.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, serverLevel.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
++                        serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlag, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point
 +                    }
 +
 +                    if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
@@ -225,12 +202,12 @@
 +
 +                    // SPIGOT-1288 - play sound stripped from ItemBlock
 +                    if (this.item instanceof BlockItem) {
-+                    // Paper start - Fix spigot sound playing for BlockItem ItemStacks
-+                    BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos();
-+                    net.minecraft.world.level.block.state.BlockState blockData = serverLevel.getBlockState(position);
-+                    net.minecraft.world.level.block.SoundType soundeffecttype = blockData.getSoundType();
-+                    // Paper end - Fix spigot sound playing for BlockItem ItemStacks
-+                        serverLevel.playSound(player, clickedPos, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
++                        // Paper start - Fix spigot sound playing for BlockItem ItemStacks
++                        BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos();
++                        net.minecraft.world.level.block.state.BlockState blockData = serverLevel.getBlockState(position);
++                        net.minecraft.world.level.block.SoundType soundeffecttype = blockData.getSoundType();
++                        // Paper end - Fix spigot sound playing for BlockItem ItemStacks
++                        serverLevel.playSound(player, clickedPos, soundeffecttype.getPlaceSound(), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
 +                    }
 +
 +                    player.awardStat(Stats.ITEM_USED.get(item));
diff --git a/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
index 3af3872029..1513242757 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/MinecartItem.java.patch
@@ -7,7 +7,7 @@
 -                    serverLevel.addFreshEntity(abstractMinecart);
 +                    // CraftBukkit start
 +                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, abstractMinecart).isCancelled()) {
-+                    if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
++                        if (context.getPlayer() != null) context.getPlayer().containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
 +                        return InteractionResult.FAIL;
 +                    }
 +                    // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
index 37e96e3a20..f3a064fec1 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/NameTagItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/NameTagItem.java
 +++ b/net/minecraft/world/item/NameTagItem.java
-@@ -18,8 +_,13 @@
+@@ -18,8 +_,14 @@
          Component component = stack.get(DataComponents.CUSTOM_NAME);
          if (component != null && target.getType().canSerialize() && target.canBeNameTagged()) {
              if (!player.level().isClientSide && target.isAlive()) {
@@ -9,10 +9,11 @@
 +                // Paper start - Add PlayerNameEntityEvent
 +                io.papermc.paper.event.player.PlayerNameEntityEvent event = new io.papermc.paper.event.player.PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity(), target.getBukkitLivingEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(stack.getHoverName()), true);
 +                if (!event.callEvent()) return InteractionResult.PASS;
++
 +                LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle();
 +                newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null);
 +                if (event.isPersistent() && newEntity instanceof Mob mob) {
-+                // Paper end - Add PlayerNameEntityEvent
++                    // Paper end - Add PlayerNameEntityEvent
                      mob.setPersistenceRequired();
                  }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
index 77ff25b46f..ca1757fbfe 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/SnowballItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/SnowballItem.java
 +++ b/net/minecraft/world/item/SnowballItem.java
-@@ -23,22 +_,38 @@
+@@ -23,22 +_,41 @@
      @Override
      public InteractionResult use(Level level, Player player, InteractionHand hand) {
          ItemStack itemInHand = player.getItemInHand(hand);
@@ -24,10 +24,10 @@
 +                player.awardStat(Stats.ITEM_USED.get(this));
 +                if (event.shouldConsume()) {
 +                    itemInHand.consume(1, player);
-+                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                } else {
++                    player.containerMenu.sendAllDataToRemote();
 +                }
-+            // Paper end - PlayerLaunchProjectileEvent
++                // Paper end - PlayerLaunchProjectileEvent
 +
 +                level.playSound(
 +                    null,
@@ -39,9 +39,12 @@
 +                    0.5F,
 +                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
 +                );
-+            } else { if (player instanceof net.minecraft.server.level.ServerPlayer) { // Paper - PlayerLaunchProjectileEvent - return fail
-+                ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
-+            } return InteractionResult.FAIL; } // Paper - PlayerLaunchProjectileEvent - return fail
++                // Paper start - PlayerLaunchProjectileEvent - return fail
++            } else {
++                player.containerMenu.sendAllDataToRemote();
++                return InteractionResult.FAIL;
++            }
++            // Paper end- PlayerLaunchProjectileEvent - return fail
 +            // CraftBukkit end
          }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
index b5afefd1a8..220fab6ac3 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ThrowablePotionItem.java.patch
@@ -1,27 +1,23 @@
 --- a/net/minecraft/world/item/ThrowablePotionItem.java
 +++ b/net/minecraft/world/item/ThrowablePotionItem.java
-@@ -22,11 +_,29 @@
+@@ -22,11 +_,25 @@
      public InteractionResult use(Level level, Player player, InteractionHand hand) {
          ItemStack itemInHand = player.getItemInHand(hand);
          if (level instanceof ServerLevel serverLevel) {
 -            Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
-+            // Projectile.spawnProjectileFromRotation(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
 +            // Paper start - PlayerLaunchProjectileEvent
 +            final Projectile.Delayed<ThrownPotion> thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemInHand, player, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F);
-+            // Paper start - PlayerLaunchProjectileEvent
 +            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity());
 +            if (event.callEvent() && thrownPotion.attemptSpawn()) {
 +                if (event.shouldConsume()) {
 +                    itemInHand.consume(1, player);
-+                } else if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++                } else {
++                    player.containerMenu.sendAllDataToRemote();
 +                }
 +
 +                player.awardStat(Stats.ITEM_USED.get(this));
 +            } else {
-+                if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+                    ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
-+                }
++                player.containerMenu.sendAllDataToRemote();
 +                return InteractionResult.FAIL;
 +            }
 +            // Paper end - PlayerLaunchProjectileEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
index b07462cc0f..1e9f3060b3 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/TridentItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/TridentItem.java
 +++ b/net/minecraft/world/item/TridentItem.java
-@@ -87,19 +_,37 @@
+@@ -87,19 +_,35 @@
                          .orElse(SoundEvents.TRIDENT_THROW);
                      player.awardStat(Stats.ITEM_USED.get(this));
                      if (level instanceof ServerLevel serverLevel) {
@@ -15,10 +15,8 @@
 +                            com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity());
 +                            if (!event.callEvent() || !tridentDelayed.attemptSpawn()) {
 +                                // CraftBukkit start
-+                            // Paper end - PlayerLaunchProjectileEvent
-+                                if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
-+                                    serverPlayer.getBukkitEntity().updateInventory();
-+                                }
++                                // Paper end - PlayerLaunchProjectileEvent
++                                player.containerMenu.sendAllDataToRemote();
 +                                return false;
 +                            }
 +                            ThrownTrident thrownTrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent

From 0895318159fd83dca33c93ccb15146006779b672 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 16:09:49 +0100
Subject: [PATCH 155/285] Readd dropped CauldronInteraction hunk

---
 .../com/mojang/brigadier/tree/CommandNode.java.patch      | 4 ++--
 .../core/cauldron/CauldronInteraction.java.patch          | 8 ++++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
index 39c18089dd..1b5e8b98af 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
@@ -38,7 +38,7 @@
      }
  
 -    public boolean canUse(final S source) {
-+    // Paper start
++    // CraftBukkit start
 +    public synchronized boolean canUse(final S source) {
 +        if (source instanceof CommandSourceStack) {
 +            try {
@@ -48,7 +48,7 @@
 +                ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking
 +            }
 +        }
-+        // Paper end
++        // CraftBukkit end
          return requirement.test(source);
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
index 3cadc7f318..46043286c6 100644
--- a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -227,7 +227,7 @@
      ) {
          return (InteractionResult)(isUnderWater(level, pos)
              ? InteractionResult.CONSUME
-@@ -269,50 +_,65 @@
+@@ -269,53 +_,68 @@
                  hand,
                  filledStack,
                  Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, Integer.valueOf(3)),
@@ -298,4 +298,8 @@
 +                // CraftBukkit end
                  stack.remove(DataComponents.DYED_COLOR);
                  player.awardStat(Stats.CLEAN_ARMOR);
-                 LayeredCauldronBlock.lowerFillLevel(state, level, pos);
+-                LayeredCauldronBlock.lowerFillLevel(state, level, pos);
++                // LayeredCauldronBlock.lowerFillLevel(state, level, pos); // CraftBukkit
+             }
+ 
+             return InteractionResult.SUCCESS;

From 1d6c40afacc159b10eeb7752334ee94354d71fe4 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:11:42 +0100
Subject: [PATCH 156/285] readd chunk pos long optimization

---
 .../minecraft/world/level/BaseCommandBlock.java.patch    | 2 +-
 .../net/minecraft/world/level/BaseSpawner.java.patch     | 5 ++---
 .../net/minecraft/world/level/ChunkPos.java.patch        | 9 +++++++++
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
index dc6271b85f..a00c43b741 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch
@@ -17,7 +17,7 @@
                          }
                      });
 -                    server.getCommands().performPrefixedCommand(commandSourceStack, this.command);
-+                    server.getCommands().dispatchServerCommand(commandSourceStack, this.command);  // CraftBukkit
++                    server.getCommands().dispatchServerCommand(commandSourceStack, this.command); // CraftBukkit
                  } catch (Throwable var6) {
                      CrashReport crashReport = CrashReport.forThrowable(var6, "Executing command block");
                      CrashReportCategory crashReportCategory = crashReport.addCategory("Command to be executed");
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
index 78ae4f91d8..273c487386 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
@@ -86,7 +86,7 @@
 +                        flag = true; // Paper
 +                        // CraftBukkit start
 +                        if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
-+                                continue;
++                            continue;
 +                        }
 +                        if (!serverLevel.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
 +                            // CraftBukkit end
@@ -106,12 +106,11 @@
      }
  
      public void load(@Nullable Level level, BlockPos pos, CompoundTag tag) {
--        this.spawnDelay = tag.getShort("Delay");
 +        // Paper start - use larger int if set
 +        if (tag.contains("Paper.Delay")) {
 +            this.spawnDelay = tag.getInt("Paper.Delay");
 +        } else {
-+            this.spawnDelay = tag.getShort("Delay");
+         this.spawnDelay = tag.getShort("Delay");
 +        }
 +        // Paper end
          boolean flag = tag.contains("SpawnData", 10);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
index 1b795bb313..891340e337 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/ChunkPos.java.patch
@@ -28,3 +28,12 @@
      }
  
      public static ChunkPos minFromRegion(int chunkX, int chunkZ) {
+@@ -74,7 +_,7 @@
+     }
+ 
+     public long toLong() {
+-        return asLong(this.x, this.z);
++        return this.longKey; // Paper
+     }
+ 
+     public static long asLong(int x, int z) {

From 6d7c3255f60f4bfe778f818f8dff3aa104e8f31e Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 16:29:38 +0100
Subject: [PATCH 157/285] Remove extra addFreshEntity call in
 DefaultDispenseItemBehavior

---
 .../core/dispenser/DefaultDispenseItemBehavior.java.patch   | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
index fcdecd3dc5..ea53919bd2 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
@@ -52,10 +52,10 @@
          double d = position.x();
          double d1 = position.y();
          double d2 = position.z();
-@@ -44,6 +_,45 @@
+@@ -43,7 +_,45 @@
+             level.random.triangle(0.2, 0.0172275 * speed),
              level.random.triangle(facing.getStepZ() * d3, 0.0172275 * speed)
          );
-         level.addFreshEntity(itemEntity);
 +        return itemEntity; // CraftBukkit
 +    }
 +
@@ -91,7 +91,7 @@
 +            return false;
 +        }
 +
-+        level.addFreshEntity(itemEntity);
+         level.addFreshEntity(itemEntity);
 +
 +        return true;
 +        // CraftBukkit end

From 46eccd8c7d083db2154b64ac1c0ce3cfa255b468 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:40:37 +0100
Subject: [PATCH 158/285] readd cactus config

---
 .../world/level/GameRules.java.patch          |  2 +-
 .../minecraft/world/level/Level.java.patch    | 21 +++++--------------
 .../world/level/block/CactusBlock.java.patch  | 21 +++++++++++++++++++
 3 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
index 2fd51d80b6..79f390f51b 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
@@ -270,7 +270,7 @@
 -            this.onChanged(context.getSource().getServer());
 +        public void setFromArgument(CommandContext<CommandSourceStack> context, String paramName, GameRules.Key<T> gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent
 +            this.updateFromArgument(context, paramName, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
-+            this.onChanged(context.getSource().getLevel());
++            this.onChanged(context.getSource().getLevel()); // CraftBukkit - per-world
          }
  
 -        public void onChanged(@Nullable MinecraftServer server) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index 8a80c15b82..82aaac77b4 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -11,14 +11,6 @@
  import net.minecraft.sounds.SoundEvent;
  import net.minecraft.sounds.SoundEvents;
  import net.minecraft.sounds.SoundSource;
-@@ -42,6 +_,7 @@
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.boss.EnderDragonPart;
- import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
-+import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.alchemy.PotionBrewing;
 @@ -79,6 +_,27 @@
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.scores.Scoreboard;
@@ -71,7 +63,7 @@
 +    public boolean isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
 +    public Map<BlockPos, org.bukkit.craftbukkit.block.CraftBlockState> capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper
 +    public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
-+    public List<ItemEntity> captureDrops;
++    public List<net.minecraft.world.entity.item.ItemEntity> captureDrops;
 +    public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
 +    public boolean populating;
 +    public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
@@ -263,7 +255,7 @@
      public boolean isInWorldBounds(BlockPos pos) {
          return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
      }
-@@ -176,25 +_,88 @@
+@@ -176,21 +_,84 @@
      }
  
      private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
@@ -283,6 +275,7 @@
      @Override
 -    public LevelChunk getChunk(int chunkX, int chunkZ) {
 -        return (LevelChunk)this.getChunk(chunkX, chunkZ, ChunkStatus.FULL);
+-    }
 +    public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
 +        // Paper start - Perf: make sure loaded chunks get the inlined variant of this function
 +        net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
@@ -347,15 +340,11 @@
 +    //  reduces need to do isLoaded before getType
 +    public final @Nullable BlockState getBlockStateIfLoadedAndInBounds(BlockPos blockposition) {
 +        return getWorldBorder().isWithinBounds(blockposition) ? getBlockStateIfLoaded(blockposition) : null;
-     }
++    }
++    // Paper end
  
      @Nullable
      @Override
-     public ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
-+        // Paper end
-         ChunkAccess chunk = this.getChunkSource().getChunk(x, z, chunkStatus, requireChunk);
-         if (chunk == null && requireChunk) {
-             throw new IllegalStateException("Should always be able to create a chunk!");
 @@ -210,6 +_,22 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
index acde2470f8..05f47c53f7 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
@@ -1,5 +1,26 @@
 --- a/net/minecraft/world/level/block/CactusBlock.java
 +++ b/net/minecraft/world/level/block/CactusBlock.java
+@@ -56,14 +_,17 @@
+                 i++;
+             }
+ 
+-            if (i < 3) {
++            if (i < level.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height
+                 int ageValue = state.getValue(AGE);
+-                if (ageValue == 15) {
++
++                int modifier = level.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution
++                if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, this.defaultBlockState()); // CraftBukkit
+                     level.setBlockAndUpdate(blockPos, this.defaultBlockState());
+                     BlockState blockState = state.setValue(AGE, Integer.valueOf(0));
+                     level.setBlock(pos, blockState, 4);
+                     level.neighborChanged(blockState, blockPos, this, null, false);
+-                } else {
++                } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                     level.setBlock(pos, state.setValue(AGE, Integer.valueOf(ageValue + 1)), 4);
+                 }
+             }
 @@ -113,7 +_,8 @@
  
      @Override

From da1947b2ad990aeb564b4ceb35ed4019a75daef1 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:48:03 +0100
Subject: [PATCH 159/285] readd chorus flower events

---
 .../level/block/ChorusFlowerBlock.java.patch  | 32 ++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
index 7909effb3a..907be03335 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
@@ -11,7 +11,7 @@
                  } else if (ageValue < 4) {
                      int i = random.nextInt(4);
                      if (flag1) {
-@@ -112,8 +_,10 @@
+@@ -112,18 +_,28 @@
                          if (level.isEmptyBlock(blockPos1)
                              && level.isEmptyBlock(blockPos1.below())
                              && allNeighborsEmpty(level, blockPos1, randomDirection.getOpposite())) {
@@ -22,3 +22,33 @@
                          }
                      }
  
+                     if (flag2) {
+                         level.setBlock(pos, ChorusPlantBlock.getStateWithConnections(level, pos, this.plant.defaultBlockState()), 2);
+                     } else {
++                        // CraftBukkit start - add event
++                        if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
+                         this.placeDeadFlower(level, pos);
++                        }
++                        // CraftBukkit end
+                     }
+                 } else {
++                    // CraftBukkit start - add event
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
+                     this.placeDeadFlower(level, pos);
++                    }
++                    // CraftBukkit end
+                 }
+             }
+         }
+@@ -261,6 +_,11 @@
+     protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+         BlockPos blockPos = hit.getBlockPos();
+         if (level instanceof ServerLevel serverLevel && projectile.mayInteract(serverLevel, blockPos) && projectile.mayBreak(serverLevel)) {
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockPos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
++                return;
++            }
++            // CraftBukkit end
+             level.destroyBlock(blockPos, true, projectile);
+         }
+     }

From a67d7adcc97b66c7ca459cdd2cab03efb737ca50 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 16:58:28 +0100
Subject: [PATCH 160/285] Remove extra shrink in ProjectileDispenseBehavior

---
 .../core/dispenser/ProjectileDispenseBehavior.java.patch     | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
index 8f98c3092d..73ef78c798 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
 +++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
-@@ -27,16 +_,39 @@
+@@ -27,17 +_,39 @@
          ServerLevel serverLevel = blockSource.level();
          Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
          Position dispensePosition = this.dispenseConfig.positionFunction().getDispensePosition(blockSource, direction);
@@ -14,6 +14,7 @@
 -            this.dispenseConfig.power(),
 -            this.dispenseConfig.uncertainty()
 -        );
+-        item.shrink(1);
 +        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
 +        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
 +        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
@@ -47,6 +48,6 @@
 +        }
 +        if (shrink) item.shrink(1); // Paper - actually handle here
 +        // CraftBukkit end
-         item.shrink(1);
          return item;
      }
+ 

From 7b4afa8b825c606f08219b823a0b1a7df5ac9d09 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 17:27:16 +0100
Subject: [PATCH 161/285] readd EntityInsideBlockEvent for pitcher crop

---
 .../cauldron/CauldronInteraction.java.patch   |  8 +++---
 .../level/block/CauldronBlock.java.patch      |  8 +++---
 .../world/level/block/CommandBlock.java.patch |  2 +-
 .../level/block/DetectorRailBlock.java.patch  |  2 +-
 .../level/block/DragonEggBlock.java.patch     | 10 +++----
 .../world/level/block/FireBlock.java.patch    |  2 +-
 .../block/LayeredCauldronBlock.java.patch     | 28 ++++++-------------
 .../level/block/PitcherCropBlock.java.patch   |  8 ++++++
 8 files changed, 32 insertions(+), 36 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
index 46043286c6..a6f2415f52 100644
--- a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -23,7 +23,7 @@
              if (potionContents != null && potionContents.is(Potions.WATER)) {
                  if (!level.isClientSide) {
 +                    // CraftBukkit start
-+                    if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++                    if (!LayeredCauldronBlock.changeLevel(level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
 +                        return InteractionResult.SUCCESS;
 +                    }
 +                    // CraftBukkit end
@@ -82,7 +82,7 @@
                  if (potionContents != null && potionContents.is(Potions.WATER)) {
                      if (!level.isClientSide) {
 +                        // CraftBukkit start
-+                        if (!LayeredCauldronBlock.changeLevel(state, level, pos, state.cycle(LayeredCauldronBlock.LEVEL), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++                        if (!LayeredCauldronBlock.changeLevel(level, pos, state.cycle(LayeredCauldronBlock.LEVEL), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
 +                            return InteractionResult.SUCCESS;
 +                        }
 +                        // CraftBukkit end
@@ -145,7 +145,7 @@
 +                }
 +                // Paper end - fire PlayerBucketFillEvent
 +                // CraftBukkit start
-+                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
++                if (!LayeredCauldronBlock.changeLevel(level, pos, Blocks.CAULDRON.defaultBlockState(), player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
 +                    return InteractionResult.SUCCESS;
 +                }
 +                // CraftBukkit end
@@ -179,7 +179,7 @@
 +            }
 +            // Paper end - fire PlayerBucketEmptyEvent
 +            // CraftBukkit start
-+            if (!LayeredCauldronBlock.changeLevel(state, level, pos, state, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
++            if (!LayeredCauldronBlock.changeLevel(level, pos, state, player, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
 +                return InteractionResult.SUCCESS;
 +            }
 +            // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
index e420669453..84db4f9fcc 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CauldronBlock.java.patch
@@ -5,7 +5,7 @@
          if (shouldHandlePrecipitation(level, precipitation)) {
              if (precipitation == Biome.Precipitation.RAIN) {
 +                // Paper start - Call CauldronLevelChangeEvent
-+                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++                if (!LayeredCauldronBlock.changeLevel(level, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
 +                    return;
 +                }
 +                // Paper end - Call CauldronLevelChangeEvent
@@ -13,7 +13,7 @@
                  level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos);
              } else if (precipitation == Biome.Precipitation.SNOW) {
 +                // Paper start - Call CauldronLevelChangeEvent
-+                if (!LayeredCauldronBlock.changeLevel(state, level, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++                if (!LayeredCauldronBlock.changeLevel(level, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
 +                    return;
 +                }
 +                // Paper end - Call CauldronLevelChangeEvent
@@ -27,7 +27,7 @@
 -            level.setBlockAndUpdate(pos, blockState);
 -            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
 +            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
-+            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++            if (!LayeredCauldronBlock.changeLevel(level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
 +                return;
 +            }
 +            // Paper end - Call CauldronLevelChangeEvent
@@ -37,7 +37,7 @@
 -            level.setBlockAndUpdate(pos, blockState);
 -            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
 +            // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
-+            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++            if (!LayeredCauldronBlock.changeLevel(level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
 +                return;
 +            }
 +            // Paper end - Call CauldronLevelChangeEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
index f285e643af..a1cc0188d9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CommandBlock.java.patch
@@ -21,7 +21,7 @@
      protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
          BlockEntity blockEntity = level.getBlockEntity(pos);
 -        if (blockEntity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) {
-+        if (blockEntity instanceof CommandBlockEntity  && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
++        if (blockEntity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission
              player.openCommandBlock((CommandBlockEntity)blockEntity);
              return InteractionResult.SUCCESS;
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
index 3dcaa1506e..d65731b15f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DetectorRailBlock.java.patch
@@ -12,7 +12,7 @@
  
      private void checkPressed(Level level, BlockPos pos, BlockState state) {
          if (this.canSurvive(state, level, pos)) {
-+            if (state.getBlock() != this) { return; } // Paper - Fix some rails connecting improperly
++            if (!state.is(this)) { return; } // Paper - Fix some rails connecting improperly
              boolean poweredValue = state.getValue(POWERED);
              boolean flag = false;
              List<AbstractMinecart> interactingMinecartOfType = this.getInteractingMinecartOfType(level, pos, AbstractMinecart.class, entity -> true);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
index 4d35f93eec..235b049f77 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DragonEggBlock.java.patch
@@ -1,16 +1,14 @@
 --- a/net/minecraft/world/level/block/DragonEggBlock.java
 +++ b/net/minecraft/world/level/block/DragonEggBlock.java
-@@ -55,6 +_,18 @@
+@@ -55,6 +_,16 @@
                  level.random.nextInt(16) - level.random.nextInt(16)
              );
              if (level.getBlockState(blockPos).isAir() && worldBorder.isWithinBounds(blockPos)) {
 +                // CraftBukkit start
-+                org.bukkit.block.Block from = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+                org.bukkit.block.Block to = level.getWorld().getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ());
++                org.bukkit.block.Block from = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++                org.bukkit.block.Block to = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos);
 +                org.bukkit.event.block.BlockFromToEvent event = new org.bukkit.event.block.BlockFromToEvent(from, to);
-+                org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+
-+                if (event.isCancelled()) {
++                if (!event.callEvent()) {
 +                    return;
 +                }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
index ac0a574cbf..ebd1c8425d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FireBlock.java.patch
@@ -132,7 +132,7 @@
                  level.setBlock(pos, this.getStateWithAge(level, pos, min), 3);
              } else {
 -                level.removeBlock(pos, false);
-+                if(blockState.getBlock() != Blocks.TNT) level.removeBlock(pos, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down
++                if (!blockState.is(Blocks.TNT)) level.removeBlock(pos, false); // Paper - TNTPrimeEvent; We might be cancelling it below, move the setAir down
              }
  
              Block block = blockState.getBlock();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
index 3bd7696353..b676bda200 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
 +++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
-@@ -61,35 +_,79 @@
+@@ -61,35 +_,69 @@
  
      @Override
      protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
@@ -12,7 +12,7 @@
 +            // CraftBukkit start - moved down
 +            // entity.clearFire();
 +            if ((entity instanceof net.minecraft.world.entity.player.Player || serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(serverLevel, pos)) { // Paper - Fixes MC-248588
-+                if (this.handleEntityOnFireInsideWithEvent(state, level, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities
++                if (this.handleEntityOnFireInside(state, level, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities
 +                    entity.clearFire();
 +                }
 +            // CraftBukkit end
@@ -22,7 +22,6 @@
  
 -    private void handleEntityOnFireInside(BlockState state, Level level, BlockPos pos) {
 +    // CraftBukkit start
-+    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent
 +    private boolean handleEntityOnFireInside(BlockState state, Level level, BlockPos pos, Entity entity) {
          if (this.precipitationType == Biome.Precipitation.SNOW) {
 -            lowerFillLevel(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), level, pos);
@@ -46,26 +45,17 @@
 +        int i = (Integer) state.getValue(LayeredCauldronBlock.LEVEL) - 1;
 +        BlockState iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) state.setValue(LayeredCauldronBlock.LEVEL, i);
 +
-+        return LayeredCauldronBlock.changeLevel(state, level, BlockPos, iblockdata1, entity, reason);
++        return LayeredCauldronBlock.changeLevel(level, BlockPos, iblockdata1, entity, reason);
 +     }
-+    // Paper start - fix powdered snow cauldron extinguishing entities
-+    protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) {
-+        if (this.precipitationType == Biome.Precipitation.SNOW) {
-+            return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-+        } else {
-+            return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
-+        }
-+    }
-+    // Paper end - fix powdered snow cauldron extinguishing entities
 +
 +    // CraftBukkit start
 +    // Paper start - Call CauldronLevelChangeEvent
-+    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
-+        return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true);
++    public static boolean changeLevel(Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
++        return changeLevel(world, blockposition, newBlock, entity, reason, true);
 +    }
 +
-+    public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
-+    // Paper end - Call CauldronLevelChangeEvent
++    public static boolean changeLevel(Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
++        // Paper end - Call CauldronLevelChangeEvent
 +        org.bukkit.craftbukkit.block.CraftBlockState newState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition);
 +        newState.setData(newBlock);
 +
@@ -89,7 +79,7 @@
              BlockState blockState = state.cycle(LEVEL);
 -            level.setBlockAndUpdate(pos, blockState);
 -            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
-+            LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
++            LayeredCauldronBlock.changeLevel(level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
          }
      }
  
@@ -100,7 +90,7 @@
 -            level.setBlockAndUpdate(pos, blockState);
 -            level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
 +            // CraftBukkit start
-+            if (!LayeredCauldronBlock.changeLevel(state, level, pos, blockState, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) {
++            if (!LayeredCauldronBlock.changeLevel(level, pos, blockState, null, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) {
 +                return;
 +            }
 +            // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
index 174bccf67d..7015f932e1 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PitcherCropBlock.java.patch
@@ -1,5 +1,13 @@
 --- a/net/minecraft/world/level/block/PitcherCropBlock.java
 +++ b/net/minecraft/world/level/block/PitcherCropBlock.java
+@@ -107,6 +_,7 @@
+ 
+     @Override
+     public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
++        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+         if (level instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+             serverLevel.destroyBlock(pos, true, entity);
+         }
 @@ -131,7 +_,7 @@
      @Override
      public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {

From a5b1875b2d22015cef507eeb7717b9fd3ec70cf8 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 18:47:15 +0100
Subject: [PATCH 162/285] readd conduit effect cause

---
 .../level/biome/MobSpawnSettings.java.patch   |  2 +-
 .../block/LayeredCauldronBlock.java.patch     | 17 ++++++-----
 .../block/PointedDripstoneBlock.java.patch    |  6 ++--
 .../world/level/block/SaplingBlock.java.patch |  2 +-
 .../level/block/ScaffoldingBlock.java.patch   |  2 +-
 .../level/block/SculkSpreader.java.patch      | 13 ++-------
 .../level/block/ShulkerBoxBlock.java.patch    |  2 +-
 .../world/level/block/SpongeBlock.java.patch  | 28 +++++++++----------
 .../level/block/TripWireHookBlock.java.patch  |  2 +-
 .../AbstractFurnaceBlockEntity.java.patch     | 10 +------
 .../entity/BeehiveBlockEntity.java.patch      |  2 +-
 .../block/entity/BellBlockEntity.java.patch   |  2 +-
 .../level/block/entity/BlockEntity.java.patch |  7 +----
 .../entity/BrewingStandBlockEntity.java.patch | 14 ++--------
 .../entity/CampfireBlockEntity.java.patch     |  4 ++-
 .../entity/ConduitBlockEntity.java.patch      | 10 ++++++-
 16 files changed, 49 insertions(+), 74 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
index eaf222a4f8..1033724319 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/biome/MobSpawnSettings.java.patch
@@ -38,7 +38,7 @@
          private final Map<MobCategory, List<MobSpawnSettings.SpawnerData>> spawners = Stream.of(MobCategory.values())
 -            .collect(ImmutableMap.toImmutableMap(key -> (MobCategory)key, value -> Lists.newArrayList()));
 +            .collect(Maps.toImmutableEnumMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> new MobList())); // Use MobList instead of ArrayList
-+            // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls
++        // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls
          private final Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap();
          private float creatureGenerationProbability = 0.1F;
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
index b676bda200..f4af9face8 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
 +++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
-@@ -61,35 +_,69 @@
+@@ -61,35 +_,68 @@
  
      @Override
      protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
@@ -50,25 +50,24 @@
 +
 +    // CraftBukkit start
 +    // Paper start - Call CauldronLevelChangeEvent
-+    public static boolean changeLevel(Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
-+        return changeLevel(world, blockposition, newBlock, entity, reason, true);
++    public static boolean changeLevel(Level world, BlockPos pos, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
++        return changeLevel(world, pos, newBlock, entity, reason, true);
 +    }
 +
-+    public static boolean changeLevel(Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
++    public static boolean changeLevel(Level world, BlockPos pos, BlockState newBlock, @javax.annotation.Nullable Entity entity, org.bukkit.event.block.CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
 +        // Paper end - Call CauldronLevelChangeEvent
-+        org.bukkit.craftbukkit.block.CraftBlockState newState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, blockposition);
++        org.bukkit.craftbukkit.block.CraftBlockState newState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(world, pos);
 +        newState.setData(newBlock);
 +
 +        org.bukkit.event.block.CauldronLevelChangeEvent event = new org.bukkit.event.block.CauldronLevelChangeEvent(
-+                world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()),
++                org.bukkit.craftbukkit.block.CraftBlock.at(world, pos),
 +                (entity == null) ? null : entity.getBukkitEntity(), reason, newState
 +        );
-+        world.getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
++        if (!event.callEvent()) {
 +            return false;
 +        }
 +        newState.update(true);
-+        if (sendGameEvent) world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent
++        if (sendGameEvent) world.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent
 +        return true;
 +    }
 +    // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
index b998179c3b..98e5a6861e 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
@@ -21,12 +21,12 @@
          } else {
              super.fallOn(level, state, pos, entity, fallDistance);
          }
-@@ -213,10 +_,12 @@
+@@ -213,10 +_,11 @@
                          if (blockPos != null) {
                              if (fluidAboveStalactite.get().sourceState.is(Blocks.MUD) && fluid == Fluids.WATER) {
                                  BlockState blockState = Blocks.CLAY.defaultBlockState();
+-                                level.setBlockAndUpdate(fluidAboveStalactite.get().pos, blockState);
 +                                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, fluidAboveStalactite.get().pos, blockState)) { // Paper - Call BlockFormEvent
-                                 level.setBlockAndUpdate(fluidAboveStalactite.get().pos, blockState);
                                  Block.pushEntitiesUp(fluidAboveStalactite.get().sourceState, blockState, level, fluidAboveStalactite.get().pos);
                                  level.gameEvent(GameEvent.BLOCK_CHANGE, fluidAboveStalactite.get().pos, GameEvent.Context.of(blockState));
                                  level.levelEvent(1504, blockPos, 0);
@@ -39,7 +39,7 @@
              createMergedTips(blockState, server, blockPos);
          } else if (blockState.isAir() || blockState.is(Blocks.WATER)) {
 -            createDripstone(server, blockPos, direction, DripstoneThickness.TIP);
-+            createDripstone(server, blockPos, direction, DripstoneThickness.TIP, pos); // CraftBukkit;
++            createDripstone(server, blockPos, direction, DripstoneThickness.TIP, pos); // CraftBukkit
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
index 9cb7276a2c..e165ace2fb 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch
@@ -29,7 +29,7 @@
 +                level.captureTreeGeneration = true;
 +                this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
 +                level.captureTreeGeneration = false;
-+                if (level.capturedBlockStates.size() > 0) {
++                if (!level.capturedBlockStates.isEmpty()) {
 +                    org.bukkit.TreeType treeType = SaplingBlock.treeType;
 +                    SaplingBlock.treeType = null;
 +                    org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld());
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
index 8ad4e9b5f3..fceeff2d27 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
@@ -5,7 +5,7 @@
          int distance = getDistance(level, pos);
          BlockState blockState = state.setValue(DISTANCE, Integer.valueOf(distance)).setValue(BOTTOM, Boolean.valueOf(this.isBottom(level, pos, distance)));
 -        if (blockState.getValue(DISTANCE) == 7) {
-+        if (blockState.getValue(DISTANCE) == 7&& !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, blockState.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state
++        if (blockState.getValue(DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, blockState.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit - BlockFadeEvent // Paper - fix wrong block state
              if (state.getValue(DISTANCE) == 7) {
                  FallingBlockEntity.fall(level, pos, blockState);
              } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
index 07f35283e7..1d958f6ac9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkSpreader.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/block/SculkSpreader.java
 +++ b/net/minecraft/world/level/block/SculkSpreader.java
-@@ -25,6 +_,7 @@
- import net.minecraft.core.Vec3i;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
 @@ -50,6 +_,7 @@
      private final int additionalDecayRate;
      private List<SculkSpreader.ChargeCursor> cursors = new ArrayList<>();
@@ -25,7 +17,7 @@
              }
          }
      }
-@@ -130,13 +_,25 @@
+@@ -130,13 +_,24 @@
      public void addCursors(BlockPos pos, int charge) {
          while (charge > 0) {
              int min = Math.min(charge, 1000);
@@ -42,8 +34,7 @@
 +            if (!this.isWorldGeneration() && fireEvent) { // CraftBukkit - SPIGOT-7475: Don't call event during world generation // Paper - add boolean to conditionally fire SculkBloomEvent
 +                org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, cursor.pos);
 +                org.bukkit.event.block.SculkBloomEvent event = new org.bukkit.event.block.SculkBloomEvent(bukkitBlock, cursor.getCharge());
-+                org.bukkit.Bukkit.getPluginManager().callEvent(event);
-+                if (event.isCancelled()) {
++                if (!event.callEvent()) {
 +                    return;
 +                }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
index ebbcb447db..44b9126421 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
@@ -7,7 +7,7 @@
 -            && canOpen(state, level, pos, shulkerBoxBlockEntity)) {
 -            player.openMenu(shulkerBoxBlockEntity);
 +            && canOpen(state, level, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check
-+            && player.openMenu(shulkerBoxBlockEntity).isPresent()) {  // Paper - Fix InventoryOpenEvent cancellation) {
++            && player.openMenu(shulkerBoxBlockEntity).isPresent()) {  // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.OPEN_SHULKER_BOX);
              PiglinAi.angerNearbyPiglins(serverLevel, player, true);
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
index f39e9bfdf1..cba02b7710 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SpongeBlock.java.patch
@@ -33,7 +33,7 @@
                              } else {
                                  if (!blockState.is(Blocks.KELP)
                                      && !blockState.is(Blocks.KELP_PLANT)
-@@ -81,16 +_,57 @@
+@@ -81,16 +_,55 @@
                                      return BlockPos.TraversalNodeStatus.SKIP;
                                  }
  
@@ -58,36 +58,34 @@
 +        // CraftBukkit start
 +        java.util.List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = blockList.getList(); // Is a clone
 +        if (!blocks.isEmpty()) {
-+            final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++            final org.bukkit.block.Block sponge = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
 +
-+            org.bukkit.event.block.SpongeAbsorbEvent event = new org.bukkit.event.block.SpongeAbsorbEvent(bblock, (java.util.List<org.bukkit.block.BlockState>) (java.util.List) blocks);
-+            level.getCraftServer().getPluginManager().callEvent(event);
-+
-+            if (event.isCancelled()) {
++            org.bukkit.event.block.SpongeAbsorbEvent event = new org.bukkit.event.block.SpongeAbsorbEvent(sponge, (java.util.List<org.bukkit.block.BlockState>) (java.util.List) blocks);
++            if (!event.callEvent()) {
 +                return false;
 +            }
 +
 +            for (org.bukkit.craftbukkit.block.CraftBlockState block : blocks) {
-+                BlockPos blockposition1 = block.getPosition();
-+                BlockState iblockdata = level.getBlockState(blockposition1);
-+                FluidState fluid = level.getFluidState(blockposition1);
++                BlockPos blockPos = block.getPosition();
++                BlockState state = level.getBlockState(blockPos);
++                FluidState fluid = level.getFluidState(blockPos);
 +
 +                if (fluid.is(FluidTags.WATER)) {
-+                    if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock(null, blockList, blockposition1, iblockdata).isEmpty()) {
++                    if (state.getBlock() instanceof BucketPickup bucketPickup && !bucketPickup.pickupBlock(null, blockList, blockPos, state).isEmpty()) {
 +                        // NOP
-+                    } else if (iblockdata.getBlock() instanceof LiquidBlock) {
++                    } else if (state.getBlock() instanceof LiquidBlock) {
 +                        // NOP
-+                    } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) {
-+                        BlockEntity tileentity = iblockdata.hasBlockEntity() ? level.getBlockEntity(blockposition1) : null;
++                    } else if (state.is(Blocks.KELP) || state.is(Blocks.KELP_PLANT) || state.is(Blocks.SEAGRASS) || state.is(Blocks.TALL_SEAGRASS)) {
++                        BlockEntity blockEntity = state.hasBlockEntity() ? level.getBlockEntity(blockPos) : null;
 +
 +                        // Paper start - Fix SpongeAbsortEvent handling
 +                        if (block.getHandle().isAir()) {
-+                        dropResources(iblockdata, level, blockposition1, tileentity);
++                        dropResources(state, level, blockPos, blockEntity);
 +                        }
 +                        // Paper end - Fix SpongeAbsortEvent handling
 +                    }
 +                }
-+                level.setBlock(blockposition1, block.getHandle(), block.getFlag());
++                level.setBlock(blockPos, block.getHandle(), block.getFlag());
 +            }
 +
 +            return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
index 8a437b9c80..661176a03d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/TripWireHookBlock.java.patch
@@ -15,7 +15,7 @@
  
              emitState(level, pos, flag2, flag3, flag, flag1);
              if (!attaching) {
-+                if (level.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - Validate tripwire hook placement before update
++                if (level.getBlockState(pos).is(Blocks.TRIPWIRE_HOOK)) // Paper - Validate tripwire hook placement before update
                  level.setBlock(pos, blockState1.setValue(FACING, direction), 3);
                  if (shouldNotifyNeighbours) {
                      notifyNeighbors(block, level, pos, direction);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
index 622ee5f7ef..1359ae7732 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
-@@ -20,7 +_,6 @@
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.WorldlyContainer;
- import net.minecraft.world.entity.ExperienceOrb;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.player.StackedItemContents;
- import net.minecraft.world.inventory.ContainerData;
- import net.minecraft.world.inventory.RecipeCraftingHolder;
 @@ -99,11 +_,44 @@
      };
      public final Reference2IntOpenHashMap<ResourceKey<Recipe<?>>> recipesUsed = new Reference2IntOpenHashMap<>();
@@ -209,7 +201,7 @@
 +            /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */
 +            : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, singleRecipeInput, level).map(holder -> holder.value().cookingTime()).orElse(200));
 +        return (int) Math.ceil (cookTime / cookSpeedMultiplier);
-+        // Paper end - cook speed multiplier AP
++        // Paper end - cook speed multiplier API
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
index 2ab50074f8..4826839013 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
@@ -238,7 +238,7 @@
              }
  
              bee.setInLoveTime(Math.max(0, bee.getInLoveTime() - ticksInHive));
-+            }  // Paper - Honor ageLock
++            } // Paper - Honor ageLock
          }
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
index 0f09f0acaf..810b2c47e5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
@@ -45,7 +45,7 @@
          return raider.isAlive() && !raider.isRemoved() && pos.closerToCenterThan(raider.position(), 48.0) && raider.getType().is(EntityTypeTags.RAIDERS);
      }
  
-+    @io.papermc.paper.annotation.DoNotUse // Paper - Add BellRevealRaiderEvent
++    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BellRevealRaiderEvent
      private static void glow(LivingEntity entity) {
 +        // Paper start - Add BellRevealRaiderEvent
 +        glow(entity, null);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
index 8ed4390459..c54e142ea3 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch
@@ -71,7 +71,7 @@
              CrashReportCategory.populateBlockDetails(reportCategory, this.level, this.worldPosition, this.level.getBlockState(this.worldPosition));
          }
      }
-@@ -247,10 +_,16 @@
+@@ -247,6 +_,12 @@
      }
  
      public final void applyComponents(DataComponentMap components, DataComponentPatch patch) {
@@ -84,11 +84,6 @@
          final Set<DataComponentType<?>> set = new HashSet<>();
          set.add(DataComponents.BLOCK_ENTITY_DATA);
          set.add(DataComponents.BLOCK_STATE);
--        final DataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
-+        final PatchedDataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(components, patch);
-         this.applyImplicitComponents(new BlockEntity.DataComponentInput() {
-             @Nullable
-             @Override
 @@ -267,6 +_,10 @@
          });
          DataComponentPatch dataComponentPatch = patch.forget(set::contains);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
index c96cf8d8fd..f3cb6d6470 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
-@@ -8,7 +_,6 @@
- import net.minecraft.core.NonNullList;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
--import net.minecraft.tags.ItemTags;
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.Containers;
- import net.minecraft.world.WorldlyContainer;
 @@ -36,6 +_,7 @@
      public static final int NUM_DATA_VALUES = 2;
      private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
@@ -76,14 +68,12 @@
  
      public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
          super(BlockEntityType.BREWING_STAND, pos, state);
-@@ -92,9 +_,22 @@
- 
+@@ -93,8 +_,21 @@
      public static void serverTick(Level level, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) {
          ItemStack itemStack = blockEntity.items.get(4);
--        if (blockEntity.fuel <= 0 && itemStack.is(ItemTags.BREWING_FUEL)) {
+         if (blockEntity.fuel <= 0 && itemStack.is(ItemTags.BREWING_FUEL)) {
 -            blockEntity.fuel = 20;
 -            itemStack.shrink(1);
-+        if (blockEntity.fuel <= 0 && itemStack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)) {
 +            // CraftBukkit start
 +            org.bukkit.event.inventory.BrewingStandFuelEvent event = new org.bukkit.event.inventory.BrewingStandFuelEvent(
 +                org.bukkit.craftbukkit.block.CraftBlock.at(level, pos),
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
index 3d53a28694..f9fd2a5136 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
@@ -8,7 +8,7 @@
  
      public CampfireBlockEntity(BlockPos pos, BlockState blockState) {
          super(BlockEntityType.CAMPFIRE, pos, blockState);
-@@ -54,14 +_,42 @@
+@@ -54,14 +_,44 @@
              ItemStack itemStack = campfire.items.get(i);
              if (!itemStack.isEmpty()) {
                  flag = true;
@@ -18,10 +18,12 @@
                  if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) {
                      SingleRecipeInput singleRecipeInput = new SingleRecipeInput(itemStack);
 -                    ItemStack itemStack1 = check.getRecipeFor(singleRecipeInput, level)
++                    // Paper start - add recipe to cook events
 +                    final var optionalCookingRecipe = check.getRecipeFor(singleRecipeInput, level);
 +                    ItemStack itemStack1 = optionalCookingRecipe
                          .map(recipe -> recipe.value().assemble(singleRecipeInput, level.registryAccess()))
                          .orElse(itemStack);
++                    // Paper end - add recipe to cook events
                      if (itemStack1.isItemEnabled(level.enabledFeatures())) {
 -                        Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), itemStack1);
 +                        // CraftBukkit start - fire BlockCookEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
index 2ca6be14cc..72506d9166 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
@@ -29,7 +29,15 @@
          int x = pos.getX();
          int y = pos.getY();
          int z = pos.getZ();
-@@ -185,6 +_,12 @@
+@@ -178,13 +_,19 @@
+         if (!entitiesOfClass.isEmpty()) {
+             for (Player player : entitiesOfClass) {
+                 if (pos.closerThan(player.blockPosition(), i) && player.isInWaterOrRain()) {
+-                    player.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true));
++                    player.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONDUIT); // CraftBukkit
+                 }
+             }
+         }
      }
  
      private static void updateDestroyTarget(Level level, BlockPos pos, BlockState state, List<BlockPos> positions, ConduitBlockEntity blockEntity) {

From 56e3949c612cea275ca3a4601f1ec161ac0af8c2 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 18:57:22 +0100
Subject: [PATCH 163/285] call the right damage event for conduit damages

---
 .../world/level/block/entity/ConduitBlockEntity.java.patch      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
index 72506d9166..31b86a4f4f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
@@ -56,7 +56,7 @@
  
 -        if (blockEntity.destroyTarget != null) {
 +        if (damageTarget && blockEntity.destroyTarget != null) { // CraftBukkit
-+            if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic(), 4.0F)) // CraftBukkit
++            if (blockEntity.destroyTarget.hurtServer((net.minecraft.server.level.ServerLevel) level, level.damageSources().magic().directBlock(level, pos), 4.0F)) // CraftBukkit
              level.playSound(
                  null,
                  blockEntity.destroyTarget.getX(),

From 62b6e85d1a83f3d6d0f7b2d61dff9c0f25bdd071 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 19:57:44 +0100
Subject: [PATCH 164/285] prevent duplicate EntityInsideBlockEvent in fire

---
 .../world/level/block/AbstractCandleBlock.java.patch   | 10 +++++-----
 .../world/level/block/BambooStalkBlock.java.patch      |  4 ++--
 .../world/level/block/BaseFireBlock.java.patch         |  3 +--
 .../block/entity/ContainerOpenersCounter.java.patch    |  6 +++---
 .../level/block/entity/CrafterBlockEntity.java.patch   |  2 +-
 .../level/block/entity/HopperBlockEntity.java.patch    |  9 +++++----
 .../level/block/entity/JukeboxBlockEntity.java.patch   |  2 +-
 .../level/block/entity/LecternBlockEntity.java.patch   |  2 +-
 .../level/block/entity/SignBlockEntity.java.patch      |  4 ++--
 .../level/block/entity/SkullBlockEntity.java.patch     |  4 ++--
 10 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
index 978c35863e..c641aa16f1 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
@@ -4,11 +4,11 @@
      @Override
      protected void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
          if (!level.isClientSide && projectile.isOnFire() && this.canBeLit(state)) {
-+           // CraftBukkit start
-+           if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, hit.getBlockPos(), projectile).isCancelled()) {
-+               return;
-+           }
-+           // CraftBukkit end
++            // CraftBukkit start
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, hit.getBlockPos(), projectile).isCancelled()) {
++                return;
++            }
++            // CraftBukkit end
              setLit(level, state, hit.getBlockPos(), true);
          }
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
index 8560405814..c5c9ef2c74 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BambooStalkBlock.java.patch
@@ -44,8 +44,8 @@
 -                    level.setBlock(pos.below(), blockState.setValue(LEAVES, BambooLeaves.SMALL), 3);
 -                    level.setBlock(blockPos, blockState1.setValue(LEAVES, BambooLeaves.NONE), 3);
 +                    // CraftBukkit start - moved down
-+                    // world.setBlock(blockposition.below(), (IBlockData) iblockdata1.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.SMALL), 3);
-+                    // world.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.NONE), 3);
++                    // level.setBlock(pos.below(), blockState.setValue(LEAVES, BambooLeaves.SMALL), 3);
++                    // level.setBlock(blockPos, blockState1.setValue(LEAVES, BambooLeaves.NONE), 3);
 +                    shouldUpdateOthers = true;
 +                    // CraftBukkit end
                  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
index aa29a091f9..a9273622c5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BaseFireBlock.java.patch
@@ -8,11 +8,10 @@
  import net.minecraft.world.level.BlockGetter;
  import net.minecraft.world.level.Level;
  import net.minecraft.world.level.block.state.BlockBehaviour;
-@@ -128,6 +_,8 @@
+@@ -128,6 +_,7 @@
  
      @Override
      protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
-+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
 +        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
          if (!entity.fireImmune()) {
              if (entity.getRemainingFireTicks() < 0) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
index 2f6c3c484c..6d0edb5d1d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
@@ -4,7 +4,7 @@
      private static final int CHECK_TICK_DELAY = 5;
      private int openCount;
      private double maxInteractionRange;
-+    public boolean opened; // CraftBukki
++    public boolean opened; // CraftBukkit
  
      protected abstract void onOpen(Level level, BlockPos pos, BlockState state);
  
@@ -24,7 +24,7 @@
 +    public void openerAPICountChanged(Level level, BlockPos blockPos, BlockState blockState, int count, int openCount) {
 +        this.openerCountChanged(level, blockPos, blockState, count, openCount);
 +    }
-+    // CraftBukkit en
++    // CraftBukkit end
 +
      protected abstract boolean isOwnContainer(Player player);
  
@@ -40,7 +40,7 @@
 +                org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
 +            }
 +        }
-+        // CraftBukkit en
++        // CraftBukkit end
 +
          if (i == 0) {
              this.onOpen(level, pos, state);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
index 212d7b7f55..537c7fdff2 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
@@ -43,7 +43,7 @@
 +        if (this.level == null) return null;
 +        return io.papermc.paper.util.MCUtil.toLocation(this.level, this.worldPosition);
 +    }
-+    // CraftBukkit en
++    // CraftBukkit end
 +
      public CrafterBlockEntity(BlockPos pos, BlockState state) {
          super(BlockEntityType.CRAFTER, pos, state);
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
index f8fb1daa33..ebb7abcb76 100644
--- 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
@@ -122,7 +122,7 @@
                      return true;
                  }
              }
-@@ -239,18 +_,54 @@
+@@ -239,18 +_,56 @@
          }
      }
  
@@ -175,12 +175,14 @@
  
              item.setCount(count);
 -            if (count == 1) {
++            // Spigot start
 +            item.shrink(origCount - itemStack.getCount());
 +            if (count <= level.spigotConfig.hopperAmount) {
++                // Spigot end
                  container.setItem(slot, item);
              }
          }
-@@ -260,12 +_,21 @@
+@@ -260,12 +_,20 @@
  
      public static boolean addItem(Container container, ItemEntity item) {
          boolean flag = false;
@@ -188,8 +190,7 @@
 +        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()) {
++        if (!event.callEvent()) {
 +            return false;
 +        }
 +        // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index 3051c3b415..53cf63280b 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -65,7 +65,7 @@
 -            .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, 0L));
 -        this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
 +        this.jukeboxSongPlayer.song = null; // CraftBukkit - reset
-+        JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), stack) // Paper - fallback to other RegistyrAccess if no level
++        JukeboxSong.fromStack(this.level != null ? this.level.registryAccess() : org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry(), stack) // Paper - fallback to other RegistryAccess if no level
 +            .ifPresent(holder -> this.jukeboxSongPlayer.setSongWithoutPlaying((Holder<JukeboxSong>)holder, ticksSinceSongStarted)); // CraftBukkit - passed ticks since song started
 +        // CraftBukkit start - add null check for level
 +        if (this.level != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
index ccef0d0f0c..3ce197ece6 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -72,7 +72,7 @@
          @Override
          public int getMaxStackSize() {
 -            return 1;
-+            return maxStack;
++            return this.maxStack; // CraftBukkit
          }
  
          @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
index a656788ab2..b84d89679a 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
@@ -30,7 +30,7 @@
              this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
          } else {
              LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
-+            if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < 32 * 32) // Paper - Dont send far away sign update
++            if (player.distanceToSqr(this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ()) < Mth.square(32)) // Paper - Don't send far away sign update
 +            ((net.minecraft.server.level.ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
          }
      }
@@ -158,7 +158,7 @@
 +        } : this.commandSource;
 +        // Paper end - Fix commands from signs not firing command events
 +        // CraftBukkit - this
-+        return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, string, (Component) component, world.getServer(), player); // Paper - Fix commands from signs not firing command events
++        return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, string, component, world.getServer(), player); // Paper - Fix commands from signs not firing command events
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
index 9261f76fc9..c28bc38459 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
@@ -29,14 +29,14 @@
 +                    LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById; // Paper - player profile events
                      return loadingCache != null && !optional.isEmpty()
 -                        ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional1 -> optional1.or(() -> optional))
-+                        ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional1 -> optional1.or(() -> optional))  // Paper - player profile events
++                        ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional1 -> optional1.or(() -> optional)) // Paper - player profile events
                          : CompletableFuture.completedFuture(Optional.empty());
                  }
              );
      }
  
 -    static CompletableFuture<Optional<GameProfile>> fetchProfileById(UUID id, Services services, BooleanSupplier cacheUninitialized) {
-+    static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id, Services services, BooleanSupplier cacheUninitialized) { // Pape
++    static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> id, Services services, BooleanSupplier cacheUninitialized) { // Paper
          return CompletableFuture.supplyAsync(() -> {
              if (cacheUninitialized.getAsBoolean()) {
                  return Optional.empty();

From 8cca65b8acd2e3a4f6ecb5cb9eea7ebdf23ec744 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 11:42:40 -0800
Subject: [PATCH 165/285] remove all the rest of the import hunks

---
 .../server/level/ServerLevel.java.patch       |  47 +----
 .../players/GameProfileCache.java.patch       |  21 +-
 .../minecraft/world/entity/Entity.java.patch  | 187 +++++++-----------
 .../world/entity/item/ItemEntity.java.patch   |  86 ++------
 .../inventory/BrewingStandMenu.java.patch     |  15 --
 .../chunk/storage/ChunkStorage.java.patch     |  40 ++--
 .../LegacyStructureDataHandler.java.patch     |  17 +-
 .../structure/StructurePiece.java.patch       |   9 -
 .../structures/IglooPieces.java.patch         |   9 -
 .../structures/OceanRuinPieces.java.patch     |   9 -
 .../structures/StrongholdPieces.java.patch    |   8 -
 .../storage/LevelStorageSource.java.patch     |   8 -
 12 files changed, 102 insertions(+), 354 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 20b894a4b0..3845d17a64 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -1,46 +1,11 @@
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -54,7 +_,6 @@
- import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
- import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
- import net.minecraft.network.protocol.game.ClientboundExplodePacket;
--import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
- import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
- import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
- import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
-@@ -120,6 +_,7 @@
- import net.minecraft.world.level.StructureManager;
- import net.minecraft.world.level.WorldGenLevel;
- import net.minecraft.world.level.biome.Biome;
-+import net.minecraft.world.level.biome.BiomeSource;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.SnowLayerBlock;
-@@ -145,7 +_,9 @@
- import net.minecraft.world.level.gameevent.DynamicGameEventListener;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.gameevent.GameEventDispatcher;
-+import net.minecraft.world.level.levelgen.FlatLevelSource;
- import net.minecraft.world.level.levelgen.Heightmap;
-+import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
- import net.minecraft.world.level.levelgen.structure.BoundingBox;
- import net.minecraft.world.level.levelgen.structure.Structure;
- import net.minecraft.world.level.levelgen.structure.StructureCheck;
-@@ -161,7 +_,7 @@
- import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- import net.minecraft.world.level.storage.LevelStorageSource;
--import net.minecraft.world.level.storage.ServerLevelData;
-+import net.minecraft.world.level.storage.PrimaryLevelData;
- import net.minecraft.world.phys.AABB;
- import net.minecraft.world.phys.Vec3;
- import net.minecraft.world.phys.shapes.BooleanOp;
 @@ -182,7 +_,7 @@
      final List<ServerPlayer> players = Lists.newArrayList();
      public final ServerChunkCache chunkSource;
      private final MinecraftServer server;
 -    public final ServerLevelData serverLevelData;
-+    public final PrimaryLevelData serverLevelData; // CraftBukkit - type
++    public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type
      private int lastSpawnChunkRadius;
      final EntityTickList entityTickList = new EntityTickList();
      public final PersistentEntitySectionManager<Entity> entityManager;
@@ -204,11 +169,11 @@
 +        this.serverLevelData.setWorld(this);
 +
 +        if (biomeProvider != null) {
-+            BiomeSource worldChunkManager = new org.bukkit.craftbukkit.generator.CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkGenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider
-+            if (chunkGenerator instanceof NoiseBasedChunkGenerator cga) {
-+                chunkGenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
-+            } else if (chunkGenerator instanceof FlatLevelSource cpf) {
-+                chunkGenerator = new FlatLevelSource(cpf.settings(), worldChunkManager);
++            net.minecraft.world.level.biome.BiomeSource worldChunkManager = new org.bukkit.craftbukkit.generator.CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkGenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider
++            if (chunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator cga) {
++                chunkGenerator = new net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
++            } else if (chunkGenerator instanceof net.minecraft.world.level.levelgen.FlatLevelSource cpf) {
++                chunkGenerator = new net.minecraft.world.level.levelgen.FlatLevelSource(cpf.settings(), worldChunkManager);
 +            }
 +        }
 +
diff --git a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
index aa0d442fc8..37842106ce 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
@@ -1,14 +1,5 @@
 --- a/net/minecraft/server/players/GameProfileCache.java
 +++ b/net/minecraft/server/players/GameProfileCache.java
-@@ -17,8 +_,6 @@
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.IOException;
--import java.io.Reader;
--import java.io.Writer;
- import java.nio.charset.StandardCharsets;
- import java.text.DateFormat;
- import java.text.ParseException;
 @@ -56,6 +_,10 @@
      private final AtomicLong operationCount = new AtomicLong();
      @Nullable
@@ -135,15 +126,6 @@
      }
  
      public void setExecutor(Executor exectutor) {
-@@ -193,7 +_,7 @@
- 
-         try {
-             Object var9;
--            try (Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) {
-+            try (java.io.Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) {
-                 JsonArray jsonArray = this.gson.fromJson(reader, JsonArray.class);
-                 if (jsonArray != null) {
-                     DateFormat dateFormat = createDateFormat();
 @@ -206,6 +_,11 @@
  
              return (List<GameProfileCache.GameProfileInfo>)var9;
@@ -169,8 +151,7 @@
          String string = this.gson.toJson((JsonElement)jsonArray);
 +        Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving
  
--        try (Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
-+        try (java.io.Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
+         try (Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) {
              writer.write(string);
          } catch (IOException var9) {
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 06d3a35857..4dfa987afe 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -1,63 +1,8 @@
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -55,6 +_,8 @@
- import net.minecraft.network.protocol.Packet;
- import net.minecraft.network.protocol.game.ClientGamePacketListener;
- import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
- import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
- import net.minecraft.network.protocol.game.VecDeltaCodec;
- import net.minecraft.network.syncher.EntityDataAccessor;
-@@ -97,8 +_,6 @@
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.ClipContext;
- import net.minecraft.world.level.Explosion;
--import net.minecraft.world.level.ItemLike;
--import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Block;
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.FenceGateBlock;
-@@ -135,7 +_,151 @@
- import net.minecraft.world.scores.Team;
+@@ -136,6 +_,114 @@
  import org.slf4j.Logger;
  
-+// CraftBukkit start
-+import net.minecraft.world.level.GameRules;
-+import net.minecraft.world.level.ItemLike;
-+import net.minecraft.world.level.Level;
-+import org.bukkit.Bukkit;
-+import org.bukkit.Location;
-+import org.bukkit.block.BlockFace;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.entity.Hanging;
-+import org.bukkit.entity.Vehicle;
-+import org.bukkit.event.entity.EntityCombustByEntityEvent;
-+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
-+import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
-+import org.bukkit.event.vehicle.VehicleEnterEvent;
-+import org.bukkit.event.vehicle.VehicleExitEvent;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.bukkit.craftbukkit.entity.CraftEntity;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.craftbukkit.event.CraftPortalEvent;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.event.entity.EntityAirChangeEvent;
-+import org.bukkit.event.entity.EntityCombustEvent;
-+import org.bukkit.event.entity.EntityDismountEvent;
-+import org.bukkit.event.entity.EntityDropItemEvent;
-+import org.bukkit.event.entity.EntityMountEvent;
-+import org.bukkit.event.entity.EntityPortalEvent;
-+import org.bukkit.event.entity.EntityPoseChangeEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.entity.EntityTeleportEvent;
-+import org.bukkit.event.entity.EntityUnleashEvent;
-+import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
-+import org.bukkit.event.player.PlayerTeleportEvent;
-+import org.bukkit.plugin.PluginManager;
-+// CraftBukkit end
-+
  public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder {
 +
 +    // CraftBukkit start
@@ -141,14 +86,14 @@
 +    // Paper end - Share random for entities to make them more random
 +    public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
 +
-+    private CraftEntity bukkitEntity;
++    private org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
 +
-+    public CraftEntity getBukkitEntity() {
++    public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
 +        if (this.bukkitEntity == null) {
 +            // Paper start - Folia schedulers
 +            synchronized (this) {
 +                if (this.bukkitEntity == null) {
-+                    return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this);
++                    return this.bukkitEntity = org.bukkit.craftbukkit.entity.CraftEntity.getEntity(this.level.getCraftServer(), this);
 +                }
 +            }
 +            // Paper end - Folia schedulers
@@ -156,7 +101,7 @@
 +        return this.bukkitEntity;
 +    }
 +    // Paper start
-+    public CraftEntity getBukkitEntityRaw() {
++    public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntityRaw() {
 +        return this.bukkitEntity;
 +    }
 +    // Paper end
@@ -226,7 +171,7 @@
 +    public boolean fixedPose = false; // Paper - Expand Pose API
 +    private final int despawnTime; // Paper - entity despawn time limit
 +
-+    public void setOrigin(@javax.annotation.Nonnull Location location) {
++    public void setOrigin(@javax.annotation.Nonnull org.bukkit.Location location) {
 +        this.origin = location.toVector();
 +        this.originWorld = location.getWorld().getUID();
 +    }
@@ -293,7 +238,7 @@
  
      public void kill(ServerLevel level) {
 -        this.remove(Entity.RemovalReason.KILLED);
-+        this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
++        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
          this.gameEvent(GameEvent.ENTITY_DIE);
      }
  
@@ -303,7 +248,7 @@
 +        this.discard(null);
 +    }
 +
-+    public final void discard(EntityRemoveEvent.Cause cause) {
++    public final void discard(org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        this.remove(Entity.RemovalReason.DISCARDED, cause);
 +        // CraftBukkit end
      }
@@ -318,7 +263,7 @@
 +        List<SynchedEntityData.DataValue<?>> list = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default
 +
 +        if (list != null && to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper
-+            to.connection.send(new ClientboundSetEntityDataPacket(this.getId(), list));
++            to.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(this.getId(), list));
 +        }
 +    }
 +    // CraftBukkit end
@@ -351,7 +296,7 @@
 +            values.add(synchedValue.value());
 +        }
 +
-+        to.connection.send(new ClientboundSetEntityDataPacket(this.id, values));
++        to.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(this.id, values));
 +    }
 +    // Paper end
 +
@@ -367,7 +312,7 @@
 +        this.setRemoved(reason, null);
 +    }
 +
-+    public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        this.setRemoved(entity_removalreason, cause);
 +        // CraftBukkit end
      }
@@ -385,7 +330,7 @@
 +        }
 +        // Paper start - Don't fire sync event during generation
 +        if (!this.generation) {
-+            this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), org.bukkit.entity.Pose.values()[pose.ordinal()]));
++            this.level.getCraftServer().getPluginManager().callEvent(new org.bukkit.event.entity.EntityPoseChangeEvent(this.getBukkitEntity(), org.bukkit.entity.Pose.values()[pose.ordinal()]));
 +        }
 +        // Paper end - Don't fire sync event during generation
 +        // CraftBukkit end
@@ -405,7 +350,7 @@
 +        if (yRot == Float.POSITIVE_INFINITY || yRot == Float.NEGATIVE_INFINITY) {
 +            if (this instanceof ServerPlayer) {
 +                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw");
-+                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
++                ((org.bukkit.craftbukkit.entity.CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
 +            }
 +            yRot = 0;
 +        }
@@ -418,7 +363,7 @@
 +        if (xRot == Float.POSITIVE_INFINITY || xRot == Float.NEGATIVE_INFINITY) {
 +            if (this instanceof ServerPlayer) {
 +                this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch");
-+                ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
++                ((org.bukkit.craftbukkit.entity.CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
 +            }
 +            xRot = 0;
 +        }
@@ -519,7 +464,7 @@
 +                // not on fire yet
 +                org.bukkit.block.Block damager = (this.lastLavaContact == null) ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact);
 +                org.bukkit.entity.Entity damagee = this.getBukkitEntity();
-+                EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
++                org.bukkit.event.entity.EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
 +                this.level.getCraftServer().getPluginManager().callEvent(combustEvent);
 +
 +                if (!combustEvent.isCancelled()) {
@@ -547,7 +492,7 @@
 +
 +    public final void igniteForSeconds(float f, boolean callEvent) {
 +        if (callEvent) {
-+            EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), f);
++            org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustEvent(this.getBukkitEntity(), f);
 +            this.level.getCraftServer().getPluginManager().callEvent(event);
 +
 +            if (event.isCancelled()) {
@@ -566,7 +511,7 @@
  
      protected void onBelowWorld() {
 -        this.discard();
-+        this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
++        this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
      }
  
      public boolean isFree(double x, double y, double z) {
@@ -583,22 +528,22 @@
                  }
  
 +                // CraftBukkit start
-+                if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) {
-+                    Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++                if (this.horizontalCollision && this.getBukkitEntity() instanceof org.bukkit.entity.Vehicle) {
++                    org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
 +                    org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
 +
 +                    if (movement.x > vec3.x) {
-+                        bl = bl.getRelative(BlockFace.EAST);
++                        bl = bl.getRelative(org.bukkit.block.BlockFace.EAST);
 +                    } else if (movement.x < vec3.x) {
-+                        bl = bl.getRelative(BlockFace.WEST);
++                        bl = bl.getRelative(org.bukkit.block.BlockFace.WEST);
 +                    } else if (movement.z > vec3.z) {
-+                        bl = bl.getRelative(BlockFace.SOUTH);
++                        bl = bl.getRelative(org.bukkit.block.BlockFace.SOUTH);
 +                    } else if (movement.z < vec3.z) {
-+                        bl = bl.getRelative(BlockFace.NORTH);
++                        bl = bl.getRelative(org.bukkit.block.BlockFace.NORTH);
 +                    }
 +
 +                    if (!bl.getType().isAir()) {
-+                        VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity
++                        org.bukkit.event.vehicle.VehicleBlockCollisionEvent event = new org.bukkit.event.vehicle.VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity
 +                        this.level.getCraftServer().getPluginManager().callEvent(event);
 +                    }
 +                }
@@ -1021,8 +966,8 @@
 +        {
 +            // Paper end - Call EntityDropItemEvent
 +            // CraftBukkit start
-+            EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
-+            Bukkit.getPluginManager().callEvent(event);
++            org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
++            org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +            if (event.isCancelled()) {
 +                return null;
 +            }
@@ -1037,10 +982,10 @@
 -                    if (player.hasInfiniteMaterials()) {
 +                    // CraftBukkit start - fire PlayerUnleashEntityEvent
 +                    // Paper start - Expand EntityUnleashEvent
-+                    org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
++                    org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
 +                    if (event.isCancelled()) {
 +                        // Paper end - Expand EntityUnleashEvent
-+                        ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
++                        ((ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
 +                        return InteractionResult.PASS;
 +                    }
 +                    // CraftBukkit end
@@ -1053,9 +998,9 @@
              if (itemInHand.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
                  if (!this.level().isClientSide()) {
 +                    // CraftBukkit start - fire PlayerLeashEntityEvent
-+                    if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
 +                        // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync
-+                        ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
++                        ((ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
 +                        player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
 +                        return InteractionResult.PASS;
 +                    }
@@ -1082,21 +1027,21 @@
  
              if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
 +                // CraftBukkit start
-+                if (vehicle.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
-+                    VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
++                if (vehicle.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && this.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
++                    org.bukkit.event.vehicle.VehicleEnterEvent event = new org.bukkit.event.vehicle.VehicleEnterEvent((org.bukkit.entity.Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
 +                    // Suppress during worldgen
 +                    if (this.valid) {
-+                        Bukkit.getPluginManager().callEvent(event);
++                        org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +                    }
 +                    if (event.isCancelled()) {
 +                        return false;
 +                    }
 +                }
 +
-+                EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), vehicle.getBukkitEntity());
++                org.bukkit.event.entity.EntityMountEvent event = new org.bukkit.event.entity.EntityMountEvent(this.getBukkitEntity(), vehicle.getBukkitEntity());
 +                // Suppress during worldgen
 +                if (this.valid) {
-+                    Bukkit.getPluginManager().callEvent(event);
++                    org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +                }
 +                if (event.isCancelled()) {
 +                    return false;
@@ -1149,28 +1094,28 @@
              throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
          } else {
 +            // CraftBukkit start
-+            CraftEntity craft = (CraftEntity) passenger.getBukkitEntity().getVehicle();
++            org.bukkit.craftbukkit.entity.CraftEntity craft = (org.bukkit.craftbukkit.entity.CraftEntity) passenger.getBukkitEntity().getVehicle();
 +            Entity orig = craft == null ? null : craft.getHandle();
-+            if (this.getBukkitEntity() instanceof Vehicle && passenger.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
-+                VehicleExitEvent event = new VehicleExitEvent(
-+                        (Vehicle) this.getBukkitEntity(),
++            if (this.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && passenger.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
++                org.bukkit.event.vehicle.VehicleExitEvent event = new org.bukkit.event.vehicle.VehicleExitEvent(
++                        (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
 +                        (org.bukkit.entity.LivingEntity) passenger.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation
 +                );
 +                // Suppress during worldgen
 +                if (this.valid) {
-+                    Bukkit.getPluginManager().callEvent(event);
++                    org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +                }
-+                CraftEntity craftn = (CraftEntity) passenger.getBukkitEntity().getVehicle();
++                org.bukkit.craftbukkit.entity.CraftEntity craftn = (org.bukkit.craftbukkit.entity.CraftEntity) passenger.getBukkitEntity().getVehicle();
 +                Entity n = craftn == null ? null : craftn.getHandle();
 +                if (event.isCancelled() || n != orig) {
 +                    return false;
 +                }
 +            }
 +
-+            EntityDismountEvent event = new EntityDismountEvent(passenger.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
++            org.bukkit.event.entity.EntityDismountEvent event = new org.bukkit.event.entity.EntityDismountEvent(passenger.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
 +            // Suppress during worldgen
 +            if (this.valid) {
-+                Bukkit.getPluginManager().callEvent(event);
++                org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +            }
 +            if (event.isCancelled()) {
 +                return false;
@@ -1220,7 +1165,7 @@
      public void setSwimming(boolean swimming) {
 +        // CraftBukkit start
 +        if (this.valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) {
-+            if (CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) {
 +                return;
 +            }
 +        }
@@ -1264,7 +1209,7 @@
      public void setAirSupply(int air) {
 -        this.entityData.set(DATA_AIR_SUPPLY_ID, air);
 +        // CraftBukkit start
-+        EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air);
++        org.bukkit.event.entity.EntityAirChangeEvent event = new org.bukkit.event.entity.EntityAirChangeEvent(this.getBukkitEntity(), air);
 +        // Suppress during worldgen
 +        if (this.valid) {
 +            event.getEntity().getServer().getPluginManager().callEvent(event);
@@ -1285,7 +1230,7 @@
 +        // CraftBukkit start
 +        final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
 +        final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity();
-+        final PluginManager pluginManager = Bukkit.getPluginManager();
++        final org.bukkit.plugin.PluginManager pluginManager = org.bukkit.Bukkit.getPluginManager();
 +        // CraftBukkit end
          if (this.remainingFireTicks == 0) {
 -            this.igniteForSeconds(8.0F);
@@ -1293,7 +1238,7 @@
 -
 -        this.hurtServer(level, this.damageSources().lightningBolt(), 5.0F);
 +            // CraftBukkit start - Call a combust event when lightning strikes
-+            EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F);
++            org.bukkit.event.entity.EntityCombustByEntityEvent entityCombustEvent = new org.bukkit.event.entity.EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F);
 +            pluginManager.callEvent(entityCombustEvent);
 +            if (!entityCombustEvent.isCancelled()) {
 +                this.igniteForSeconds(entityCombustEvent.getDuration(), false);
@@ -1306,8 +1251,8 @@
 +        }
 +
 +        // CraftBukkit start
-+        if (thisBukkitEntity instanceof Hanging) {
-+            HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity);
++        if (thisBukkitEntity instanceof org.bukkit.entity.Hanging) {
++            org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity);
 +            pluginManager.callEvent(hangingEvent);
 +
 +            if (hangingEvent.isCancelled()) {
@@ -1365,7 +1310,7 @@
  
      public void restoreFrom(Entity entity) {
 +        // Paper start - Forward CraftEntity in teleport command
-+        CraftEntity bukkitEntity = entity.bukkitEntity;
++        org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = entity.bukkitEntity;
 +        if (bukkitEntity != null) {
 +            bukkitEntity.setHandle(this);
 +            this.bukkitEntity = bukkitEntity;
@@ -1388,14 +1333,14 @@
 +            // CraftBukkit start
 +            PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
 +            Vec3 velocity = absolutePosition.deltaMovement(); // Paper
-+            Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTransition.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
++            org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(absolutePosition.position(), teleportTransition.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
 +            // Paper start - gateway-specific teleport event
-+            final EntityTeleportEvent teleEvent;
++            final org.bukkit.event.entity.EntityTeleportEvent teleEvent;
 +            if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
 +                teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity));
 +                teleEvent.callEvent();
 +            } else {
-+                teleEvent = CraftEventFactory.callEntityTeleportEvent(this, to);
++                teleEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, to);
 +            }
 +            // Paper end - gateway-specific teleport event
 +            if (teleEvent.isCancelled() || teleEvent.getTo() == null) {
@@ -1403,12 +1348,12 @@
 +            }
 +            if (!to.equals(teleEvent.getTo())) {
 +                to = teleEvent.getTo();
-+                teleportTransition = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
++                teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
 +                // Paper start - Call EntityPortalExitEvent
 +                velocity = Vec3.ZERO;
 +            }
 +            if (this.portalProcess != null) { // if in a portal
-+                CraftEntity bukkitEntity = this.getBukkitEntity();
++                org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = this.getBukkitEntity();
 +                org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
 +                    bukkitEntity,
 +                    bukkitEntity.getLocation(), to.clone(),
@@ -1420,7 +1365,7 @@
 +                if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) {
 +                    to = event.getTo().clone();
 +                    velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
-+                    teleportTransition = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
++                    teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
 +                }
 +            }
 +            if (this.isRemoved()) {
@@ -1462,7 +1407,7 @@
 +        this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause
 +        if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed
 +            // Paper start - Expand EntityUnleashEvent
-+            final EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit
++            final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.UNKNOWN, false); // CraftBukkit
 +            event.callEvent();
 +            if (!event.isDropLeash()) {
 +                leashable.removeLeash();
@@ -1478,9 +1423,9 @@
      }
  
 +    // CraftBukkit start
-+    public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
++    public org.bukkit.craftbukkit.event.CraftPortalEvent callPortalEvent(Entity entity, org.bukkit.Location exit, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
 +        org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
-+        Location enter = bukkitEntity.getLocation();
++        org.bukkit.Location enter = bukkitEntity.getLocation();
 +
 +        // Paper start
 +        final org.bukkit.PortalType portalType = switch (cause) {
@@ -1489,13 +1434,13 @@
 +            case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet
 +            default -> org.bukkit.PortalType.CUSTOM;
 +        };
-+        EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType);
++        org.bukkit.event.entity.EntityPortalEvent event = new org.bukkit.event.entity.EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType);
 +        // Paper end
 +        event.getEntity().getServer().getPluginManager().callEvent(event);
 +        if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) {
 +            return null;
 +        }
-+        return new CraftPortalEvent(event);
++        return new org.bukkit.craftbukkit.event.CraftPortalEvent(event);
 +    }
 +    // CraftBukkit end
 +
@@ -1515,7 +1460,7 @@
 -    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
 +    // CraftBukkit start
 +    public final boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
-+        return this.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN);
++        return this.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
 +    }
 +    public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
 +        // CraftBukkit end
@@ -1629,13 +1574,13 @@
 +        }
 +
 +        @Override
-+        public CommandSender getBukkitSender(CommandSourceStack wrapper) {
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
 +            return Entity.this.getBukkitEntity();
 +        }
 +
 +        @Override
 +        public boolean acceptsSuccess() {
-+            return ((ServerLevel) Entity.this.level()).getGameRules().getBoolean(GameRules.RULE_SENDCOMMANDFEEDBACK);
++            return ((ServerLevel) Entity.this.level()).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_SENDCOMMANDFEEDBACK);
 +        }
 +
 +        @Override
@@ -1731,8 +1676,8 @@
 +    }
 +
 +    @Override
-+    public final void setRemoved(Entity.RemovalReason removalReason, EntityRemoveEvent.Cause cause) {
-+        CraftEventFactory.callEntityRemoveEvent(this, cause);
++    public final void setRemoved(Entity.RemovalReason removalReason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause);
 +        // CraftBukkit end
 +        final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
          if (this.removalReason == null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 0eecee5ed4..5db64d6e7d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -1,60 +1,5 @@
 --- a/net/minecraft/world/entity/item/ItemEntity.java
 +++ b/net/minecraft/world/entity/item/ItemEntity.java
-@@ -3,18 +_,6 @@
- import java.util.Objects;
- import java.util.UUID;
- import javax.annotation.Nullable;
--import net.minecraft.core.BlockPos;
--import net.minecraft.nbt.CompoundTag;
--import net.minecraft.network.chat.Component;
--import net.minecraft.network.syncher.EntityDataAccessor;
--import net.minecraft.network.syncher.EntityDataSerializers;
--import net.minecraft.network.syncher.SynchedEntityData;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.sounds.SoundSource;
--import net.minecraft.stats.Stats;
--import net.minecraft.tags.FluidTags;
--import net.minecraft.tags.ItemTags;
--import net.minecraft.util.Mth;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityType;
-@@ -22,7 +_,6 @@
- import net.minecraft.world.entity.MoverType;
- import net.minecraft.world.entity.SlotAccess;
- import net.minecraft.world.entity.TraceableEntity;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Explosion;
-@@ -31,6 +_,27 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.phys.Vec3;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.network.chat.Component;
-+import net.minecraft.network.syncher.EntityDataAccessor;
-+import net.minecraft.network.syncher.EntityDataSerializers;
-+import net.minecraft.network.syncher.SynchedEntityData;
-+// CraftBukkit start
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.sounds.SoundSource;
-+import net.minecraft.stats.Stats;
-+import net.minecraft.tags.FluidTags;
-+import net.minecraft.tags.ItemTags;
-+import net.minecraft.util.Mth;
-+import org.bukkit.craftbukkit.event.CraftEventFactory;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.entity.EntityPickupItemEvent;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.player.PlayerPickupItemEvent;
-+// CraftBukkit end
-+import org.bukkit.event.player.PlayerAttemptPickupItemEvent; // Paper
- 
- public class ItemEntity extends Entity implements TraceableEntity {
-     private static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK);
 @@ -49,6 +_,9 @@
      @Nullable
      public UUID target;
@@ -84,7 +29,7 @@
      public void tick() {
          if (this.getItem().isEmpty()) {
 -            this.discard();
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
          } else {
              super.tick();
              if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
@@ -117,12 +62,12 @@
 -    }
 +            if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate
 +                // CraftBukkit start - fire ItemDespawnEvent
-+                if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
++                if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
 +                    this.age = 0;
 +                    return;
 +                }
 +                // CraftBukkit end
-+                this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
 +            }
 +        }
 +    }
@@ -146,7 +91,7 @@
 +                return;
 +            }
 +            // CraftBukkit end
-+            this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
 +        }
 +    }
 +    // Spigot end
@@ -196,7 +141,7 @@
  
      private static void merge(ItemEntity destinationEntity, ItemStack destinationStack, ItemEntity originEntity, ItemStack originStack) {
 +        // CraftBukkit start
-+        if (!CraftEventFactory.callItemMergeEvent(originEntity, destinationEntity)) {
++        if (!org.bukkit.craftbukkit.event.CraftEventFactory.callItemMergeEvent(originEntity, destinationEntity)) {
 +            return;
 +        }
 +        // CraftBukkit end
@@ -205,7 +150,7 @@
          destinationEntity.age = Math.min(destinationEntity.age, originEntity.age);
          if (originStack.isEmpty()) {
 -            originEntity.discard();
-+            originEntity.discard(EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause
++            originEntity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause
          }
      }
  
@@ -214,7 +159,7 @@
              return false;
          } else {
 +            // CraftBukkit start
-+            if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount)) {
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount)) {
 +                return false;
 +            }
 +            // CraftBukkit end
@@ -224,7 +169,7 @@
              if (this.health <= 0) {
                  this.getItem().onDestroyed(this);
 -                this.discard();
-+                this.discard(EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
++                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
              }
  
              return true;
@@ -240,7 +185,7 @@
          compound.putShort("Health", (short)this.health);
          compound.putShort("Age", (short)this.age);
          compound.putShort("PickupDelay", (short)this.pickupDelay);
-@@ -347,22 +_,95 @@
+@@ -347,9 +_,19 @@
          } else {
              this.setItem(ItemStack.EMPTY);
          }
@@ -261,10 +206,7 @@
          }
      }
  
-     @Override
--    public void playerTouch(Player entity) {
-+    public void playerTouch(net.minecraft.world.entity.player.Player entity) {
-         if (!this.level().isClientSide) {
+@@ -359,10 +_,73 @@
              ItemStack item = this.getItem();
              Item item1 = item.getItem();
              int count = item.getCount();
@@ -275,7 +217,7 @@
 +
 +            // Paper start - PlayerAttemptPickupItemEvent
 +            if (this.pickupDelay <= 0) {
-+                PlayerAttemptPickupItemEvent attemptEvent = new PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                org.bukkit.event.player.PlayerAttemptPickupItemEvent attemptEvent = new org.bukkit.event.player.PlayerAttemptPickupItemEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                this.level().getCraftServer().getPluginManager().callEvent(attemptEvent);
 +
 +                flyAtPlayer = attemptEvent.getFlyAtPlayer();
@@ -291,7 +233,7 @@
 +            if (this.pickupDelay <= 0 && canHold > 0) {
 +                item.setCount(canHold);
 +                // Call legacy event
-+                PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                org.bukkit.event.player.PlayerPickupItemEvent playerEvent = new org.bukkit.event.player.PlayerPickupItemEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                playerEvent.setCancelled(!playerEvent.getPlayer().getCanPickupItems());
 +                this.level().getCraftServer().getPluginManager().callEvent(playerEvent);
 +                flyAtPlayer = playerEvent.getFlyAtPlayer(); // Paper
@@ -306,7 +248,7 @@
 +                }
 +
 +                // Call newer event afterwards
-+                EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++                org.bukkit.event.entity.EntityPickupItemEvent entityEvent = new org.bukkit.event.entity.EntityPickupItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
 +                entityEvent.setCancelled(!entityEvent.getEntity().getCanPickupItems());
 +                this.level().getCraftServer().getPluginManager().callEvent(entityEvent);
 +                if (entityEvent.isCancelled()) {
@@ -335,7 +277,7 @@
                  entity.take(this, count);
                  if (item.isEmpty()) {
 -                    this.discard();
-+                    this.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
++                    this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
                      item.setCount(count);
                  }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
index f7754c7cbf..c60f509644 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -1,20 +1,5 @@
 --- a/net/minecraft/world/inventory/BrewingStandMenu.java
 +++ b/net/minecraft/world/inventory/BrewingStandMenu.java
-@@ -1,6 +_,5 @@
- package net.minecraft.world.inventory;
- 
--import java.util.Optional;
- import net.minecraft.advancements.CriteriaTriggers;
- import net.minecraft.core.Holder;
- import net.minecraft.core.component.DataComponents;
-@@ -16,6 +_,7 @@
- import net.minecraft.world.item.alchemy.Potion;
- import net.minecraft.world.item.alchemy.PotionBrewing;
- import net.minecraft.world.item.alchemy.PotionContents;
-+import java.util.Optional;
- 
- public class BrewingStandMenu extends AbstractContainerMenu {
-     static final ResourceLocation EMPTY_SLOT_FUEL = ResourceLocation.withDefaultNamespace("container/slot/brewing_fuel");
 @@ -33,29 +_,50 @@
      private final Container brewingStand;
      public final ContainerData brewingStandData;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
index f7f4038d0e..9188002f34 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
@@ -1,29 +1,11 @@
 --- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -15,10 +_,16 @@
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtUtils;
- import net.minecraft.resources.ResourceKey;
-+import net.minecraft.server.level.ServerChunkCache;
-+import net.minecraft.server.level.ServerLevel;
- import net.minecraft.util.datafix.DataFixTypes;
- import net.minecraft.world.level.ChunkPos;
--import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.chunk.ChunkGenerator;
-+// CraftBukkit start
-+import java.util.concurrent.ExecutionException;
-+import net.minecraft.world.level.chunk.status.ChunkStatus;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- 
 @@ -38,17 +_,63 @@
          return this.worker.isOldChunkAround(pos, radius);
      }
  
 +    // CraftBukkit start
-+    private boolean check(ServerChunkCache cps, int x, int z) {
++    private boolean check(net.minecraft.server.level.ServerChunkCache cps, int x, int z) {
 +        if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
 +        ChunkPos pos = new ChunkPos(x, z);
 +        if (cps != null) {
@@ -36,7 +18,7 @@
 +        CompoundTag nbt;
 +        try {
 +            nbt = this.read(pos).get().orElse(null);
-+        } catch (InterruptedException | ExecutionException ex) {
++        } catch (InterruptedException | java.util.concurrent.ExecutionException ex) {
 +            throw new RuntimeException(ex);
 +        }
 +        if (nbt != null) {
@@ -45,8 +27,8 @@
 +                return true;
 +            }
 +
-+            ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
-+            if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) {
++            net.minecraft.world.level.chunk.status.ChunkStatus status = net.minecraft.world.level.chunk.status.ChunkStatus.byName(level.getString("Status"));
++            if (status != null && status.isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.FEATURES)) {
 +                return true;
 +            }
 +        }
@@ -56,13 +38,13 @@
 +
      public CompoundTag upgradeChunkTag(
 -        ResourceKey<Level> levelKey,
-+        ResourceKey<LevelStem> levelKey,
++        ResourceKey<net.minecraft.world.level.dimension.LevelStem> levelKey,
          Supplier<DimensionDataStorage> storage,
          CompoundTag chunkData,
 -        Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
 +        Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
 +        ChunkPos pos,
-+        @Nullable LevelAccessor generatoraccess
++        @Nullable net.minecraft.world.level.LevelAccessor generatoraccess
 +        // CraftBukkit end
      ) {
          int version = getVersion(chunkData);
@@ -74,7 +56,7 @@
 +                if (version < 1466) {
 +                    CompoundTag level = chunkData.getCompound("Level");
 +                    if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
-+                        ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
++                        net.minecraft.server.level.ServerChunkCache cps = (generatoraccess == null) ? null : ((net.minecraft.server.level.ServerLevel) generatoraccess).getChunkSource();
 +                        if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) {
 +                            level.putBoolean("LightPopulated", true);
 +                        }
@@ -90,7 +72,7 @@
  
 +                // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty
 +                boolean stopBelowZero = false;
-+                boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks;
++                boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((net.minecraft.server.level.ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks;
 +
 +                if (version <= 2730 && !belowZeroGenerationInExistingChunks) {
 +                    stopBelowZero = "full".equals(chunkData.getCompound("Level").getString("Status"));
@@ -101,7 +83,7 @@
                  chunkData = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, chunkData, Math.max(1493, version));
 +                // Spigot start
 +                if (stopBelowZero) {
-+                    chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
++                    chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN).toString());
 +                }
 +                // Spigot end
                  removeDatafixingContext(chunkData);
@@ -112,7 +94,7 @@
      }
  
 -    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, Supplier<DimensionDataStorage> storage) {
-+    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
++    private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<net.minecraft.world.level.dimension.LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
          LegacyStructureDataHandler legacyStructureDataHandler = this.legacyStructureHandler;
          if (legacyStructureDataHandler == null) {
              synchronized (this) {
@@ -121,7 +103,7 @@
  
      public static void injectDatafixingContext(
 -        CompoundTag chunkData, ResourceKey<Level> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
-+        CompoundTag chunkData, ResourceKey<LevelStem> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey // CraftBukkit
++        CompoundTag chunkData, ResourceKey<net.minecraft.world.level.dimension.LevelStem> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey // CraftBukkit
      ) {
          CompoundTag compoundTag = new CompoundTag();
          compoundTag.putString("dimension", levelKey.location().toString());
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
index f363fd7dbb..23f014d704 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
@@ -1,33 +1,24 @@
 --- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
 +++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
-@@ -18,7 +_,7 @@
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.util.datafix.DataFixTypes;
- import net.minecraft.world.level.ChunkPos;
--import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.storage.DimensionDataStorage;
- 
- public class LegacyStructureDataHandler {
 @@ -217,17 +_,17 @@
          }
      }
  
 -    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, @Nullable DimensionDataStorage storage) {
 -        if (level == Level.OVERWORLD) {
-+    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, @Nullable DimensionDataStorage storage) { // CraftBukkit
-+        if (level == LevelStem.OVERWORLD) { // CraftBukkit
++    public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<net.minecraft.world.level.dimension.LevelStem> level, @Nullable DimensionDataStorage storage) { // CraftBukkit
++        if (level == net.minecraft.world.level.dimension.LevelStem.OVERWORLD) { // CraftBukkit
              return new LegacyStructureDataHandler(
                  storage,
                  ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"),
                  ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument")
              );
 -        } else if (level == Level.NETHER) {
-+        } else if (level == LevelStem.NETHER) { // CraftBukkit
++        } else if (level == net.minecraft.world.level.dimension.LevelStem.NETHER) { // CraftBukkit
              List<String> list = ImmutableList.of("Fortress");
              return new LegacyStructureDataHandler(storage, list, list);
 -        } else if (level == Level.END) {
-+        } else if (level == LevelStem.END) { // CraftBukkit
++        } else if (level == net.minecraft.world.level.dimension.LevelStem.END) { // CraftBukkit
              List<String> list = ImmutableList.of("EndCity");
              return new LegacyStructureDataHandler(storage, list, list);
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
index e53e4b3cd9..9baa2a5f6d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
@@ -1,14 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java
 +++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java
-@@ -26,8 +_,6 @@
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
- import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
--import net.minecraft.world.level.block.entity.DispenserBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
 @@ -47,7 +_,7 @@
      private Rotation rotation;
      protected int genDepth;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
index b94822ff02..55007e0dc7 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
@@ -1,14 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
 +++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
-@@ -13,8 +_,6 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
 @@ -102,10 +_,13 @@
          protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
              if ("chest".equals(name)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
index 7153875623..ed5789e0f5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
@@ -1,14 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
 +++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
-@@ -25,8 +_,6 @@
- import net.minecraft.world.level.block.ChestBlock;
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
--import net.minecraft.world.level.block.entity.BlockEntity;
--import net.minecraft.world.level.block.entity.ChestBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.ChunkGenerator;
- import net.minecraft.world.level.levelgen.Heightmap;
 @@ -314,14 +_,20 @@
          @Override
          protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
index 1821e06e5d..75de4affeb 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
 +++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
-@@ -7,7 +_,6 @@
- import net.minecraft.core.Direction;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.util.RandomSource;
--import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.StructureManager;
- import net.minecraft.world.level.WorldGenLevel;
 @@ -870,10 +_,13 @@
                  BlockPos worldPos = this.getWorldPos(5, 3, 6);
                  if (box.isInside(worldPos)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
index d6448bf4b9..dd0b1a8c19 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/storage/LevelStorageSource.java
 +++ b/net/minecraft/world/level/storage/LevelStorageSource.java
-@@ -66,7 +_,6 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelSettings;
- import net.minecraft.world.level.WorldDataConfiguration;
--import net.minecraft.world.level.dimension.DimensionType;
- import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.WorldDimensions;
- import net.minecraft.world.level.levelgen.WorldGenSettings;
 @@ -145,6 +_,7 @@
          PrimaryLevelData primaryLevelData = PrimaryLevelData.parse(
              dynamic, levelSettings, complete.specialWorldProperty(), worldGenSettings.options(), lifecycle

From de10a277e2200f649fc584f2e453abfadee20c07 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 12:09:18 -0800
Subject: [PATCH 166/285] fix a mis-applied hunk in Projectile

---
 .../server/level/ServerLevel.java.patch       |  2 +-
 .../entity/projectile/Projectile.java.patch   | 30 +++++++------------
 2 files changed, 11 insertions(+), 21 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 3845d17a64..181ca0c6e9 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -139,7 +139,7 @@
          Executor dispatcher,
          LevelStorageSource.LevelStorageAccess levelStorageAccess,
 -        ServerLevelData serverLevelData,
-+        PrimaryLevelData serverLevelData, // CraftBukkit
++        net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, // CraftBukkit
          ResourceKey<Level> dimension,
          LevelStem levelStem,
          ChunkProgressListener progressListener,
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
index 637335db7b..1df6bae85c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
@@ -78,30 +78,20 @@
              factory.create(level, owner, spawnedFrom),
              level,
              spawnedFrom,
-@@ -200,7 +_,22 @@
-         float velocity,
-         float inaccuracy
+@@ -206,7 +_,12 @@
+     public static <T extends Projectile> T spawnProjectileUsingShoot(
+         T projectile, ServerLevel level, ItemStack spawnedFrom, double x, double y, double z, float velocity, float inaccuracy
      ) {
--        return spawnProjectile(factory.create(level, owner, spawnedFrom), level, spawnedFrom, projectile -> projectile.shoot(x, y, z, velocity, inaccuracy));
-+        // Paper start - fixes and addition to spawn reason API
-+        return Projectile.spawnProjectileUsingShootDelayed(factory, level, spawnedFrom, owner, x, y, z, velocity, inaccuracy).spawn();
+-        return spawnProjectile(projectile, level, spawnedFrom, projectile1 -> projectile.shoot(x, y, z, velocity, inaccuracy));
++    // Paper start - fixes and addition to spawn reason API
++        return spawnProjectileUsingShootDelayed(projectile, level, spawnedFrom, x, y, z, velocity, inaccuracy).spawn();
 +    }
-+    public static <T extends Projectile> Delayed<T> spawnProjectileUsingShootDelayed(
-+        Projectile.ProjectileFactory<T> factory,
-+        ServerLevel level,
-+        ItemStack spawnedFrom,
-+        LivingEntity owner,
-+        double x,
-+        double y,
-+        double z,
-+        float velocity,
-+        float inaccuracy
-+    ) {
-+        return spawnProjectileDelayed(factory.create(level, owner, spawnedFrom), level, spawnedFrom, projectile -> projectile.shoot(x, y, z, velocity, inaccuracy));
-+        // Paper end - fixes and addition to spawn reason API
++    public static <T extends Projectile> Delayed<T> spawnProjectileUsingShootDelayed(T projectile, ServerLevel level, ItemStack spawnedFrom, double x, double y, double z, float velocity, float inaccuracy) {
++        return spawnProjectileDelayed(projectile, level, spawnedFrom, projectile1 -> projectile.shoot(x, y, z, velocity, inaccuracy));
++    // Paper end - fixes and addition to spawn reason API
      }
  
-     public static <T extends Projectile> T spawnProjectileUsingShoot(
+     public static <T extends Projectile> T spawnProjectile(T projectile, ServerLevel level, ItemStack spawnedFrom) {
 @@ -214,11 +_,45 @@
      }
  

From 6dcb4a33b6155ad814c57fdea3c3103ce139ee93 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 15 Dec 2024 21:28:08 +0100
Subject: [PATCH 167/285] Fix some compilation errors

---
 .../arguments/EntityArgument.java.patch       |  2 +-
 .../server/commands/DebugCommand.java.patch   | 16 ++++++++++++++
 .../world/entity/animal/Dolphin.java.patch    |  9 --------
 .../entity/npc/ClientSideMerchant.java.patch  | 16 ++++++++++++++
 .../level/EmptyBlockAndTintGetter.java.patch  | 21 +++++++++++++++++++
 .../block/ConcretePowderBlock.java.patch      |  8 ++++---
 .../level/block/FlowerPotBlock.java.patch     |  2 +-
 .../level/block/NetherPortalBlock.java.patch  |  3 ++-
 .../world/level/block/PumpkinBlock.java.patch |  2 +-
 .../ExplorationMapFunction.java.patch         |  2 +-
 .../craftbukkit/entity/CraftEntity.java       | 12 +++++------
 .../java/org/spigotmc/ActivationRange.java    |  8 +++----
 12 files changed, 74 insertions(+), 27 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/server/commands/DebugCommand.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/entity/npc/ClientSideMerchant.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/EmptyBlockAndTintGetter.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
index 30696dc07f..e2b9c3d155 100644
--- a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
@@ -7,7 +7,7 @@
 +        // CraftBukkit start
 +        return this.parse(reader, allowSelectors, true);
 +    }
-+    private EntitySelector parse(StringReader reader, boolean allowSelectors, boolean overridePermissions) throws CommandSyntaxException {
++    public EntitySelector parse(StringReader reader, boolean allowSelectors, boolean overridePermissions) throws CommandSyntaxException {
 +        // CraftBukkit end
          int i = 0;
          EntitySelectorParser entitySelectorParser = new EntitySelectorParser(reader, allowSelectors);
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DebugCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DebugCommand.java.patch
new file mode 100644
index 0000000000..9d1cc61c46
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/server/commands/DebugCommand.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/server/commands/DebugCommand.java
++++ b/net/minecraft/server/commands/DebugCommand.java
+@@ -262,6 +_,13 @@
+             return true;
+         }
+ 
++        // Paper start
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(final CommandSourceStack wrapper) {
++            throw new UnsupportedOperationException();
++        }
++        // Paper end
++
+         @Override
+         public void close() {
+             IOUtils.closeQuietly((Writer)this.output);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
index 281bb05b75..a9f720be0d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
@@ -39,15 +39,6 @@
              }
          }
      }
-@@ -341,7 +_,7 @@
- 
-     @Nullable
-     @Override
--    protected SoundEvent getDeathSound() {
-+    public SoundEvent getDeathSound() { // Paper - decompile error
-         return SoundEvents.DOLPHIN_DEATH;
-     }
- 
 @@ -497,7 +_,7 @@
  
          @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/ClientSideMerchant.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/ClientSideMerchant.java.patch
new file mode 100644
index 0000000000..b19fe89536
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/ClientSideMerchant.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/npc/ClientSideMerchant.java
++++ b/net/minecraft/world/entity/npc/ClientSideMerchant.java
+@@ -56,6 +_,13 @@
+         return this.source == player;
+     }
+ 
++    // Paper start
++    @Override
++    public org.bukkit.craftbukkit.inventory.CraftMerchant getCraftMerchant() {
++        throw new UnsupportedOperationException();
++    }
++    // Paper end
++
+     @Override
+     public int getVillagerXp() {
+         return this.xp;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockAndTintGetter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockAndTintGetter.java.patch
new file mode 100644
index 0000000000..6ca1c8d13e
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/EmptyBlockAndTintGetter.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/EmptyBlockAndTintGetter.java
++++ b/net/minecraft/world/level/EmptyBlockAndTintGetter.java
+@@ -39,6 +_,18 @@
+         return Blocks.AIR.defaultBlockState();
+     }
+ 
++    // Paper start
++    @Override
++    public @org.jetbrains.annotations.Nullable BlockState getBlockStateIfLoaded(final BlockPos blockposition) {
++        return null;
++    }
++
++    @Override
++    public @org.jetbrains.annotations.Nullable FluidState getFluidIfLoaded(final BlockPos blockposition) {
++        return null;
++    }
++    // Paper end
++
+     @Override
+     public FluidState getFluidState(BlockPos pos) {
+         return Fluids.EMPTY.defaultFluidState();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
index 09d88b80ed..3c8335c0a5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/ConcretePowderBlock.java
 +++ b/net/minecraft/world/level/block/ConcretePowderBlock.java
-@@ -38,7 +_,7 @@
+@@ -38,16 +_,33 @@
      @Override
      public void onLand(Level level, BlockPos pos, BlockState state, BlockState replaceableState, FallingBlockEntity fallingBlock) {
          if (shouldSolidify(level, pos, replaceableState)) {
@@ -9,8 +9,10 @@
          }
      }
  
-@@ -47,7 +_,24 @@
-         BlockGetter level = context.getLevel();
+     @Override
+     public BlockState getStateForPlacement(BlockPlaceContext context) {
+-        BlockGetter level = context.getLevel();
++        Level level = context.getLevel(); // Paper
          BlockPos clickedPos = context.getClickedPos();
          BlockState blockState = level.getBlockState(clickedPos);
 -        return shouldSolidify(level, clickedPos, blockState) ? this.concrete.defaultBlockState() : super.getStateForPlacement(context);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
index bf55cfd303..26f504f933 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/FlowerPotBlock.java.patch
@@ -24,7 +24,7 @@
          } else {
              ItemStack itemStack = new ItemStack(this.potted);
 +            // Paper start - Add PlayerFlowerPotManipulateEvent
-+            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++            org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
 +            org.bukkit.inventory.ItemStack pottedStack = new org.bukkit.inventory.ItemStack(org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.potted));
 +
 +            io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, pottedStack, false);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
index d6707c3b66..cb2a17d059 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
@@ -62,6 +62,7 @@
              WorldBorder worldBorder = level1.getWorldBorder();
              double teleportationScale = DimensionType.getTeleportationScale(level.dimensionType(), level1.dimensionType());
              BlockPos blockPos = worldBorder.clampToBounds(entity.getX() * teleportationScale, entity.getY(), entity.getZ() * teleportationScale);
+-            return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder);
 +            // Paper start - Configurable portal search radius
 +            int portalSearchRadius = level1.paperConfig().environment.portalSearchRadius;
 +            if (entity.level().paperConfig().environment.portalSearchVanillaDimensionScaling && flag) { // flag = is going to nether
@@ -76,8 +77,8 @@
 +            level1 = ((org.bukkit.craftbukkit.CraftWorld) event.getTo().getWorld()).getHandle();
 +            worldBorder = level1.getWorldBorder();
 +            blockPos = worldBorder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ());
++            return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius());
 +            // CraftBukkit end
-             return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder);
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
index 20e12e9b1d..281a3997a5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/PumpkinBlock.java.patch
@@ -5,7 +5,7 @@
              return InteractionResult.SUCCESS;
          } else {
 +            // Paper start - Add PlayerShearBlockEvent
-+            io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>());
++            io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), new java.util.ArrayList<>());
 +            event.getDrops().add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(new ItemStack(Items.PUMPKIN_SEEDS, 4)));
 +            if (!event.callEvent()) {
 +                return InteractionResult.PASS;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
index 1d206d5719..29f028e11a 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/functions/ExplorationMapFunction.java.patch
@@ -14,7 +14,7 @@
 +                    return stack;
 +                }
 +                // Paper end - Configurable cartographer treasure maps
-+                BlockPos blockPos = level.findNearestMapStructure(this.destination, BlockPos.containing(vec3), this.searchRadius, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredLootTable.or(!this.skipKnownStructures)); // Paper - Configurable cartographer treasure maps
++                BlockPos blockPos = level.findNearestMapStructure(this.destination, BlockPos.containing(vec3), this.searchRadius, !level.paperConfig().environment.treasureMaps.findAlreadyDiscoveredLootTable.or(!this.skipKnownStructures)); // Paper - Configurable cartographer treasure maps
                  if (blockPos != null) {
                      ItemStack itemStack = MapItem.create(level, blockPos.getX(), blockPos.getZ(), this.zoom, true, true);
                      MapItem.renderBiomePreviewMap(level, itemStack);
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index b25b10c24a..98c1a4cff9 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -193,8 +193,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     @Override
     public boolean isOnGround() {
-        if (this.entity instanceof AbstractArrow) {
-            return ((AbstractArrow) this.entity).isInGround();
+        if (this.entity instanceof AbstractArrow abstractArrow) {
+            return abstractArrow.isInGround();
         }
         return this.entity.onGround();
     }
@@ -417,7 +417,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     @Override
     public org.bukkit.entity.Entity getPassenger() {
-        return this.isEmpty() ? null : this.getHandle().passengers.get(0).getBukkitEntity();
+        return this.isEmpty() ? null : this.getHandle().getPassengers().getFirst().getBukkitEntity();
     }
 
     @Override
@@ -433,7 +433,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     @Override
     public List<org.bukkit.entity.Entity> getPassengers() {
-        return Lists.newArrayList(Lists.transform(this.getHandle().passengers, (Function<Entity, org.bukkit.entity.Entity>) input -> input.getBukkitEntity()));
+        return Lists.newArrayList(Lists.transform(this.getHandle().getPassengers(), (Function<Entity, org.bukkit.entity.Entity>) Entity::getBukkitEntity));
     }
 
     @Override
@@ -852,12 +852,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     @Override
     public int getPortalCooldown() {
-        return this.getHandle().portalCooldown;
+        return this.getHandle().getPortalCooldown();
     }
 
     @Override
     public void setPortalCooldown(int cooldown) {
-        this.getHandle().portalCooldown = cooldown;
+        this.getHandle().setPortalCooldown(cooldown);;
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/spigotmc/ActivationRange.java b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
index 34297951d3..607e2edb5c 100644
--- a/paper-server/src/main/java/org/spigotmc/ActivationRange.java
+++ b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
@@ -142,9 +142,9 @@ public class ActivationRange
     }
 
     /**
-     * Checks for the activation state of all entities in this chunk.
+     * Tries to activate an entity.
      *
-     * @param chunk
+     * @param entity
      */
     private static void activateEntity(Entity entity)
     {
@@ -172,13 +172,13 @@ public class ActivationRange
     public static boolean checkEntityImmunities(Entity entity)
     {
         // quick checks.
-        if ( entity.wasTouchingWater || entity.getRemainingFireTicks() > 0 )
+        if ( entity.isInWater() || entity.getRemainingFireTicks() > 0 )
         {
             return true;
         }
         if ( !( entity instanceof AbstractArrow ) )
         {
-            if ( !entity.onGround() || !entity.passengers.isEmpty() || entity.isPassenger() )
+            if ( !entity.onGround() || !entity.getPassengers().isEmpty() || entity.isPassenger() )
             {
                 return true;
             }

From acd43900f57fce8b894e2ae0639608776f42cebd Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 12:51:34 -0800
Subject: [PATCH 168/285] remove more imports and cleanup

---
 .../brigadier/tree/CommandNode.java.patch     |  28 +--
 .../SimpleCriterionTrigger.java.patch         |  12 +-
 .../minecraft/commands/Commands.java.patch    |  33 +---
 .../dispenser/DispenseItemBehavior.java.patch |  16 +-
 .../net/minecraft/server/Bootstrap.java.patch | 124 ++++++-------
 .../net/minecraft/server/Main.java.patch      |  10 +-
 .../commands/TeleportCommand.java.patch       |  10 +-
 .../server/level/ServerPlayer.java.patch      |  40 +----
 .../level/ServerPlayerGameMode.java.patch     |  10 +-
 .../ServerCommonPacketListenerImpl.java.patch |  85 ++++-----
 .../java/org/spigotmc/ActivationRange.java    | 168 +++++++-----------
 .../main/java/org/spigotmc/TrackingRange.java |  32 ++--
 12 files changed, 205 insertions(+), 363 deletions(-)

diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
index 1b5e8b98af..1431fc6d44 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
@@ -1,26 +1,10 @@
 --- a/com/mojang/brigadier/tree/CommandNode.java
 +++ b/com/mojang/brigadier/tree/CommandNode.java
-@@ -3,6 +_,7 @@
- 
- package com.mojang.brigadier.tree;
- 
-+// CHECKSTYLE:OFF
- import com.mojang.brigadier.AmbiguityConsumer;
- import com.mojang.brigadier.Command;
- import com.mojang.brigadier.RedirectModifier;
-@@ -22,6 +_,7 @@
- import java.util.Set;
- import java.util.concurrent.CompletableFuture;
- import java.util.function.Predicate;
-+import net.minecraft.commands.CommandSourceStack;
- 
- public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
-     private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
 @@ -32,6 +_,16 @@
      private final RedirectModifier<S> modifier;
      private final boolean forks;
      private Command<S> command;
-+    public CommandNode<CommandSourceStack> clientNode; // Paper - Brigadier API
++    public CommandNode<net.minecraft.commands.CommandSourceStack> clientNode; // Paper - Brigadier API
 +    public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> unwrappedCached = null; // Paper - Brigadier Command API
 +    public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> wrappedCached = null; // Paper - Brigadier Command API
 +    // CraftBukkit start
@@ -40,12 +24,12 @@
 -    public boolean canUse(final S source) {
 +    // CraftBukkit start
 +    public synchronized boolean canUse(final S source) {
-+        if (source instanceof CommandSourceStack) {
++        if (source instanceof final net.minecraft.commands.CommandSourceStack css) {
 +            try {
-+                ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper - Thread Safe Vanilla Command permission checking
++                css.currentCommand.put(Thread.currentThread(), this); // Paper - Thread Safe Vanilla Command permission checking
 +                return this.requirement.test(source);
 +            } finally {
-+                ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking
++                css.currentCommand.remove(Thread.currentThread()); // Paper - Thread Safe Vanilla Command permission checking
 +            }
 +        }
 +        // CraftBukkit end
@@ -72,11 +56,11 @@
 -            final LiteralCommandNode<S> literal = literals.get(text);
 +            // Paper start - prioritize mc commands in function parsing
 +            LiteralCommandNode<S> literal = null;
-+            if (source instanceof CommandSourceStack css && css.source == net.minecraft.commands.CommandSource.NULL) {
++            if (source instanceof net.minecraft.commands.CommandSourceStack css && css.source == net.minecraft.commands.CommandSource.NULL) {
 +                if (!text.contains(":")) {
 +                    literal = this.literals.get("minecraft:" + text);
 +                }
-+            } else if (source instanceof CommandSourceStack css && css.source instanceof net.minecraft.world.level.BaseCommandBlock) {
++            } else if (source instanceof net.minecraft.commands.CommandSourceStack css && css.source instanceof net.minecraft.world.level.BaseCommandBlock) {
 +                if (css.getServer().server.getCommandBlockOverride(text) && !text.contains(":")) {
 +                    literal = this.literals.get("minecraft:" + text);
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
index f953971ef6..1817a4cb37 100644
--- a/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
+++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
@@ -5,23 +5,23 @@
  
  public abstract class SimpleCriterionTrigger<T extends SimpleCriterionTrigger.SimpleInstance> implements CriterionTrigger<T> {
 -    private final Map<PlayerAdvancements, Set<CriterionTrigger.Listener<T>>> players = Maps.newIdentityHashMap();
-+    // private final Map<PlayerAdvancements, Set<CriterionTrigger.Listener<T>>> players = Maps.newIdentityHashMap(); // Paper - fix AdvancementDataPlayer leak; moved into AdvancementDataPlayer to fix memory leak
++    // private final Map<PlayerAdvancements, Set<CriterionTrigger.Listener<T>>> players = Maps.newIdentityHashMap(); // Paper - fix PlayerAdvancements leak; moved into PlayerAdvancements to fix memory leak
  
      @Override
      public final void addPlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener<T> listener) {
 -        this.players.computeIfAbsent(playerAdvancements, advancements -> Sets.newHashSet()).add(listener);
-+        playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener);  // Paper - fix AdvancementDataPlayer leak
++        playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener);  // Paper - fix PlayerAdvancements leak
      }
  
      @Override
      public final void removePlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener<T> listener) {
 -        Set<CriterionTrigger.Listener<T>> set = this.players.get(playerAdvancements);
-+        Set<CriterionTrigger.Listener<T>> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
++        Set<CriterionTrigger.Listener<T>> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix PlayerAdvancements leak
          if (set != null) {
              set.remove(listener);
              if (set.isEmpty()) {
 -                this.players.remove(playerAdvancements);
-+                playerAdvancements.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
++                playerAdvancements.criterionData.remove(this); // Paper - fix PlayerAdvancements leak
              }
          }
      }
@@ -29,13 +29,13 @@
      @Override
      public final void removePlayerListeners(PlayerAdvancements playerAdvancements) {
 -        this.players.remove(playerAdvancements);
-+        playerAdvancements.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak
++        playerAdvancements.criterionData.remove(this); // Paper - fix PlayerAdvancements leak
      }
  
      protected void trigger(ServerPlayer player, Predicate<T> testTrigger) {
          PlayerAdvancements advancements = player.getAdvancements();
 -        Set<CriterionTrigger.Listener<T>> set = this.players.get(advancements);
-+        Set<CriterionTrigger.Listener<T>> set = (Set) advancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
++        Set<CriterionTrigger.Listener<T>> set = (Set) advancements.criterionData.get(this); // Paper - fix PlayerAdvancements leak
          if (set != null && !set.isEmpty()) {
 -            LootContext lootContext = EntityPredicate.createContext(player, player);
 +            LootContext lootContext = null; // EntityPredicate.createContext(player, player); // Paper - Perf: lazily create LootContext for criterions
diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
index 843b1af707..954cc79823 100644
--- a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
@@ -1,20 +1,5 @@
 --- a/net/minecraft/commands/Commands.java
 +++ b/net/minecraft/commands/Commands.java
-@@ -139,6 +_,14 @@
- import net.minecraft.world.level.GameRules;
- import org.slf4j.Logger;
- 
-+// CraftBukkit start
-+import com.google.common.base.Joiner;
-+import java.util.Collection;
-+import java.util.LinkedHashSet;
-+import org.bukkit.event.player.PlayerCommandSendEvent;
-+import org.bukkit.event.server.ServerCommandEvent;
-+// CraftBukkit end
-+
- public class Commands {
-     private static final ThreadLocal<ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal<>();
-     private static final Logger LOGGER = LogUtils.getLogger();
 @@ -251,6 +_,30 @@
              PublishCommand.register(this.dispatcher);
          }
@@ -52,12 +37,12 @@
  
 +    // CraftBukkit start
 +    public void dispatchServerCommand(CommandSourceStack sender, String command) {
-+        Joiner joiner = Joiner.on(" ");
++        com.google.common.base.Joiner joiner = com.google.common.base.Joiner.on(" ");
 +        if (command.startsWith("/")) {
 +            command = command.substring(1);
 +        }
 +
-+        ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command);
++        org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(sender.getBukkitSender(), command);
 +        org.bukkit.Bukkit.getPluginManager().callEvent(event);
 +        if (event.isCancelled()) {
 +            return;
@@ -135,7 +120,7 @@
 +            // Paper start - Add UnknownCommandEvent
 +            final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
 +            // source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage()));
-+            builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(var7.getRawMessage()));
++            builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(var7.getRawMessage()));
 +            // Paper end - Add UnknownCommandEvent
              if (var7.getInput() != null && var7.getCursor() >= 0) {
                  int min = Math.min(var7.getInput().length(), var7.getCursor());
@@ -180,7 +165,7 @@
 +        // Register Vanilla commands into builtRoot as before
 +        // Paper start - Perf: Async command map building
 +        // Copy root children to avoid concurrent modification during building
-+        final Collection<CommandNode<CommandSourceStack>> commandNodes = new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren());
++        final java.util.Collection<CommandNode<CommandSourceStack>> commandNodes = new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren());
 +        COMMAND_SENDING_POOL.execute(() -> this.sendAsync(player, commandNodes));
 +    }
 +
@@ -195,7 +180,7 @@
 +        new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy()
 +    );
 +
-+    private void sendAsync(ServerPlayer player, Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
++    private void sendAsync(ServerPlayer player, java.util.Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
 +        // Paper end - Perf: Async command map building
 +        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
 +        // Paper - brigadier API removes the need to fill the map twice
@@ -204,7 +189,7 @@
 -        this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
 +        this.fillUsableCommands(dispatcherRootChildren, rootCommandNode, player.createCommandSourceStack(), map); // Paper - Perf: Async command map building; pass copy of children
 +
-+        Collection<String> bukkit = new LinkedHashSet<>();
++        java.util.Collection<String> bukkit = new java.util.LinkedHashSet<>();
 +        for (CommandNode node : rootCommandNode.getChildren()) {
 +            bukkit.add(node.getName());
 +        }
@@ -215,10 +200,10 @@
 +        });
 +    }
 +
-+    private void runSync(ServerPlayer player, Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootCommandNode) {
++    private void runSync(ServerPlayer player, java.util.Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootCommandNode) {
 +        // Paper end - Perf: Async command map building
 +        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootCommandNode, true).callEvent(); // Paper - Brigadier API
-+        PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
++        org.bukkit.event.player.PlayerCommandSendEvent event = new org.bukkit.event.player.PlayerCommandSendEvent(player.getBukkitEntity(), new java.util.LinkedHashSet<>(bukkit));
 +        event.getPlayer().getServer().getPluginManager().callEvent(event);
 +
 +        // Remove labels that were removed during the event
@@ -234,7 +219,7 @@
  
      private void fillUsableCommands(
 -        CommandNode<CommandSourceStack> rootCommandSource,
-+        Collection<CommandNode<CommandSourceStack>> children, // Paper - Perf: Async command map building; pass copy of children
++        java.util.Collection<CommandNode<CommandSourceStack>> children, // Paper - Perf: Async command map building; pass copy of children
          CommandNode<SharedSuggestionProvider> rootSuggestion,
          CommandSourceStack source,
          Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> commandNodeToSuggestionNode
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
index 687221251f..fed8dc6488 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
@@ -1,15 +1,5 @@
 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
 +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
-@@ -43,7 +_,9 @@
- import net.minecraft.world.level.block.CandleCakeBlock;
- import net.minecraft.world.level.block.CarvedPumpkinBlock;
- import net.minecraft.world.level.block.DispenserBlock;
-+import net.minecraft.world.level.block.LiquidBlockContainer;
- import net.minecraft.world.level.block.RespawnAnchorBlock;
-+import net.minecraft.world.level.block.SaplingBlock;
- import net.minecraft.world.level.block.ShulkerBoxBlock;
- import net.minecraft.world.level.block.SkullBlock;
- import net.minecraft.world.level.block.TntBlock;
 @@ -82,16 +_,48 @@
                  Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
                  EntityType<?> type = ((SpawnEggItem)item.getItem()).getType(blockSource.level().registryAccess(), item);
@@ -195,7 +185,7 @@
 +                /* Taken from SolidBucketItem#emptyContents */
 +                boolean willEmptyContentsSolidBucketItem = dispensibleContainerItem instanceof net.minecraft.world.item.SolidBucketItem && level.isInWorldBounds(blockPos) && iblockdata.isAir();
 +                /* Taken from BucketItem#emptyContents */
-+                boolean willEmptyBucketItem = dispensibleContainerItem instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, blockPos, iblockdata, bucketItem.content)));
++                boolean willEmptyBucketItem = dispensibleContainerItem instanceof final net.minecraft.world.item.BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof net.minecraft.world.level.block.LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, level, blockPos, iblockdata, bucketItem.content)));
 +                if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
 +                // Paper end - correctly check if the bucket place will succeed
 +                    org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
@@ -357,8 +347,8 @@
 +                // CraftBukkit start
 +                level.captureTreeGeneration = false;
 +                if (level.capturedBlockStates.size() > 0) {
-+                    org.bukkit.TreeType treeType = SaplingBlock.treeType;
-+                    SaplingBlock.treeType = null;
++                    org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
++                    net.minecraft.world.level.block.SaplingBlock.treeType = null;
 +                    org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld());
 +                    List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
 +                    level.capturedBlockStates.clear();
diff --git a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
index b1ee24c3e7..701d502fec 100644
--- a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
@@ -1,15 +1,5 @@
 --- a/net/minecraft/server/Bootstrap.java
 +++ b/net/minecraft/server/Bootstrap.java
-@@ -17,6 +_,9 @@
- import net.minecraft.core.dispenser.DispenseItemBehavior;
- import net.minecraft.core.registries.BuiltInRegistries;
- import net.minecraft.locale.Language;
-+import net.minecraft.util.datafix.fixes.BlockStateData;
-+import net.minecraft.util.datafix.fixes.ItemIdFix;
-+import net.minecraft.util.datafix.fixes.ItemSpawnEggFix;
- import net.minecraft.world.effect.MobEffect;
- import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.ai.attributes.Attribute;
 @@ -43,6 +_,7 @@
          if (!isBootstrapped) {
              isBootstrapped = true;
@@ -32,67 +22,67 @@
                      bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
                  }
 +                // CraftBukkit start - easier than fixing the decompile
-+                BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
-+                BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}");
-+                BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}");
-+                BlockStateData.register(1011, "{Name:'minecraft:oak_sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}");
-+                BlockStateData.register(1012, "{Name:'minecraft:oak_sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}");
-+                BlockStateData.register(1013, "{Name:'minecraft:oak_sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}");
-+                BlockStateData.register(1014, "{Name:'minecraft:oak_sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}");
-+                BlockStateData.register(1015, "{Name:'minecraft:oak_sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}");
-+                BlockStateData.register(1016, "{Name:'minecraft:oak_sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}");
-+                BlockStateData.register(1017, "{Name:'minecraft:oak_sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}");
-+                BlockStateData.register(1018, "{Name:'minecraft:oak_sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}");
-+                BlockStateData.register(1019, "{Name:'minecraft:oak_sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}");
-+                BlockStateData.register(1020, "{Name:'minecraft:oak_sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}");
-+                BlockStateData.register(1021, "{Name:'minecraft:oak_sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}");
-+                BlockStateData.register(1022, "{Name:'minecraft:oak_sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}");
-+                BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}");
-+                ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1011, "{Name:'minecraft:oak_sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1012, "{Name:'minecraft:oak_sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1013, "{Name:'minecraft:oak_sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1014, "{Name:'minecraft:oak_sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1015, "{Name:'minecraft:oak_sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1016, "{Name:'minecraft:oak_sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1017, "{Name:'minecraft:oak_sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1018, "{Name:'minecraft:oak_sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1019, "{Name:'minecraft:oak_sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1020, "{Name:'minecraft:oak_sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1021, "{Name:'minecraft:oak_sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1022, "{Name:'minecraft:oak_sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}");
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign");
 +
-+                BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"});
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"});
 +
-+                ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard");
-+                ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals");
-+                ItemIdFix.ITEM_NAMES.put(411, "minecraft:rabbit");
-+                ItemIdFix.ITEM_NAMES.put(412, "minecraft:cooked_rabbit");
-+                ItemIdFix.ITEM_NAMES.put(413, "minecraft:rabbit_stew");
-+                ItemIdFix.ITEM_NAMES.put(414, "minecraft:rabbit_foot");
-+                ItemIdFix.ITEM_NAMES.put(415, "minecraft:rabbit_hide");
-+                ItemIdFix.ITEM_NAMES.put(416, "minecraft:armor_stand");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(411, "minecraft:rabbit");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(412, "minecraft:cooked_rabbit");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(413, "minecraft:rabbit_stew");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(414, "minecraft:rabbit_foot");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(415, "minecraft:rabbit_hide");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(416, "minecraft:armor_stand");
 +
-+                ItemIdFix.ITEM_NAMES.put(423, "minecraft:mutton");
-+                ItemIdFix.ITEM_NAMES.put(424, "minecraft:cooked_mutton");
-+                ItemIdFix.ITEM_NAMES.put(425, "minecraft:banner");
-+                ItemIdFix.ITEM_NAMES.put(426, "minecraft:end_crystal");
-+                ItemIdFix.ITEM_NAMES.put(427, "minecraft:spruce_door");
-+                ItemIdFix.ITEM_NAMES.put(428, "minecraft:birch_door");
-+                ItemIdFix.ITEM_NAMES.put(429, "minecraft:jungle_door");
-+                ItemIdFix.ITEM_NAMES.put(430, "minecraft:acacia_door");
-+                ItemIdFix.ITEM_NAMES.put(431, "minecraft:dark_oak_door");
-+                ItemIdFix.ITEM_NAMES.put(432, "minecraft:chorus_fruit");
-+                ItemIdFix.ITEM_NAMES.put(433, "minecraft:chorus_fruit_popped");
-+                ItemIdFix.ITEM_NAMES.put(434, "minecraft:beetroot");
-+                ItemIdFix.ITEM_NAMES.put(435, "minecraft:beetroot_seeds");
-+                ItemIdFix.ITEM_NAMES.put(436, "minecraft:beetroot_soup");
-+                ItemIdFix.ITEM_NAMES.put(437, "minecraft:dragon_breath");
-+                ItemIdFix.ITEM_NAMES.put(438, "minecraft:splash_potion");
-+                ItemIdFix.ITEM_NAMES.put(439, "minecraft:spectral_arrow");
-+                ItemIdFix.ITEM_NAMES.put(440, "minecraft:tipped_arrow");
-+                ItemIdFix.ITEM_NAMES.put(441, "minecraft:lingering_potion");
-+                ItemIdFix.ITEM_NAMES.put(442, "minecraft:shield");
-+                ItemIdFix.ITEM_NAMES.put(443, "minecraft:elytra");
-+                ItemIdFix.ITEM_NAMES.put(444, "minecraft:spruce_boat");
-+                ItemIdFix.ITEM_NAMES.put(445, "minecraft:birch_boat");
-+                ItemIdFix.ITEM_NAMES.put(446, "minecraft:jungle_boat");
-+                ItemIdFix.ITEM_NAMES.put(447, "minecraft:acacia_boat");
-+                ItemIdFix.ITEM_NAMES.put(448, "minecraft:dark_oak_boat");
-+                ItemIdFix.ITEM_NAMES.put(449, "minecraft:totem_of_undying");
-+                ItemIdFix.ITEM_NAMES.put(450, "minecraft:shulker_shell");
-+                ItemIdFix.ITEM_NAMES.put(452, "minecraft:iron_nugget");
-+                ItemIdFix.ITEM_NAMES.put(453, "minecraft:knowledge_book");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(423, "minecraft:mutton");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(424, "minecraft:cooked_mutton");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(425, "minecraft:banner");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(426, "minecraft:end_crystal");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(427, "minecraft:spruce_door");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(428, "minecraft:birch_door");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(429, "minecraft:jungle_door");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(430, "minecraft:acacia_door");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(431, "minecraft:dark_oak_door");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(432, "minecraft:chorus_fruit");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(433, "minecraft:chorus_fruit_popped");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(434, "minecraft:beetroot");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(435, "minecraft:beetroot_seeds");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(436, "minecraft:beetroot_soup");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(437, "minecraft:dragon_breath");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(438, "minecraft:splash_potion");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(439, "minecraft:spectral_arrow");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(440, "minecraft:tipped_arrow");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(441, "minecraft:lingering_potion");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(442, "minecraft:shield");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(443, "minecraft:elytra");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(444, "minecraft:spruce_boat");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(445, "minecraft:birch_boat");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(446, "minecraft:jungle_boat");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(447, "minecraft:acacia_boat");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(448, "minecraft:dark_oak_boat");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(449, "minecraft:totem_of_undying");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(450, "minecraft:shulker_shell");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(452, "minecraft:iron_nugget");
++                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(453, "minecraft:knowledge_book");
 +
-+                ItemSpawnEggFix.ID_TO_ENTITY[23] = "Arrow";
++                net.minecraft.util.datafix.fixes.ItemSpawnEggFix.ID_TO_ENTITY[23] = "Arrow";
 +                // CraftBukkit end
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/Main.java.patch b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
index 001042f60c..f166107df6 100644
--- a/paper-server/patches/sources/net/minecraft/server/Main.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
-@@ -37,6 +_,7 @@
- import net.minecraft.server.dedicated.DedicatedServerProperties;
- import net.minecraft.server.dedicated.DedicatedServerSettings;
- import net.minecraft.server.level.progress.LoggerChunkProgressListener;
-+import net.minecraft.server.packs.PackType;
- import net.minecraft.server.packs.repository.PackRepository;
- import net.minecraft.server.packs.repository.ServerPacksSource;
- import net.minecraft.util.Mth;
 @@ -67,8 +_,9 @@
          reason = "System.out needed before bootstrap"
      )
@@ -144,7 +136,7 @@
 +                com.google.common.io.Files.write("{\n"
 +                        + "    \"pack\": {\n"
 +                        + "        \"description\": \"Data pack for resources provided by Bukkit plugins\",\n"
-+                        + "        \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n"
++                        + "        \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.SERVER_DATA) + "\n"
 +                        + "    }\n"
 +                        + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8);
 +            } catch (java.io.IOException ex) {
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
index 4513244012..52f25acba1 100644
--- a/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/server/commands/TeleportCommand.java
 +++ b/net/minecraft/server/commands/TeleportCommand.java
-@@ -20,6 +_,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.network.chat.Component;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.LivingEntity;
 @@ -290,7 +_,31 @@
              float f1 = relatives.contains(Relative.X_ROT) ? xRot - target.getXRot() : xRot;
              float f2 = Mth.wrapDegrees(f);
@@ -15,7 +7,7 @@
 -            if (target.teleportTo(level, d, d1, d2, relatives, f2, f3, true)) {
 +            // CraftBukkit start - Teleport event
 +            boolean result;
-+            if (target instanceof ServerPlayer player) {
++            if (target instanceof final net.minecraft.server.level.ServerPlayer player) {
 +                result = player.teleportTo(level, d, d1, d2, relatives, f2, f3, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND);
 +            } else {
 +                org.bukkit.Location to = new org.bukkit.Location(level.getWorld(), d, d1, d2, f2, f3);
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index afffdcd318..f74c99f4ac 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -1,33 +1,5 @@
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -135,10 +_,12 @@
- import net.minecraft.world.entity.projectile.ThrownEnderpearl;
- import net.minecraft.world.entity.vehicle.AbstractBoat;
- import net.minecraft.world.entity.vehicle.AbstractMinecart;
-+import net.minecraft.world.food.FoodData;
- import net.minecraft.world.inventory.AbstractContainerMenu;
- import net.minecraft.world.inventory.ContainerListener;
- import net.minecraft.world.inventory.ContainerSynchronizer;
- import net.minecraft.world.inventory.HorseInventoryMenu;
-+import net.minecraft.world.inventory.InventoryMenu;
- import net.minecraft.world.inventory.ResultSlot;
- import net.minecraft.world.inventory.Slot;
- import net.minecraft.world.item.Item;
-@@ -158,12 +_,14 @@
- import net.minecraft.world.level.biome.BiomeManager;
- import net.minecraft.world.level.block.BedBlock;
- import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.ChestBlock;
- import net.minecraft.world.level.block.HorizontalDirectionalBlock;
- import net.minecraft.world.level.block.RespawnAnchorBlock;
- import net.minecraft.world.level.block.entity.BlockEntity;
- import net.minecraft.world.level.block.entity.CommandBlockEntity;
- import net.minecraft.world.level.block.entity.SignBlockEntity;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.portal.TeleportTransition;
- import net.minecraft.world.level.saveddata.maps.MapId;
 @@ -223,7 +_,8 @@
      private int levitationStartTime;
      private boolean disconnected;
@@ -152,7 +124,7 @@
 +        this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> {
 +            this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem());
 +        });
-+        this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem());
++        this.containerSynchronizer.sendSlotChange(this.inventoryMenu, net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, this.getOffhandItem());
 +    }
 +
 +    // Yes, this doesn't match Vanilla, but it's the best we can do for now.
@@ -699,7 +671,7 @@
              ServerLevel serverLevel = this.serverLevel();
 -            ResourceKey<Level> resourceKey = serverLevel.dimension();
 +            // CraftBukkit start
-+            ResourceKey<LevelStem> resourceKey = serverLevel.getTypeKey();
++            ResourceKey<net.minecraft.world.level.dimension.LevelStem> resourceKey = serverLevel.getTypeKey();
 +
 +            org.bukkit.Location enter = this.getBukkitEntity().getLocation();
 +            PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
@@ -758,7 +730,7 @@
                  ProfilerFiller profilerFiller = Profiler.get();
                  profilerFiller.push("moving");
 -                if (resourceKey == Level.OVERWORLD && level.dimension() == Level.NETHER) {
-+                if (level != null && resourceKey == LevelStem.OVERWORLD && level.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event
++                if (level != null && resourceKey == net.minecraft.world.level.dimension.LevelStem.OVERWORLD && level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event
                      this.enteredNetherPosition = this.position();
                  }
  
@@ -998,9 +970,9 @@
 +                    // SPIGOT-5263 - close chest if cancelled
 +                    if (menu instanceof Container) {
 +                        ((Container) menu).stopOpen(this);
-+                    } else if (menu instanceof ChestBlock.DoubleInventory) {
++                    } else if (menu instanceof net.minecraft.world.level.block.ChestBlock.DoubleInventory doubleInventory) {
 +                        // SPIGOT-5355 - double chests too :(
-+                        ((ChestBlock.DoubleInventory) menu).inventorylargechest.stopOpen(this);
++                        doubleInventory.inventorylargechest.stopOpen(this);
 +                        // Paper start - Fix InventoryOpenEvent cancellation
 +                    } else if (!this.enderChestInventory.isActiveChest(null)) {
 +                        this.enderChestInventory.stopOpen(this);
@@ -1653,7 +1625,7 @@
 +        this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn
 +        this.setRemainingFireTicks(0);
 +        this.fallDistance = 0;
-+        this.foodData = new FoodData();
++        this.foodData = new net.minecraft.world.food.FoodData();
 +        this.experienceLevel = this.newLevel;
 +        this.totalExperience = this.newTotalExp;
 +        this.experienceProgress = 0;
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index eac24793a9..a889ac8c79 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -13,6 +_,7 @@
- import net.minecraft.world.InteractionResult;
- import net.minecraft.world.MenuProvider;
- import net.minecraft.world.entity.EquipmentSlot;
-+import net.minecraft.world.item.DoubleHighBlockItem;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.context.UseOnContext;
- import net.minecraft.world.item.enchantment.EnchantmentHelper;
 @@ -41,6 +_,8 @@
      private BlockPos delayedDestroyPos = BlockPos.ZERO;
      private int delayedTickStart;
@@ -392,7 +384,7 @@
 +                // Paper end - Don't resync blocks
 +            } else if (blockState.getBlock() instanceof net.minecraft.world.level.block.CakeBlock) {
 +                player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
-+            } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) {
++            } else if (this.interactItemStack.getItem() instanceof net.minecraft.world.item.DoubleHighBlockItem) {
 +                // send a correcting update to the client, as it already placed the upper half of the bisected item
 +                //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks
 +
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
index 43bacc9139..81c6160738 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -1,26 +1,11 @@
 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
-@@ -27,30 +_,82 @@
+@@ -27,30 +_,67 @@
  import net.minecraft.util.profiling.Profiler;
  import org.slf4j.Logger;
  
 -public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener {
-+// CraftBukkit start
-+import io.netty.buffer.ByteBuf;
-+import java.util.concurrent.ExecutionException;
-+import net.minecraft.network.ConnectionProtocol;
-+import net.minecraft.network.protocol.common.custom.DiscardedPayload;
-+import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.server.level.ServerPlayer;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.craftbukkit.util.CraftLocation;
-+import org.bukkit.craftbukkit.util.Waitable;
-+import org.bukkit.event.player.PlayerKickEvent;
-+import org.bukkit.event.player.PlayerResourcePackStatusEvent;
-+
-+public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection {
-+    // CraftBukkit end
++public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection { // CraftBukkit
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final int LATENCY_CHECK_INTERVAL = 15000;
      private static final int CLOSED_LISTENER_TIMEOUT = 15000;
@@ -39,16 +24,16 @@
      private int latency;
      private volatile boolean suspendFlushingOnServerThread = false;
 +    // CraftBukkit start
-+    protected final ServerPlayer player;
++    protected final net.minecraft.server.level.ServerPlayer player;
 +    protected final org.bukkit.craftbukkit.CraftServer cserver;
 +    public boolean processedDisconnect;
 +    // CraftBukkit end
 +    public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
 +    private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
-+    protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
++    protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
  
 -    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
-+    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { // CraftBukkit
++    public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit
          this.server = server;
          this.connection = connection;
          this.keepAliveTime = Util.getMillis();
@@ -61,7 +46,7 @@
 +        this.cserver = server.server;
 +    }
 +
-+    public CraftPlayer getCraftPlayer() {
++    public org.bukkit.craftbukkit.entity.CraftPlayer getCraftPlayer() {
 +        return this.player == null ? null : this.player.getBukkitEntity();
 +    }
 +
@@ -71,7 +56,7 @@
 +    }
 +
 +    @Override
-+    public ConnectionProtocol getProtocol() {
++    public net.minecraft.network.ConnectionProtocol getProtocol() {
 +        return this.protocol();
 +    }
 +
@@ -106,7 +91,7 @@
              this.keepAlivePending = false;
          } else if (!this.isSingleplayerOwner()) {
 -            this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
-+            this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
++            this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
          }
      }
  
@@ -114,8 +99,8 @@
      public void handlePong(ServerboundPongPacket packet) {
      }
  
-+    private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); // CraftBukkit
-+    private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit
++    private static final net.minecraft.resources.ResourceLocation CUSTOM_REGISTER = net.minecraft.resources.ResourceLocation.withDefaultNamespace("register"); // CraftBukkit
++    private static final net.minecraft.resources.ResourceLocation CUSTOM_UNREGISTER = net.minecraft.resources.ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit
 +
      @Override
      public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
@@ -126,12 +111,12 @@
 +            this.player.clientBrandName = brand;
 +        }
 +        // Paper end - Brand support
-+        if (!(packet.payload() instanceof DiscardedPayload)) {
++        if (!(packet.payload() instanceof net.minecraft.network.protocol.common.custom.DiscardedPayload)) {
 +            return;
 +        }
 +        PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
-+        ResourceLocation identifier = packet.payload().type().id();
-+        ByteBuf payload = ((DiscardedPayload)packet.payload()).data();
++        net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id();
++        io.netty.buffer.ByteBuf payload = ((net.minecraft.network.protocol.common.custom.DiscardedPayload)packet.payload()).data();
 +
 +        if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) {
 +            try {
@@ -141,7 +126,7 @@
 +                }
 +            } catch (Exception ex) {
 +                ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex);
-+                this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
++                this.disconnect(Component.literal("Invalid payload REGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
 +            try {
@@ -151,7 +136,7 @@
 +                }
 +            } catch (Exception ex) {
 +                ServerGamePacketListenerImpl.LOGGER.error("Couldn't unregister custom payload", ex);
-+                this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
++                this.disconnect(Component.literal("Invalid payload UNREGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        } else {
 +            try {
@@ -169,7 +154,7 @@
 +                this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data);
 +            } catch (Exception ex) {
 +                ServerGamePacketListenerImpl.LOGGER.error("Couldn't dispatch custom payload", ex);
-+                this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
++                this.disconnect(Component.literal("Invalid custom payload!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
 +            }
 +        }
 +    }
@@ -186,7 +171,7 @@
              LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
 -            this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
 -        }
-+            this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
++            this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
 +        }
 +        // Paper start - adventure pack callbacks
 +        // call the callbacks before the previously-existing event so the event has final say
@@ -201,9 +186,9 @@
 +        }
 +        // Paper end
 +        // Paper start - store last pack status
-+        PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()];
++        org.bukkit.event.player.PlayerResourcePackStatusEvent.Status packStatus = org.bukkit.event.player.PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()];
 +        this.player.getBukkitEntity().resourcePackStatus = packStatus;
-+        this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit
++        this.cserver.getPluginManager().callEvent(new org.bukkit.event.player.PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit
 +        // Paper end - store last pack status
      }
  
@@ -216,7 +201,7 @@
 +            return;
 +        }
 +        // CraftBukkit end
-+        this.disconnect(DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
++        this.disconnect(DISCONNECT_UNEXPECTED_QUERY, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
      }
  
      protected void keepConnectionAlive() {
@@ -232,7 +217,7 @@
 +        long elapsedTime = currentTime - this.keepAliveTime;
 +        if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
 +            if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
-+                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
++                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
 +            } else if (this.checkIfClosed(currentTime)) { // Paper
                  this.keepAlivePending = true;
 -                this.keepAliveTime = millis;
@@ -251,7 +236,7 @@
          if (this.closed) {
              if (time - this.closedListenerTime >= 15000L) {
 -                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
-+                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
++                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
              }
  
              return false;
@@ -262,8 +247,8 @@
 +        // CraftBukkit start
 +        if (packet == null || this.processedDisconnect) { // Spigot
 +            return;
-+        } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) {
-+            this.player.compassTarget = CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld());
++        } else if (packet instanceof net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) {
++            this.player.compassTarget = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld());
 +        }
 +        // CraftBukkit end
          if (packet.isTerminal()) {
@@ -275,10 +260,10 @@
  
 +    // Paper start - adventure
 +    public void disconnect(final net.kyori.adventure.text.Component reason) {
-+        this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
++        this.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
 +    }
 +
-+    public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
++    public void disconnect(final net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
 +        this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
 +    }
 +    // Paper end - adventure
@@ -290,21 +275,21 @@
 -
 -    public void disconnect(DisconnectionDetails disconnectionDetails) {
 +        // Paper start - kick event causes
-+        this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
++        this.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
 +    }
 +
-+    public void disconnect(final Component reason, PlayerKickEvent.Cause cause) {
++    public void disconnect(final Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
 +        this.disconnect(new DisconnectionDetails(reason), cause);
 +        // Paper end - kick event causes
 +    }
 +
-+    public void disconnect(DisconnectionDetails disconnectionDetails, PlayerKickEvent.Cause cause) { // Paper - kick event cause
++    public void disconnect(DisconnectionDetails disconnectionDetails, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event cause
 +        // CraftBukkit start - fire PlayerKickEvent
 +        if (this.processedDisconnect) {
 +            return;
 +        }
 +        if (!this.cserver.isPrimaryThread()) {
-+            Waitable waitable = new Waitable() {
++            org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() {
 +                @Override
 +                protected Object evaluate() {
 +                    ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes
@@ -318,7 +303,7 @@
 +                waitable.get();
 +            } catch (InterruptedException e) {
 +                Thread.currentThread().interrupt();
-+            } catch (ExecutionException e) {
++            } catch (java.util.concurrent.ExecutionException e) {
 +                throw new RuntimeException(e);
 +            }
 +            return;
@@ -326,7 +311,7 @@
 +
 +        net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure
 +
-+        PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
++        org.bukkit.event.player.PlayerKickEvent event = new org.bukkit.event.player.PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
 +
 +        if (this.cserver.getServer().isRunning()) {
 +            this.cserver.getPluginManager().callEvent(event);
@@ -358,15 +343,15 @@
 +    }
 +
 +    // Paper start - add proper async disconnect
-+    public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
++    public void disconnectAsync(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
 +        this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
 +    }
 +
-+    public void disconnectAsync(Component reason, PlayerKickEvent.Cause cause) {
++    public void disconnectAsync(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
 +        this.disconnectAsync(new DisconnectionDetails(reason), cause);
 +    }
 +
-+    public void disconnectAsync(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) {
++    public void disconnectAsync(DisconnectionDetails disconnectionInfo, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
 +        if (this.cserver.isPrimaryThread()) {
 +            this.disconnect(disconnectionInfo, cause);
 +            return;
diff --git a/paper-server/src/main/java/org/spigotmc/ActivationRange.java b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
index 607e2edb5c..d2d43209ac 100644
--- a/paper-server/src/main/java/org/spigotmc/ActivationRange.java
+++ b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
@@ -29,11 +29,12 @@ import net.minecraft.world.entity.raid.Raider;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.phys.AABB;
 
-public class ActivationRange
-{
+public final class ActivationRange {
 
-    public enum ActivationType
-    {
+    private ActivationRange() {
+    }
+
+    public enum ActivationType {
         WATER, // Paper
         FLYING_MONSTER, // Paper
         VILLAGER, // Paper
@@ -42,10 +43,10 @@ public class ActivationRange
         RAIDER,
         MISC;
 
-        AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 );
+        AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0);
     }
 
-    static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 );
+    static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0);
 
     /**
      * Initializes an entities type on construction to specify what group this
@@ -54,19 +55,14 @@ public class ActivationRange
      * @param entity
      * @return group id
      */
-    public static ActivationType initializeEntityActivationType(Entity entity)
-    {
-        if ( entity instanceof Raider )
-        {
+    public static ActivationType initializeEntityActivationType(final Entity entity) {
+        if (entity instanceof Raider) {
             return ActivationType.RAIDER;
-        } else if ( entity instanceof Monster || entity instanceof Slime )
-        {
+        } else if (entity instanceof Monster || entity instanceof Slime) {
             return ActivationType.MONSTER;
-        } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature )
-        {
+        } else if (entity instanceof PathfinderMob || entity instanceof AmbientCreature) {
             return ActivationType.ANIMAL;
-        } else
-        {
+        } else {
             return ActivationType.MISC;
         }
     }
@@ -78,31 +74,25 @@ public class ActivationRange
      * @param config
      * @return boolean If it should always tick.
      */
-    public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
-    {
-        if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 )
-                || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 )
-                || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 )
-                || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 )
-                || entity instanceof Player
-                || entity instanceof ThrowableProjectile
-                || entity instanceof EnderDragon
-                || entity instanceof EnderDragonPart
-                || entity instanceof WitherBoss
-                || entity instanceof AbstractHurtingProjectile
-                || entity instanceof LightningBolt
-                || entity instanceof PrimedTnt
-                || entity instanceof net.minecraft.world.entity.item.FallingBlockEntity // Paper - Always tick falling blocks
-                || entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart // Paper
-                || entity instanceof net.minecraft.world.entity.vehicle.AbstractBoat // Paper
-                || entity instanceof EndCrystal
-                || entity instanceof FireworkRocketEntity
-                || entity instanceof ThrownTrident )
-        {
-            return true;
-        }
-
-        return false;
+    public static boolean initializeEntityActivationState(final Entity entity, final SpigotWorldConfig config) {
+        return (entity.activationType == ActivationType.MISC && config.miscActivationRange == 0)
+            || (entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0)
+            || (entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0)
+            || (entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0)
+            || entity instanceof Player
+            || entity instanceof ThrowableProjectile
+            || entity instanceof EnderDragon
+            || entity instanceof EnderDragonPart
+            || entity instanceof WitherBoss
+            || entity instanceof AbstractHurtingProjectile
+            || entity instanceof LightningBolt
+            || entity instanceof PrimedTnt
+            || entity instanceof net.minecraft.world.entity.item.FallingBlockEntity // Paper - Always tick falling blocks
+            || entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart // Paper
+            || entity instanceof net.minecraft.world.entity.vehicle.AbstractBoat // Paper
+            || entity instanceof EndCrystal
+            || entity instanceof FireworkRocketEntity
+            || entity instanceof ThrownTrident;
     }
 
     /**
@@ -111,31 +101,28 @@ public class ActivationRange
      *
      * @param world
      */
-    public static void activateEntities(Level world)
-    {
+    public static void activateEntities(final Level world) {
         final int miscActivationRange = world.spigotConfig.miscActivationRange;
         final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
         final int animalActivationRange = world.spigotConfig.animalActivationRange;
         final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
 
-        int maxRange = Math.max( monsterActivationRange, animalActivationRange );
-        maxRange = Math.max( maxRange, raiderActivationRange );
-        maxRange = Math.max( maxRange, miscActivationRange );
-        maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange );
+        int maxRange = Math.max(monsterActivationRange, animalActivationRange);
+        maxRange = Math.max(maxRange, raiderActivationRange);
+        maxRange = Math.max(maxRange, miscActivationRange);
+        maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange);
 
-        for ( Player player : world.players() )
-        {
+        for (final Player player : world.players()) {
             player.activatedTick = MinecraftServer.currentTick;
-            if ( world.spigotConfig.ignoreSpectatorActivation && player.isSpectator() )
-            {
+            if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) {
                 continue;
             }
 
-            ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange );
-            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange );
-            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange );
-            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange );
-            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange );
+            ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, 256, maxRange);
+            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, 256, miscActivationRange);
+            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, 256, raiderActivationRange);
+            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, 256, animalActivationRange);
+            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, 256, monsterActivationRange);
 
             world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity);
         }
@@ -146,17 +133,13 @@ public class ActivationRange
      *
      * @param entity
      */
-    private static void activateEntity(Entity entity)
-    {
-        if ( MinecraftServer.currentTick > entity.activatedTick )
-        {
-            if ( entity.defaultActivationState )
-            {
+    private static void activateEntity(final Entity entity) {
+        if (MinecraftServer.currentTick > entity.activatedTick) {
+            if (entity.defaultActivationState) {
                 entity.activatedTick = MinecraftServer.currentTick;
                 return;
             }
-            if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) )
-            {
+            if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) {
                 entity.activatedTick = MinecraftServer.currentTick;
             }
         }
@@ -169,60 +152,43 @@ public class ActivationRange
      * @param entity
      * @return
      */
-    public static boolean checkEntityImmunities(Entity entity)
-    {
+    public static boolean checkEntityImmunities(final Entity entity) {
         // quick checks.
-        if ( entity.isInWater() || entity.getRemainingFireTicks() > 0 )
-        {
+        if (entity.isInWater() || entity.getRemainingFireTicks() > 0) {
             return true;
         }
-        if ( !( entity instanceof AbstractArrow ) )
-        {
-            if ( !entity.onGround() || !entity.getPassengers().isEmpty() || entity.isPassenger() )
-            {
+        if (!(entity instanceof final AbstractArrow abstractArrow)) {
+            if (!entity.onGround() || !entity.getPassengers().isEmpty() || entity.isPassenger()) {
                 return true;
             }
-        } else if ( !( (AbstractArrow) entity ).isInGround() )
-        {
+        } else if (!abstractArrow.isInGround()) {
             return true;
         }
         // special cases.
-        if ( entity instanceof LivingEntity )
-        {
-            LivingEntity living = (LivingEntity) entity;
-            if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 )
-            {
+        if (entity instanceof final LivingEntity living) {
+            if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || !living.activeEffects.isEmpty()) {
                 return true;
             }
-            if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null )
-            {
+            if (entity instanceof final PathfinderMob pathfinderMob && pathfinderMob.getTarget() != null) {
                 return true;
             }
-            if ( entity instanceof Villager && ( (Villager) entity ).canBreed() )
-            {
+            if (entity instanceof final Villager villager && villager.canBreed()) {
                 return true;
             }
-            if ( entity instanceof Animal )
-            {
-                Animal animal = (Animal) entity;
-                if ( animal.isBaby() || animal.isInLove() )
-                {
+            if (entity instanceof final Animal animal) {
+                if (animal.isBaby() || animal.isInLove()) {
                     return true;
                 }
-                if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() )
-                {
+                if (entity instanceof final Sheep sheep && sheep.isSheared()) {
                     return true;
                 }
             }
-            if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive
+            if (entity instanceof final Creeper creeper && creeper.isIgnited()) { // isExplosive
                 return true;
             }
         }
         // SPIGOT-6644: Otherwise the target refresh tick will be missed
-        if (entity instanceof ExperienceOrb) {
-            return true;
-        }
-        return false;
+        return entity instanceof ExperienceOrb;
     }
 
     /**
@@ -231,8 +197,7 @@ public class ActivationRange
      * @param entity
      * @return
      */
-    public static boolean checkIfActive(Entity entity)
-    {
+    public static boolean checkIfActive(final Entity entity) {
         // Never safe to skip fireworks or item gravity
         if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick
             return true;
@@ -241,13 +206,10 @@ public class ActivationRange
         boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
 
         // Should this entity tick?
-        if ( !isActive )
-        {
-            if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
-            {
+        if (!isActive) {
+            if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
                 // Check immunities every 20 ticks.
-                if ( ActivationRange.checkEntityImmunities( entity ) )
-                {
+                if (ActivationRange.checkEntityImmunities(entity)) {
                     // Triggered some sort of immunity, give 20 full ticks before we check again.
                     entity.activatedTick = MinecraftServer.currentTick + 20;
                 }
diff --git a/paper-server/src/main/java/org/spigotmc/TrackingRange.java b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
index bb06f89a29..039f8abeb5 100644
--- a/paper-server/src/main/java/org/spigotmc/TrackingRange.java
+++ b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
@@ -8,8 +8,10 @@ import net.minecraft.world.entity.decoration.ItemFrame;
 import net.minecraft.world.entity.decoration.Painting;
 import net.minecraft.world.entity.item.ItemEntity;
 
-public class TrackingRange
-{
+public final class TrackingRange {
+
+    private TrackingRange() {
+    }
 
     /**
      * Gets the range an entity should be 'tracked' by players and visible in
@@ -19,17 +21,14 @@ public class TrackingRange
      * @param defaultRange Default range defined by Mojang
      * @return
      */
-    public static int getEntityTrackingRange(Entity entity, int defaultRange)
-    {
-        if ( defaultRange == 0 )
-        {
+    public static int getEntityTrackingRange(final Entity entity, final int defaultRange) {
+        if (defaultRange == 0) {
             return defaultRange;
         }
-        SpigotWorldConfig config = entity.level().spigotConfig;
-        if ( entity instanceof ServerPlayer )
-        {
+        final SpigotWorldConfig config = entity.level().spigotConfig;
+        if (entity instanceof ServerPlayer) {
             return config.playerTrackingRange;
-        // Paper start - Simplify and set water mobs to animal tracking range
+            // Paper start - Simplify and set water mobs to animal tracking range
         }
         switch (entity.activationType) {
             case RAIDER:
@@ -42,16 +41,15 @@ public class TrackingRange
                 return config.animalTrackingRange;
             case MISC:
         }
-        if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb )
+        if (entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb) {
         // Paper end
-        {
             return config.miscTrackingRange;
-        } else if ( entity instanceof Display )
-        {
+        } else if (entity instanceof Display) {
             return config.displayTrackingRange;
-        } else
-        {
-            if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt
+        } else {
+            if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) {
+                return ((net.minecraft.server.level.ServerLevel) (entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt
+            }
             return config.otherTrackingRange;
         }
     }

From 8f1dcdd0b72f941644d5d7943028f1f3fa54fa7b Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Sun, 15 Dec 2024 22:39:52 +0100
Subject: [PATCH 169/285] update restamp, add back entity ATs, rebuild patches

---
 build-data/paper.at                             | 17 +++++++++++++++++
 build.gradle.kts                                |  2 +-
 .../minecraft/world/entity/Entity.java.patch    | 12 ++++++------
 .../world/entity/npc/Villager.java.patch        |  9 ---------
 .../world/entity/npc/WanderingTrader.java.patch |  9 ---------
 .../world/level/block/ChestBlock.java.patch     |  2 +-
 6 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index 8cb2a85b2a..320cbacc90 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -176,6 +176,23 @@ public net.minecraft.world.entity.Display$TextDisplay getTextOpacity()B
 public net.minecraft.world.entity.Display$TextDisplay setFlags(B)V
 public net.minecraft.world.entity.Display$TextDisplay setText(Lnet/minecraft/network/chat/Component;)V
 public net.minecraft.world.entity.Display$TextDisplay setTextOpacity(B)V
+public net.minecraft.world.entity.Entity FLAG_INVISIBLE
+public net.minecraft.world.entity.Entity getEncodeId()Ljava/lang/String;
+public net.minecraft.world.entity.Entity getFireImmuneTicks()I
+public net.minecraft.world.entity.Entity getSharedFlag(I)Z
+public net.minecraft.world.entity.Entity hasVisualFire
+public net.minecraft.world.entity.Entity isInBubbleColumn()Z
+public net.minecraft.world.entity.Entity isInRain()Z
+public net.minecraft.world.entity.Entity isInvulnerableToBase(Lnet/minecraft/world/damagesource/DamageSource;)Z
+public net.minecraft.world.entity.Entity onGround
+public net.minecraft.world.entity.Entity passengers
+public net.minecraft.world.entity.Entity portalCooldown
+public net.minecraft.world.entity.Entity random
+public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V
+public net.minecraft.world.entity.Entity setRot(FF)V
+public net.minecraft.world.entity.Entity setSharedFlag(IZ)V
+public net.minecraft.world.entity.Entity unsetRemoved()V
+public net.minecraft.world.entity.Entity wasTouchingWater
 public net.minecraft.world.entity.ExperienceOrb count
 public net.minecraft.world.entity.ExperienceOrb value
 public net.minecraft.world.entity.GlowSquid setDarkTicks(I)V
diff --git a/build.gradle.kts b/build.gradle.kts
index 2360d32e7a..33a763a905 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -11,7 +11,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.1" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.2" apply false
 }
 
 subprojects {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 4dfa987afe..7bd2ceef50 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -119,11 +119,11 @@
      public double zOld;
      public boolean noPhysics;
      private boolean wasOnFire;
--    protected final RandomSource random = RandomSource.create();
-+    protected final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+-    public final RandomSource random = RandomSource.create();
++    public final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
      public int tickCount;
      private int remainingFireTicks = -this.getFireImmuneTicks();
-     protected boolean wasTouchingWater;
+     public boolean wasTouchingWater;
 @@ -233,7 +_,7 @@
      protected UUID uuid = Mth.createInsecureUUID(this.random);
      protected String stringUUID = this.uuid.toString();
@@ -341,7 +341,7 @@
 @@ -390,6 +_,32 @@
      }
  
-     protected void setRot(float yRot, float xRot) {
+     public void setRot(float yRot, float xRot) {
 +        // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
 +        if (Float.isNaN(yRot)) {
 +            yRot = 0;
@@ -1193,7 +1193,7 @@
 +        // CraftBukkit - end
      }
  
-     protected boolean getSharedFlag(int flag) {
+     public boolean getSharedFlag(int flag) {
 @@ -2472,7 +_,7 @@
      }
  
@@ -1695,7 +1695,7 @@
 +        // Paper end - Folia schedulers
      }
  
-     protected void unsetRemoved() {
+     public void unsetRemoved() {
          this.removalReason = null;
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
index 61d20a9b99..a650c82715 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
@@ -88,15 +88,6 @@
                  double d = 0.3 + 0.0625 * amplifier;
                  int i = (int)Math.floor(d * merchantOffer1.getBaseCostA().getCount());
                  merchantOffer1.addToSpecialPriceDiff(-Math.max(i, 1));
-@@ -559,7 +_,7 @@
-     }
- 
-     @Override
--    protected SoundEvent getDeathSound() {
-+    public SoundEvent getDeathSound() {
-         return SoundEvents.VILLAGER_DEATH;
-     }
- 
 @@ -594,7 +_,7 @@
          }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
index d5e6a224ee..9e1d1a6ace 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
@@ -70,15 +70,6 @@
          }
      }
  
-@@ -204,7 +_,7 @@
-     }
- 
-     @Override
--    protected SoundEvent getDeathSound() {
-+    public SoundEvent getDeathSound() {
-         return SoundEvents.WANDERING_TRADER_DEATH;
-     }
- 
 @@ -241,7 +_,7 @@
  
      private void maybeDespawn() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
index 5200deab3e..55671a0957 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ChestBlock.java.patch
@@ -52,7 +52,7 @@
 @@ -285,7 +_,14 @@
      @Nullable
      @Override
-     protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
+     public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
 -        return this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null);
 +    // CraftBukkit start
 +        return this.getMenuProvider(state, level, pos, false);

From f5cd5989a4831ff47c6a4e13aba457ebe58f8422 Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@minidigger.dev>
Date: Sun, 15 Dec 2024 22:54:44 +0100
Subject: [PATCH 170/285] add an manual AT to CommandNode for now

---
 .../com/mojang/brigadier/tree/CommandNode.java.patch      | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
index 1431fc6d44..b8df0ccf0b 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
@@ -1,6 +1,12 @@
 --- a/com/mojang/brigadier/tree/CommandNode.java
 +++ b/com/mojang/brigadier/tree/CommandNode.java
-@@ -32,6 +_,16 @@
+@@ -27,11 +_,21 @@
+     private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
+     private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
+     private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
+-    private final Predicate<S> requirement;
++    public final Predicate<S> requirement; // Paper
+     private final CommandNode<S> redirect;
      private final RedirectModifier<S> modifier;
      private final boolean forks;
      private Command<S> command;

From ec57b99bf5f9eb178ac229d2ec2fe6163f7a347a Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 23:07:15 +0100
Subject: [PATCH 171/285] readd removal cause for thrown eggs

---
 .../world/entity/animal/Bee.java.patch        |  2 +-
 .../boss/enderdragon/EnderDragon.java.patch   | 19 +++++----
 .../entity/boss/wither/WitherBoss.java.patch  |  8 ++--
 .../decoration/BlockAttachedEntity.java.patch |  9 ++---
 .../entity/item/FallingBlockEntity.java.patch |  2 +-
 .../world/entity/item/ItemEntity.java.patch   |  6 +--
 .../monster/AbstractSkeleton.java.patch       | 25 +++++++++---
 .../world/entity/monster/Slime.java.patch     | 40 ++++++++++++-------
 .../world/entity/monster/Spider.java.patch    |  2 +-
 .../world/entity/monster/Witch.java.patch     | 12 +++---
 .../world/entity/monster/Zombie.java.patch    | 15 ++-----
 .../entity/monster/ZombifiedPiglin.java.patch |  4 +-
 .../entity/monster/piglin/PiglinAi.java.patch |  2 +-
 .../entity/npc/AbstractVillager.java.patch    |  2 +-
 .../world/entity/npc/Villager.java.patch      |  2 +-
 .../npc/WanderingTraderSpawner.java.patch     |  4 +-
 .../projectile/AbstractArrow.java.patch       |  2 +-
 .../entity/projectile/ThrownEgg.java.patch    |  9 ++++-
 .../minecraft/world/item/ItemUtils.java.patch |  4 +-
 .../TeleportRandomlyConsumeEffect.java.patch  |  2 +-
 .../entity/BrewingStandBlockEntity.java.patch |  3 +-
 .../bukkit/craftbukkit/entity/CraftItem.java  |  4 +-
 22 files changed, 96 insertions(+), 82 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
index 3891fbabb2..d8e007e816 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
@@ -102,7 +102,7 @@
 +            if (!super.hurtServer(level, damageSource, amount)) return false; // CraftBukkit - Only stop pollinating if entity was damaged
              this.beePollinateGoal.stopPollinating();
 -            return super.hurtServer(level, damageSource, amount);
-+            return true; //  CraftBukkit - Only stop pollinating if entity was damaged
++            return true; // CraftBukkit - Only stop pollinating if entity was damaged
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
index beddc1e044..48b66a876e 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -183,7 +183,7 @@
          if (this.level() instanceof ServerLevel serverLevel) {
 -            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
 -                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F));
-+            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) {  // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
++            if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
 +                ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
              }
  
@@ -195,16 +195,16 @@
 +                for (net.minecraft.server.level.ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
 +                    double deltaX = this.getX() - player.getX();
 +                    double deltaZ = this.getZ() - player.getZ();
-+                    double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++                    double distanceSquared = Mth.square(deltaX) + Mth.square(deltaZ);
 +                    final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule
 +                    if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Paper - respect global sound events gamerule
-+                    if (distanceSquared > viewDistance * viewDistance) {
++                    if (distanceSquared > Mth.square(viewDistance)) {
 +                        double deltaLength = Math.sqrt(distanceSquared);
 +                        double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
 +                        double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_DRAGON_DEATH, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
 +                    } else {
-+                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
++                        player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_DRAGON_DEATH, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true));
 +                    }
 +                }
 +                // CraftBukkit end
@@ -260,7 +260,7 @@
              float max = Math.max((float)Math.sqrt(heightmapPos.distToCenterSqr(this.position())) / 4.0F, 1.0F);
              float f = 6.0F / max;
              float xRot = this.getXRot();
-@@ -883,4 +_,20 @@
+@@ -883,4 +_,19 @@
      protected float sanitizeScale(float scale) {
          return 1.0F;
      }
@@ -270,14 +270,13 @@
 +    public int getExpReward(ServerLevel worldserver, Entity entity) {
 +        // CraftBukkit - Moved from #tickDeath method
 +        boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
-+        short short0 = 500;
++        int i = 500;
 +
 +        if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
-+            short0 = 12000;
++            i = 12000;
 +        }
 +
-+        return flag ? short0 : 0;
++        return flag ? i : 0;
 +    }
 +    // CraftBukkit end
-+
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
index b4eaea2841..9d6f177cef 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -34,16 +34,16 @@
 +                    for (ServerPlayer player : level.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
 +                        double deltaX = this.getX() - player.getX();
 +                        double deltaZ = this.getZ() - player.getZ();
-+                        double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++                        double distanceSquared = Mth.square(deltaX) + Mth.square(deltaZ);
 +                        final double soundRadiusSquared = level.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule
 +                        if (!level.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule
-+                        if (distanceSquared > viewDistance * viewDistance) {
++                        if (distanceSquared > Mth.square(viewDistance)) {
 +                            double deltaLength = Math.sqrt(distanceSquared);
 +                            double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
 +                            double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
-+                            player.connection.send(new ClientboundLevelEventPacket(1023, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
++                            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_WITHER_BOSS_SPAWN, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
 +                        } else {
-+                            player.connection.send(new ClientboundLevelEventPacket(1023, this.blockPosition(), 0, true));
++                            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_WITHER_BOSS_SPAWN, this.blockPosition(), 0, true));
 +                        }
 +                    }
 +                    // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
index a5583937ae..55a016fcd9 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/BlockAttachedEntity.java.patch
@@ -9,7 +9,7 @@
      protected BlockPos pos;
  
      protected BlockAttachedEntity(EntityType<? extends BlockAttachedEntity> entityType, Level level) {
-@@ -38,10 +_,29 @@
+@@ -38,10 +_,26 @@
      public void tick() {
          if (this.level() instanceof ServerLevel serverLevel) {
              this.checkBelowWorld();
@@ -18,12 +18,9 @@
                  this.checkInterval = 0;
                  if (!this.isRemoved() && !this.survives()) {
 -                    this.discard();
-+                    // this.discard();
 +                    // CraftBukkit start - fire break events
-+                    net.minecraft.world.level.block.state.BlockState material = this.level().getBlockState(this.blockPosition());
-+                    org.bukkit.event.hanging.HangingBreakEvent.RemoveCause cause;
-+
-+                    if (!material.isAir()) {
++                    final org.bukkit.event.hanging.HangingBreakEvent.RemoveCause cause;
++                    if (!this.level().getBlockState(this.blockPosition()).isAir()) {
 +                        // TODO: This feels insufficient to catch 100% of suffocation cases
 +                        cause = org.bukkit.event.hanging.HangingBreakEvent.RemoveCause.OBSTRUCTION;
 +                    } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
index 32fc663482..5b8f2129c4 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -128,7 +128,7 @@
          }
  
          compound.putBoolean("CancelDrop", this.cancelDrop);
-+        if (!autoExpire) {compound.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
++        if (!this.autoExpire) compound.putBoolean("Paper.AutoExpire", false); // Paper - Expand FallingBlock API
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 5db64d6e7d..7e9f5a0f27 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -51,7 +51,7 @@
                      f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F;
                  }
  
-@@ -184,11 +_,42 @@
+@@ -184,11 +_,40 @@
                  }
              }
  
@@ -75,14 +75,12 @@
 +    // Spigot start - copied from above
 +    @Override
 +    public void inactiveTick() {
-+        // Paper start - remove anti tick skipping measures / wall time - copied from above
 +        if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
 +            --this.pickupDelay;
 +        }
 +        if (this.age != -32768) {
 +            ++this.age;
 +        }
-+        // Paper end - remove anti tick skipping measures / wall time - copied from above
 +
 +        if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate
 +            // CraftBukkit start - fire ItemDespawnEvent
@@ -202,7 +200,7 @@
  
          if (this.getItem().isEmpty()) {
 -            this.discard();
-+            this.discard(null);  // CraftBukkit - add Bukkit remove cause
++            this.discard(null); // CraftBukkit - add Bukkit remove cause
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
index 35bee18fdb..50e3f4e66b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
@@ -1,17 +1,30 @@
 --- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
 +++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
-@@ -64,6 +_,11 @@
+@@ -64,6 +_,7 @@
              AbstractSkeleton.this.setAggressive(true);
          }
      };
-+    // Paper start - shouldBurnInDay API
-+    private boolean shouldBurnInDay = true;
-+    public boolean shouldBurnInDay() { return shouldBurnInDay; }
-+    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
-+    // Paper end - shouldBurnInDay API
++    private boolean shouldBurnInDay = true; // Paper - shouldBurnInDay API
  
      protected AbstractSkeleton(EntityType<? extends AbstractSkeleton> entityType, Level level) {
          super(entityType, level);
+@@ -88,6 +_,16 @@
+         return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.25);
+     }
+ 
++    // Paper start - shouldBurnInDay API
++    public boolean shouldBurnInDay() {
++        return this.shouldBurnInDay;
++    }
++
++    public void setShouldBurnInDay(boolean shouldBurnInDay) {
++        this.shouldBurnInDay = shouldBurnInDay;
++    }
++    // Paper end - shouldBurnInDay API
++
+     @Override
+     protected void playStepSound(BlockPos pos, BlockState block) {
+         this.playSound(this.getStepSound(), 0.15F, 1.0F);
 @@ -97,7 +_,7 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
index 2d78bee973..fc7af2dd2c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
@@ -1,5 +1,13 @@
 --- a/net/minecraft/world/entity/monster/Slime.java
 +++ b/net/minecraft/world/entity/monster/Slime.java
+@@ -56,6 +_,7 @@
+     public float squish;
+     public float oSquish;
+     private boolean wasOnGround;
++    private boolean canWander = true; // Paper - Slime pathfinder events
+ 
+     public Slime(EntityType<? extends Slime> entityType, Level level) {
+         super(entityType, level);
 @@ -110,6 +_,7 @@
          super.addAdditionalSaveData(compound);
          compound.putInt("Size", this.getSize() - 1);
@@ -114,6 +122,23 @@
                  return checkMobSpawnRules(entityType, level, spawnReason, pos, random);
              }
          }
+@@ -355,6 +_,16 @@
+         return super.getDefaultDimensions(pose).scale(this.getSize());
+     }
+ 
++    // Paper start - Slime pathfinder events
++    public boolean canWander() {
++        return this.canWander;
++    }
++
++    public void setWander(boolean canWander) {
++        this.canWander = canWander;
++    }
++    // Paper end - Slime pathfinder events
++
+     static class SlimeAttackGoal extends Goal {
+         private final Slime slime;
+         private int growTiredTimer;
 @@ -367,7 +_,16 @@
          @Override
          public boolean canUse() {
@@ -202,18 +227,3 @@
              }
  
              if (this.slime.getMoveControl() instanceof Slime.SlimeMoveControl slimeMoveControl) {
-@@ -536,4 +_,14 @@
-             }
-         }
-     }
-+    // Paper start - Slime pathfinder events
-+    private boolean canWander = true;
-+    public boolean canWander() {
-+        return canWander;
-+    }
-+
-+    public void setWander(boolean canWander) {
-+        this.canWander = canWander;
-+    }
-+    // Paper end - Slime pathfinder events
- }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
index 8bf70bc126..cbaaaef4ef 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
@@ -14,7 +14,7 @@
      @Override
      public boolean canBeAffected(MobEffectInstance potioneffect) {
 -        return !potioneffect.is(MobEffects.POISON) && super.canBeAffected(potioneffect);
-+        return potioneffect.is(MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(potioneffect); // Paper - Add config for mobs immune to default effects
++        return (!potioneffect.is(MobEffects.POISON) || !this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect) && super.canBeAffected(potioneffect); // Paper - Add config for mobs immune to default effects
      }
  
      public boolean isClimbing() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
index e1dbaaaa93..d7a8e9a59e 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Witch.java.patch
@@ -6,7 +6,7 @@
                      PotionContents potionContents = mainHandItem.get(DataComponents.POTION_CONTENTS);
 +                    // Paper start - WitchConsumePotionEvent
 +                    if (mainHandItem.is(Items.POTION)) {
-+                        com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack));
++                        com.destroystokyo.paper.event.entity.WitchConsumePotionEvent event = new com.destroystokyo.paper.event.entity.WitchConsumePotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(mainHandItem));
 +                        potionContents = event.callEvent() ? org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getPotion()).get(DataComponents.POTION_CONTENTS) : null;
 +                    }
 +                    // Paper end - WitchConsumePotionEvent
@@ -55,13 +55,13 @@
 +        this.usingTime = this.getMainHandItem().getUseDuration(this);
 +        this.setUsingItem(true);
 +        if (!this.isSilent()) {
-+            this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F);
++            this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.WITCH_DRINK, this.getSoundSource(), 1.0F, 0.8F + this.random.nextFloat() * 0.4F);
 +        }
 +
-+        AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
++        AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
 +
-+        attributemodifiable.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID);
-+        attributemodifiable.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING);
++        attribute.removeModifier(Witch.SPEED_MODIFIER_DRINKING_ID);
++        attribute.addTransientModifier(Witch.SPEED_MODIFIER_DRINKING);
 +    }
 +    // Paper end
 +
@@ -73,7 +73,7 @@
              if (this.level() instanceof ServerLevel serverLevel) {
                  ItemStack itemStack = PotionContents.createItemStack(Items.SPLASH_POTION, holder);
 +                // Paper start - WitchThrowPotionEvent
-+                com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack));
++                com.destroystokyo.paper.event.entity.WitchThrowPotionEvent event = new com.destroystokyo.paper.event.entity.WitchThrowPotionEvent((org.bukkit.entity.Witch) this.getBukkitEntity(), (org.bukkit.entity.LivingEntity) target.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack));
 +                if (!event.callEvent()) {
 +                    return;
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
index 1b2d06c538..b70be66183 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
@@ -11,7 +11,7 @@
      private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
      private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(
          ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05F, AttributeModifier.Operation.ADD_VALUE
-@@ -87,13 +_,16 @@
+@@ -87,13 +_,15 @@
      private static final EntityDimensions BABY_DIMENSIONS = EntityType.ZOMBIE.getDimensions().scale(0.5F).withEyeHeight(0.93F);
      private static final float BREAK_DOOR_CHANCE = 0.1F;
      public static final Predicate<Difficulty> DOOR_BREAKING_PREDICATE = difficulty -> difficulty == Difficulty.HARD;
@@ -20,7 +20,6 @@
      private boolean canBreakDoors;
      private int inWaterTime;
      public int conversionTime;
-+    // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time
 +    private boolean shouldBurnInDay = true; // Paper - Add more Zombie API
  
      public Zombie(EntityType<? extends Zombie> entityType, Level level) {
@@ -77,14 +76,6 @@
              }
          }
      }
-@@ -221,6 +_,7 @@
-         }
- 
-         super.tick();
-+        // this.lastTick = MinecraftServer.currentTick; // CraftBukkit // Paper - remove anti tick skipping measures / wall time
-     }
- 
-     @Override
 @@ -251,7 +_,14 @@
          super.aiStep();
      }
@@ -111,7 +102,7 @@
 -            zombie -> zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier())
 -        );
 +            // CraftBukkit start
-+            zombie -> {zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier());},
++            zombie -> { zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier()); },
 +            org.bukkit.event.entity.EntityTransformEvent.TransformReason.DROWNED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DROWNED);
 +        if (converted == null) {
 +            ((org.bukkit.entity.Zombie) this.getBukkitEntity()).setConversionTime(-1); // CraftBukkit - SPIGOT-5208: End conversion to stop event spam
@@ -214,7 +205,7 @@
      public boolean killedEntity(ServerLevel level, LivingEntity entity) {
          boolean flag = super.killedEntity(level, entity);
 -        if ((level.getDifficulty() == Difficulty.NORMAL || level.getDifficulty() == Difficulty.HARD) && entity instanceof Villager villager) {
-+        final double fallbackChance = level.getDifficulty() == Difficulty.HARD ? 100d : level.getDifficulty() == Difficulty.NORMAL ? 50d : 0d; // Paper - Configurable chance of villager zombie infection
++        final double fallbackChance = level.getDifficulty() == Difficulty.HARD ? 100 : level.getDifficulty() == Difficulty.NORMAL ? 50 : 0; // Paper - Configurable chance of villager zombie infection
 +        if (this.random.nextDouble() * 100 < level.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && entity instanceof Villager villager) { // Paper - Configurable chance of villager zombie infection
              if (level.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
                  return flag;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
index dca58ab24d..ea135560b2 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
@@ -13,7 +13,7 @@
          this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0, false));
          this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
 -        this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
-+        this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this)).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation
++        this.targetSelector.addGoal(1, this.pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this)).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation
          this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
          this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
      }
@@ -52,7 +52,7 @@
 +        this.level().getCraftServer().getPluginManager().callEvent(event);
 +        if (event.isCancelled()) {
 +            this.setPersistentAngerTarget(null);
-+            pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation
++            this.pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation
 +            return;
 +        }
 +        this.setRemainingPersistentAngerTime(event.getNewAnger());
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
index 524c238be3..467622eb81 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
@@ -21,7 +21,7 @@
 -        if (itemEntity.getItem().is(Items.GOLD_NUGGET)) {
 +        // CraftBukkit start
 +        // Paper start - EntityPickupItemEvent fixes; fix event firing twice
-+        if (itemEntity.getItem().is(Items.GOLD_NUGGET)/* && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()*/) { // Paper
++        if (itemEntity.getItem().is(Items.GOLD_NUGGET)) { // Paper
 +            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) return;
 +            piglin.onItemPickup(itemEntity); // Paper - moved from Piglin#pickUpItem - call prior to item entity modification
 +            // Paper end
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
index 458bc1fb33..06aabb0b2f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch
@@ -55,7 +55,7 @@
 +            offer.increaseUses();
 +        }
 +        if (event == null || event.isRewardingExp()) {
-+                this.rewardTradeXp(offer);
++            this.rewardTradeXp(offer);
 +        }
 +        this.notifyTrade(offer);
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
index a650c82715..7c58286a0c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
@@ -22,7 +22,7 @@
 +    // Spigot Start
 +    @Override
 +    public void inactiveTick() {
-+    // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
++        // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
 +        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
 +            this.customServerAiStep((ServerLevel) this.level());
 +        }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
index 8884c9e247..61198c578a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
@@ -14,7 +14,7 @@
 -            serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
 -        }
 +        // Paper start - Add Wandering Trader spawn rate config options
-+         this.tickDelay = Integer.MIN_VALUE;
++        this.tickDelay = Integer.MIN_VALUE;
 +        // this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay();
 +        // this.spawnChance = serverLevelData.getWanderingTraderSpawnChance();
 +        // if (this.spawnDelay == 0 && this.spawnChance == 0) {
@@ -81,7 +81,7 @@
  
                      this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID());
 -                    wanderingTrader.setDespawnDelay(48000);
-+                    //wanderingTrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent
++                    // wanderingTrader.setDespawnDelay(48000); // Paper - moved above, modifiable by plugins on CreatureSpawnEvent
                      wanderingTrader.setWanderTarget(blockPos1);
                      wanderingTrader.restrictTo(blockPos1, 16);
                      return true;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
index e6732700cf..337fa022f4 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -204,7 +204,7 @@
 +            ItemStack itemstack = this.getPickupItem();
 +            if (this.pickup == Pickup.ALLOWED && !itemstack.isEmpty() && entity.getInventory().canHold(itemstack) > 0) {
 +                ItemEntity item = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack);
-+                org.bukkit.event.player.PlayerPickupArrowEvent event = new org.bukkit.event.player.PlayerPickupArrowEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.level().getCraftServer(), item), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity());
++                org.bukkit.event.player.PlayerPickupArrowEvent event = new org.bukkit.event.player.PlayerPickupArrowEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) item.getBukkitEntity(), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity());
 +                // event.setCancelled(!entityhuman.canPickUpLoot); TODO
 +                this.level().getCraftServer().getPluginManager().callEvent(event);
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
index 4c2c2ef103..a19b02d297 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/projectile/ThrownEgg.java
 +++ b/net/minecraft/world/entity/projectile/ThrownEgg.java
-@@ -59,22 +_,56 @@
+@@ -59,28 +_,62 @@
      protected void onHit(HitResult result) {
          super.onHit(result);
          if (!this.level().isClientSide) {
@@ -61,3 +61,10 @@
                      }
                  }
              }
+ 
+             this.level().broadcastEntityEvent(this, (byte)3);
+-            this.discard();
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
index 0956357249..0f2be6fd1c 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemUtils.java.patch
@@ -6,8 +6,8 @@
          if (!level.isClientSide) {
 -            contents.forEach(itemStack -> level.addFreshEntity(new ItemEntity(level, container.getX(), container.getY(), container.getZ(), itemStack)));
 +            // Paper start - call EntityDropItemEvent
-+            contents.forEach(stack -> {
-+                ItemEntity droppedItem = new ItemEntity(level, container.getX(), container.getY(), container.getZ(), stack);
++            contents.forEach(itemStack -> {
++                ItemEntity droppedItem = new ItemEntity(level, container.getX(), container.getY(), container.getZ(), itemStack);
 +                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(container.getBukkitEntity(), (org.bukkit.entity.Item) droppedItem.getBukkitEntity());
 +                if (event.callEvent()) {
 +                    level.addFreshEntity(droppedItem);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
index 9a43ef7d58..36547c65ef 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/consume_effects/TeleportRandomlyConsumeEffect.java.patch
@@ -9,7 +9,7 @@
 +            java.util.Optional<Boolean> status = entity.randomTeleport(d, d1, d2, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT);
 +
 +            // teleport event was canceled, no more tries
-+            if (!status.isPresent()) break;
++            if (status.isEmpty()) break;
 +            if (status.get()) {
 +                // CraftBukkit end
                  level.gameEvent(GameEvent.TELEPORT, vec3, GameEvent.Context.of(entity));
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
index f3cb6d6470..e470fbe622 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -16,7 +16,7 @@
                  default -> 0;
              };
          }
-@@ -57,14 +_,50 @@
+@@ -57,14 +_,49 @@
                      break;
                  case 1:
                      BrewingStandBlockEntity.this.fuel = value;
@@ -36,7 +36,6 @@
          }
      };
 +    // CraftBukkit start - add fields and methods
-+    // private int lastTick = MinecraftServer.currentTick; // Paper - remove anti tick skipping measures / wall time
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
index 30d62ee4d5..7a3d982b13 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
@@ -10,8 +10,8 @@ import org.bukkit.inventory.ItemStack;
 public class CraftItem extends CraftEntity implements Item {
 
     // Paper start
-    private final static int NO_AGE_TIME = (int) Short.MIN_VALUE;
-    private final static int NO_PICKUP_TIME = (int) Short.MAX_VALUE;
+    private final static int NO_AGE_TIME = Short.MIN_VALUE;
+    private final static int NO_PICKUP_TIME = Short.MAX_VALUE;
     // Paper end
 
     public CraftItem(CraftServer server, ItemEntity entity) {

From cb51a4fbcd3871226a06462c0cdeb4dd330d2359 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 14:12:31 -0800
Subject: [PATCH 172/285] more compile fixes

---
 build-data/paper.at                              |  2 +-
 .../mojang/brigadier/tree/CommandNode.java.patch |  2 +-
 .../gametest/framework/GameTestServer.java.patch | 16 ++++++++++++++++
 .../world/entity/LivingEntity.java.patch         |  2 +-
 .../world/entity/animal/Wolf.java.patch          |  2 +-
 .../entity/projectile/AbstractArrow.java.patch   |  9 +++++++++
 .../entity/projectile/Projectile.java.patch      | 12 ++++++++++--
 .../world/level/block/BedBlock.java.patch        |  2 +-
 .../entity/AbstractFurnaceBlockEntity.java.patch |  2 +-
 .../craftbukkit/entity/AbstractProjectile.java   |  2 +-
 10 files changed, 42 insertions(+), 9 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch

diff --git a/build-data/paper.at b/build-data/paper.at
index 320cbacc90..04c669edaa 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -357,6 +357,7 @@ public net.minecraft.world.entity.monster.Shulker setRawPeekAmount(I)V
 public net.minecraft.world.entity.monster.Skeleton DATA_STRAY_CONVERSION_ID
 public net.minecraft.world.entity.monster.Skeleton conversionTime
 public net.minecraft.world.entity.monster.Skeleton inPowderSnowTime
+public net.minecraft.world.entity.monster.SpellcasterIllager getCurrentSpell()Lnet/minecraft/world/entity/monster/SpellcasterIllager$IllagerSpell;
 public net.minecraft.world.entity.monster.SpellcasterIllager$IllagerSpell
 public net.minecraft.world.entity.monster.Strider steering
 public net.minecraft.world.entity.monster.Vex hasLimitedLife
@@ -431,7 +432,6 @@ public net.minecraft.world.entity.projectile.FishingHook timeUntilLured
 public net.minecraft.world.entity.projectile.FishingHook$FishHookState
 public net.minecraft.world.entity.projectile.LargeFireball explosionPower
 public net.minecraft.world.entity.projectile.Projectile cachedOwner
-public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z
 public net.minecraft.world.entity.projectile.Projectile hasBeenShot
 public net.minecraft.world.entity.projectile.Projectile leftOwner
 public net.minecraft.world.entity.projectile.Projectile ownerUUID
diff --git a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
index b8df0ccf0b..bb97c0d52c 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/tree/CommandNode.java.patch
@@ -5,7 +5,7 @@
      private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
      private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
 -    private final Predicate<S> requirement;
-+    public final Predicate<S> requirement; // Paper
++    public Predicate<S> requirement; // Paper - public-f
      private final CommandNode<S> redirect;
      private final RedirectModifier<S> modifier;
      private final boolean forks;
diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch
new file mode 100644
index 0000000000..b03cf4110d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/gametest/framework/GameTestServer.java
++++ b/net/minecraft/gametest/framework/GameTestServer.java
+@@ -304,6 +_,13 @@
+         return false;
+     }
+ 
++    // Paper start
++    @Override
++    public org.bukkit.command.CommandSender getBukkitSender(final net.minecraft.commands.CommandSourceStack wrapper) {
++        throw new UnsupportedOperationException("Not supported.");
++    }
++    // Paper end
++
+     @Override
+     public boolean isSingleplayerOwner(GameProfile profile) {
+         return false;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index 19054bee9f..6f3c797b1f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -834,7 +834,7 @@
 +    protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
  
 -    protected void dropExperience(ServerLevel level, @Nullable Entity entity) {
-+    protected int getExpReward(ServerLevel level, @Nullable Entity entity) { // CraftBukkit
++    public int getExpReward(ServerLevel level, @Nullable Entity entity) { // CraftBukkit
          if (!this.wasExperienceConsumed()
              && (
                  this.isAlwaysExperienceDropper()
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
index f783d4c772..6bcb88fa10 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Wolf.java.patch
@@ -75,7 +75,7 @@
              DyeColor dyeColor = dyeItem.getDyeColor();
              if (dyeColor != this.getCollarColor()) {
 +                // Paper start - Add EntityDyeEvent and CollarColorable interface
-+                final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) dyeColor.getId()), player.getBukkitEntity());
++                final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) dyeColor.getId()), (org.bukkit.entity.Player) player.getBukkitEntity());
 +                if (!event.callEvent()) {
 +                    return InteractionResult.FAIL;
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
index 337fa022f4..d215e5ce83 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -93,6 +93,15 @@
      @Override
      protected double getDefaultGravity() {
          return 0.05;
+@@ -329,7 +_,7 @@
+         this.life = 0;
+     }
+ 
+-    protected boolean isInGround() {
++    public boolean isInGround() { // Paper - protected -> public
+         return this.entityData.get(IN_GROUND);
+     }
+ 
 @@ -347,8 +_,8 @@
  
      protected void tickDespawn() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
index 1df6bae85c..9052fd22ba 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch
@@ -175,7 +175,7 @@
              this.onDeflection(entity, deflectedByPlayer);
          }
  
-@@ -297,6 +_,11 @@
+@@ -297,15 +_,35 @@
      }
  
      protected void onHitBlock(BlockHitResult result) {
@@ -187,7 +187,15 @@
          BlockState blockState = this.level().getBlockState(result.getBlockPos());
          blockState.onProjectileHit(this.level(), blockState, result, this);
      }
-@@ -306,6 +_,15 @@
+ 
++    // Paper start
++    public boolean canHitEntityPublic(final Entity target) {
++        return this.canHitEntity(target);
++    }
++    // Paper end
++
+     protected boolean canHitEntity(Entity target) {
+         if (!target.canBeHitByProjectile()) {
              return false;
          } else {
              Entity owner = this.getOwner();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
index dd115140b2..c400669e8f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
@@ -34,7 +34,7 @@
 +                        // Paper end - PlayerBedFailEnterEvent
 +                    // CraftBukkit start - handling bed explosion from below here
 +                    if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent
-+                        this.explodeBed(finaliblockdata, world, finalblockposition);
++                        this.explodeBed(finaliblockdata, level, finalblockposition);
 +                    } else
 +                    // CraftBukkit end
                      if (bedSleepingProblem.getMessage() != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
index 1359ae7732..5b5640d1f8 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -191,7 +191,7 @@
      }
  
 -    public static int getTotalCookTime(ServerLevel level, AbstractFurnaceBlockEntity furnace) {
-+    private static int getTotalCookTime(@Nullable ServerLevel level, AbstractFurnaceBlockEntity furnace, RecipeType<? extends AbstractCookingRecipe> recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API
++    public static int getTotalCookTime(@Nullable ServerLevel level, AbstractFurnaceBlockEntity furnace, RecipeType<? extends AbstractCookingRecipe> recipeType, double cookSpeedMultiplier) { // Paper - cook speed multiplier API
          SingleRecipeInput singleRecipeInput = new SingleRecipeInput(furnace.getItem(0));
 -        return furnace.quickCheck.getRecipeFor(singleRecipeInput, level).map(recipe -> recipe.value().cookingTime()).orElse(200);
 +        // Paper start - cook speed multiplier API
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
index 591af9d0d2..e8d82054d1 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
@@ -40,7 +40,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti
 
     @Override
     public boolean canHitEntity(org.bukkit.entity.Entity entity) {
-        return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle());
+        return this.getHandle().canHitEntityPublic(((CraftEntity) entity).getHandle());
     }
 
     @Override

From 9bb6731cc4e2158f4acd5572740b32895b154206 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sun, 15 Dec 2024 23:24:28 +0100
Subject: [PATCH 173/285] readd potion splash effect cause

---
 .../world/entity/projectile/ThrownEnderpearl.java.patch       | 4 +++-
 .../minecraft/world/entity/projectile/ThrownPotion.java.patch | 4 ++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
index 36963eb6e5..8fdaf1cd3c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
@@ -20,7 +20,7 @@
                              }
                          }
  
-@@ -138,15 +_,15 @@
+@@ -138,15 +_,17 @@
                              owner.setPortalCooldown();
                          }
  
@@ -29,11 +29,13 @@
 -                                serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
 -                            )
 -                        );
++                        // CraftBukkit start - moved up
 +                        // ServerPlayer serverPlayer1 = serverPlayer.teleport(
 +                        //     new TeleportTransition(
 +                        //         serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
 +                        //     )
 +                        // );
++                        // CraftBukkit end - moved up
                          if (serverPlayer1 != null) {
                              serverPlayer1.resetFallDistance();
                              serverPlayer1.resetCurrentImpulseContext();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
index 254faacccd..0fbeafb100 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
@@ -33,7 +33,7 @@
 -                    this.applySplash(
 -                        serverLevel, potionContents.getAllEffects(), result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null
 +                    showParticles = this.applySplash(
-+                        serverLevel, potionContents.getAllEffects(), result != null && result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null, result  // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
++                        serverLevel, potionContents.getAllEffects(), result != null && result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null, result // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
                      );
                  }
              }
@@ -172,7 +172,7 @@
 +                            effect, i, mobEffectInstance.getAmplifier(), mobEffectInstance.isAmbient(), mobEffectInstance.isVisible()
 +                        );
 +                        if (!mobEffectInstance1.endsWithin(20)) {
-+                            livingEntity.addEffect(mobEffectInstance1, effectSource);
++                            livingEntity.addEffect(mobEffectInstance1, effectSource, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_SPLASH); // CraftBukkit
 +                        }
 +                    }
 +                }

From 4cc2be301d871f0153b325dab5e38ca5ae58bab6 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 14:35:59 -0800
Subject: [PATCH 174/285] it compiles?

---
 .../io/papermc/paper/FeatureHooks.java.patch  |  7 ++-
 .../framework/GameTestServer.java.patch       | 35 ++++++++++--
 .../chat/ComponentSerialization.java.patch    |  4 +-
 .../world/entity/monster/Spider.java.patch    |  2 +-
 .../projectile/AbstractArrow.java.patch       | 55 +++++++++++++++++--
 .../minecraft/world/item/ItemStack.java.patch |  6 +-
 .../entity/CraftAbstractArrow.java            |  2 +-
 7 files changed, 95 insertions(+), 16 deletions(-)

diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
index af8d9a6358..69bf0f769f 100644
--- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
+++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
@@ -1,6 +1,6 @@
 --- /dev/null
 +++ b/io/papermc/paper/FeatureHooks.java
-@@ -1,0 +_,72 @@
+@@ -1,0 +_,77 @@
 +package io.papermc.paper;
 +
 +import io.papermc.paper.command.PaperSubcommand;
@@ -16,6 +16,7 @@
 +import net.minecraft.core.Registry;
 +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
 +import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.monster.Spider;
 +import net.minecraft.world.level.ChunkPos;
 +import net.minecraft.world.level.Level;
 +import net.minecraft.world.level.biome.Biome;
@@ -72,4 +73,8 @@
 +    public static boolean isChunkSent(final ServerPlayer player, final long chunkKey) {
 +        return player.getChunkTrackingView().contains(new ChunkPos(chunkKey));
 +    }
++
++    public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
++        return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
++    }
 +}
diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch
index b03cf4110d..5bfe64d450 100644
--- a/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch
@@ -1,16 +1,43 @@
 --- a/net/minecraft/gametest/framework/GameTestServer.java
 +++ b/net/minecraft/gametest/framework/GameTestServer.java
-@@ -304,6 +_,13 @@
+@@ -139,6 +_,8 @@
+         BlockPos spawnPos
+     ) {
+         super(
++            null, // Paper
++            null, // Paper
+             serverThread,
+             storageSource,
+             packRepository,
+@@ -154,8 +_,15 @@
+ 
+     @Override
+     public boolean initServer() {
+-        this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) {});
+-        this.loadLevel();
++        // Paper start
++        this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) {
++            @Override
++            public void loadAndSaveFiles() {
++                throw new UnsupportedOperationException("Should not be called in a GameTestServer");
++            }
++        });
++        this.loadLevel("blah");
++        // Paper end
+         ServerLevel serverLevel = this.overworld();
+         this.testBatches = Lists.newArrayList(GameTestBatchFactory.fromTestFunction(this.testFunctions, serverLevel));
+         serverLevel.setDefaultSpawnPos(this.spawnPos, 0.0F);
+@@ -303,6 +_,13 @@
+     public boolean shouldInformAdmins() {
          return false;
      }
- 
++
 +    // Paper start
 +    @Override
 +    public org.bukkit.command.CommandSender getBukkitSender(final net.minecraft.commands.CommandSourceStack wrapper) {
 +        throw new UnsupportedOperationException("Not supported.");
 +    }
 +    // Paper end
-+
+ 
      @Override
      public boolean isSingleplayerOwner(GameProfile profile) {
-         return false;
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
index 1ae5d92462..daf7751fe7 100644
--- a/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch
@@ -65,8 +65,8 @@
                  .apply(instance, MutableComponent::new)
          );
 +        // Paper start - adventure; create separate codec for each locale
-+        final Codec<Component> origCodec = codec;
-+        codec = new Codec<>() {
++        final Codec<Component> origCodec = codec1;
++        codec1 = new Codec<>() {
 +            @Override
 +            public <T> DataResult<com.mojang.datafixers.util.Pair<Component, T>> decode(final DynamicOps<T> ops, final T input) {
 +                return origCodec.decode(ops, input);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
index cbaaaef4ef..16a88af185 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Spider.java.patch
@@ -5,7 +5,7 @@
          super.tick();
          if (!this.level().isClientSide) {
 -            this.setClimbing(this.horizontalCollision);
-+            this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border)
++            this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !(io.papermc.paper.FeatureHooks.isSpiderCollidingWithWorldBorder(this) && this.level().getWorldBorder().isInsideCloseToBorder(this, this.getBoundingBox())))); // Paper - Add config option for spider worldborder climbing (Inflate by +EPSILON as collision will just barely place us outside border)
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
index d215e5ce83..23dc1ce9bf 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -8,10 +8,25 @@
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.item.Item;
  import net.minecraft.world.item.ItemStack;
-@@ -74,6 +_,16 @@
+@@ -63,16 +_,26 @@
+     protected int inGroundTime;
+     public AbstractArrow.Pickup pickup = AbstractArrow.Pickup.DISALLOWED;
+     public int shakeTime;
+-    private int life;
++    public int life; // Paper - private -> public
+     private double baseDamage = 2.0;
+-    private SoundEvent soundEvent = this.getDefaultHitGroundSoundEvent();
++    public SoundEvent soundEvent = this.getDefaultHitGroundSoundEvent(); // Paper - private -> public
      @Nullable
-     private ItemStack firedFromWeapon = null;
- 
+     private IntOpenHashSet piercingIgnoreEntityIds;
+     @Nullable
+     private List<Entity> piercedAndKilledEntities;
+-    private ItemStack pickupItemStack = this.getDefaultPickupItem();
++    public ItemStack pickupItemStack = this.getDefaultPickupItem(); // Paper - private -> public
+     @Nullable
+-    private ItemStack firedFromWeapon = null;
++    public ItemStack firedFromWeapon = null; // Paper - private -> public
++
 +    // Spigot Start
 +    @Override
 +    public void inactiveTick() {
@@ -21,10 +36,9 @@
 +        super.inactiveTick();
 +    }
 +    // Spigot End
-+
+ 
      protected AbstractArrow(EntityType<? extends AbstractArrow> entityType, Level level) {
          super(entityType, level);
-     }
 @@ -87,7 +_,13 @@
          ItemStack pickupItemStack,
          @Nullable ItemStack firedFromWeapon
@@ -231,3 +245,34 @@
              }
          }
      }
+@@ -643,7 +_,7 @@
+         };
+     }
+ 
+-    protected ItemStack getPickupItem() {
++    public ItemStack getPickupItem() { // Paper - protected -> public
+         return this.pickupItemStack.copy();
+     }
+ 
+@@ -675,7 +_,7 @@
+         this.setFlag(1, critArrow);
+     }
+ 
+-    private void setPierceLevel(byte pierceLevel) {
++    public void setPierceLevel(byte pierceLevel) { // Paper - private -> public
+         this.entityData.set(PIERCE_LEVEL, pierceLevel);
+     }
+ 
+@@ -687,6 +_,12 @@
+             this.entityData.set(ID_FLAGS, (byte)(b & ~id));
+         }
+     }
++
++    // Paper start
++    public void setPickupItemStackPublic(final ItemStack pickupItemStack) {
++        this.setPickupItemStack(pickupItemStack);
++    }
++    // Paper end
+ 
+     protected void setPickupItemStack(ItemStack pickupItemStack) {
+         if (!pickupItemStack.isEmpty()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
index 4675b2f09b..9eba8654c6 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
@@ -219,11 +219,13 @@
  
              return interactionResult;
          }
-@@ -470,30 +_,69 @@
+@@ -469,31 +_,70 @@
+         return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
      }
  
-     public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
+-    public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
 -        int i = this.processDurabilityChange(damage, level, player);
++    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
 +        // Paper start - add force boolean overload
 +        this.hurtAndBreak(damage, level, player, onBreak, false);
 +    }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
index d0c30fd12a..af2c1ad8cd 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
@@ -150,7 +150,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr
     @Override
     public void setItemStack(final ItemStack stack) {
         Preconditions.checkArgument(stack != null, "ItemStack cannot be null");
-        this.getHandle().setPickupItemStack(CraftItemStack.asNMSCopy(stack));
+        this.getHandle().setPickupItemStackPublic(CraftItemStack.asNMSCopy(stack));
     }
 
     @Override

From 7617b7cd09a6bf9f139d245db8c1ae90b53f1c91 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 14:45:40 -0800
Subject: [PATCH 175/285] adjust classreader tests for new directory structure

---
 .../bukkit/event/EntityRemoveEventTest.java   |  2 +-
 .../provider/ClassNodeArgumentProvider.java   |  2 -
 .../provider/ClassReaderArgumentProvider.java | 43 ++-----------------
 .../bukkit/support/test/ClassNodeTest.java    |  4 +-
 .../bukkit/support/test/ClassReaderTest.java  |  4 +-
 5 files changed, 7 insertions(+), 48 deletions(-)

diff --git a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java b/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
index 87b1f48797..56d2eb4b92 100644
--- a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
+++ b/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
@@ -18,7 +18,7 @@ import org.objectweb.asm.tree.MethodNode;
 @Normal
 public class EntityRemoveEventTest {
 
-    @ClassNodeTest(value = {ClassNodeTest.ClassType.CRAFT_BUKKIT, ClassNodeTest.ClassType.MINECRAFT_MODIFIED, ClassNodeTest.ClassType.MINECRAFT_UNMODIFIED},
+    @ClassNodeTest(value = ClassNodeTest.ClassType.CRAFT_BUKKIT,
             excludedClasses = EntityAccess.class,
             excludedPackages = "net/minecraft/gametest/framework")
     public void testForMissing(ClassNode classNode) throws ClassNotFoundException {
diff --git a/paper-server/src/test/java/org/bukkit/support/provider/ClassNodeArgumentProvider.java b/paper-server/src/test/java/org/bukkit/support/provider/ClassNodeArgumentProvider.java
index 44eae40868..8a9319acba 100644
--- a/paper-server/src/test/java/org/bukkit/support/provider/ClassNodeArgumentProvider.java
+++ b/paper-server/src/test/java/org/bukkit/support/provider/ClassNodeArgumentProvider.java
@@ -44,8 +44,6 @@ public class ClassNodeArgumentProvider implements ArgumentsProvider, AnnotationC
             newValues[i] = switch (this.classTypes[i]) {
                 case BUKKIT -> ClassReaderTest.ClassType.BUKKIT;
                 case CRAFT_BUKKIT -> ClassReaderTest.ClassType.CRAFT_BUKKIT;
-                case MINECRAFT_UNMODIFIED -> ClassReaderTest.ClassType.MINECRAFT_UNMODIFIED;
-                case MINECRAFT_MODIFIED -> ClassReaderTest.ClassType.MINECRAFT_MODIFIED;
             };
         }
 
diff --git a/paper-server/src/test/java/org/bukkit/support/provider/ClassReaderArgumentProvider.java b/paper-server/src/test/java/org/bukkit/support/provider/ClassReaderArgumentProvider.java
index 5386eee2ff..da065a157a 100644
--- a/paper-server/src/test/java/org/bukkit/support/provider/ClassReaderArgumentProvider.java
+++ b/paper-server/src/test/java/org/bukkit/support/provider/ClassReaderArgumentProvider.java
@@ -1,6 +1,5 @@
 package org.bukkit.support.provider;
 
-import static org.junit.jupiter.api.Assertions.*;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -13,8 +12,6 @@ import java.nio.file.Path;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.stream.Stream;
-import net.minecraft.WorldVersion;
-import net.minecraft.server.Main;
 import org.bukkit.Bukkit;
 import org.bukkit.support.test.ClassReaderTest;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -27,15 +24,12 @@ public class ClassReaderArgumentProvider implements ArgumentsProvider, Annotatio
 
     // Needs to be a class, which is present in the source, and not a test class
     private static final URI CRAFT_BUKKIT_CLASSES;
-    // Needs to be a class, which is from the minecraft package and not patch by CraftBukkit
-    private static final URI MINECRAFT_CLASSES;
     // Needs to be a class, which is from the bukkit package and not a CraftBukkit class
     private static final URI BUKKIT_CLASSES;
 
     static {
         try {
-            CRAFT_BUKKIT_CLASSES = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
-            MINECRAFT_CLASSES = WorldVersion.class.getProtectionDomain().getCodeSource().getLocation().toURI();
+            CRAFT_BUKKIT_CLASSES = org.bukkit.craftbukkit.Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
             BUKKIT_CLASSES = Bukkit.class.getProtectionDomain().getCodeSource().getLocation().toURI();
         } catch (URISyntaxException e) {
             throw new RuntimeException(e);
@@ -63,19 +57,11 @@ public class ClassReaderArgumentProvider implements ArgumentsProvider, Annotatio
     }
 
     public Stream<ClassReader> getClassReaders() {
-        assertNotEquals(ClassReaderArgumentProvider.CRAFT_BUKKIT_CLASSES, ClassReaderArgumentProvider.MINECRAFT_CLASSES, """
-                The Minecraft and CraftBukkit uri point to the same directory / file.
-                Please make sure the CRAFT_BUKKIT_CLASSES points to the test class directory and MINECRAFT_CLASSES to the minecraft server jar.
-                """);
 
         Stream<InputStream> result = Stream.empty();
 
-        if (this.contains(ClassReaderTest.ClassType.MINECRAFT_UNMODIFIED)) {
-            result = Stream.concat(result, this.readMinecraftClasses());
-        }
-
-        if (this.contains(ClassReaderTest.ClassType.CRAFT_BUKKIT) || this.contains(ClassReaderTest.ClassType.MINECRAFT_MODIFIED)) {
-            result = Stream.concat(result, this.readCraftBukkitAndOrMinecraftModifiedClasses(this.contains(ClassReaderTest.ClassType.CRAFT_BUKKIT), this.contains(ClassReaderTest.ClassType.MINECRAFT_MODIFIED)));
+        if (this.contains(ClassReaderTest.ClassType.CRAFT_BUKKIT)) {
+            result = Stream.concat(result, this.createCraftBukkitClasses());
         }
 
         if (this.contains(ClassReaderTest.ClassType.BUKKIT)) {
@@ -103,10 +89,6 @@ public class ClassReaderArgumentProvider implements ArgumentsProvider, Annotatio
         return false;
     }
 
-    private Stream<InputStream> readMinecraftClasses() {
-        return this.readJarFile(ClassReaderArgumentProvider.MINECRAFT_CLASSES, true);
-    }
-
     private Stream<InputStream> readBukkitClasses() {
         return this.readJarFile(ClassReaderArgumentProvider.BUKKIT_CLASSES, false);
     }
@@ -159,13 +141,12 @@ public class ClassReaderArgumentProvider implements ArgumentsProvider, Annotatio
         return true;
     }
 
-    private Stream<InputStream> readCraftBukkitAndOrMinecraftModifiedClasses(boolean craftBukkit, boolean minecraftModified) {
+    private Stream<InputStream> createCraftBukkitClasses() {
         try {
             return Files.walk(Path.of(ClassReaderArgumentProvider.CRAFT_BUKKIT_CLASSES))
                     .map(Path::toFile)
                     .filter(File::isFile)
                     .filter(file -> file.getName().endsWith(".class"))
-                    .filter(file -> this.shouldInclude(this.removeHomeDirectory(file), craftBukkit, minecraftModified))
                     .filter(file -> this.filterPackageNames(this.removeHomeDirectory(file)))
                     .filter(file -> this.filterClass(this.removeHomeDirectory(file)))
                     .map(file -> {
@@ -184,22 +165,6 @@ public class ClassReaderArgumentProvider implements ArgumentsProvider, Annotatio
         return file.getAbsolutePath().substring(ClassReaderArgumentProvider.CRAFT_BUKKIT_CLASSES.getPath().length());
     }
 
-    private boolean shouldInclude(String name, boolean craftBukkit, boolean minecraftModified) {
-        if (craftBukkit && minecraftModified) {
-            return true;
-        }
-
-        if (craftBukkit) {
-            return name.startsWith("org/bukkit/craftbukkit/");
-        }
-
-        if (minecraftModified) {
-            return name.startsWith("net/minecraft/");
-        }
-
-        return false;
-    }
-
     private void closeJarFile(JarFile jarFile) {
         try {
             jarFile.close();
diff --git a/paper-server/src/test/java/org/bukkit/support/test/ClassNodeTest.java b/paper-server/src/test/java/org/bukkit/support/test/ClassNodeTest.java
index 41b284dbe5..b9fcccb203 100644
--- a/paper-server/src/test/java/org/bukkit/support/test/ClassNodeTest.java
+++ b/paper-server/src/test/java/org/bukkit/support/test/ClassNodeTest.java
@@ -14,7 +14,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource;
 @ParameterizedTest
 public @interface ClassNodeTest {
 
-    ClassType[] value() default {ClassType.BUKKIT, ClassType.CRAFT_BUKKIT, ClassType.MINECRAFT_UNMODIFIED, ClassType.MINECRAFT_MODIFIED};
+    ClassType[] value() default {ClassType.BUKKIT, ClassType.CRAFT_BUKKIT};
 
     Class<?>[] excludedClasses() default {};
 
@@ -23,7 +23,5 @@ public @interface ClassNodeTest {
     enum ClassType {
         BUKKIT,
         CRAFT_BUKKIT,
-        MINECRAFT_UNMODIFIED,
-        MINECRAFT_MODIFIED,
     }
 }
diff --git a/paper-server/src/test/java/org/bukkit/support/test/ClassReaderTest.java b/paper-server/src/test/java/org/bukkit/support/test/ClassReaderTest.java
index 9ebec1ff65..1ebe57127b 100644
--- a/paper-server/src/test/java/org/bukkit/support/test/ClassReaderTest.java
+++ b/paper-server/src/test/java/org/bukkit/support/test/ClassReaderTest.java
@@ -14,7 +14,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource;
 @ParameterizedTest
 public @interface ClassReaderTest {
 
-    ClassType[] value() default {ClassType.BUKKIT, ClassType.CRAFT_BUKKIT, ClassType.MINECRAFT_UNMODIFIED, ClassType.MINECRAFT_MODIFIED};
+    ClassType[] value() default {ClassType.BUKKIT, ClassType.CRAFT_BUKKIT};
 
     Class<?>[] excludedClasses() default {};
 
@@ -23,7 +23,5 @@ public @interface ClassReaderTest {
     enum ClassType {
         BUKKIT,
         CRAFT_BUKKIT,
-        MINECRAFT_UNMODIFIED,
-        MINECRAFT_MODIFIED,
     }
 }

From b40c4e21791fdaf65cb85637e2bf209cc1b42d06 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 15:07:32 -0800
Subject: [PATCH 176/285] fix initial runtime errors

---
 .../net/minecraft/server/level/ChunkHolder.java.patch       | 6 +++---
 .../world/level/storage/LevelStorageSource.java.patch       | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
index 9cb5de711a..20c5bbee04 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
@@ -7,9 +7,9 @@
 -    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 -    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 -    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
-+    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
-+    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
-+    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
++    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
++    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
++    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
      public int oldTicketLevel;
      private int ticketLevel;
      private int queueLevel;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
index dd0b1a8c19..599d60a432 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -4,7 +4,7 @@
          PrimaryLevelData primaryLevelData = PrimaryLevelData.parse(
              dynamic, levelSettings, complete.specialWorldProperty(), worldGenSettings.options(), lifecycle
          );
-+        primaryLevelData.pdc = dynamic.castTyped(NbtOps.INSTANCE).getElement("BukkitValues", null); // CraftBukkit - Add PDC to world
++        primaryLevelData.pdc = (Tag) dynamic.getElement("BukkitValues", null); // CraftBukkit - Add PDC to world
          return new LevelDataAndDimensions(primaryLevelData, complete);
      }
  

From e3b23b59f3d86fb28f3e8024d0702ea7633c1c97 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 15:16:33 -0800
Subject: [PATCH 177/285] more runtime errors

---
 .../protocol/common/custom/DiscardedPayload.java.patch        | 4 ++--
 .../sources/net/minecraft/server/MinecraftServer.java.patch   | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
index 034c2906c4..38c086da6b 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
 +++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
-@@ -4,13 +_,15 @@
+@@ -4,13 +_,14 @@
  import net.minecraft.network.codec.StreamCodec;
  import net.minecraft.resources.ResourceLocation;
  
@@ -13,7 +13,7 @@
 +        }, buffer -> {
              int i = buffer.readableBytes();
              if (i >= 0 && i <= maxSize) {
-                 buffer.skipBytes(i);
+-                buffer.skipBytes(i);
 -                return new DiscardedPayload(id);
 +                return new DiscardedPayload(id, buffer.readBytes(i)); // CraftBukkit
              } else {
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index 8b04cb50d9..eb9c14d4a5 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -69,7 +69,8 @@
 +
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          AtomicReference<S> atomicReference = new AtomicReference<>();
-         Thread thread = new Thread(() -> atomicReference.get().runServer(), "Server thread");
+-        Thread thread = new Thread(() -> atomicReference.get().runServer(), "Server thread");
++        Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> atomicReference.get().runServer(), "Server thread");
          thread.setUncaughtExceptionHandler((thread1, exception) -> LOGGER.error("Uncaught exception in server thread", exception));
 +        thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority
          if (Runtime.getRuntime().availableProcessors() > 4) {

From 4912737b8d15f62d0029a792862c3ce6008bdf4c Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 15:20:33 -0800
Subject: [PATCH 178/285] fix issue in ServerPlayerGameMode

---
 .../server/level/ServerPlayerGameMode.java.patch      | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index a889ac8c79..d0b244566b 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -41,6 +_,8 @@
+@@ -41,28 +_,49 @@
      private BlockPos delayedDestroyPos = BlockPos.ZERO;
      private int delayedTickStart;
      private int lastSentState = -1;
@@ -9,16 +9,17 @@
  
      public ServerPlayerGameMode(ServerPlayer player) {
          this.player = player;
-@@ -48,21 +_,39 @@
+         this.level = player.serverLevel();
      }
  
++    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper
      public boolean changeGameModeForPlayer(GameType gameModeForPlayer) {
 +        // Paper start - Expand PlayerGameModeChangeEvent
 +        org.bukkit.event.player.PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameModeForPlayer, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
 +        return event != null && event.isCancelled();
 +    }
 +    @Nullable
-+    public org.bukkit.event.player.PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause playerGameModeChangeCause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
++    public org.bukkit.event.player.PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameModeForPlayer, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause playerGameModeChangeCause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
 +        // Paper end - Expand PlayerGameModeChangeEvent
          if (gameModeForPlayer == this.gameModeForPlayer) {
 -            return false;
@@ -28,7 +29,7 @@
 +            // CraftBukkit start
 +            org.bukkit.event.player.PlayerGameModeChangeEvent event = new org.bukkit.event.player.PlayerGameModeChangeEvent(
 +                this.player.getBukkitEntity(),
-+                org.bukkit.GameMode.getByValue(gameMode.getId()),
++                org.bukkit.GameMode.getByValue(gameModeForPlayer.getId()),
 +                playerGameModeChangeCause, // Paper
 +                cancelMessage
 +            );
@@ -36,7 +37,7 @@
 +                return event; // Paper - Expand PlayerGameModeChangeEvent
 +            }
 +            // CraftBukkit end
-+            this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571
++            this.setGameModeForPlayer(gameModeForPlayer, this.gameModeForPlayer); // Paper - Fix MC-259571
              this.player.onUpdateAbilities();
              this.player
                  .server

From 9dd321396f9db7ebbf4f40d676d3b09b7ee9cfe6 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 15 Dec 2024 15:29:21 -0800
Subject: [PATCH 179/285] temporarily disable config cache

---
 gradle.properties | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 27969fea17..f39859fbab 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,7 +6,8 @@ mcVersion=1.21.4
 updatingMinecraft=false
 updateTaskListIssue=https://github.com/PaperMC/Paper/issues/11736
 
-org.gradle.configuration-cache=true
+# todo - bundler/paperclip tasks not compatible yet when using libraries
+org.gradle.configuration-cache=false
 org.gradle.caching=true
 org.gradle.parallel=true
 org.gradle.vfs.watch=false

From 1af7f42af528098c972c450c84f65015835dceb2 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Mon, 16 Dec 2024 00:40:25 +0100
Subject: [PATCH 180/285] fix mapping issue in PlayerChatMessage

---
 .../net/minecraft/network/chat/PlayerChatMessage.java.patch | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
index bf7ae66175..cd887f5354 100644
--- a/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/PlayerChatMessage.java.patch
@@ -51,10 +51,10 @@
 -        Component component = !message.equals(Component.literal(this.signedContent())) ? message : null;
 +        // Paper start - adventure
 +        final Component component;
-+        if (unsignedContent instanceof io.papermc.paper.adventure.AdventureComponent advComponent) {
-+            component = this.signedContent().equals(io.papermc.paper.adventure.AdventureCodecs.tryCollapseToString(advComponent.adventure$component())) ? null : unsignedContent;
++        if (message instanceof io.papermc.paper.adventure.AdventureComponent advComponent) {
++            component = this.signedContent().equals(io.papermc.paper.adventure.AdventureCodecs.tryCollapseToString(advComponent.adventure$component())) ? null : message;
 +        } else {
-+            component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null;
++            component = !message.equals(Component.literal(this.signedContent())) ? message : null;
 +        }
 +        // Paper end - adventure
          return new PlayerChatMessage(this.link, this.signature, this.signedBody, component, this.filterMask);

From d98c8f9f5bbde9957d1a1011f09dec52694b32c0 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:40:25 -0700
Subject: [PATCH 181/285] update paperweight to 2.0.0-beta.3

---
 build.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 33a763a905..80d3fb5d1b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -11,7 +11,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.2" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.3" apply false
 }
 
 subprojects {

From 3a82d04b7e5f4f4f2cbde9d231ceb9a81ef93bbe Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:59:39 -0700
Subject: [PATCH 182/285] minor paper-server build fixes and tweaks

---
 paper-server/build.gradle.kts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index b0b491497e..6daa9070fd 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -263,7 +263,7 @@ fun TaskContainer.registerRunTask(
     name: String,
     block: JavaExec.() -> Unit
 ): TaskProvider<JavaExec> = register<JavaExec>(name) {
-    group = "paper"
+    group = "runs"
     mainClass.set("org.bukkit.craftbukkit.Main")
     standardInput = System.`in`
     workingDir = rootProject.layout.projectDirectory
@@ -324,7 +324,7 @@ tasks.registerRunTask("runBundler") {
 }
 tasks.registerRunTask("runReobfBundler") {
     description = "Spin up a test server from the reobf bundler jar"
-    classpath(rootProject.tasks.named<io.papermc.paperweight.tasks.CreateBundlerJar>("createReobfBundlerJar").flatMap { it.outputZip })
+    classpath(tasks.named<io.papermc.paperweight.tasks.CreateBundlerJar>("createReobfBundlerJar").flatMap { it.outputZip })
     mainClass.set(null as String?)
 }
 tasks.registerRunTask("runPaperclip") {
@@ -334,6 +334,6 @@ tasks.registerRunTask("runPaperclip") {
 }
 tasks.registerRunTask("runReobfPaperclip") {
     description = "Spin up a test server from the reobf Paperclip jar"
-    classpath(rootProject.tasks.named<io.papermc.paperweight.tasks.CreatePaperclipJar>("createReobfPaperclipJar").flatMap { it.outputZip })
+    classpath(tasks.named<io.papermc.paperweight.tasks.CreatePaperclipJar>("createReobfPaperclipJar").flatMap { it.outputZip })
     mainClass.set(null as String?)
 }

From 2e14d98ebec0a634074d23a72218c27df5774c40 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 15 Dec 2024 20:33:21 -0500
Subject: [PATCH 183/285] Fix item dropping

---
 .../sources/net/minecraft/server/level/ServerPlayer.java.patch  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index f74c99f4ac..8d5685a16c 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -1447,7 +1447,7 @@
              double d = this.getEyeY() - 0.3F;
 +            // Paper start
 +            ItemStack tmp = droppedItem.copy();
-+            tmp.setCount(0);
++            droppedItem.setCount(0);
 +            droppedItem = tmp;
 +            // Paper end
              ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), d, this.getZ(), droppedItem);

From 59a1f78750f5efe44dfc4eacbe763b742a54cfb2 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 15 Dec 2024 20:52:03 -0500
Subject: [PATCH 184/285] Fix sleeping

---
 .../minecraft/server/level/ServerPlayer.java.patch | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index 8d5685a16c..debc79d7cc 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -825,15 +825,15 @@
              this.enteredNetherPosition = null;
          }
      }
-@@ -1110,16 +_,21 @@
-     @Override
-     public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at) {
-         Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
+@@ -1107,19 +_,18 @@
+         this.containerMenu.broadcastChanges();
+     }
+ 
+-    @Override
+-    public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at) {
+-        Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
 +    // CraftBukkit start - moved bed result checks from below into separate method
-+        return getBedResult(at, direction);
-+    }
 +    private Either<Player.BedSleepingProblem, Unit> getBedResult(BlockPos at, Direction direction) {
-+    // CraftBukkit end - moved bed result checks from below into separate method
          if (this.isSleeping() || !this.isAlive()) {
              return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
 -        } else if (!this.level().dimensionType().natural()) {

From 189781f2cf1fc15499860181865e4354f803deb7 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 15 Dec 2024 21:16:40 -0500
Subject: [PATCH 185/285] Fix entity collision

---
 .../world/entity/EntitySelector.java.patch    | 40 +++++++++++++++----
 1 file changed, 33 insertions(+), 7 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
index 5ecfaf92e4..dcb0fd0d64 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/EntitySelector.java.patch
@@ -24,7 +24,7 @@
  
      private EntitySelector() {
      }
-@@ -26,15 +_,20 @@
+@@ -26,29 +_,34 @@
      }
  
      public static Predicate<Entity> pushableBy(Entity entity) {
@@ -38,12 +38,38 @@
          return (Predicate<Entity>)(collisionRule == Team.CollisionRule.NEVER
              ? Predicates.alwaysFalse()
              : NO_SPECTATORS.and(
-                 pushedEntity -> {
+-                pushedEntity -> {
 -                    if (!pushedEntity.isPushable()) {
-+                    if (!pushedEntity.isCollidable(ignoreClimbing) || !pushedEntity.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(pushedEntity)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule
++            pushedEntity -> {
++                if (!pushedEntity.isCollidable(ignoreClimbing) || !pushedEntity.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(pushedEntity)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule
++                    return false;
++                } else if (!entity.level().isClientSide || pushedEntity instanceof Player && ((Player)pushedEntity).isLocalPlayer()) {
++                    Team team1 = pushedEntity.getTeam();
++                    Team.CollisionRule collisionRule1 = team1 == null ? Team.CollisionRule.ALWAYS : team1.getCollisionRule();
++                    if (collisionRule1 == Team.CollisionRule.NEVER || (pushedEntity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions)) { // Paper - Configurable player collision
                          return false;
 -                    } else if (!entity.level().isClientSide || pushedEntity instanceof Player && ((Player)pushedEntity).isLocalPlayer()) {
-+                    } else if (pushedEntity instanceof Player && entity instanceof Player && !io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { // Paper - Configurable player collision
-                         Team team1 = pushedEntity.getTeam();
-                         Team.CollisionRule collisionRule1 = team1 == null ? Team.CollisionRule.ALWAYS : team1.getCollisionRule();
-                         if (collisionRule1 == Team.CollisionRule.NEVER) {
+-                        Team team1 = pushedEntity.getTeam();
+-                        Team.CollisionRule collisionRule1 = team1 == null ? Team.CollisionRule.ALWAYS : team1.getCollisionRule();
+-                        if (collisionRule1 == Team.CollisionRule.NEVER) {
+-                            return false;
+-                        } else {
+-                            boolean flag = team != null && team.isAlliedTo(team1);
+-                            return (collisionRule != Team.CollisionRule.PUSH_OWN_TEAM && collisionRule1 != Team.CollisionRule.PUSH_OWN_TEAM || !flag)
+-                                && (collisionRule != Team.CollisionRule.PUSH_OTHER_TEAMS && collisionRule1 != Team.CollisionRule.PUSH_OTHER_TEAMS || flag);
+-                        }
+                     } else {
+-                        return false;
++                        boolean flag = team != null && team.isAlliedTo(team1);
++                        return (collisionRule != Team.CollisionRule.PUSH_OWN_TEAM && collisionRule1 != Team.CollisionRule.PUSH_OWN_TEAM || !flag)
++                            && (collisionRule != Team.CollisionRule.PUSH_OTHER_TEAMS && collisionRule1 != Team.CollisionRule.PUSH_OTHER_TEAMS || flag);
+                     }
++                } else {
++                    return false;
+                 }
+-            ));
++            }
++        ));
+     }
+ 
+     public static Predicate<Entity> notRiding(Entity entity) {

From b21c0686c0d1f094a011c0c9b1030e3d2e993276 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 15 Dec 2024 21:43:15 -0500
Subject: [PATCH 186/285] Fix broadcast sending to sender multiple times

---
 .../sources/net/minecraft/server/players/PlayerList.java.patch  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
index 90f421d2a8..80fad33f9d 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -1044,8 +1044,8 @@
 +                    : disguised);
 +                continue;
 +            }
++            serverPlayer.sendChatMessage(outgoingChatMessage, flag2, boundChatType, unsignedFunction == null ? null : unsignedFunction.apply(serverPlayer.getBukkitEntity()));
 +            // Paper end
-+            sender.sendChatMessage(outgoingChatMessage, flag2, boundChatType, unsignedFunction == null ? null : unsignedFunction.apply(serverPlayer.getBukkitEntity())); // Paper
              flag1 |= flag2 && message.isFullyFiltered();
          }
  

From 360006bc7f2b893a4371f0cc0b57fca770ff344d Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 15 Dec 2024 22:27:32 -0500
Subject: [PATCH 187/285] Fix bouncy items

This reverts EAR diff to be how it is paper proper-- seems like alot of this got dropped.
---
 .../server/level/ServerLevel.java.patch       | 45 +++++++++++++------
 1 file changed, 31 insertions(+), 14 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 181ca0c6e9..30adc7ef05 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -464,35 +464,52 @@
      }
  
      public void resetEmptyTime() {
-@@ -747,12 +_,20 @@
-     }
- 
-     public void tickNonPassenger(Entity entity) {
-+        // Spigot start
-+        if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
-+            entity.tickCount++;
-+            entity.inactiveTick();
-+            return;
-+        }
-+        // Spigot end
-         entity.setOldPosAndRot();
-         ProfilerFiller profilerFiller = Profiler.get();
+@@ -752,15 +_,20 @@
          entity.tickCount++;
          profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
          profilerFiller.incrementCounter("tickNonPassenger");
++        // Spigot start
++        final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
++        if (isActive) {
          entity.tick();
 +        entity.postTick(); // CraftBukkit
++        } else {entity.inactiveTick();} // Spigot end
          profilerFiller.pop();
  
          for (Entity entity1 : entity.getPassengers()) {
-@@ -770,6 +_,7 @@
+-            this.tickPassenger(entity, entity1);
++            this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
+         }
+     }
+ 
+-    private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
++    private void tickPassenger(Entity ridingEntity, Entity passengerEntity, boolean isActive) { // Paper - EAR 2
+         if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
+             passengerEntity.stopRiding();
+         } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
+@@ -769,11 +_,21 @@
+             ProfilerFiller profilerFiller = Profiler.get();
              profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
              profilerFiller.incrementCounter("tickPassenger");
++            // Paper start - EAR 2
++            if (isActive) {
              passengerEntity.rideTick();
 +            passengerEntity.postTick(); // CraftBukkit
++            } else {
++            passengerEntity.setDeltaMovement(Vec3.ZERO);
++            passengerEntity.inactiveTick();
++            // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
++            ridingEntity.positionRider(passengerEntity);
++            // Paper end - EAR 2
++            }
              profilerFiller.pop();
  
              for (Entity entity : passengerEntity.getPassengers()) {
+-                this.tickPassenger(passengerEntity, entity);
++                this.tickPassenger(passengerEntity, entity, isActive); // Paper - EAR 2
+             }
+         }
+     }
 @@ -786,6 +_,7 @@
      public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
          ServerChunkCache chunkSource = this.getChunkSource();

From 43d4fa05433b37d2c884a542620118c47827760d Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 15 Dec 2024 20:36:04 -0700
Subject: [PATCH 188/285] Replace copied RenamerImpl file with library patch

---
 build-data/dev-imports.txt                    |   1 -
 .../art/internal/RenamerImpl.java.patch       |  67 ++++
 .../neoforged/art/internal/RenamerImpl.java   | 308 ------------------
 3 files changed, 67 insertions(+), 309 deletions(-)
 create mode 100644 paper-server/patches/sources/net/neoforged/art/internal/RenamerImpl.java.patch
 delete mode 100644 paper-server/src/main/java/net/neoforged/art/internal/RenamerImpl.java

diff --git a/build-data/dev-imports.txt b/build-data/dev-imports.txt
index 302359e8a3..1d9862a950 100644
--- a/build-data/dev-imports.txt
+++ b/build-data/dev-imports.txt
@@ -12,4 +12,3 @@
 #     mc_data chat_type/chat.json
 #     mc_data dimension_type/overworld.json
 #
-
diff --git a/paper-server/patches/sources/net/neoforged/art/internal/RenamerImpl.java.patch b/paper-server/patches/sources/net/neoforged/art/internal/RenamerImpl.java.patch
new file mode 100644
index 0000000000..69b4834654
--- /dev/null
+++ b/paper-server/patches/sources/net/neoforged/art/internal/RenamerImpl.java.patch
@@ -0,0 +1,67 @@
+--- a/net/neoforged/art/internal/RenamerImpl.java
++++ b/net/neoforged/art/internal/RenamerImpl.java
+@@ -35,7 +_,7 @@
+ import net.neoforged.cliutils.progress.ProgressReporter;
+ import org.objectweb.asm.Opcodes;
+ 
+-class RenamerImpl implements Renamer {
++public class RenamerImpl implements Renamer { // Paper - public
+     private static final ProgressReporter PROGRESS = ProgressReporter.getDefault();
+     static final int MAX_ASM_VERSION = Opcodes.ASM9;
+     private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
+@@ -75,6 +_,11 @@
+ 
+     @Override
+     public void run(File input, File output) {
++        // Paper start - Add remappingSelf
++        this.run(input, output, false);
++    }
++    public void run(File input, File output, boolean remappingSelf) {
++        // Paper end
+         if (!this.setup)
+             this.setup();
+ 
+@@ -105,10 +_,10 @@
+                 String name = e.getName();
+                 byte[] data;
+                 try (InputStream entryInput = in.getInputStream(e)) {
+-                    data = readAllBytes(entryInput, e.getSize());
++                    data = entryInput.readAllBytes(); // Paper - Use readAllBytes
+                 }
+ 
+-                if (name.endsWith(".class"))
++                if (name.endsWith(".class") && !name.contains("META-INF/")) // Paper - Skip META-INF entries
+                     oldEntries.add(ClassEntry.create(name, e.getTime(), data));
+                 else if (name.equals(MANIFEST_NAME))
+                     oldEntries.add(ManifestEntry.create(e.getTime(), data));
+@@ -163,15 +_,29 @@
+             List<Entry> newEntries = async.invokeAll(oldEntries, Entry::getName, this::processEntry);
+ 
+             logger.accept("Adding extras");
+-            transformers.forEach(t -> newEntries.addAll(t.getExtras()));
++            // Paper start - I'm pretty sure the duplicates are because the input is already on the classpath
++            List<Entry> finalNewEntries = newEntries;
++            transformers.forEach(t -> finalNewEntries.addAll(t.getExtras()));
+ 
+             Set<String> seen = new HashSet<>();
++            if (remappingSelf) {
++                // deduplicate
++                List<Entry> n = new ArrayList<>();
++                for (final Entry e : newEntries) {
++                    if (seen.add(e.getName())) {
++                        n.add(e);
++                    }
++                }
++                newEntries = n;
++            } else {
+             String dupes = newEntries.stream().map(Entry::getName)
+                 .filter(n -> !seen.add(n))
+                 .sorted()
+                 .collect(Collectors.joining(", "));
+             if (!dupes.isEmpty())
+                 throw new IllegalStateException("Duplicate entries detected: " + dupes);
++            }
++            // Paper end
+ 
+             // We care about stable output, so sort, and single thread write.
+             logger.accept("Sorting");
diff --git a/paper-server/src/main/java/net/neoforged/art/internal/RenamerImpl.java b/paper-server/src/main/java/net/neoforged/art/internal/RenamerImpl.java
deleted file mode 100644
index 73b20a92f3..0000000000
--- a/paper-server/src/main/java/net/neoforged/art/internal/RenamerImpl.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Forge Auto Renaming Tool
- * Copyright (c) 2021
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation version 2.1
- * of the License.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-package net.neoforged.art.internal;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
-
-import net.neoforged.cliutils.JarUtils;
-import net.neoforged.cliutils.progress.ProgressReporter;
-import org.objectweb.asm.Opcodes;
-
-import net.neoforged.art.api.ClassProvider;
-import net.neoforged.art.api.Renamer;
-import net.neoforged.art.api.Transformer;
-import net.neoforged.art.api.Transformer.ClassEntry;
-import net.neoforged.art.api.Transformer.Entry;
-import net.neoforged.art.api.Transformer.ManifestEntry;
-import net.neoforged.art.api.Transformer.ResourceEntry;
-
-public class RenamerImpl implements Renamer { // Paper - public
-    private static final ProgressReporter PROGRESS = ProgressReporter.getDefault();
-    static final int MAX_ASM_VERSION = Opcodes.ASM9;
-    private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
-    private final List<File> libraries;
-    private final List<Transformer> transformers;
-    private final SortedClassProvider sortedClassProvider;
-    private final List<ClassProvider> classProviders;
-    private final int threads;
-    private final Consumer<String> logger;
-    private final Consumer<String> debug;
-    private boolean setup = false;
-    private ClassProvider libraryClasses;
-
-    RenamerImpl(List<File> libraries, List<Transformer> transformers, SortedClassProvider sortedClassProvider, List<ClassProvider> classProviders,
-                int threads, Consumer<String> logger, Consumer<String> debug) {
-        this.libraries = libraries;
-        this.transformers = transformers;
-        this.sortedClassProvider = sortedClassProvider;
-        this.classProviders = Collections.unmodifiableList(classProviders);
-        this.threads = threads;
-        this.logger = logger;
-        this.debug = debug;
-    }
-
-    private void setup() {
-        if (this.setup)
-            return;
-
-        this.setup = true;
-
-        ClassProvider.Builder libraryClassesBuilder = ClassProvider.builder().shouldCacheAll(true);
-        this.logger.accept("Adding Libraries to Inheritance");
-        this.libraries.forEach(f -> libraryClassesBuilder.addLibrary(f.toPath()));
-
-        this.libraryClasses = libraryClassesBuilder.build();
-    }
-
-    @Override
-    public void run(File input, File output) {
-        // Paper start - Add remappingSelf
-        this.run(input, output, false);
-    }
-    public void run(File input, File output, boolean remappingSelf) {
-        // Paper end
-        if (!this.setup)
-            this.setup();
-
-        if (Boolean.getBoolean(ProgressReporter.ENABLED_PROPERTY)) {
-            try {
-                PROGRESS.setMaxProgress(JarUtils.getFileCountInZip(input));
-            } catch (IOException e) {
-                logger.accept("Failed to read zip file count: " + e);
-            }
-        }
-
-        input = Objects.requireNonNull(input).getAbsoluteFile();
-        output = Objects.requireNonNull(output).getAbsoluteFile();
-
-        if (!input.exists())
-            throw new IllegalArgumentException("Input file not found: " + input.getAbsolutePath());
-
-        logger.accept("Reading Input: " + input.getAbsolutePath());
-        PROGRESS.setStep("Reading input jar");
-        // Read everything from the input jar!
-        List<Entry> oldEntries = new ArrayList<>();
-        try (ZipFile in = new ZipFile(input)) {
-            int amount = 0;
-            for (Enumeration<? extends ZipEntry> entries = in.entries(); entries.hasMoreElements();) {
-                final ZipEntry e = entries.nextElement();
-                if (e.isDirectory())
-                    continue;
-                String name = e.getName();
-                byte[] data;
-                try (InputStream entryInput = in.getInputStream(e)) {
-                    data = entryInput.readAllBytes(); // Paper - Use readAllBytes
-                }
-
-                if (name.endsWith(".class") && !name.contains("META-INF/")) // Paper - Skip META-INF entries
-                    oldEntries.add(ClassEntry.create(name, e.getTime(), data));
-                else if (name.equals(MANIFEST_NAME))
-                    oldEntries.add(ManifestEntry.create(e.getTime(), data));
-                else if (name.equals("javadoctor.json"))
-                    oldEntries.add(Transformer.JavadoctorEntry.create(e.getTime(), data));
-                else
-                    oldEntries.add(ResourceEntry.create(name, e.getTime(), data));
-
-                if ((++amount) % 10 == 0) {
-                    PROGRESS.setProgress(amount);
-                }
-            }
-        } catch (IOException e) {
-            throw new RuntimeException("Could not parse input: " + input.getAbsolutePath(), e);
-        }
-
-        this.sortedClassProvider.clearCache();
-        ArrayList<ClassProvider> classProviders = new ArrayList<>(this.classProviders);
-        classProviders.add(0, this.libraryClasses);
-        this.sortedClassProvider.classProviders = classProviders;
-
-        AsyncHelper async = new AsyncHelper(threads);
-        try {
-
-            /* Disabled until we do something with it
-            // Gather original file Hashes, so that we can detect changes and update the manifest if necessary
-            log("Gathering original hashes");
-            Map<String, String> oldHashes = async.invokeAll(oldEntries,
-                e -> new Pair<>(e.getName(), HashFunction.SHA256.hash(e.getData()))
-            ).stream().collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
-            */
-
-            PROGRESS.setProgress(0);
-            PROGRESS.setIndeterminate(true);
-            PROGRESS.setStep("Processing entries");
-
-            List<ClassEntry> ourClasses = oldEntries.stream()
-                .filter(e -> e instanceof ClassEntry && !e.getName().startsWith("META-INF/"))
-                .map(ClassEntry.class::cast)
-                .collect(Collectors.toList());
-
-            // Add the original classes to the inheritance map, TODO: Multi-Release somehow?
-            logger.accept("Adding input to inheritance map");
-            ClassProvider.Builder inputClassesBuilder = ClassProvider.builder();
-            async.consumeAll(ourClasses, ClassEntry::getClassName, c ->
-                inputClassesBuilder.addClass(c.getName().substring(0, c.getName().length() - 6), c.getData())
-            );
-            classProviders.add(0, inputClassesBuilder.build());
-
-            // Process everything
-            logger.accept("Processing entries");
-            List<Entry> newEntries = async.invokeAll(oldEntries, Entry::getName, this::processEntry);
-
-            logger.accept("Adding extras");
-            // Paper start - I'm pretty sure the duplicates are because the input is already on the classpath
-            List<Entry> finalNewEntries = newEntries;
-            transformers.forEach(t -> finalNewEntries.addAll(t.getExtras()));
-
-            Set<String> seen = new HashSet<>();
-            if (remappingSelf) {
-                // deduplicate
-                List<Entry> n = new ArrayList<>();
-                for (final Entry e : newEntries) {
-                    if (seen.add(e.getName())) {
-                        n.add(e);
-                    }
-                }
-                newEntries = n;
-            } else {
-            String dupes = newEntries.stream().map(Entry::getName)
-                .filter(n -> !seen.add(n))
-                .sorted()
-                .collect(Collectors.joining(", "));
-            if (!dupes.isEmpty())
-                throw new IllegalStateException("Duplicate entries detected: " + dupes);
-            }
-            // Paper end
-
-            // We care about stable output, so sort, and single thread write.
-            logger.accept("Sorting");
-            Collections.sort(newEntries, this::compare);
-
-            if (!output.getParentFile().exists())
-                output.getParentFile().mkdirs();
-
-            seen.clear();
-
-            PROGRESS.setMaxProgress(newEntries.size());
-            PROGRESS.setStep("Writing output");
-
-            logger.accept("Writing Output: " + output.getAbsolutePath());
-            try (OutputStream fos = new BufferedOutputStream(Files.newOutputStream(output.toPath()));
-                 ZipOutputStream zos = new ZipOutputStream(fos)) {
-
-                int amount = 0;
-                for (Entry e : newEntries) {
-                    String name = e.getName();
-                    int idx = name.lastIndexOf('/');
-                    if (idx != -1)
-                        addDirectory(zos, seen, name.substring(0, idx));
-
-                    logger.accept("  " + name);
-                    ZipEntry entry = new ZipEntry(name);
-                    entry.setTime(e.getTime());
-                    zos.putNextEntry(entry);
-                    zos.write(e.getData());
-                    zos.closeEntry();
-
-                    if ((++amount) % 10 == 0) {
-                        PROGRESS.setProgress(amount);
-                    }
-                }
-
-                PROGRESS.setProgress(amount);
-            }
-        } catch (final IOException e) {
-            throw new RuntimeException("Could not write to file " + output.getAbsolutePath(), e);
-        } finally {
-            async.shutdown();
-        }
-    }
-
-    private byte[] readAllBytes(InputStream in, long size) throws IOException {
-        // This program will crash if size exceeds MAX_INT anyway since arrays are limited to 32-bit indices
-        ByteArrayOutputStream tmp = new ByteArrayOutputStream(size >= 0 ? (int) size : 0);
-
-        byte[] buffer = new byte[8192];
-        int read;
-        while ((read = in.read(buffer)) != -1) {
-            tmp.write(buffer, 0, read);
-        }
-
-        return tmp.toByteArray();
-    }
-
-    // Tho Directory entries are not strictly necessary, we add them because some bad implementations of Zip extractors
-    // attempt to extract files without making sure the parents exist.
-    private void addDirectory(ZipOutputStream zos, Set<String> seen, String path) throws IOException {
-        if (!seen.add(path))
-            return;
-
-        int idx = path.lastIndexOf('/');
-        if (idx != -1)
-            addDirectory(zos, seen, path.substring(0, idx));
-
-        logger.accept("  " + path + '/');
-        ZipEntry dir = new ZipEntry(path + '/');
-        dir.setTime(Entry.STABLE_TIMESTAMP);
-        zos.putNextEntry(dir);
-        zos.closeEntry();
-    }
-
-    private Entry processEntry(final Entry start) {
-        Entry entry = start;
-        for (Transformer transformer : RenamerImpl.this.transformers) {
-            entry = entry.process(transformer);
-            if (entry == null)
-                return null;
-        }
-        return entry;
-    }
-
-    private int compare(Entry o1, Entry o2) {
-        // In order for JarInputStream to work, MANIFEST has to be the first entry, so make it first!
-        if (MANIFEST_NAME.equals(o1.getName()))
-            return MANIFEST_NAME.equals(o2.getName()) ? 0 : -1;
-        if (MANIFEST_NAME.equals(o2.getName()))
-            return MANIFEST_NAME.equals(o1.getName()) ? 0 :  1;
-        return o1.getName().compareTo(o2.getName());
-    }
-
-    @Override
-    public void close() throws IOException {
-        this.sortedClassProvider.close();
-    }
-}

From e6559c553365946353881a7765b478930ce4b583 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 15 Dec 2024 21:09:46 -0700
Subject: [PATCH 189/285] Update paperweight to 2.0.0-beta.4

---
 build.gradle.kts              | 2 +-
 paper-server/build.gradle.kts | 7 -------
 2 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 80d3fb5d1b..91c3fd17b8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -11,7 +11,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.3" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.4" apply false
 }
 
 subprojects {
diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 6daa9070fd..9a2fab0245 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -1,5 +1,4 @@
 import io.papermc.paperweight.attribute.DevBundleOutput
-import io.papermc.paperweight.core.ext
 import io.papermc.paperweight.util.*
 import java.time.Instant
 
@@ -50,12 +49,6 @@ tasks.generateDevelopmentBundle {
     )
 }
 
-// TODO remove me again, this is just to not run setupMacheSource when patches change
-tasks.setupMacheSources {
-    paperPatches.setFrom(project.ext.paper.sourcePatchDir.dir("com"), project.ext.paper.featurePatchDir)
-    // paperPatches.from(project.ext.paper.sourcePatchDir, project.ext.paper.featurePatchDir)
-}
-
 abstract class Services {
     @get:Inject
     abstract val softwareComponentFactory: SoftwareComponentFactory

From a15bb2bbd0bdca210ef58a371bc12feaf6a0e4cb Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 09:56:45 +0100
Subject: [PATCH 190/285] Fix and clean up MapItemSavedData Spigot patch It's
 been dysfunctional for a good while, though I don't think it's even needed.
 That can be investigated later

---
 .../server/level/ServerLevel.java.patch       | 32 +++++++------------
 .../craftbukkit/entity/CraftEntity.java       |  2 +-
 2 files changed, 12 insertions(+), 22 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 30adc7ef05..daa7e73901 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -1097,7 +1097,7 @@
                      String string = "onTrackingStart called during navigation iteration";
                      Util.logAndPauseIfInIde(
                          "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")
-@@ -1757,10 +_,61 @@
+@@ -1757,10 +_,51 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
@@ -1120,28 +1120,18 @@
          public void onTrackingEnd(Entity entity) {
 +            org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
 +            // Spigot start
-+            if ( entity instanceof Player )
-+            {
-+                com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) ->
-+                {
-+                    for (Object o : worldData.cache.values() )
-+                    {
-+                        if ( o instanceof MapItemSavedData )
-+                        {
-+                            MapItemSavedData map = (MapItemSavedData) o;
-+                            map.carriedByPlayers.remove( (Player) entity );
-+                            for (
-+                                java.util.Iterator<net.minecraft.world.level.saveddata.maps.MapItemSavedData.HoldingPlayer> iter = map.carriedBy.iterator();
-+                                iter.hasNext();
-+                            ) {
-+                                if ( iter.next().player == entity )
-+                                {
-+                                    iter.remove();
-+                                }
-+                            }
++            // TODO I don't think this is needed anymore
++            if (entity instanceof Player player) {
++                for (final ServerLevel level : ServerLevel.this.getServer().getAllLevels()) {
++                    for (final Optional<net.minecraft.world.level.saveddata.SavedData> savedData : level.getDataStorage().cache.values()) {
++                        if (savedData.isEmpty() || !(savedData.get() instanceof MapItemSavedData map)) {
++                            continue;
 +                        }
++
++                        map.carriedByPlayers.remove(player);
++                        map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player);
 +                    }
-+                } );
++                }
 +            }
 +            // Spigot end
 +            // Spigot Start
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 98c1a4cff9..8c3a6a15dd 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -857,7 +857,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     @Override
     public void setPortalCooldown(int cooldown) {
-        this.getHandle().setPortalCooldown(cooldown);;
+        this.getHandle().setPortalCooldown(cooldown);
     }
 
     @Override

From a148433e1eadbc7309cf2bb945316ddd2e93fed7 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 10:09:11 +0100
Subject: [PATCH 191/285] Update Improve-Maps-in-item-frames patch

---
 ...dBounds-and-getBlockState-for-inlin.patch} |   0
 ...item-frames-performance-and-bug-fixe.patch | 151 ++++++++++--------
 .../bukkit/craftbukkit/map/RenderData.java    |  11 +-
 3 files changed, 83 insertions(+), 79 deletions(-)
 rename feature-patches/{1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch => 0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch} (100%)

diff --git a/feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/feature-patches/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
similarity index 100%
rename from feature-patches/1038-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
rename to feature-patches/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
diff --git a/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index afafe8972f..8cd9865ac4 100644
--- a/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -1,5 +1,3 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <aikar@aikar.co>
 Date: Fri, 29 Apr 2016 20:02:00 -0400
 Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes
 
@@ -12,78 +10,108 @@ custom renderers are in use, defaulting to the much simpler Vanilla system.
 
 Additionally, numerous issues to player position tracking on maps has been fixed.
 
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-                             {
-                                 if ( iter.next().player == entity )
-                                 {
-+                                    map.decorations.remove(entity.getName().getString()); // Paper
-                                     iter.remove();
-                                 }
-                             }
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index 91a1330d7308c9faadaf773d056493e1df5dcd1e..1d7e9492a474c99dff372d6b57f1f195e42d5114 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -2296,7 +2296,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+                         }
+ 
+                         map.carriedByPlayers.remove(player);
+-                        map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player);
++                        if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) {
++                            map.decorations.remove(player.getName().getString()); // Paper
++                        }
+                     }
+                 }
+             }
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
+index e3a4cf9cd03670705391f4dc68f193d7cee656e0..f9c9485a051f5fa6d508b8a194c9f17657e1a0f4 100644
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -2654,6 +2654,14 @@ public class ServerPlayer extends Player {
                  this.awardStat(Stats.DROP);
              }
  
 +            // Paper start - remove player from map on drop
-+            if (itemstack.getItem() == net.minecraft.world.item.Items.FILLED_MAP) {
-+                net.minecraft.world.level.saveddata.maps.MapItemSavedData worldmap = net.minecraft.world.item.MapItem.getSavedData(itemstack, this.level());
-+                if (worldmap != null) {
-+                    worldmap.tickCarriedBy(this, itemstack);
-+                    }
++            if (item.getItem() == net.minecraft.world.item.Items.FILLED_MAP) {
++                final MapItemSavedData mapData = MapItem.getSavedData(item, this.level());
++                if (mapData != null) {
++                    mapData.tickCarriedBy(this, item);
 +                }
-+            // Paper end
-             return entityitem;
++            }
++            // Paper end - remove player from map on drop
+             return itemEntity;
          }
      }
-diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
+diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+index fd50bd77e4dc7b18240afe5505c6e6f0f869c127..f60c2f3a3dfc69f50225b6ee7333ada5e9dfd090 100644
+--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -67,6 +67,7 @@ public class MapItemSavedData extends SavedData {
      public final Map<String, MapDecoration> decorations = Maps.newLinkedHashMap();
      private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
      private int trackedDecorationCount;
 +    private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper
  
      // CraftBukkit start
-     public final CraftMapView mapView;
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
+     public final org.bukkit.craftbukkit.map.CraftMapView mapView;
+@@ -92,6 +93,7 @@ public class MapItemSavedData extends SavedData {
          // CraftBukkit start
-         this.mapView = new CraftMapView(this);
-         this.server = (CraftServer) org.bukkit.Bukkit.getServer();
+         this.mapView = new org.bukkit.craftbukkit.map.CraftMapView(this);
+         this.server = (org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer();
 +        this.vanillaRender.buffer = colors; // Paper
          // CraftBukkit end
      }
  
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
-         if (abyte.length == 16384) {
-             worldmap.colors = abyte;
+@@ -163,6 +165,7 @@ public class MapItemSavedData extends SavedData {
+         if (byteArray.length == 16384) {
+             mapItemSavedData.colors = byteArray;
          }
-+        worldmap.vanillaRender.buffer = abyte; // Paper
++        mapItemSavedData.vanillaRender.buffer = byteArray; // Paper
  
-         RegistryOps<Tag> registryops = registries.createSerializationContext(NbtOps.INSTANCE);
-         List<MapBanner> list = (List) MapBanner.LIST_CODEC.parse(registryops, nbt.get("banners")).resultOrPartial((s) -> {
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
-             --this.trackedDecorationCount;
+         RegistryOps<Tag> registryOps = levelRegistry.createSerializationContext(NbtOps.INSTANCE);
+ 
+@@ -337,7 +340,7 @@ public class MapItemSavedData extends SavedData {
+             this.trackedDecorationCount--;
          }
  
 -        this.setDecorationsDirty();
-+        if (mapicon != null) this.setDecorationsDirty(); // Paper - only mark dirty if a change occurs
++        if (mapDecoration != null) this.setDecorationsDirty(); // Paper - only mark dirty if a change occurs
      }
  
-     public static void addTargetDecoration(ItemStack stack, BlockPos pos, String id, Holder<MapDecorationType> decorationType) {
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
- 
-     public class HoldingPlayer {
+     public static void addTargetDecoration(ItemStack stack, BlockPos pos, String type, Holder<MapDecorationType> mapDecorationType) {
+@@ -610,7 +613,16 @@ public class MapItemSavedData extends SavedData {
+         @Nullable
+         Packet<?> nextUpdatePacket(MapId mapId) {
+             MapItemSavedData.MapPatch mapPatch;
+-            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
++            // Paper start
++            if (!this.dirtyData && this.tick % 5 != 0) {
++                // this won't end up sending, so don't render it!
++                this.tick++;
++                return null;
++            }
++
++            final boolean vanillaMaps = this.shouldUseVanillaMap();
++            org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper
++            // Paper end
+             if (this.dirtyData) {
+                 this.dirtyData = false;
+                 mapPatch = this.createPatch(render.buffer); // CraftBukkit
+@@ -623,6 +635,7 @@ public class MapItemSavedData extends SavedData {
+                 this.dirtyDecorations = false;
+                 // CraftBukkit start
+                 java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
++                if (vanillaMaps) this.addSeenPlayers(icons); // Paper
  
+                 for (org.bukkit.map.MapCursor cursor : render.cursors) {
+                     if (cursor.isVisible()) {
+@@ -658,6 +671,23 @@ public class MapItemSavedData extends SavedData {
+         private void markDecorationsDirty() {
+             this.dirtyDecorations = true;
+         }
++
 +        // Paper start
 +        private void addSeenPlayers(java.util.Collection<MapDecoration> icons) {
 +            org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity();
@@ -95,30 +123,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +            });
 +        }
++
 +        private boolean shouldUseVanillaMap() {
-+            return mapView.getRenderers().size() == 1 && mapView.getRenderers().get(0).getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class;
++            return mapView.getRenderers().size() == 1 && mapView.getRenderers().getFirst().getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class;
 +        }
 +        // Paper end
-         public final Player player;
-         private boolean dirtyData = true;
-         private int minDirtyX;
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
-         @Nullable
-         Packet<?> nextUpdatePacket(MapId mapId) {
-             MapItemSavedData.MapPatch worldmap_c;
--            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
-+            if (!this.dirtyData && this.tick % 5 != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it!
-+            boolean vanillaMaps = shouldUseVanillaMap(); // Paper
-+            org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper
+     }
  
-             if (this.dirtyData) {
-                 this.dirtyData = false;
-@@ -0,0 +0,0 @@ public class MapItemSavedData extends SavedData {
-                 // CraftBukkit start
-                 java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
- 
-+                if (vanillaMaps) addSeenPlayers(icons); // Paper
-+
-                 for (org.bukkit.map.MapCursor cursor : render.cursors) {
-                     if (cursor.isVisible()) {
-                         icons.add(new MapDecoration(CraftMapCursor.CraftType.bukkitToMinecraftHolder(cursor.getType()), cursor.getX(), cursor.getY(), cursor.getDirection(), Optional.ofNullable(PaperAdventure.asVanilla(cursor.caption()))));
+     record MapDecorationLocation(Holder<MapDecorationType> type, byte x, byte y, byte rot) {
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/map/RenderData.java b/paper-server/src/main/java/org/bukkit/craftbukkit/map/RenderData.java
index 503a31b4ef..782f5df380 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/map/RenderData.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/map/RenderData.java
@@ -1,16 +1,11 @@
 package org.bukkit.craftbukkit.map;
 
 import java.util.ArrayList;
+import java.util.List;
 import org.bukkit.map.MapCursor;
 
 public class RenderData {
 
-    public byte[] buffer;
-    public final ArrayList<MapCursor> cursors;
-
-    public RenderData() {
-        this.buffer = new byte[128 * 128];
-        this.cursors = new ArrayList<MapCursor>();
-    }
-
+    public final List<MapCursor> cursors = new ArrayList<>();
+    public byte[] buffer = new byte[128 * 128];
 }

From 797abd11ddd47fea7d0b925d887fd792b360cbe4 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 10:19:57 +0100
Subject: [PATCH 192/285] Update Optimize-Network-Manager

---
 ...-Manager-and-add-advanced-packet-sup.patch | 174 +++++++++---------
 1 file changed, 85 insertions(+), 89 deletions(-)

diff --git a/feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
index 51ccefe733..b3be838455 100644
--- a/feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
+++ b/feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
@@ -27,34 +27,34 @@ and then catch exceptions and close if they fire.
 
 Part of this commit was authored by: Spottedleaf, sandtechnology
 
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
+index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc9924666634488044c666fd1 100644
+--- a/net/minecraft/network/Connection.java
++++ b/net/minecraft/network/Connection.java
+@@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
      private final PacketFlow receiving;
      private volatile boolean sendLoginDisconnect = true;
 -    private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
-+    private final Queue<WrappedConsumer> pendingActions = Queues.newConcurrentLinkedQueue(); // Paper
++    private final Queue<WrappedConsumer> pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network
      public Channel channel;
      public SocketAddress address;
      // Spigot Start
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-     public java.net.InetSocketAddress virtualHost;
-     private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing
-     // Paper end
+@@ -145,6 +145,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+     }
+     // Paper end - packet limiter
+     @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address
 +    // Paper start - Optimize network
 +    public boolean isPending = true;
 +    public boolean queueImmunity;
 +    // Paper end - Optimize network
  
-     // Paper start - add utility methods
-     public final net.minecraft.server.level.ServerPlayer getPlayer() {
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+     public Connection(PacketFlow receiving) {
+         this.receiving = receiving;
+@@ -425,11 +429,38 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      }
  
-     public void send(Packet<?> packet, @Nullable PacketSendListener callbacks, boolean flush) {
+     public void send(Packet<?> packet, @Nullable PacketSendListener listener, boolean flush) {
 -        if (this.isConnected()) {
 -            this.flushQueue();
 +        // Paper start - Optimize network: Handle oversized packets better
@@ -67,17 +67,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        if (connected && (InnerUtil.canSendImmediate(this, packet)
 +            || (io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.pendingActions.isEmpty()
 +            && (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())))) {
-             this.sendPacket(packet, callbacks, flush);
+             this.sendPacket(packet, listener, flush);
          } else {
--            this.pendingActions.add((networkmanager) -> {
--                networkmanager.sendPacket(packet, callbacks, flush);
--            });
--        }
+-            this.pendingActions.add(connection -> connection.sendPacket(packet, listener, flush));
 +            // Write the packets to the queue, then flush - antixray hooks there already
 +            final java.util.List<Packet<?>> extraPackets = InnerUtil.buildExtraPackets(packet);
 +            final boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty();
 +            if (!hasExtraPackets) {
-+                this.pendingActions.add(new PacketSendAction(packet, callbacks, flush));
++                this.pendingActions.add(new PacketSendAction(packet, listener, flush));
 +            } else {
 +                final java.util.List<PacketSendAction> actions = new java.util.ArrayList<>(1 + extraPackets.size());
 +                actions.add(new PacketSendAction(packet, null, false)); // Delay the future listener until the end of the extra packets
@@ -85,31 +82,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                for (int i = 0, len = extraPackets.size(); i < len;) {
 +                    final Packet<?> extraPacket = extraPackets.get(i);
 +                    final boolean end = ++i == len;
-+                    actions.add(new PacketSendAction(extraPacket, end ? callbacks : null, end)); // Append listener to the end
++                    actions.add(new PacketSendAction(extraPacket, end ? listener : null, end)); // Append listener to the end
 +                }
 +
 +                this.pendingActions.addAll(actions);
 +            }
- 
++
 +            this.flushQueue();
 +            // Paper end - Optimize network
-+        }
-     }
- 
-     public void runOnceConnected(Consumer<Connection> task) {
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-             this.flushQueue();
-             task.accept(this);
-         } else {
--            this.pendingActions.add(task);
-+            this.pendingActions.add(new WrappedConsumer(task)); // Paper - Optimize network
          }
- 
-     }
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      }
  
-     private void doSendPacket(Packet<?> packet, @Nullable PacketSendListener callbacks, boolean flush) {
+@@ -438,7 +469,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+             this.flushQueue();
+             action.accept(this);
+         } else {
+-            this.pendingActions.add(action);
++            this.pendingActions.add(new WrappedConsumer(action)); // Paper - Optimize network
+         }
+     }
+ 
+@@ -452,6 +483,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+     }
+ 
+     private void doSendPacket(Packet<?> packet, @Nullable PacketSendListener sendListener, boolean flush) {
 +        // Paper start - Optimize network
 +        final net.minecraft.server.level.ServerPlayer player = this.getPlayer();
 +        if (!this.isConnected()) {
@@ -118,18 +114,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +        try {
 +        // Paper end - Optimize network
-         ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet);
- 
-         if (callbacks != null) {
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+         ChannelFuture channelFuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet);
+         if (sendListener != null) {
+             channelFuture.addListener(future -> {
+@@ -467,14 +506,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
              });
          }
  
 +        // Paper start - Optimize network
 +        if (packet.hasFinishListener()) {
-+            channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
++            channelFuture.addListener((ChannelFutureListener) future -> packet.onPacketDispatchFinish(player, future));
 +        }
-         channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+         channelFuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
 +        } catch (final Exception e) {
 +            LOGGER.error("NetworkException: {}", player, e);
 +            this.disconnect(Component.translatable("disconnect.genericReason", "Internal Exception: " + e.getMessage()));
@@ -145,16 +141,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -            this.pendingActions.add(Connection::flush);
 +            this.pendingActions.add(new WrappedConsumer(Connection::flush)); // Paper - Optimize network
          }
- 
      }
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
  
+@@ -486,16 +535,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+         }
      }
  
 -    private void flushQueue() {
 -        if (this.channel != null && this.channel.isOpen()) {
--            Queue queue = this.pendingActions;
--
 +    // Paper start - Optimize network: Rewrite this to be safer if ran off main thread
 +    private boolean flushQueue() {
 +        if (!this.isConnected()) {
@@ -165,7 +159,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        } else if (this.isPending) {
 +            // Should only happen during login/status stages
              synchronized (this.pendingActions) {
--                Consumer consumer;
+-                Consumer<Connection> consumer;
+-                while ((consumer = this.pendingActions.poll()) != null) {
+-                    consumer.accept(this);
 +                return this.processQueue();
 +            }
 +        }
@@ -176,9 +172,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        if (this.pendingActions.isEmpty()) {
 +            return true;
 +        }
- 
--                while ((consumer = (Consumer) this.pendingActions.poll()) != null) {
--                    consumer.accept(this);
++
 +        // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
 +        // But if we are not on main due to login/status, the parent is synchronized on packetQueue
 +        final java.util.Iterator<WrappedConsumer> iterator = this.pendingActions.iterator();
@@ -199,12 +193,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                if (!packet.isReady()) {
 +                    return false;
                  }
-+            }
- 
+             }
++
 +            iterator.remove();
 +            if (queued.tryMarkConsumed()) {
 +                queued.accept(this);
-             }
++            }
          }
 +        return true;
      }
@@ -212,35 +206,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
      private static int joinAttemptsThisTick; // Paper - Buffer joins to world
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-     public void disconnect(DisconnectionDetails disconnectionInfo) {
+@@ -561,6 +651,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
          // Spigot Start
          this.preparing = false;
-+        this.clearPacketQueue(); // Paper - Optimize network
          // Spigot End
++        this.clearPacketQueue(); // Paper - Optimize network
          if (this.channel == null) {
-             this.delayedDisconnect = disconnectionInfo;
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+             this.delayedDisconnect = disconnectionDetails;
+         }
+@@ -749,7 +840,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public void handleDisconnection() {
          if (this.channel != null && !this.channel.isOpen()) {
              if (this.disconnectionHandled) {
--                Connection.LOGGER.warn("handleDisconnection() called twice");
-+                // Connection.LOGGER.warn("handleDisconnection() called twice"); // Paper - Don't log useless message
+-                LOGGER.warn("handleDisconnection() called twice");
++                // LOGGER.warn("handleDisconnection() called twice"); // Paper - Don't log useless message
              } else {
                  this.disconnectionHandled = true;
-                 PacketListener packetlistener = this.getPacketListener();
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- 
-                     packetlistener1.onDisconnect(disconnectiondetails);
+                 PacketListener packetListener = this.getPacketListener();
+@@ -760,7 +851,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+                     );
+                     packetListener1.onDisconnect(disconnectionDetails);
                  }
 -                this.pendingActions.clear(); // Free up packet queue.
 +                this.clearPacketQueue(); // Paper - Optimize network
                  // Paper start - Add PlayerConnectionCloseEvent
-                 final PacketListener packetListener = this.getPacketListener();
                  if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-     public void setBandwidthLogger(LocalSampleLogger log) {
-         this.bandwidthDebugMonitor = new BandwidthDebugMonitor(log);
+                     /* Player was logged in, either game listener or configuration listener */
+@@ -795,4 +886,93 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+     public void setBandwidthLogger(LocalSampleLogger bandwithLogger) {
+         this.bandwidthDebugMonitor = new BandwidthDebugMonitor(bandwithLogger);
      }
 +
 +    // Paper start - Optimize network
@@ -332,11 +326,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Optimize network
  }
-diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/Packet.java
-+++ b/src/main/java/net/minecraft/network/protocol/Packet.java
-@@ -0,0 +0,0 @@ public interface Packet<T extends PacketListener> {
+diff --git a/net/minecraft/network/protocol/Packet.java b/net/minecraft/network/protocol/Packet.java
+index 65ff8b9112ec76eeac48c679044fc02ae7d4ffeb..e4789584cbe43959681a8522c66eab58369bebd0 100644
+--- a/net/minecraft/network/protocol/Packet.java
++++ b/net/minecraft/network/protocol/Packet.java
+@@ -35,4 +35,32 @@ public interface Packet<T extends PacketListener> {
      static <B extends ByteBuf, T extends Packet<?>> StreamCodec<B, T> codec(StreamMemberEncoder<B, T> encoder, StreamDecoder<B, T> decoder) {
          return StreamCodec.ofMember(encoder, decoder);
      }
@@ -352,7 +346,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @param player Null if not at PLAY stage yet
 +     * @param future Can be null if packet was cancelled
 +     */
-+    default void onPacketDispatchFinish(@org.jetbrains.annotations.Nullable net.minecraft.server.level.ServerPlayer player, @org.jetbrains.annotations.Nullable io.netty.channel.ChannelFuture future) {}
++    default void onPacketDispatchFinish(@org.jetbrains.annotations.Nullable net.minecraft.server.level.ServerPlayer player, @org.jetbrains.annotations.Nullable io.netty.channel.ChannelFuture future) {
++    }
 +
 +    default boolean hasFinishListener() {
 +        return false;
@@ -368,28 +363,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end
  }
-diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-@@ -0,0 +0,0 @@ public class ServerConnectionListener {
-     final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
+diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
+index 18fa53903cd6500ae65d993a6fe7f49d6b069339..b68adf37af7172671163d4a8074d2bfa97724b4b 100644
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -66,11 +66,13 @@ public class ServerConnectionListener {
+ 
      // Paper start - prevent blocking on adding a new connection while the server is ticking
      private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
 +    private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper - Optimize network
+ 
      private final void addPending() {
          Connection connection;
-         while ((connection = pending.poll()) != null) {
-             connections.add(connection);
+         while ((connection = this.pending.poll()) != null) {
+             this.connections.add(connection);
 +            connection.isPending = false; // Paper - Optimize network
          }
      }
      // Paper end - prevent blocking on adding a new connection while the server is ticking
-@@ -0,0 +0,0 @@ public class ServerConnectionListener {
-                         ;
-                     }
+@@ -120,6 +122,7 @@ public class ServerConnectionListener {
+                                     } catch (ChannelException var5) {
+                                     }
  
-+                    if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper - Optimize network
-                     ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30));
- 
-                     if (ServerConnectionListener.this.server.repliesToStatus()) {
++                                    if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper - Optimize network
+                                     ChannelPipeline channelPipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30));
+                                     if (ServerConnectionListener.this.server.repliesToStatus()) {
+                                         channelPipeline.addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));

From 901cf13d01285a572ad1a23f4745de2e2674b542 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 10:43:57 +0100
Subject: [PATCH 193/285] Update more feature patches

---
 ...041-Allow-Saving-of-Oversized-Chunks.patch | 101 ++++++++----------
 ...e-Oversized-block-entities-in-chunks.patch |  40 +++----
 ...heck-distance-in-entity-interactions.patch |  80 +++++++-------
 ...056-optimize-dirt-and-snow-spreading.patch |  84 +++++++--------
 ...e-getChunkAt-calls-for-loaded-chunks.patch |  51 +++++----
 5 files changed, 170 insertions(+), 186 deletions(-)

diff --git a/feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch b/feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch
index 6c06181f47..9c998777de 100644
--- a/feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch
+++ b/feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch
@@ -30,36 +30,23 @@ This fix also maintains compatability if someone switches server jars to one wit
 this fix, as the data will remain in the oversized file. Once the server returns
 to a jar with this fix, the data will be restored.
 
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -0,0 +0,0 @@ import java.nio.file.LinkOption;
- import java.nio.file.Path;
- import java.nio.file.StandardCopyOption;
- import java.nio.file.StandardOpenOption;
-+import java.util.zip.InflaterInputStream; // Paper
- import javax.annotation.Nullable;
- import net.minecraft.Util;
- import net.minecraft.resources.ResourceLocation;
- import net.minecraft.util.profiling.jfr.JvmProfiler;
-+import net.minecraft.nbt.CompoundTag; // Paper
-+import net.minecraft.nbt.NbtIo; // Paper
- import net.minecraft.world.level.ChunkPos;
- import org.slf4j.Logger;
- 
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
-         this.usedSectors = new RegionBitmap();
-         this.info = storageKey;
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
+index 2b4fa89f15a42ddd627983fbd1377fb7c9864896..7491644233d52dc56d83de5ad3373f6a9a455378 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -54,6 +54,7 @@ public class RegionFile implements AutoCloseable {
+         this.info = info;
          this.path = path;
-+        initOversizedState(); // Paper
-         this.version = compressionFormat;
-         if (!Files.isDirectory(directory, new LinkOption[0])) {
-             throw new IllegalArgumentException("Expected directory, got " + String.valueOf(directory.toAbsolutePath()));
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
- 
+         this.version = version;
++        this.initOversizedState(); // Paper
+         if (!Files.isDirectory(externalFileDir)) {
+             throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath());
+         } else {
+@@ -424,4 +425,75 @@ public class RegionFile implements AutoCloseable {
+     interface CommitOp {
+         void run() throws IOException;
      }
- 
++
 +    // Paper start
 +    private final byte[] oversized = new byte[1024];
 +    private int oversizedCount;
@@ -78,9 +65,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static int getChunkIndex(int x, int z) {
 +        return (x & 31) + (z & 31) * 32;
 +    }
++
 +    synchronized boolean isOversized(int x, int z) {
 +        return this.oversized[getChunkIndex(x, z)] == 1;
 +    }
++
 +    synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
 +        final int offset = getChunkIndex(x, z);
 +        boolean previous = this.oversized[offset] == 1;
@@ -120,22 +109,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
 +    }
 +
-+    synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
++    synchronized net.minecraft.nbt.CompoundTag getOversizedData(int x, int z) throws IOException {
 +        Path file = getOversizedFile(x, z);
-+        try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) {
-+            return NbtIo.read((java.io.DataInput) out);
++        try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new java.util.zip.InflaterInputStream(Files.newInputStream(file))))) {
++            return net.minecraft.nbt.NbtIo.read((java.io.DataInput) out);
 +        }
 +
 +    }
 +    // Paper end
-     private class ChunkBuffer extends ByteArrayOutputStream {
- 
-         private final ChunkPos pos;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
+ }
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+index 5ac84d6b47e7fdc16e1c09b739829de3d316bf5b..51bf310423013d0ae9d3202d66e36a053a767197 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -47,6 +47,43 @@ public final class RegionFileStorage implements AutoCloseable {
          }
      }
  
@@ -148,7 +135,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        synchronized (regionfile) {
 +            try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
 +                CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
-+                CompoundTag chunk = NbtIo.read((DataInput) datainputstream);
++                CompoundTag chunk = NbtIo.read(datainputstream);
 +                if (oversizedData == null) {
 +                    return chunk;
 +                }
@@ -177,26 +164,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end
 +
      @Nullable
-     public CompoundTag read(ChunkPos pos) throws IOException {
+     public CompoundTag read(ChunkPos chunkPos) throws IOException {
          // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
+@@ -55,6 +92,12 @@ public final class RegionFileStorage implements AutoCloseable {
+             return null;
+         }
          // CraftBukkit end
-         DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
- 
 +        // Paper start
-+        if (regionfile.isOversized(pos.x, pos.z)) {
-+            printOversizedLog("Loading Oversized Chunk!", regionfile.getPath(), pos.x, pos.z);
-+            return readOversizedChunk(regionfile, pos);
++        if (regionFile.isOversized(chunkPos.x, chunkPos.z)) {
++            printOversizedLog("Loading Oversized Chunk!", regionFile.getPath(), chunkPos.x, chunkPos.z);
++            return readOversizedChunk(regionFile, chunkPos);
 +        }
 +        // Paper end
-         CompoundTag nbttagcompound;
-         label43:
-         {
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
  
-             try {
-                 NbtIo.write(nbt, (DataOutput) dataoutputstream);
-+                regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
-             } catch (Throwable throwable) {
-                 if (dataoutputstream != null) {
-                     try {
+         CompoundTag var4;
+         try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) {
+@@ -90,6 +133,7 @@ public final class RegionFileStorage implements AutoCloseable {
+         } else {
+             try (DataOutputStream chunkDataOutputStream = regionFile.getChunkDataOutputStream(chunkPos)) {
+                 NbtIo.write(chunkData, chunkDataOutputStream);
++                regionFile.setOversized(chunkPos.x, chunkPos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
+             }
+         }
+     }
diff --git a/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch b/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch
index da2af1a0ea..127068aa94 100644
--- a/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch
+++ b/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch
@@ -8,49 +8,49 @@ creating too large of a packet to sed.
 
 Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
 
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacketData {
+diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+index 5d1943d37dfad0c12e77179f0866851532d983e9..0a7e6c639d5125a135a43476bbb2ef2d682f743c 100644
+--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+@@ -27,6 +27,14 @@ public class ClientboundLevelChunkPacketData {
      private final CompoundTag heightmaps;
      private final byte[] buffer;
      private final List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData;
 +    // Paper start - Handle oversized block entities in chunks
 +    private final java.util.List<net.minecraft.network.protocol.Packet<?>> extraPackets = new java.util.ArrayList<>();
-+    private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
++    private static final int BLOCK_ENTITY_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
 +
 +    public List<net.minecraft.network.protocol.Packet<?>> getExtraPackets() {
 +        return this.extraPackets;
 +    }
 +    // Paper end - Handle oversized block entities in chunks
  
-     // Paper start - Anti-Xray - Add chunk packet info
-     @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); }
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacketData {
-         extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo);
-         // Paper end
+     public ClientboundLevelChunkPacketData(LevelChunk levelChunk) {
+         this.heightmaps = new CompoundTag();
+@@ -40,8 +48,18 @@ public class ClientboundLevelChunkPacketData {
+         this.buffer = new byte[calculateChunkSize(levelChunk)];
+         extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk);
          this.blockEntitiesData = Lists.newArrayList();
 +        int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks
  
-         for (Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) {
+         for (Entry<BlockPos, BlockEntity> entryx : levelChunk.getBlockEntities().entrySet()) {
 +            // Paper start - Handle oversized block entities in chunks
-+            if (++totalTileEntities > TE_LIMIT) {
-+                var packet = entry2.getValue().getUpdatePacket();
++            if (++totalTileEntities > BLOCK_ENTITY_LIMIT) {
++                net.minecraft.network.protocol.Packet<ClientGamePacketListener> packet = entryx.getValue().getUpdatePacket();
 +                if (packet != null) {
 +                    this.extraPackets.add(packet);
 +                    continue;
 +                }
 +            }
 +            // Paper end - Handle oversized block entities in chunks
-             this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(entry2.getValue()));
+             this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(entryx.getValue()));
          }
      }
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
+diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+index aadf2dccb996e422cacf8bb510cc642e69ee4972..5288de783481b7e932017c679b9eaa715b8826c6 100644
+--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+@@ -66,4 +66,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
      public ClientboundLightUpdatePacketData getLightData() {
          return this.lightData;
      }
diff --git a/feature-patches/1055-Check-distance-in-entity-interactions.patch b/feature-patches/1055-Check-distance-in-entity-interactions.patch
index d478e65bcc..6572f8d99e 100644
--- a/feature-patches/1055-Check-distance-in-entity-interactions.patch
+++ b/feature-patches/1055-Check-distance-in-entity-interactions.patch
@@ -4,65 +4,65 @@ Date: Mon, 2 Aug 2021 10:10:40 +0200
 Subject: [PATCH] Check distance in entity interactions
 
 
-diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/Util.java
-+++ b/src/main/java/net/minecraft/Util.java
-@@ -0,0 +0,0 @@ public class Util {
-         .filter(fileSystemProvider -> fileSystemProvider.getScheme().equalsIgnoreCase("jar"))
+diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java
+index 60952bd49a89b8d6247d0c8bac837e5b3d586a76..fe84fe69a2a9ed95ec45a9e5af6e6f5a5a74edda 100644
+--- a/net/minecraft/Util.java
++++ b/net/minecraft/Util.java
+@@ -130,6 +130,7 @@ public class Util {
          .findFirst()
          .orElseThrow(() -> new IllegalStateException("No jar file system provider found"));
+     private static Consumer<String> thePauser = string -> {};
 +    public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Check distance in entity interactions
-     private static Consumer<String> thePauser = message -> {
-     };
  
-diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
-+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                 if (!source.is(DamageTypeTags.IS_PROJECTILE)) {
-                     Entity entity = source.getDirectEntity();
+     public static <K, V> Collector<Entry<? extends K, ? extends V>, ?, Map<K, V>> toMap() {
+         return Collectors.toMap(Entry::getKey, Entry::getValue);
+diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
+index 88f8d462728231627c3ee7557518a2e04b4fd199..e118fd567427064c6ad6637f874ed146e67b2ee8 100644
+--- a/net/minecraft/world/entity/LivingEntity.java
++++ b/net/minecraft/world/entity/LivingEntity.java
+@@ -1392,7 +1392,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+                 this.hurtCurrentlyUsedShield(amount);
+                 f1 = amount;
+                 amount = 0.0F;
+-                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity) {
++                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity && livingEntity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Check distance in entity interactions
+                     this.blockUsingShield(livingEntity);
+                 }
  
--                    if (entity instanceof LivingEntity) {
-+                    if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Check distance in entity interactions
-                         LivingEntity entityliving = (LivingEntity) entity;
- 
-                         this.blockUsingShield(entityliving);
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                         d0 = source.getSourcePosition().x() - this.getX();
-                         d1 = source.getSourcePosition().z() - this.getZ();
+@@ -1477,6 +1477,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
+                         d = damageSource.getSourcePosition().x() - this.getX();
+                         d1 = damageSource.getSourcePosition().z() - this.getZ();
                      }
 +                    // Paper start - Check distance in entity interactions; see for loop in knockback method
-+                    if (Math.abs(d0) > 200) {
-+                        d0 = Math.random() - Math.random();
++                    if (Math.abs(d) > 200) {
++                        d = Math.random() - Math.random();
 +                    }
 +                    if (Math.abs(d1) > 200) {
 +                        d1 = Math.random() - Math.random();
 +                    }
 +                    // Paper end - Check distance in entity interactions
  
-                     this.knockback(0.4000000059604645D, d0, d1, entity1, entity1 == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
+                     this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
                      if (!flag) {
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -2352,7 +2360,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
-                 Entity entity = damagesource.getDirectEntity();
+                 Entity entity = damageSource.getDirectEntity();
  
--                if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
-+                if (!damagesource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Fix shield disable inconsistency & Check distance in entity interactions
+-                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
++                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Fix shield disable inconsistency & Check distance in entity interactions
                      this.blockUsingShield((LivingEntity) entity);
                  }
              }
-diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
-+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java
-@@ -0,0 +0,0 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
-             double d2 = (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D;
- 
-             if (this.level().noCollision(this, this.getBoundingBox().move(0.0D, d2 - this.getY(), 0.0D))) {
+diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java
+index 5cd65e94ac7830aaa2a64057fc2a81478b55ea41..b9cb86717d7e6c05eb97f3b1bbf1d0111a0ba6ed 100644
+--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
++++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
+@@ -641,7 +641,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
+             this.waterLevel = this.getY(1.0);
+             double d2 = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
+             if (this.level().noCollision(this, this.getBoundingBox().move(0.0, d2 - this.getY(), 0.0))) {
 -                this.setPos(this.getX(), d2, this.getZ());
 +                this.move(MoverType.SELF, new Vec3(0.0D, d2 - this.getY(), 0.0D)); // Paper - Check distance in entity interactions // TODO Still needed??
-                 this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
-                 this.lastYd = 0.0D;
+                 this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
+                 this.lastYd = 0.0;
              }
diff --git a/feature-patches/1056-optimize-dirt-and-snow-spreading.patch b/feature-patches/1056-optimize-dirt-and-snow-spreading.patch
index c13eaa6f09..2423e0adc9 100644
--- a/feature-patches/1056-optimize-dirt-and-snow-spreading.patch
+++ b/feature-patches/1056-optimize-dirt-and-snow-spreading.patch
@@ -4,75 +4,75 @@ Date: Fri, 22 Jan 2021 21:50:18 +0100
 Subject: [PATCH] optimize dirt and snow spreading
 
 
-diff --git a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
-@@ -0,0 +0,0 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
+diff --git a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
+index 11937aa74efe08bdbd66a619c7a825f91d971afd..722f2b9a24679e0fc67aae2cd27051f96f962efe 100644
+--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
+@@ -17,8 +17,13 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
      }
  
-     private static boolean canBeGrass(BlockState state, LevelReader world, BlockPos pos) {
-+    // Paper start - Perf: optimize dirt and snow spreading
-+        return canBeGrass(world.getChunk(pos), state, world, pos);
+     private static boolean canBeGrass(BlockState state, LevelReader levelReader, BlockPos pos) {
++        // Paper start - Perf: optimize dirt and snow spreading
++        return canBeGrass(levelReader.getChunk(pos), state, levelReader, pos);
 +    }
-+    private static boolean canBeGrass(net.minecraft.world.level.chunk.ChunkAccess chunk, BlockState state, LevelReader world, BlockPos pos) {
-+    // Paper end - Perf: optimize dirt and snow spreading
-         BlockPos blockposition1 = pos.above();
--        BlockState iblockdata1 = world.getBlockState(blockposition1);
-+        BlockState iblockdata1 = chunk.getBlockState(blockposition1); // Paper - Perf: optimize dirt and snow spreading
- 
-         if (iblockdata1.is(Blocks.SNOW) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 1) {
++    private static boolean canBeGrass(net.minecraft.world.level.chunk.ChunkAccess chunk, BlockState state, LevelReader levelReader, BlockPos pos) {
++        // Paper end - Perf: optimize dirt and snow spreading
+         BlockPos blockPos = pos.above();
+-        BlockState blockState = levelReader.getBlockState(blockPos);
++        BlockState blockState = chunk.getBlockState(blockPos); // Paper - Perf: optimize dirt and snow spreading
+         if (blockState.is(Blocks.SNOW) && blockState.getValue(SnowLayerBlock.LAYERS) == 1) {
              return true;
-@@ -0,0 +0,0 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
+         } else if (blockState.getFluidState().getAmount() == 8) {
+@@ -33,14 +38,27 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
      protected abstract MapCodec<? extends SpreadingSnowyDirtBlock> codec();
  
-     private static boolean canPropagate(BlockState state, LevelReader world, BlockPos pos) {
-+    // Paper start - Perf: optimize dirt and snow spreading
-+        return canPropagate(world.getChunk(pos), state, world, pos);
+     private static boolean canPropagate(BlockState state, LevelReader level, BlockPos pos) {
++        // Paper start - Perf: optimize dirt and snow spreading
++        return canPropagate(level.getChunk(pos), state, level, pos);
 +    }
 +
-+    private static boolean canPropagate(net.minecraft.world.level.chunk.ChunkAccess chunk, BlockState state, LevelReader world, BlockPos pos) {
-+    // Paper end - Perf: optimize dirt and snow spreading
-         BlockPos blockposition1 = pos.above();
- 
--        return SpreadingSnowyDirtBlock.canBeGrass(state, world, pos) && !world.getFluidState(blockposition1).is(FluidTags.WATER);
-+        return SpreadingSnowyDirtBlock.canBeGrass(chunk, state, world, pos) && !chunk.getFluidState(blockposition1).is(FluidTags.WATER); // Paper - Perf: optimize dirt and snow spreading
++    private static boolean canPropagate(net.minecraft.world.level.chunk.ChunkAccess chunk, BlockState state, LevelReader level, BlockPos pos) {
++        // Paper end - Perf: optimize dirt and snow spreading
+         BlockPos blockPos = pos.above();
+-        return canBeGrass(state, level, pos) && !level.getFluidState(blockPos).is(FluidTags.WATER);
++        return canBeGrass(chunk, state, level, pos) && !chunk.getFluidState(blockPos).is(FluidTags.WATER); // Paper - Perf: optimize dirt and snow spreading
      }
  
      @Override
-     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-         if (this instanceof GrassBlock && world.paperConfig().tickRates.grassSpread != 1 && (world.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % world.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks
--        if (!SpreadingSnowyDirtBlock.canBeGrass(state, world, pos)) {
+     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+         if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks
+-        if (!canBeGrass(state, level, pos)) {
 +        // Paper start - Perf: optimize dirt and snow spreading
-+        final net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = world.getChunkIfLoaded(pos);
++        final net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = level.getChunkIfLoaded(pos);
 +        if (cachedBlockChunk == null) { // Is this needed?
 +            return;
 +        }
-+        if (!SpreadingSnowyDirtBlock.canBeGrass(cachedBlockChunk, state, world, pos)) {
++
++        if (!canBeGrass(cachedBlockChunk, state, level, pos)) {
 +            // Paper end - Perf: optimize dirt and snow spreading
              // CraftBukkit start
-             if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
+             if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
                  return;
-@@ -0,0 +0,0 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
-                 for (int i = 0; i < 4; ++i) {
-                     BlockPos blockposition1 = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
+@@ -53,8 +71,20 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock {
  
--                    if (world.getBlockState(blockposition1).is(Blocks.DIRT) && SpreadingSnowyDirtBlock.canPropagate(iblockdata1, world, blockposition1)) {
--                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(world.getBlockState(blockposition1.above())))); // CraftBukkit
+                 for (int i = 0; i < 4; i++) {
+                     BlockPos blockPos = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
+-                    if (level.getBlockState(blockPos).is(Blocks.DIRT) && canPropagate(blockState, level, blockPos)) {
+-                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState.setValue(SNOWY, Boolean.valueOf(isSnowySetting(level.getBlockState(blockPos.above()))))); // CraftBukkit
 +                    // Paper start - Perf: optimize dirt and snow spreading
-+                    if (pos.getX() == blockposition1.getX() && pos.getY() == blockposition1.getY() && pos.getZ() == blockposition1.getZ()) {
++                    if (pos.getX() == blockPos.getX() && pos.getY() == blockPos.getY() && pos.getZ() == blockPos.getZ()) {
 +                        continue;
 +                    }
 +
 +                    final net.minecraft.world.level.chunk.ChunkAccess access;
-+                    if (cachedBlockChunk.locX == blockposition1.getX() >> 4 && cachedBlockChunk.locZ == blockposition1.getZ() >> 4) {
++                    if (cachedBlockChunk.locX == blockPos.getX() >> 4 && cachedBlockChunk.locZ == blockPos.getZ() >> 4) {
 +                        access = cachedBlockChunk;
 +                    } else {
-+                        access = world.getChunkAt(blockposition1);
++                        access = level.getChunkAt(blockPos);
 +                    }
-+                    if (access.getBlockState(blockposition1).is(Blocks.DIRT) && SpreadingSnowyDirtBlock.canPropagate(access, iblockdata1, world, blockposition1)) {
-+                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, (BlockState) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(access.getBlockState(blockposition1.above())))); // CraftBukkit
-+                    // Paper end - Perf: optimize dirt and snow spreading
++                    if (access.getBlockState(blockPos).is(Blocks.DIRT) && SpreadingSnowyDirtBlock.canPropagate(access, blockState, level, blockPos)) {
++                        org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, (BlockState) blockState.setValue(SpreadingSnowyDirtBlock.SNOWY, isSnowySetting(access.getBlockState(blockPos.above())))); // CraftBukkit
++                        // Paper end - Perf: optimize dirt and snow spreading
                      }
                  }
              }
diff --git a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
index 9f549ec79e..1138fb6f90 100644
--- a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
+++ b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
@@ -6,13 +6,13 @@ Subject: [PATCH] Optimise getChunkAt calls for loaded chunks
 bypass the need to get a player chunk, then get the either,
 then unwrap it...
 
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
-                 return this.getChunk(x, z, leastStatus, create);
-             }, this.mainThreadProcessor).join();
+diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
+index d310e7489fc4ecede8deef59241444769d87b0a1..f268808cb250e374f2d5652b20eece011e87dcfb 100644
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -218,6 +218,12 @@ public class ServerChunkCache extends ChunkSource {
+         if (Thread.currentThread() != this.mainThread) {
+             return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(x, z, chunkStatus, requireChunk), this.mainThreadProcessor).join();
          } else {
 +            // Paper start - Perf: Optimise getChunkAt calls for loaded chunks
 +            LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z);
@@ -20,41 +20,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                return ifLoaded;
 +            }
 +            // Paper end - Perf: Optimise getChunkAt calls for loaded chunks
-             ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-             gameprofilerfiller.incrementCounter("getChunk");
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+             ProfilerFiller profilerFiller = Profiler.get();
+             profilerFiller.incrementCounter("getChunk");
+             long packedChunkPos = ChunkPos.asLong(x, z);
+@@ -252,30 +258,7 @@ public class ServerChunkCache extends ChunkSource {
          if (Thread.currentThread() != this.mainThread) {
              return null;
          } else {
 -            Profiler.get().incrementCounter("getChunkNow");
--            long k = ChunkPos.asLong(chunkX, chunkZ);
+-            long packedChunkPos = ChunkPos.asLong(chunkX, chunkZ);
 -
--            ChunkAccess ichunkaccess;
--
--            for (int l = 0; l < 4; ++l) {
--                if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) {
--                    ichunkaccess = this.lastChunk[l];
--                    return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null;
+-            for (int i = 0; i < 4; i++) {
+-                if (packedChunkPos == this.lastChunkPos[i] && this.lastChunkStatus[i] == ChunkStatus.FULL) {
+-                    ChunkAccess chunkAccess = this.lastChunk[i];
+-                    return chunkAccess instanceof LevelChunk ? (LevelChunk)chunkAccess : null;
 -                }
 -            }
 -
--            ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
--
--            if (playerchunk == null) {
+-            ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
+-            if (visibleChunkIfPresent == null) {
 -                return null;
 -            } else {
--                ichunkaccess = playerchunk.getChunkIfPresent(ChunkStatus.FULL);
--                if (ichunkaccess != null) {
--                    this.storeInCache(k, ichunkaccess, ChunkStatus.FULL);
--                    if (ichunkaccess instanceof LevelChunk) {
--                        return (LevelChunk) ichunkaccess;
+-                ChunkAccess chunkAccess = visibleChunkIfPresent.getChunkIfPresent(ChunkStatus.FULL);
+-                if (chunkAccess != null) {
+-                    this.storeInCache(packedChunkPos, chunkAccess, ChunkStatus.FULL);
+-                    if (chunkAccess instanceof LevelChunk) {
+-                        return (LevelChunk)chunkAccess;
 -                    }
 -                }
 -
 -                return null;
 -            }
-+            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
++            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunk
          }
      }
  

From 9bf310baefbf4d63b26a8d9b311b0427b0ef2ba9 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 11:00:21 +0100
Subject: [PATCH 194/285] Update more more feature patches

---
 .../1049-Optimize-Voxel-Shape-Merging.patch   | 109 ++++++------
 ...y-type-tags-suggestions-in-selectors.patch | 156 +++++++++---------
 ...e-getChunkAt-calls-for-loaded-chunks.patch |   2 +-
 3 files changed, 133 insertions(+), 134 deletions(-)

diff --git a/feature-patches/1049-Optimize-Voxel-Shape-Merging.patch b/feature-patches/1049-Optimize-Voxel-Shape-Merging.patch
index 16a5277743..b9255fa095 100644
--- a/feature-patches/1049-Optimize-Voxel-Shape-Merging.patch
+++ b/feature-patches/1049-Optimize-Voxel-Shape-Merging.patch
@@ -29,32 +29,31 @@ and compute a deterministic result for the MergerList values.
 Additionally, this lets us avoid even allocating new objects for this too, further
 reducing memory usage.
 
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java
-@@ -0,0 +0,0 @@ public class IndirectMerger implements IndexMerger {
+diff --git a/net/minecraft/world/phys/shapes/IndirectMerger.java b/net/minecraft/world/phys/shapes/IndirectMerger.java
+index 60b56a5086b8aad0fad693f686b89138b3a4c80d..5b079ec43c259368d0eed4e8385880712abda4f7 100644
+--- a/net/minecraft/world/phys/shapes/IndirectMerger.java
++++ b/net/minecraft/world/phys/shapes/IndirectMerger.java
+@@ -10,12 +10,32 @@ public class IndirectMerger implements IndexMerger {
      private final int[] firstIndices;
      private final int[] secondIndices;
      private final int resultLength;
 +    // Paper start
-+    private static final int[] INFINITE_B_1 = new int[]{1, 1};
-+    private static final int[] INFINITE_B_0 = new int[]{0, 0};
-+    private static final int[] INFINITE_C = new int[]{0, 1};
++    private static final int[] INFINITE_B_1 = {1, 1};
++    private static final int[] INFINITE_B_0 = {0, 0};
++    private static final int[] INFINITE_C = {0, 1};
 +    // Paper end
  
-     public IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) {
+     public IndirectMerger(DoubleList lower, DoubleList upper, boolean excludeUpper, boolean excludeLower) {
          double d = Double.NaN;
-         int i = first.size();
-         int j = second.size();
-         int k = i + j;
+         int size = lower.size();
+         int size1 = upper.size();
+         int i = size + size1;
 +        // Paper start - optimize common path of infinity doublelist
-+        int size = first.size();
-+        double tail = first.getDouble(size - 1);
-+        double head = first.getDouble(0);
-+        if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) {
-+            this.result = second.toDoubleArray();
-+            this.resultLength = second.size();
++        double tail = lower.getDouble(size - 1);
++        double head = lower.getDouble(0);
++        if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !excludeUpper && !excludeLower && (size == 2 || size == 4)) {
++            this.result = upper.toDoubleArray();
++            this.resultLength = upper.size();
 +            if (size == 2) {
 +                this.firstIndices = INFINITE_B_0;
 +            } else {
@@ -64,60 +63,60 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return;
 +        }
 +        // Paper end
-         this.result = new double[k];
-         this.firstIndices = new int[k];
-         this.secondIndices = new int[k];
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-@@ -0,0 +0,0 @@ public final class Shapes {
+         this.result = new double[i];
+         this.firstIndices = new int[i];
+         this.secondIndices = new int[i];
+diff --git a/net/minecraft/world/phys/shapes/Shapes.java b/net/minecraft/world/phys/shapes/Shapes.java
+index e1b4c4b53844b0755e0640a05e8782fd9a7700a2..e759221fb54aa510d2d8bbba47e1d794367aec6d 100644
+--- a/net/minecraft/world/phys/shapes/Shapes.java
++++ b/net/minecraft/world/phys/shapes/Shapes.java
+@@ -279,9 +279,22 @@ public final class Shapes {
      }
  
      @VisibleForTesting
--    protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
-+    private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // Paper - private
+-    protected static IndexMerger createIndexMerger(int size, DoubleList list1, DoubleList list2, boolean excludeUpper, boolean excludeLower) {
++    private static IndexMerger createIndexMerger(int size, DoubleList list1, DoubleList list2, boolean excludeUpper, boolean excludeLower) { // Paper - private
 +        // Paper start - fast track the most common scenario
 +        // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause
 +        // This is actually the most common path, so jump to it straight away
-+        if (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) {
-+            return new IndirectMerger(first, second, includeFirst, includeSecond);
++        if (list1.getDouble(0) == Double.NEGATIVE_INFINITY && list1.getDouble(list1.size() - 1) == Double.POSITIVE_INFINITY) {
++            return new IndirectMerger(list1, list2, excludeUpper, excludeLower);
 +        }
 +        // Split out rest to hopefully inline the above
-+        return lessCommonMerge(size, first, second, includeFirst, includeSecond);
++        return lessCommonMerge(size, list1, list2, excludeUpper, excludeLower);
 +    }
 +
-+    private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
-         int i = first.size() - 1;
-         int j = second.size() - 1;
++    private static IndexMerger lessCommonMerge(int size, DoubleList list1, DoubleList list2, boolean excludeUpper, boolean excludeLower) {
++        // Paper end - fast track the most common scenario
+         int i = list1.size() - 1;
+         int i1 = list2.size() - 1;
 +        // Paper note - Rewrite below as optimized order if instead of nasty ternary
-         if (first instanceof CubePointRange && second instanceof CubePointRange) {
-             long l = lcm(i, j);
-             if ((long)size * l <= 256L) {
-@@ -0,0 +0,0 @@ public final class Shapes {
+         if (list1 instanceof CubePointRange && list2 instanceof CubePointRange) {
+             long l = lcm(i, i1);
+             if (size * l <= 256L) {
+@@ -289,14 +302,21 @@ public final class Shapes {
              }
          }
  
--        if (first.getDouble(i) < second.getDouble(0) - 1.0E-7) {
+-        if (list1.getDouble(i) < list2.getDouble(0) - 1.0E-7) {
 +        // Paper start - Identical happens more often than Disjoint
-+        if (i == j && Objects.equals(first, second)) {
-+            if (first instanceof IdenticalMerger) {
-+                return (IndexMerger) first;
-+            } else if (second instanceof IdenticalMerger) {
-+                return (IndexMerger) second;
++        if (i == i1 && Objects.equals(list1, list2)) {
++            if (list1 instanceof IdenticalMerger) {
++                return (IndexMerger) list1;
++            } else if (list2 instanceof IdenticalMerger) {
++                return (IndexMerger) list2;
 +            }
-+            return new IdenticalMerger(first);
-+        } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7) {
-             return new NonOverlappingMerger(first, second, false);
-         } else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7) {
-             return new NonOverlappingMerger(second, first, true);
++            return new IdenticalMerger(list1);
++        } else if (list1.getDouble(i) < list2.getDouble(0) - 1.0E-7) {
++            // Paper end - Identical happens more often than Disjoint
+             return new NonOverlappingMerger(list1, list2, false);
+         } else if (list2.getDouble(i1) < list1.getDouble(0) - 1.0E-7) {
+             return new NonOverlappingMerger(list2, list1, true);
          } else {
--            return (IndexMerger)(i == j && Objects.equals(first, second)
--                ? new IdenticalMerger(first)
--                : new IndirectMerger(first, second, includeFirst, includeSecond));
-+            return new IndirectMerger(first, second, includeFirst, includeSecond);
+-            return (IndexMerger)(i == i1 && Objects.equals(list1, list2)
+-                ? new IdenticalMerger(list1)
+-                : new IndirectMerger(list1, list2, excludeUpper, excludeLower));
++            return new IndirectMerger(list1, list2, excludeUpper, excludeLower); // Paper - Identical happens more often than Disjoint
          }
-+        // Paper end
      }
  
-     public interface DoubleLineConsumer {
diff --git a/feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch b/feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch
index d5c45f7291..0f7eeec9d8 100644
--- a/feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch
+++ b/feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch
@@ -9,11 +9,11 @@ when if this was fixed on the client, that wouldn't be needed.
 
 Mojira Issue: https://bugs.mojang.com/browse/MC-235045
 
-diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
-+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-@@ -0,0 +0,0 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
+diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java
+index 704a63890a06d793f8ac3452838917e7c7335232..75262c8c9eaecb4a88a94f4076d67119c67a97da 100644
+--- a/net/minecraft/commands/CommandSourceStack.java
++++ b/net/minecraft/commands/CommandSourceStack.java
+@@ -652,4 +652,20 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
          return this.source.getBukkitSender(this);
      }
      // CraftBukkit end
@@ -34,92 +34,92 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - tell clients to ask server for suggestions for EntityArguments
  }
-diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/commands/Commands.java
-+++ b/src/main/java/net/minecraft/commands/Commands.java
-@@ -0,0 +0,0 @@ public class Commands {
-         Iterator iterator = children.iterator();
-         // Paper end - Perf: Async command map building
- 
+diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
+index 45fa9cdc34e78613e346138aa92a6d3bbdee374c..e422a266de555bbf77eee201df9e4c5d89f7b801 100644
+--- a/net/minecraft/commands/Commands.java
++++ b/net/minecraft/commands/Commands.java
+@@ -510,6 +510,7 @@ public class Commands {
+         Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> commandNodeToSuggestionNode
+     ) {
+         commandNodeToSuggestionNode.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains(":")); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
 +        boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments
-         while (iterator.hasNext()) {
-             CommandNode<CommandSourceStack> commandnode2 = (CommandNode) iterator.next();
+         for (CommandNode<CommandSourceStack> commandNode : children) { // Paper - Perf: Async command map building; pass copy of children
              // Paper start - Brigadier API
-@@ -0,0 +0,0 @@ public class Commands {
- 
-                     if (requiredargumentbuilder.getSuggestionsProvider() != null) {
-                         requiredargumentbuilder.suggests(SuggestionProviders.safelySwap(requiredargumentbuilder.getSuggestionsProvider()));
+             if (commandNode.clientNode != null) {
+@@ -572,6 +573,12 @@ public class Commands {
+                     RequiredArgumentBuilder<SharedSuggestionProvider, ?> requiredArgumentBuilder = (RequiredArgumentBuilder<SharedSuggestionProvider, ?>)argumentBuilder;
+                     if (requiredArgumentBuilder.getSuggestionsProvider() != null) {
+                         requiredArgumentBuilder.suggests(SuggestionProviders.safelySwap(requiredArgumentBuilder.getSuggestionsProvider()));
 +                        // Paper start - tell clients to ask server for suggestions for EntityArguments
-+                        registeredAskServerSuggestionsForTree = requiredargumentbuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER;
-+                    } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredargumentbuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) {
-+                        requiredargumentbuilder.suggests(requiredargumentbuilder.getType()::listSuggestions);
++                        registeredAskServerSuggestionsForTree = requiredArgumentBuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER;
++                    } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredArgumentBuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) {
++                        requiredArgumentBuilder.suggests(requiredArgumentBuilder.getType()::listSuggestions);
 +                        registeredAskServerSuggestionsForTree = true; // You can only
 +                        // Paper end - tell clients to ask server for suggestions for EntityArguments
                      }
                  }
  
-diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
-+++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
-@@ -0,0 +0,0 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
-             final boolean permission = object instanceof CommandSourceStack stack
-                     ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector")
-                     : icompletionprovider.hasPermission(2);
--            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission);
-+            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission, true); // Paper - tell clients to ask server for suggestions for EntityArguments
+diff --git a/net/minecraft/commands/arguments/EntityArgument.java b/net/minecraft/commands/arguments/EntityArgument.java
+index ba9a7636e158222bcfb50ae8487f29df6fdbdd0c..8957b99116cb087198fe372d4d2c2b3397fed19f 100644
+--- a/net/minecraft/commands/arguments/EntityArgument.java
++++ b/net/minecraft/commands/arguments/EntityArgument.java
+@@ -138,7 +138,7 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
+             final boolean permission = sharedSuggestionProvider instanceof CommandSourceStack stack
+                 ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector")
+                 : sharedSuggestionProvider.hasPermission(2);
+-            EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, permission);
++            EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, permission, true); // Paper - tell clients to ask server for suggestions for EntityArguments
              // Paper end - Fix EntityArgument permissions
  
              try {
-@@ -0,0 +0,0 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
-             }
- 
-             return argumentparserselector.fillSuggestions(suggestionsbuilder, (suggestionsbuilder1) -> {
--                Collection<String> collection = icompletionprovider.getOnlinePlayerNames();
-+                // Paper start - tell clients to ask server for suggestions for EntityArguments
-+                final Collection<String> collection;
-+                if (icompletionprovider instanceof CommandSourceStack commandSourceStack && commandSourceStack.getEntity() instanceof ServerPlayer sourcePlayer) {
-+                    collection = new java.util.ArrayList<>();
-+                    for (final ServerPlayer player : commandSourceStack.getServer().getPlayerList().getPlayers()) {
-+                        if (sourcePlayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
-+                            collection.add(player.getGameProfile().getName());
+@@ -149,7 +149,19 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
+             return entitySelectorParser.fillSuggestions(
+                 builder,
+                 offsetBuilder -> {
+-                    Collection<String> onlinePlayerNames = sharedSuggestionProvider.getOnlinePlayerNames();
++                    // Paper start - tell clients to ask server for suggestions for EntityArguments
++                    final Collection<String> onlinePlayerNames;
++                    if (sharedSuggestionProvider instanceof CommandSourceStack commandSourceStack && commandSourceStack.getEntity() instanceof ServerPlayer sourcePlayer) {
++                        onlinePlayerNames = new java.util.ArrayList<>();
++                        for (final ServerPlayer player : commandSourceStack.getServer().getPlayerList().getPlayers()) {
++                            if (sourcePlayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
++                                onlinePlayerNames.add(player.getGameProfile().getName());
++                            }
 +                        }
++                    } else {
++                        onlinePlayerNames = sharedSuggestionProvider.getOnlinePlayerNames();
 +                    }
-+                } else {
-+                    collection = icompletionprovider.getOnlinePlayerNames();
-+                }
-+                // Paper end - tell clients to ask server for suggestions for EntityArguments
-                 Iterable<String> iterable = this.playersOnly ? collection : Iterables.concat(collection, icompletionprovider.getSelectedEntities());
- 
-                 SharedSuggestionProvider.suggest((Iterable) iterable, suggestionsbuilder1);
-diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
-+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
-@@ -0,0 +0,0 @@ public class EntitySelectorParser {
++                    // Paper end - tell clients to ask server for suggestions for EntityArguments
+                     Iterable<String> iterable = (Iterable<String>)(this.playersOnly
+                         ? onlinePlayerNames
+                         : Iterables.concat(onlinePlayerNames, sharedSuggestionProvider.getSelectedEntities()));
+diff --git a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
+index a6f232747df631f6afe440606bea94c588f1a0dd..fb42630741674c6cbd20b7d45d78dea1dc73a78f 100644
+--- a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
++++ b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
+@@ -115,8 +115,15 @@ public class EntitySelectorParser {
      private boolean hasScores;
      private boolean hasAdvancements;
      private boolean usesSelectors;
 +    public boolean parsingEntityArgumentSuggestions; // Paper - tell clients to ask server for suggestions for EntityArguments
  
-     public EntitySelectorParser(StringReader reader, boolean atAllowed) {
+     public EntitySelectorParser(StringReader reader, boolean allowSelectors) {
 +        // Paper start - tell clients to ask server for suggestions for EntityArguments
-+        this(reader, atAllowed, false);
++        this(reader, allowSelectors, false);
 +    }
-+    public EntitySelectorParser(StringReader reader, boolean atAllowed, boolean parsingEntityArgumentSuggestions) {
++    public EntitySelectorParser(StringReader reader, boolean allowSelectors, boolean parsingEntityArgumentSuggestions) {
 +        this.parsingEntityArgumentSuggestions = parsingEntityArgumentSuggestions;
 +        // Paper end - tell clients to ask server for suggestions for EntityArguments
-         this.distance = MinMaxBounds.Doubles.ANY;
-         this.level = MinMaxBounds.Ints.ANY;
-         this.rotX = WrappedMinMaxBounds.ANY;
-diff --git a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
-+++ b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
-@@ -0,0 +0,0 @@ public class EntitySelectorOptions {
+         this.reader = reader;
+         this.allowSelectors = allowSelectors;
+     }
+diff --git a/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java b/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
+index f6b58139aace70436034f0a16370236d975cb4ae..ee9949c41d38817b21b6f4fd728059a46fddf135 100644
+--- a/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
++++ b/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
+@@ -76,6 +76,19 @@ public class EntitySelectorOptions {
      public static final DynamicCommandExceptionType ERROR_ENTITY_TYPE_INVALID = new DynamicCommandExceptionType(
-         entity -> Component.translatableEscape("argument.entity.options.type.invalid", entity)
+         type -> Component.translatableEscape("argument.entity.options.type.invalid", type)
      );
 +    // Paper start - tell clients to ask server for suggestions for EntityArguments
 +    public static final DynamicCommandExceptionType ERROR_ENTITY_TAG_INVALID = new DynamicCommandExceptionType((object) -> {
@@ -135,18 +135,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    });
 +    // Paper end - tell clients to ask server for suggestions for EntityArguments
  
-     private static void register(String id, EntitySelectorOptions.Modifier handler, Predicate<EntitySelectorParser> condition, Component description) {
-         OPTIONS.put(id, new EntitySelectorOptions.Option(handler, condition, description));
-@@ -0,0 +0,0 @@ public class EntitySelectorOptions {
+     private static void register(String id, EntitySelectorOptions.Modifier handler, Predicate<EntitySelectorParser> predicate, Component tooltip) {
+         OPTIONS.put(id, new EntitySelectorOptions.Option(handler, predicate, tooltip));
+@@ -299,6 +312,12 @@ public class EntitySelectorOptions {
  
-                         if (reader.isTag()) {
-                             TagKey<EntityType<?>> tagKey = TagKey.create(Registries.ENTITY_TYPE, ResourceLocation.read(reader.getReader()));
+                         if (parser.isTag()) {
+                             TagKey<EntityType<?>> tagKey = TagKey.create(Registries.ENTITY_TYPE, ResourceLocation.read(parser.getReader()));
 +                            // Paper start - tell clients to ask server for suggestions for EntityArguments; throw error if invalid entity tag (only on suggestions to keep cmd success behavior)
-+                            if (reader.parsingEntityArgumentSuggestions && io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(tagKey).isEmpty()) {
-+                                reader.getReader().setCursor(i);
-+                                throw ERROR_ENTITY_TAG_INVALID.createWithContext(reader.getReader(), tagKey);
++                            if (parser.parsingEntityArgumentSuggestions && io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(tagKey).isEmpty()) {
++                                parser.getReader().setCursor(cursor);
++                                throw ERROR_ENTITY_TAG_INVALID.createWithContext(parser.getReader(), tagKey);
 +                            }
 +                            // Paper end - tell clients to ask server for suggestions for EntityArguments
-                             reader.addPredicate(entity -> entity.getType().is(tagKey) != bl);
+                             parser.addPredicate(entity -> entity.getType().is(tagKey) != shouldInvertValue);
                          } else {
-                             ResourceLocation resourceLocation = ResourceLocation.read(reader.getReader());
+                             ResourceLocation resourceLocation = ResourceLocation.read(parser.getReader());
diff --git a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
index 1138fb6f90..0fcf7dd22f 100644
--- a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
+++ b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
@@ -51,7 +51,7 @@ index d310e7489fc4ecede8deef59241444769d87b0a1..f268808cb250e374f2d5652b20eece01
 -
 -                return null;
 -            }
-+            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunk
++            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
          }
      }
  

From 520ab93fbf2d5ae5893e0648300bda15195284b1 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 11:34:20 +0100
Subject: [PATCH 195/285] Fix bad method call in EquipmentDispenseItemBehavior

---
 ...1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch | 4 ++--
 .../dispenser/EquipmentDispenseItemBehavior.java.patch    | 8 ++++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
index 0fcf7dd22f..bb441c5742 100644
--- a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
+++ b/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
@@ -15,7 +15,7 @@ index d310e7489fc4ecede8deef59241444769d87b0a1..f268808cb250e374f2d5652b20eece01
              return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(x, z, chunkStatus, requireChunk), this.mainThreadProcessor).join();
          } else {
 +            // Paper start - Perf: Optimise getChunkAt calls for loaded chunks
-+            LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z);
++            LevelChunk ifLoaded = this.getChunkAtIfCachedImmediately(x, z);
 +            if (ifLoaded != null) {
 +                return ifLoaded;
 +            }
@@ -51,7 +51,7 @@ index d310e7489fc4ecede8deef59241444769d87b0a1..f268808cb250e374f2d5652b20eece01
 -
 -                return null;
 -            }
-+            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
++            return this.getChunkAtIfCachedImmediately(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
index b3e53ec5b6..08275c166f 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch
@@ -1,7 +1,11 @@
 --- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
 +++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
-@@ -17,7 +_,13 @@
-         return dispenseEquipment(blockSource, item) ? item : super.execute(blockSource, item);
+@@ -14,10 +_,16 @@
+ 
+     @Override
+     protected ItemStack execute(BlockSource blockSource, ItemStack item) {
+-        return dispenseEquipment(blockSource, item) ? item : super.execute(blockSource, item);
++        return dispenseEquipment(blockSource, item, this) ? item : super.execute(blockSource, item); // Paper - fix possible StackOverflowError
      }
  
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper

From 6a0c3c3efc11e7b559412d4e4afb17095eca6afe Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 11:54:00 +0100
Subject: [PATCH 196/285] Fix tests

---
 .../gametest/framework/GameTestInfo.java.patch        | 11 +++++++++++
 .../gametest/framework/StructureUtils.java.patch      | 11 +++++++++++
 .../gametest/framework/TestCommand.java.patch         | 11 +++++++++++
 .../java/org/bukkit/event/EntityRemoveEventTest.java  |  3 +++
 4 files changed, 36 insertions(+)
 create mode 100644 paper-server/patches/sources/net/minecraft/gametest/framework/GameTestInfo.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/gametest/framework/StructureUtils.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/gametest/framework/TestCommand.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestInfo.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestInfo.java.patch
new file mode 100644
index 0000000000..91fda07c6b
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/gametest/framework/GameTestInfo.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/gametest/framework/GameTestInfo.java
++++ b/net/minecraft/gametest/framework/GameTestInfo.java
+@@ -241,7 +_,7 @@
+             AABB structureBounds = this.getStructureBounds();
+             List<Entity> entitiesOfClass = this.getLevel()
+                 .getEntitiesOfClass(Entity.class, structureBounds.inflate(1.0), entity -> !(entity instanceof Player));
+-            entitiesOfClass.forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED));
++            entitiesOfClass.forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD)); // Paper
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/StructureUtils.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/StructureUtils.java.patch
new file mode 100644
index 0000000000..23eeabf7b9
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/gametest/framework/StructureUtils.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/gametest/framework/StructureUtils.java
++++ b/net/minecraft/gametest/framework/StructureUtils.java
+@@ -187,7 +_,7 @@
+         level.clearBlockEvents(boundingBox1);
+         AABB aabb = AABB.of(boundingBox1);
+         List<Entity> entitiesOfClass = level.getEntitiesOfClass(Entity.class, aabb, entity -> !(entity instanceof Player));
+-        entitiesOfClass.forEach(Entity::discard);
++        entitiesOfClass.forEach(entity -> entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD)); // Paper
+     }
+ 
+     public static BlockPos getTransformedFarCorner(BlockPos pos, Vec3i offset, Rotation rotation) {
diff --git a/paper-server/patches/sources/net/minecraft/gametest/framework/TestCommand.java.patch b/paper-server/patches/sources/net/minecraft/gametest/framework/TestCommand.java.patch
new file mode 100644
index 0000000000..6ca1c0d342
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/gametest/framework/TestCommand.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/gametest/framework/TestCommand.java
++++ b/net/minecraft/gametest/framework/TestCommand.java
+@@ -278,7 +_,7 @@
+     }
+ 
+     private static int resetGameTestInfo(GameTestInfo gameTestInfo) {
+-        gameTestInfo.getLevel().getEntities(null, gameTestInfo.getStructureBounds()).stream().forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED));
++        gameTestInfo.getLevel().getEntities(null, gameTestInfo.getStructureBounds()).stream().forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD)); // Paper
+         gameTestInfo.getStructureBlockEntity().placeStructure(gameTestInfo.getLevel());
+         StructureUtils.removeBarriers(gameTestInfo.getStructureBounds(), gameTestInfo.getLevel());
+         say(gameTestInfo.getLevel(), "Reset succeded for: " + gameTestInfo.getTestName(), ChatFormatting.GREEN);
diff --git a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java b/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
index 56d2eb4b92..0d198f2592 100644
--- a/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
+++ b/paper-server/src/test/java/org/bukkit/event/EntityRemoveEventTest.java
@@ -103,6 +103,9 @@ public class EntityRemoveEventTest {
         }
 
         Class<?> ownerClass = Class.forName(owner.replace('/', '.'), false, this.getClass().getClassLoader());
+        if (ownerClass == EntityAccess.class) {
+            return false;
+        }
 
         // Found missing discard, remove or setRemoved method call
         return EntityAccess.class.isAssignableFrom(ownerClass);

From ff1c3c0077ecc5647f4c09aaa0941a3c567528ff Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 12:25:25 +0100
Subject: [PATCH 197/285] More feature patches

---
 CONTRIBUTING.md                               |   3 +-
 ...ocity-compression-and-cipher-natives.patch | 309 ++++++++----------
 ...oalSelector-Goal.Flag-Set-operations.patch | 124 ++++---
 paper-server/build.gradle.kts                 |   5 +
 4 files changed, 206 insertions(+), 235 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9e48879b0e..38fb143438 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -169,13 +169,14 @@ move it under the line of the patch you wish to modify;
 
 1. Make your change while at HEAD;
 1. Make a fixup commit. `git commit -a --fixup <hashOfPatchToFix>`;
+   - If you want to modify a per-file patch, use `git commit -a --fixup file`
    - You can also use `--squash` instead of `--fixup` if you want the commit
    message to also be changed.
    - You can get the hash by looking at `git log` or `git blame`; your IDE can
   assist you too.
    - Alternatively, if you only know the name of the patch, you can do
   `git commit -a --fixup "Subject of Patch name"`.
-1. Rebase with autosquash: `git rebase -i --autosquash base`.
+1. Rebase with autosquash: `git rebase -i --autosquash mache/main`.
 This will automatically move your fixup commit to the right place, and you just
 need to "save" the changes.
 1. Type `./gradlew rebuildPatches` in the root directory;
diff --git a/feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch b/feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch
index b339b20f9b..8ec9093e28 100644
--- a/feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch
+++ b/feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch
@@ -3,30 +3,12 @@ From: Andrew Steinborn <git@steinborn.me>
 Date: Mon, 26 Jul 2021 02:15:17 -0400
 Subject: [PATCH] Use Velocity compression and cipher natives
 
-== AT ==
-private-f net.minecraft.network.CompressionDecoder inflater
 
-diff --git a/build.gradle.kts b/build.gradle.kts
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/build.gradle.kts
-+++ b/build.gradle.kts
-@@ -0,0 +0,0 @@ dependencies {
-     runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0")
-     runtimeOnly("com.mysql:mysql-connector-j:9.1.0")
-     runtimeOnly("com.lmax:disruptor:3.4.4") // Paper
-+    // Paper start - Use Velocity cipher
-+    implementation("com.velocitypowered:velocity-native:3.3.0-SNAPSHOT") {
-+        isTransitive = false
-+    }
-+    // Paper end - Use Velocity cipher
- 
-     runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6")
-     runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
-diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/CipherDecoder.java
-+++ b/src/main/java/net/minecraft/network/CipherDecoder.java
-@@ -0,0 +0,0 @@ import java.util.List;
+diff --git a/net/minecraft/network/CipherDecoder.java b/net/minecraft/network/CipherDecoder.java
+index 429325ffb7db2b85ed271ddf3da64c6fdc593673..4a445cb0ab19c6eca94fdc2172e1b286d1947c63 100644
+--- a/net/minecraft/network/CipherDecoder.java
++++ b/net/minecraft/network/CipherDecoder.java
+@@ -7,14 +7,30 @@ import java.util.List;
  import javax.crypto.Cipher;
  
  public class CipherDecoder extends MessageToMessageDecoder<ByteBuf> {
@@ -39,13 +21,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.cipher = cipher;  // Paper - Use Velocity cipher
      }
  
-     protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
--        list.add(this.cipher.decipher(channelHandlerContext, byteBuf));
+     @Override
+     protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws Exception {
+-        out.add(this.cipher.decipher(context, in));
 +        // Paper start - Use Velocity cipher
-+        ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
++        ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.cipher, in);
 +        try {
-+            cipher.process(compatible);
-+            list.add(compatible);
++            this.cipher.process(compatible);
++            out.add(compatible);
 +        } catch (Exception e) {
 +            compatible.release(); // compatible will never be used if we throw an exception
 +            throw e;
@@ -56,19 +39,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper start - Use Velocity cipher
 +    @Override
 +    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
-+        cipher.close();
++        this.cipher.close();
 +    }
 +    // Paper end - Use Velocity cipher
  }
-diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/CipherEncoder.java
-+++ b/src/main/java/net/minecraft/network/CipherEncoder.java
-@@ -0,0 +0,0 @@ import io.netty.buffer.ByteBuf;
- import io.netty.channel.ChannelHandlerContext;
+diff --git a/net/minecraft/network/CipherEncoder.java b/net/minecraft/network/CipherEncoder.java
+index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..e087a35bcaf326c37a3e58f4d06165a61747c5a9 100644
+--- a/net/minecraft/network/CipherEncoder.java
++++ b/net/minecraft/network/CipherEncoder.java
+@@ -5,15 +5,31 @@ import io.netty.channel.ChannelHandlerContext;
  import io.netty.handler.codec.MessageToByteEncoder;
  import javax.crypto.Cipher;
-+import java.util.List;
  
 -public class CipherEncoder extends MessageToByteEncoder<ByteBuf> {
 -    private final CipherBase cipher;
@@ -81,13 +62,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.cipher = cipher;  // Paper - Use Velocity cipher
      }
  
--    protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception {
--        this.cipher.encipher(byteBuf, byteBuf2);
-+    protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
-+        // Paper start - Use Velocity cipher
-+        ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
++    // Paper start - Use Velocity cipher
+     @Override
+-    protected void encode(ChannelHandlerContext context, ByteBuf message, ByteBuf out) throws Exception {
+-        this.cipher.encipher(message, out);
++    protected void encode(ChannelHandlerContext context, ByteBuf message, java.util.List<Object> list) throws Exception {
++        ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.cipher, message);
 +        try {
-+            cipher.process(compatible);
++            this.cipher.process(compatible);
 +            list.add(compatible);
 +        } catch (Exception e) {
 +            compatible.release(); // compatible will never be used if we throw an exception
@@ -99,57 +81,58 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper start - Use Velocity cipher
 +    @Override
 +    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
-+        cipher.close();
++        this.cipher.close();
 +    }
 +    // Paper end - Use Velocity cipher
  }
-diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/CompressionDecoder.java
-+++ b/src/main/java/net/minecraft/network/CompressionDecoder.java
-@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
+diff --git a/net/minecraft/network/CompressionDecoder.java b/net/minecraft/network/CompressionDecoder.java
+index fcf0e557fbcbd5f306625096d859578fe8511734..2c7f935fcecb24a4394fdde523219a5b5984a673 100644
+--- a/net/minecraft/network/CompressionDecoder.java
++++ b/net/minecraft/network/CompressionDecoder.java
+@@ -12,14 +12,22 @@ import java.util.zip.Inflater;
+ public class CompressionDecoder extends ByteToMessageDecoder {
      public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152;
      public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608;
-     private Inflater inflater;
 +    private com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher
+     private Inflater inflater;
      private int threshold;
      private boolean validateDecompressed;
  
 +    // Paper start - Use Velocity cipher
 +    @io.papermc.paper.annotation.DoNotUse
-     public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) {
-+        this(null, compressionThreshold, rejectsBadPackets);
+     public CompressionDecoder(int threshold, boolean validateDecompressed) {
++        this(null, threshold, validateDecompressed);
 +    }
-+    public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) {
-         this.threshold = compressionThreshold;
-         this.validateDecompressed = rejectsBadPackets;
++    public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold, boolean validateDecompressed) {
+         this.threshold = threshold;
+         this.validateDecompressed = validateDecompressed;
 -        this.inflater = new Inflater();
 +        this.inflater = compressor == null ? new Inflater() : null;
 +        this.compressor = compressor;
 +        // Paper end - Use Velocity cipher
      }
  
-     protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
-@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
+     @Override
+@@ -39,14 +47,42 @@ public class CompressionDecoder extends ByteToMessageDecoder {
                      }
                  }
  
 +                if (inflater != null) { // Paper - Use Velocity cipher; fallback to vanilla inflater
-                 this.setupInflaterInput(byteBuf);
-                 ByteBuf byteBuf2 = this.inflate(channelHandlerContext, i);
+                 this.setupInflaterInput(in);
+                 ByteBuf byteBuf = this.inflate(context, i);
                  this.inflater.reset();
-                 list.add(byteBuf2);
+                 out.add(byteBuf);
 +                return; // Paper - Use Velocity cipher
 +                } // Paper - use velocity compression
 +
 +                // Paper start - Use Velocity cipher
 +                int claimedUncompressedSize = i; // OBFHELPER
-+                ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf);
-+                ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize);
++                ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.compressor, in);
++                ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(context.alloc(), this.compressor, claimedUncompressedSize);
 +                try {
 +                    this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
-+                    list.add(uncompressed);
-+                    byteBuf.clear();
++                    out.add(uncompressed);
++                    in.clear();
 +                } catch (Exception e) {
 +                    uncompressed.release();
 +                    throw e;
@@ -163,36 +146,36 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
 +    // Paper start - Use Velocity cipher
 +    @Override
-+    public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
++    public void handlerRemoved0(ChannelHandlerContext ctx) {
 +        if (this.compressor != null) {
 +            this.compressor.close();
 +        }
 +    }
 +    // Paper end - Use Velocity cipher
 +
-     private void setupInflaterInput(ByteBuf buf) {
+     private void setupInflaterInput(ByteBuf buffer) {
          ByteBuffer byteBuffer;
-         if (buf.nioBufferCount() > 0) {
-@@ -0,0 +0,0 @@ public class CompressionDecoder extends ByteToMessageDecoder {
+         if (buffer.nioBufferCount() > 0) {
+@@ -81,7 +117,13 @@ public class CompressionDecoder extends ByteToMessageDecoder {
          }
      }
  
--    public void setThreshold(int compressionThreshold, boolean rejectsBadPackets) {
+-    public void setThreshold(int threshold, boolean validateDecompressed) {
 +    // Paper start - Use Velocity cipher
-+    public void setThreshold(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) {
++    public void setThreshold(com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold, boolean validateDecompressed) {
 +        if (this.compressor == null && compressor != null) { // Only re-configure once. Re-reconfiguring would require closing the native compressor.
 +            this.compressor = compressor;
 +            this.inflater = null;
 +        }
-+    // Paper end - Use Velocity cipher
-         this.threshold = compressionThreshold;
-         this.validateDecompressed = rejectsBadPackets;
++        // Paper end - Use Velocity cipher
+         this.threshold = threshold;
+         this.validateDecompressed = validateDecompressed;
      }
-diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/CompressionEncoder.java
-+++ b/src/main/java/net/minecraft/network/CompressionEncoder.java
-@@ -0,0 +0,0 @@ import io.netty.handler.codec.MessageToByteEncoder;
+diff --git a/net/minecraft/network/CompressionEncoder.java b/net/minecraft/network/CompressionEncoder.java
+index bc674b08a41d5529fe06c6d3f077051cf4138f73..ea8a894158c44c2e7943dea43ecd8e1f0075b18f 100644
+--- a/net/minecraft/network/CompressionEncoder.java
++++ b/net/minecraft/network/CompressionEncoder.java
+@@ -6,17 +6,31 @@ import io.netty.handler.codec.MessageToByteEncoder;
  import java.util.zip.Deflater;
  
  public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
@@ -200,16 +183,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    @javax.annotation.Nullable private final byte[] encodeBuf; // Paper - Use Velocity cipher
 +    @javax.annotation.Nullable // Paper - Use Velocity cipher
      private final Deflater deflater;
-+    @javax.annotation.Nullable // Paper - Use Velocity cipher
-+    private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher
++    @javax.annotation.Nullable private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher
      private int threshold;
  
 +    // Paper start - Use Velocity cipher
-     public CompressionEncoder(int compressionThreshold) {
-+        this(null, compressionThreshold);
+     public CompressionEncoder(int threshold) {
++        this(null, threshold);
 +    }
-+    public CompressionEncoder(@javax.annotation.Nullable com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) {
-         this.threshold = compressionThreshold;
++    public CompressionEncoder(@javax.annotation.Nullable com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold) {
+         this.threshold = threshold;
 -        this.deflater = new Deflater();
 +        if (compressor == null) {
 +            this.encodeBuf = new byte[8192];
@@ -222,40 +204,46 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - Use Velocity cipher
      }
  
--    protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) {
-+    protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - Use Velocity cipher
-         int i = byteBuf.readableBytes();
+     @Override
+-    protected void encode(ChannelHandlerContext context, ByteBuf encodingByteBuf, ByteBuf byteBuf) {
++    protected void encode(ChannelHandlerContext context, ByteBuf encodingByteBuf, ByteBuf byteBuf) throws Exception { // Paper - Use Velocity cipher
+         int i = encodingByteBuf.readableBytes();
          if (i > 8388608) {
              throw new IllegalArgumentException("Packet too big (is " + i + ", should be less than 8388608)");
-@@ -0,0 +0,0 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
-                 VarInt.write(byteBuf2, 0);
-                 byteBuf2.writeBytes(byteBuf);
+@@ -25,6 +39,7 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
+                 VarInt.write(byteBuf, 0);
+                 byteBuf.writeBytes(encodingByteBuf);
              } else {
 +                if (this.deflater != null) { // Paper - Use Velocity cipher
-                 byte[] bs = new byte[i];
-                 byteBuf.readBytes(bs);
-                 VarInt.write(byteBuf2, bs.length);
-@@ -0,0 +0,0 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
+                 byte[] bytes = new byte[i];
+                 encodingByteBuf.readBytes(bytes);
+                 VarInt.write(byteBuf, bytes.length);
+@@ -37,6 +52,17 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
                  }
  
                  this.deflater.reset();
-+                // Paper start - Use Velocity cipher
++                    // Paper start - Use Velocity cipher
 +                    return;
 +                }
 +
-+                VarInt.write(byteBuf2, i);
-+                final ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf);
++                VarInt.write(byteBuf, i);
++                final ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.compressor, encodingByteBuf);
 +                try {
-+                    this.compressor.deflate(compatibleIn, byteBuf2);
++                    this.compressor.deflate(compatibleIn, byteBuf);
 +                } finally {
 +                    compatibleIn.release();
 +                }
              }
          }
      }
- 
+@@ -48,4 +74,31 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
+     public void setThreshold(int threshold) {
+         this.threshold = threshold;
+     }
++
++    // Paper start - Use Velocity cipher
 +    @Override
-+    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{
++    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
 +        if (this.compressor != null) {
 +            // We allocate bytes to be compressed plus 1 byte. This covers two cases:
 +            //
@@ -273,44 +261,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    @Override
-+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
++    public void handlerRemoved(ChannelHandlerContext ctx) {
 +        if (this.compressor != null) {
 +            this.compressor.close();
-+            // Paper end - Use Velocity cipher
 +        }
 +    }
-+
-     public int getThreshold() {
-         return this.threshold;
-     }
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-         return networkmanager;
++    // Paper end - Use Velocity cipher
+ }
+diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
+index c4bb28857ee11dccc9924666634488044c666fd1..8fe485c5bf79804bb4d1f774f95a92b14a576e80 100644
+--- a/net/minecraft/network/Connection.java
++++ b/net/minecraft/network/Connection.java
+@@ -770,11 +770,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+         return connection;
      }
  
--    public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
+-    public void setEncryptionKey(Cipher decryptingCipher, Cipher encryptingCipher) {
 -        this.encrypted = true;
--        this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
--        this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+-        this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptingCipher));
+-        this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptingCipher));
 +    // Paper start - Use Velocity cipher
-+//    public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
-+//        this.encrypted = true;
-+//        this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
-+//        this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
-+//    }
-+
-+    public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException {
++    public void setEncryptionKey(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException {
 +        if (!this.encrypted) {
 +            try {
-+                com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
-+                com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
++                com.velocitypowered.natives.encryption.VelocityCipher decryptionCipher = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
++                com.velocitypowered.natives.encryption.VelocityCipher encryptionCipher = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
 +
 +                this.encrypted = true;
-+                this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption));
-+                this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption));
++                this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
++                this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
 +            } catch (java.security.GeneralSecurityException e) {
 +                throw new net.minecraft.util.CryptException(e);
 +            }
@@ -320,68 +299,56 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public boolean isEncrypted() {
          return this.encrypted;
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- 
-     public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) {
-         if (compressionThreshold >= 0) {
+@@ -813,16 +824,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+     // Paper end - add proper async disconnect
+     public void setupCompression(int threshold, boolean validateDecompressed) {
+         if (threshold >= 0) {
 +            com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher
-             ChannelHandler channelhandler = this.channel.pipeline().get("decompress");
- 
-             if (channelhandler instanceof CompressionDecoder) {
-                 CompressionDecoder packetdecompressor = (CompressionDecoder) channelhandler;
- 
--                packetdecompressor.setThreshold(compressionThreshold, rejectsBadPackets);
-+                packetdecompressor.setThreshold(compressor, compressionThreshold, rejectsBadPackets); // Paper - Use Velocity cipher
+             if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder compressionDecoder) {
+-                compressionDecoder.setThreshold(threshold, validateDecompressed);
++                compressionDecoder.setThreshold(compressor, threshold, validateDecompressed); // Paper - Use Velocity cipher
              } else {
--                this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets));
-+                this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - Use Velocity cipher
+-                this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(threshold, validateDecompressed));
++                this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressor, threshold, validateDecompressed)); // Paper - Use Velocity cipher
              }
  
-             channelhandler = this.channel.pipeline().get("compress");
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- 
-                 packetcompressor.setThreshold(compressionThreshold);
+             if (this.channel.pipeline().get("compress") instanceof CompressionEncoder compressionEncoder) {
+                 compressionEncoder.setThreshold(threshold);
              } else {
--                this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold));
-+                this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher
+-                this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(threshold));
++                this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressor, threshold)); // Paper - Use Velocity cipher
              }
              this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
          } else {
-diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-@@ -0,0 +0,0 @@ public class ServerConnectionListener {
+diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
+index b68adf37af7172671163d4a8074d2bfa97724b4b..9d9f1b93a68bbc3e201408a3669bba7f73006218 100644
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -108,6 +108,10 @@ public class ServerConnectionListener {
+                 LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
              }
              // Paper end - Warn people with console access that HAProxy is in use.
- 
 +            // Paper start - Use Velocity cipher
 +            ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
 +            ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
 +            // Paper end - Use Velocity cipher
-+
-             this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
-                 protected void initChannel(Channel channel) {
-                     try {
-diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-@@ -0,0 +0,0 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ 
+             this.channels
+                 .add(
+diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+index d5d1cdc0759338ce1554b19689fca90d2573189e..507c6b2628cab56e00b64fe1b21f873e717eda2d 100644
+--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -276,11 +276,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
              }
  
-             SecretKey secretkey = packet.getSecretKey(privatekey);
--            Cipher cipher = Crypt.getCipher(2, secretkey);
--            Cipher cipher1 = Crypt.getCipher(1, secretkey);
-+            // Paper start - Use Velocity cipher
-+//            Cipher cipher = Crypt.getCipher(2, secretkey);
-+//            Cipher cipher1 = Crypt.getCipher(1, secretkey);
-+            // Paper end - Use Velocity cipher
- 
-             s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16);
+             SecretKey secretKey = packet.getSecretKey(_private);
+-            Cipher cipher = Crypt.getCipher(2, secretKey);
+-            Cipher cipher1 = Crypt.getCipher(1, secretKey);
+             string = new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretKey)).toString(16);
              this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING;
 -            this.connection.setEncryptionKey(cipher, cipher1);
-+            this.connection.setupEncryption(secretkey); // Paper - Use Velocity cipher
-         } catch (CryptException cryptographyexception) {
-             throw new IllegalStateException("Protocol error", cryptographyexception);
++            this.connection.setEncryptionKey(secretKey); // Paper - Use Velocity cipher
+         } catch (CryptException var7) {
+             throw new IllegalStateException("Protocol error", var7);
          }
diff --git a/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
index 4200e29c19..a550caaf22 100644
--- a/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
+++ b/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
@@ -6,20 +6,19 @@ Subject: [PATCH] Optimize GoalSelector Goal.Flag Set operations
 Optimise the stream.anyMatch statement to move to a bitset
 where we can replace the call with a single bitwise operation.
 
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.entity.Entity;
+diff --git a/net/minecraft/world/entity/ai/goal/Goal.java b/net/minecraft/world/entity/ai/goal/Goal.java
+index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16efa2db8d24 100644
+--- a/net/minecraft/world/entity/ai/goal/Goal.java
++++ b/net/minecraft/world/entity/ai/goal/Goal.java
+@@ -7,7 +7,15 @@ import net.minecraft.world.entity.Entity;
  import net.minecraft.world.level.Level;
  
  public abstract class Goal {
 -    private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class);
-+    private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be.
 +    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
 +
 +    // Paper start - remove streams from pathfindergoalselector; make sure types are not empty
-+    public Goal() {
++    protected Goal() {
 +        if (this.goalTypes.size() == 0) {
 +            this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
 +        }
@@ -28,23 +27,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public abstract boolean canUse();
  
-@@ -0,0 +0,0 @@ public abstract class Goal {
+@@ -33,8 +41,13 @@ public abstract class Goal {
      }
  
-     public void setFlags(EnumSet<Goal.Flag> controls) {
+     public void setFlags(EnumSet<Goal.Flag> flagSet) {
 -        this.flags.clear();
--        this.flags.addAll(controls);
+-        this.flags.addAll(flagSet);
 +        // Paper start - remove streams from pathfindergoalselector
 +        this.goalTypes.clear();
-+        this.goalTypes.addAllUnchecked(controls);
++        this.goalTypes.addAllUnchecked(flagSet);
 +        if (this.goalTypes.size() == 0) {
 +            this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
 +        }
-+        // Paper end - remove streams from pathfindergoalselector
++        // Paper end - remove streams from pathfindergoalselector;
      }
  
      @Override
-@@ -0,0 +0,0 @@ public abstract class Goal {
+@@ -42,18 +55,20 @@ public abstract class Goal {
          return this.getClass().getSimpleName();
      }
  
@@ -56,6 +55,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - remove streams from pathfindergoalselector
      }
  
+ 
      // Paper start - Mob Goal API
      public boolean hasFlag(final Goal.Flag flag) {
 -        return this.flags.contains(flag);
@@ -66,51 +66,52 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -        this.flags.add(flag);
 +        this.goalTypes.addUnchecked(flag);
      }
-     // Paper end - Mob Goal API
  
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-@@ -0,0 +0,0 @@ public class GoalSelector {
+     // Paper end - Mob Goal API
+diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
+index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..88e7e245824b670652878e03a2142a13e97508fe 100644
+--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
+@@ -24,7 +24,9 @@ public class GoalSelector {
      };
      private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
      private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
 -    private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
 +    private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
 +    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
-     private int curRate; // Paper - EAR 2
++
  
      public void addGoal(int priority, Goal goal) {
-@@ -0,0 +0,0 @@ public class GoalSelector {
-         this.availableGoals.removeIf(wrappedGoalx -> wrappedGoalx.getGoal() == goal);
+         this.availableGoals.add(new WrappedGoal(priority, goal));
+@@ -45,18 +47,18 @@ public class GoalSelector {
+         this.availableGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal);
      }
  
--    private static boolean goalContainsAnyFlags(WrappedGoal goal, EnumSet<Goal.Flag> controls) {
--        for (Goal.Flag flag : goal.getFlags()) {
--            if (controls.contains(flag)) {
+-    private static boolean goalContainsAnyFlags(WrappedGoal goal, EnumSet<Goal.Flag> flag) {
+-        for (Goal.Flag flag1 : goal.getFlags()) {
+-            if (flag.contains(flag1)) {
 -                return true;
 -            }
 -        }
 -
 -        return false;
-+    // Paper start
-+    private static boolean goalContainsAnyFlags(WrappedGoal goal, ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> controls) {
++    // Paper start - Perf: optimize goal types
++    private static boolean goalContainsAnyFlags(WrappedGoal goal, ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> flags) {
 +        return goal.getFlags().hasCommonElements(controls);
      }
  
-     private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map<Goal.Flag, WrappedGoal> goalsByControl) {
--        for (Goal.Flag flag : goal.getFlags()) {
+     private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map<Goal.Flag, WrappedGoal> flag) {
+-        for (Goal.Flag flag1 : goal.getFlags()) {
 +        long flagIterator = goal.getFlags().getBackingSet();
 +        int wrappedGoalSize = goal.getFlags().size();
 +        for (int i = 0; i < wrappedGoalSize; ++i) {
-+            final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)];
++            final Goal.Flag flag1 = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)];
 +            flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator);
-+            // Paper end
-             if (!goalsByControl.getOrDefault(flag, NO_GOAL).canBeReplacedBy(goal)) {
++            // Paper end - Perf: optimize goal types
+             if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) {
                  return false;
              }
-@@ -0,0 +0,0 @@ public class GoalSelector {
+@@ -70,7 +72,7 @@ public class GoalSelector {
          profilerFiller.push("goalCleanup");
  
          for (WrappedGoal wrappedGoal : this.availableGoals) {
@@ -119,53 +120,50 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  wrappedGoal.stop();
              }
          }
-@@ -0,0 +0,0 @@ public class GoalSelector {
+@@ -80,11 +82,14 @@ public class GoalSelector {
          profilerFiller.push("goalUpdate");
  
-         for (WrappedGoal wrappedGoal2 : this.availableGoals) {
--            if (!wrappedGoal2.isRunning()
--                && !goalContainsAnyFlags(wrappedGoal2, this.disabledFlags)
--                && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags)
--                && wrappedGoal2.canUse()) {
--                for (Goal.Flag flag : wrappedGoal2.getFlags()) {
+         for (WrappedGoal wrappedGoalx : this.availableGoals) {
+-            if (!wrappedGoalx.isRunning()
+-                && !goalContainsAnyFlags(wrappedGoalx, this.disabledFlags)
+-                && goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags)
+-                && wrappedGoalx.canUse()) {
+-                for (Goal.Flag flag : wrappedGoalx.getFlags()) {
 +            // Paper start
-+            if (!wrappedGoal2.isRunning() && !goalContainsAnyFlags(wrappedGoal2, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags) && wrappedGoal2.canUse()) {
-+                long flagIterator = wrappedGoal2.getFlags().getBackingSet();
-+                int wrappedGoalSize = wrappedGoal2.getFlags().size();
++            if (!wrappedGoalx.isRunning() && !goalContainsAnyFlags(wrappedGoalx, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags) && wrappedGoalx.canUse()) {
++                long flagIterator = wrappedGoalx.getFlags().getBackingSet();
++                int wrappedGoalSize = wrappedGoalx.getFlags().size();
 +                for (int i = 0; i < wrappedGoalSize; ++i) {
 +                    final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)];
 +                    flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator);
 +                    // Paper end
-                     WrappedGoal wrappedGoal3 = this.lockedFlags.getOrDefault(flag, NO_GOAL);
-                     wrappedGoal3.stop();
-                     this.lockedFlags.put(flag, wrappedGoal2);
-@@ -0,0 +0,0 @@ public class GoalSelector {
+                     WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL);
+                     wrappedGoal1.stop();
+                     this.lockedFlags.put(flag, wrappedGoalx);
+@@ -116,11 +121,11 @@ public class GoalSelector {
      }
  
-     public void disableControlFlag(Goal.Flag control) {
--        this.disabledFlags.add(control);
-+        this.goalTypes.addUnchecked(control); // Paper - remove streams from pathfindergoalselector
+     public void disableControlFlag(Goal.Flag flag) {
+-        this.disabledFlags.add(flag);
++        this.goalTypes.addUnchecked(flag); // Paper - remove streams from pathfindergoalselector
      }
  
-     public void enableControlFlag(Goal.Flag control) {
--        this.disabledFlags.remove(control);
-+        this.goalTypes.removeUnchecked(control); // Paper - remove streams from pathfindergoalselector
+     public void enableControlFlag(Goal.Flag flag) {
+-        this.disabledFlags.remove(flag);
++        this.goalTypes.removeUnchecked(flag); // Paper - remove streams from pathfindergoalselector
      }
  
-     public void setControlFlag(Goal.Flag control, boolean enabled) {
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java
-@@ -0,0 +0,0 @@ public class WrappedGoal extends Goal {
+     public void setControlFlag(Goal.Flag flag, boolean enabled) {
+diff --git a/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/net/minecraft/world/entity/ai/goal/WrappedGoal.java
+index 4bdbd323b642ed3422948fe24780be8b503602dc..5aaad796d2e2f7b57b1fd911a1498cdf235c36be 100644
+--- a/net/minecraft/world/entity/ai/goal/WrappedGoal.java
++++ b/net/minecraft/world/entity/ai/goal/WrappedGoal.java
+@@ -69,7 +69,7 @@ public class WrappedGoal extends Goal {
      }
  
      @Override
 -    public EnumSet<Goal.Flag> getFlags() {
-+    // Paper start - remove streams from pathfindergoalselector
-+    public ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() {
++    public ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() { // Paper - remove streams from pathfindergoalselector
          return this.goal.getFlags();
-+        // Paper end - remove streams from pathfindergoalselector
      }
  
-     public boolean isRunning() {
diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 9a2fab0245..4844881912 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -149,6 +149,11 @@ dependencies {
     runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0")
     runtimeOnly("com.mysql:mysql-connector-j:9.1.0")
     runtimeOnly("com.lmax:disruptor:3.4.4") // Paper
+    // Paper start - Use Velocity cipher
+    implementation("com.velocitypowered:velocity-native:3.3.0-SNAPSHOT") {
+        isTransitive = false
+    }
+    // Paper end - Use Velocity cipher
 
     runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6")
     runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")

From 47c06357f71ff81759d3748ef0c26a6921e25d63 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 13:03:53 +0100
Subject: [PATCH 198/285] Initial update to EAR patch

---
 .../1043-Entity-Activation-Range-2.0.patch    | 683 +++---------------
 ...ptimize-Collision-to-not-load-chunks.patch |  74 +-
 .../java/org/spigotmc/ActivationRange.java    | 229 +++++-
 .../java/org/spigotmc/SpigotWorldConfig.java  |  46 ++
 4 files changed, 385 insertions(+), 647 deletions(-)

diff --git a/feature-patches/1043-Entity-Activation-Range-2.0.patch b/feature-patches/1043-Entity-Activation-Range-2.0.patch
index d9a21e0900..2a9fee105b 100644
--- a/feature-patches/1043-Entity-Activation-Range-2.0.patch
+++ b/feature-patches/1043-Entity-Activation-Range-2.0.patch
@@ -13,84 +13,29 @@ Adds water Mobs to activation range config and nerfs fish
 Adds flying monsters to control ghast and phantoms
 Adds villagers as separate config
 
-== AT ==
-public net.minecraft.world.entity.Entity isInsidePortal
 
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
- 
-     public void tickNonPassenger(Entity entity) {
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index 1d7e9492a474c99dff372d6b57f1f195e42d5114..aa5eed48871b5ab67d18213e532271241aae9182 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -963,10 +963,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         profilerFiller.incrementCounter("tickNonPassenger");
          // Spigot start
--        if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
-+        /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out EAR 2
-             entity.tickCount++;
-             entity.inactiveTick();
-             return;
--        }
-+        }*/ // Paper - comment out EAR 2
-         // Spigot end
-         entity.setOldPosAndRot();
-         ProfilerFiller gameprofilerfiller = Profiler.get();
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-             return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
-         });
-         gameprofilerfiller.incrementCounter("tickNonPassenger");
-+        final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
+         final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
+-        if (isActive) {
 +        if (isActive) { // Paper - EAR 2
          entity.tick();
          entity.postTick(); // CraftBukkit
-+        } else { entity.inactiveTick(); } // Paper - EAR 2
-         gameprofilerfiller.pop();
-         Iterator iterator = entity.getPassengers().iterator();
+-        } else {entity.inactiveTick();} // Spigot end
++        } else {entity.inactiveTick();} // Paper - EAR 2
+         profilerFiller.pop();
  
-         while (iterator.hasNext()) {
-             Entity entity1 = (Entity) iterator.next();
- 
--            this.tickPassenger(entity, entity1);
-+            this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
-         }
- 
-     }
- 
--    private void tickPassenger(Entity vehicle, Entity passenger) {
-+    private void tickPassenger(Entity vehicle, Entity passenger, boolean isActive) { // Paper - EAR 2
-         if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) {
-             if (passenger instanceof Player || this.entityTickList.contains(passenger)) {
-                 passenger.setOldPosAndRot();
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-                     return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString();
-                 });
-                 gameprofilerfiller.incrementCounter("tickPassenger");
-+                // Paper start - EAR 2
-+                if (isActive) {
-                 passenger.rideTick();
-                 passenger.postTick(); // CraftBukkit
-+                } else {
-+                passenger.setDeltaMovement(Vec3.ZERO);
-+                passenger.inactiveTick();
-+                // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
-+                vehicle.positionRider(passenger);
-+                }
-+                // Paper end - EAR 2
-                 gameprofilerfiller.pop();
-                 Iterator iterator = passenger.getPassengers().iterator();
- 
-                 while (iterator.hasNext()) {
-                     Entity entity2 = (Entity) iterator.next();
- 
--                    this.tickPassenger(passenger, entity2);
-+                    this.tickPassenger(passenger, entity2, isActive); // Paper - EAR 2
-                 }
- 
-             }
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+         for (Entity entity1 : entity.getPassengers()) {
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
+index 0aed1e455dec32c3d53d8fb2f1047e1bf177f171..8c2441a6c7abc3a80426923c1ea42000283ee167 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -383,6 +383,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      // Spigot end
      protected int numCollisions = 0; // Paper - Cap entity collisions
      public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals
@@ -99,7 +44,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
      // Paper start - Entity origin API
      @javax.annotation.Nullable
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -959,6 +961,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          } else {
              this.wasOnFire = this.isOnFire();
              if (type == MoverType.PISTON) {
@@ -108,7 +53,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  movement = this.limitPistonMovement(movement);
                  if (movement.equals(Vec3.ZERO)) {
                      return;
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -972,6 +976,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
                  this.stuckSpeedMultiplier = Vec3.ZERO;
                  this.setDeltaMovement(Vec3.ZERO);
              }
@@ -121,12 +66,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // Paper end
  
              movement = this.maybeBackOffFromEdge(movement, type);
-             Vec3 vec3d1 = this.collide(movement);
-diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/Mob.java
-+++ b/src/main/java/net/minecraft/world/entity/Mob.java
-@@ -0,0 +0,0 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
+             Vec3 vec3 = this.collide(movement);
+diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
+index f7d69db61d1293510428ae275e8a50571dde5ddf..1ed07fd23985a6bf8cf8300f74c92b7531a79fc6 100644
+--- a/net/minecraft/world/entity/Mob.java
++++ b/net/minecraft/world/entity/Mob.java
+@@ -215,6 +215,19 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
          return this.lookControl;
      }
  
@@ -144,14 +89,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end
 +
      public MoveControl getMoveControl() {
-         Entity entity = this.getControlledVehicle();
- 
-diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java
-+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java
-@@ -0,0 +0,0 @@ public abstract class PathfinderMob extends Mob {
-         super(type, world);
+         return this.getControlledVehicle() instanceof Mob mob ? mob.getMoveControl() : this.moveControl;
+     }
+diff --git a/net/minecraft/world/entity/PathfinderMob.java b/net/minecraft/world/entity/PathfinderMob.java
+index 0caf50ec50f056b83a20bbc6a2fe0144593aef39..af59a700755654eb68d6bf57d0712c4a2ac6c09b 100644
+--- a/net/minecraft/world/entity/PathfinderMob.java
++++ b/net/minecraft/world/entity/PathfinderMob.java
+@@ -17,6 +17,8 @@ public abstract class PathfinderMob extends Mob {
+         super(entityType, level);
      }
  
 +    public BlockPos movingTarget; public BlockPos getMovingTarget() { return movingTarget; } // Paper
@@ -159,20 +104,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public float getWalkTargetValue(BlockPos pos) {
          return this.getWalkTargetValue(pos, this.level());
      }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
-@@ -0,0 +0,0 @@ public class GoalSelector {
+diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
+index 88e7e245824b670652878e03a2142a13e97508fe..2730e76228b03b1ec52108f394c20fe003801b02 100644
+--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
+@@ -22,12 +22,12 @@ public class GoalSelector {
+             return false;
+         }
+     };
++    private int curRate; // Paper - EAR 2
      private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
      private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
-     private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
-+    private int curRate; // Paper - EAR 2
+     private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
+     private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
  
+-
      public void addGoal(int priority, Goal goal) {
          this.availableGoals.add(new WrappedGoal(priority, goal));
-@@ -0,0 +0,0 @@ public class GoalSelector {
-         this.availableGoals.removeIf(goal -> predicate.test(goal.getGoal()));
+     }
+@@ -37,6 +37,22 @@ public class GoalSelector {
+         this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal()));
      }
  
 +    // Paper start - EAR 2
@@ -180,6 +131,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.curRate++;
 +        return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct
 +    }
++
 +    public boolean hasTasks() {
 +        for (WrappedGoal task : this.availableGoals) {
 +            if (task.isRunning()) {
@@ -189,16 +141,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return false;
 +    }
 +    // Paper end - EAR 2
++
      public void removeGoal(Goal goal) {
          for (WrappedGoal wrappedGoal : this.availableGoals) {
              if (wrappedGoal.getGoal() == goal && wrappedGoal.isRunning()) {
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
-@@ -0,0 +0,0 @@ public abstract class MoveToBlockGoal extends Goal {
-     public MoveToBlockGoal(PathfinderMob mob, double speed, int range) {
-         this(mob, speed, range, 1);
+diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+index 789fea258d70e60d38271ebb31270562dc7eb3ab..d0ab3db7bbd2942db19f473474371b20ce822608 100644
+--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+@@ -23,6 +23,14 @@ public abstract class MoveToBlockGoal extends Goal {
+     public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) {
+         this(mob, speedModifier, searchRange, 1);
      }
 +    // Paper start - activation range improvements
 +    @Override
@@ -209,21 +162,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end
  
-     public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) {
+     public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange, int verticalSearchRange) {
          this.mob = mob;
-@@ -0,0 +0,0 @@ public abstract class MoveToBlockGoal extends Goal {
-                         mutableBlockPos.setWithOffset(blockPos, m, k - 1, n);
+@@ -113,6 +121,7 @@ public abstract class MoveToBlockGoal extends Goal {
+                         mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5);
                          if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) {
                              this.blockPos = mutableBlockPos;
 +                            this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper
                              return true;
                          }
                      }
-diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-@@ -0,0 +0,0 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
+index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9ce712a462 100644
+--- a/net/minecraft/world/entity/npc/Villager.java
++++ b/net/minecraft/world/entity/npc/Villager.java
+@@ -269,18 +269,33 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
      @Override
      public void inactiveTick() {
          // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
@@ -232,14 +185,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper start
 +        if (this.getUnhappyCounter() > 0) {
 +            this.setUnhappyCounter(this.getUnhappyCounter() - 1);
-+        }
+         }
 +        if (this.isEffectiveAi()) {
 +            if (this.level().spigotConfig.tickInactiveVillagers) {
 +                this.customServerAiStep(this.level().getMinecraftWorld());
 +            } else {
 +                this.customServerAiStep(this.level().getMinecraftWorld(), true);
 +            }
-         }
++        }
 +        maybeDecayGossip();
 +        // Paper end
          super.inactiveTick();
@@ -247,50 +200,49 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // Spigot End
  
      @Override
-     protected void customServerAiStep(ServerLevel world) {
+     protected void customServerAiStep(ServerLevel level) {
 +        // Paper start - EAR 2
-+        this.customServerAiStep(world, false);
++        this.customServerAiStep(level, false);
 +    }
-+    protected void customServerAiStep(ServerLevel world, final boolean inactive) {
++    protected void customServerAiStep(ServerLevel level, final boolean inactive) {
 +        // Paper end - EAR 2
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("villagerBrain");
--        this.getBrain().tick(world, this);
-+        if (!inactive) this.getBrain().tick(world, this);
-         gameprofilerfiller.pop();
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("villagerBrain");
+-        this.getBrain().tick(level, this);
++        if (!inactive) this.getBrain().tick(level, this); // Paper - EAR 2
+         profilerFiller.pop();
          if (this.assignProfessionWhenSpawned) {
              this.assignProfessionWhenSpawned = false;
-@@ -0,0 +0,0 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+@@ -304,7 +319,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
              this.lastTradedPlayer = null;
          }
  
 -        if (!this.isNoAi() && this.random.nextInt(100) == 0) {
 +        if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper - EAR 2
-             Raid raid = world.getRaidAt(this.blockPosition());
- 
-             if (raid != null && raid.isActive() && !raid.isOver()) {
-@@ -0,0 +0,0 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+             Raid raidAt = level.getRaidAt(this.blockPosition());
+             if (raidAt != null && raidAt.isActive() && !raidAt.isOver()) {
+                 level.broadcastEntityEvent(this, (byte)42);
+@@ -314,6 +329,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
          if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) {
              this.stopTrading();
          }
 +        if (inactive) return; // Paper - EAR 2
  
-         super.customServerAiStep(world);
+         super.customServerAiStep(level);
      }
-diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java
-+++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java
-@@ -0,0 +0,0 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
-         if (bl != this.isEnabled()) {
-             this.setEnabled(bl);
+diff --git a/net/minecraft/world/entity/vehicle/MinecartHopper.java b/net/minecraft/world/entity/vehicle/MinecartHopper.java
+index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c116aee9b35 100644
+--- a/net/minecraft/world/entity/vehicle/MinecartHopper.java
++++ b/net/minecraft/world/entity/vehicle/MinecartHopper.java
+@@ -47,6 +47,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
+         if (flag != this.isEnabled()) {
+             this.setEnabled(flag);
          }
 +        this.immunize();  // Paper
      }
  
      public boolean isEnabled() {
-@@ -0,0 +0,0 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
+@@ -100,11 +101,13 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
  
      public boolean suckInItems() {
          if (HopperBlockEntity.suckInItems(this.level(), this)) {
@@ -304,9 +256,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      return true;
                  }
              }
-@@ -0,0 +0,0 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
-     public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
-         return new HopperMenu(syncId, playerInventory, this);
+@@ -139,4 +142,11 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
+     public AbstractContainerMenu createMenu(int id, Inventory playerInventory) {
+         return new HopperMenu(id, playerInventory, this);
      }
 +
 +    // Paper start
@@ -316,13 +268,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end
 +
  }
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index e0239091729f6be138c037951fd5c138497ee358..051a86e8a723aeb86ffa06e3cd5fab102a808cde 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -153,6 +153,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
-     public List<ItemEntity> captureDrops;
+     public List<net.minecraft.world.entity.item.ItemEntity> captureDrops;
      public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
 +    // Paper start
 +    public int wakeupInactiveRemainingAnimals;
@@ -333,443 +285,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public boolean populating;
      public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
      // Paper start - add paper world config
-diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
-@@ -0,0 +0,0 @@ public class PistonMovingBlockEntity extends BlockEntity {
+diff --git a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
+index 754cfdcd5a28287aa3545aaffdce1e391cbefc1e..1e6e940fca9d96ef410c7bf05524bd9b24db4a79 100644
+--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
++++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java
+@@ -149,6 +149,10 @@ public class PistonMovingBlockEntity extends BlockEntity {
                                  }
  
-                                 entity.setDeltaMovement(e, g, h);
-+                                // Paper - EAR items stuck in in slime pushed by a piston
+                                 entity.setDeltaMovement(d1, d2, d3);
++                                // Paper - EAR items stuck in slime pushed by a piston
 +                                entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10);
 +                                entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10);
 +                                // Paper end
                                  break;
                              }
                          }
-diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/spigotmc/ActivationRange.java
-+++ b/src/main/java/org/spigotmc/ActivationRange.java
-@@ -0,0 +0,0 @@
- package org.spigotmc;
- 
-+import net.minecraft.core.BlockPos;
- import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerChunkCache;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.ExperienceOrb;
-+import net.minecraft.world.entity.FlyingMob;
- import net.minecraft.world.entity.LightningBolt;
- import net.minecraft.world.entity.LivingEntity;
-+import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.PathfinderMob;
-+import net.minecraft.world.entity.ai.Brain;
- import net.minecraft.world.entity.ambient.AmbientCreature;
- import net.minecraft.world.entity.animal.Animal;
-+import net.minecraft.world.entity.animal.Bee;
- import net.minecraft.world.entity.animal.Sheep;
-+import net.minecraft.world.entity.animal.WaterAnimal;
-+import net.minecraft.world.entity.animal.horse.Llama;
- import net.minecraft.world.entity.boss.EnderDragonPart;
- import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
- import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
-@@ -0,0 +0,0 @@ import net.minecraft.world.entity.boss.wither.WitherBoss;
- import net.minecraft.world.entity.item.ItemEntity;
- import net.minecraft.world.entity.item.PrimedTnt;
- import net.minecraft.world.entity.monster.Creeper;
--import net.minecraft.world.entity.monster.Monster;
--import net.minecraft.world.entity.monster.Slime;
-+import net.minecraft.world.entity.monster.Enemy;
-+import net.minecraft.world.entity.monster.Pillager;
- import net.minecraft.world.entity.npc.Villager;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.entity.projectile.AbstractArrow;
- import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
-+import net.minecraft.world.entity.projectile.EyeOfEnder;
- import net.minecraft.world.entity.projectile.FireworkRocketEntity;
- import net.minecraft.world.entity.projectile.ThrowableProjectile;
- import net.minecraft.world.entity.projectile.ThrownTrident;
-@@ -0,0 +0,0 @@ public class ActivationRange
- 
-         AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 );
-     }
-+    // Paper start
-+
-+    static net.minecraft.world.entity.schedule.Activity[] VILLAGER_PANIC_IMMUNITIES = {
-+        net.minecraft.world.entity.schedule.Activity.HIDE,
-+        net.minecraft.world.entity.schedule.Activity.PRE_RAID,
-+        net.minecraft.world.entity.schedule.Activity.RAID,
-+        net.minecraft.world.entity.schedule.Activity.PANIC
-+    };
-+
-+    private static int checkInactiveWakeup(Entity entity) {
-+        Level world = entity.level();
-+        SpigotWorldConfig config = world.spigotConfig;
-+        long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
-+        if (entity.activationType == ActivationType.VILLAGER) {
-+            if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
-+                world.wakeupInactiveRemainingVillagers--;
-+                return config.wakeUpInactiveVillagersFor;
-+            }
-+        } else if (entity.activationType == ActivationType.ANIMAL) {
-+            if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
-+                world.wakeupInactiveRemainingAnimals--;
-+                return config.wakeUpInactiveAnimalsFor;
-+            }
-+        } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
-+            if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
-+                world.wakeupInactiveRemainingFlying--;
-+                return config.wakeUpInactiveFlyingFor;
-+            }
-+        } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
-+            if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
-+                world.wakeupInactiveRemainingMonsters--;
-+                return config.wakeUpInactiveMonstersFor;
-+            }
-+        }
-+        return -1;
-+    }
-+    // Paper end
- 
-     static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 );
- 
-@@ -0,0 +0,0 @@ public class ActivationRange
-      */
-     public static ActivationType initializeEntityActivationType(Entity entity)
-     {
-+        if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper
-+        else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper
-+        else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future
-         if ( entity instanceof Raider )
-         {
-             return ActivationType.RAIDER;
--        } else if ( entity instanceof Monster || entity instanceof Slime )
-+        } else if ( entity instanceof Enemy ) // Paper - correct monster check
-         {
-             return ActivationType.MONSTER;
-         } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature )
-@@ -0,0 +0,0 @@ public class ActivationRange
-      */
-     public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
-     {
--        if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 )
--                || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 )
--                || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 )
--                || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 )
-+        if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 )
-+                || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 )
-+                || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 )
-+                || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 )
-+                || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper
-+                || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper
-+                || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper
-+                || entity instanceof EyeOfEnder // Paper
-                 || entity instanceof Player
-                 || entity instanceof ThrowableProjectile
-                 || entity instanceof EnderDragon
-@@ -0,0 +0,0 @@ public class ActivationRange
-         final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
-         final int animalActivationRange = world.spigotConfig.animalActivationRange;
-         final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
-+        // Paper start
-+        final int waterActivationRange = world.spigotConfig.waterActivationRange;
-+        final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
-+        final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
-+        world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
-+        world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
-+        world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
-+        world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
-+        final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource();
-+        // Paper end
- 
-         int maxRange = Math.max( monsterActivationRange, animalActivationRange );
-         maxRange = Math.max( maxRange, raiderActivationRange );
-         maxRange = Math.max( maxRange, miscActivationRange );
-+        // Paper start
-+        maxRange = Math.max( maxRange, flyingActivationRange );
-+        maxRange = Math.max( maxRange, waterActivationRange );
-+        maxRange = Math.max( maxRange, villagerActivationRange );
-+        // Paper end
-         maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange );
- 
-         for ( Player player : world.players() )
-@@ -0,0 +0,0 @@ public class ActivationRange
-                 continue;
-             }
- 
--            ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange );
--            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange );
--            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange );
--            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange );
--            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange );
-+            // Paper start
-+            int worldHeight = world.getHeight();
-+            ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange );
-+            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, worldHeight, miscActivationRange );
-+            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, worldHeight, raiderActivationRange );
-+            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, worldHeight, animalActivationRange );
-+            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, worldHeight, monsterActivationRange );
-+            ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, worldHeight, waterActivationRange );
-+            ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, worldHeight, flyingActivationRange );
-+            ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange );
-+            // Paper end
- 
--            world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity);
-+            // Paper start
-+            java.util.List<Entity> entities = world.getEntities((Entity)null, ActivationRange.maxBB, null);
-+            boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking
-+            for (Entity entity : entities) {
-+                // Paper start - Configurable marker ticking
-+                if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) {
-+                    continue;
-+                }
-+                // Paper end - Configurable marker ticking
-+                ActivationRange.activateEntity(entity);
-+            }
-+            // Paper end
-         }
-     }
- 
-@@ -0,0 +0,0 @@ public class ActivationRange
-      * @param entity
-      * @return
-      */
--    public static boolean checkEntityImmunities(Entity entity)
-+    public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity
-     {
-+        // Paper start
-+        SpigotWorldConfig config = entity.level().spigotConfig;
-+        int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
-+        if (inactiveWakeUpImmunity > -1) {
-+            return inactiveWakeUpImmunity;
-+        }
-+        if (entity.getRemainingFireTicks() > 0) {
-+            return 2;
-+        }
-+        if (entity.activatedImmunityTick >= MinecraftServer.currentTick) {
-+            return 1;
-+        }
-+        long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
-+        // Paper end
-         // quick checks.
--        if ( entity.wasTouchingWater || entity.getRemainingFireTicks() > 0 )
-+        if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper
-         {
--            return true;
-+            return 100; // Paper
-+        }
-+        // Paper start
-+        if ( !entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D )
-+        {
-+            return 100;
-         }
-+        // Paper end
-         if ( !( entity instanceof AbstractArrow ) )
-         {
--            if ( !entity.onGround() || !entity.passengers.isEmpty() || entity.isPassenger() )
-+            if ( (!entity.onGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic
-             {
--                return true;
-+                return 10; // Paper
-             }
-         } else if ( !( (AbstractArrow) entity ).isInGround() )
-         {
--            return true;
-+            return 1; // Paper
-         }
-         // special cases.
-         if ( entity instanceof LivingEntity )
-         {
-             LivingEntity living = (LivingEntity) entity;
--            if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 )
-+            if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing()) // Paper
-             {
--                return true;
-+                return 1; // Paper
-             }
--            if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null )
-+            if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper
-             {
--                return true;
-+                return 20; // Paper
-             }
--            if ( entity instanceof Villager && ( (Villager) entity ).canBreed() )
-+            // Paper start
-+            if (entity instanceof Bee) {
-+                Bee bee = (Bee)entity;
-+                BlockPos movingTarget = bee.getMovingTarget();
-+                if (bee.isAngry() ||
-+                    (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) ||
-+                    (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget))
-+                ) {
-+                    return 20;
-+                }
-+            }
-+            if ( entity instanceof Villager ) {
-+                Brain<Villager> behaviorController = ((Villager) entity).getBrain();
-+
-+                if (config.villagersActiveForPanic) {
-+                    for (net.minecraft.world.entity.schedule.Activity activity : VILLAGER_PANIC_IMMUNITIES) {
-+                        if (behaviorController.isActive(activity)) {
-+                            return 20*5;
-+                        }
-+                    }
-+                }
-+
-+                if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) {
-+                    if (behaviorController.isActive(net.minecraft.world.entity.schedule.Activity.WORK)) {
-+                        return config.villagersWorkImmunityFor;
-+                    }
-+                }
-+            }
-+            if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() )
-             {
--                return true;
-+                return 1;
-             }
-+            // Paper end
-             if ( entity instanceof Animal )
-             {
-                 Animal animal = (Animal) entity;
-                 if ( animal.isBaby() || animal.isInLove() )
-                 {
--                    return true;
-+                    return 5; // Paper
-                 }
-                 if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() )
-                 {
--                    return true;
-+                    return 1; // Paper
-                 }
-             }
-             if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive
--                return true;
-+                return 20; // Paper
-+            }
-+            // Paper start
-+            if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) {
-+                return 0;
-             }
-+            if (entity instanceof Pillager) {
-+                Pillager pillager = (Pillager) entity;
-+                // TODO:?
-+            }
-+            // Paper end
-         }
-         // SPIGOT-6644: Otherwise the target refresh tick will be missed
-         if (entity instanceof ExperienceOrb) {
--            return true;
-+            return 20; // Paper
-         }
--        return false;
-+        return -1; // Paper
-     }
- 
-     /**
-@@ -0,0 +0,0 @@ public class ActivationRange
-         if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick
-             return true;
-         }
-+        // Paper start - special case always immunities
-+        // immunize brand new entities, dead entities, and portal scenarios
-+        if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || (entity.portalProcess != null && !entity.portalProcess.hasExpired()) || entity.portalCooldown > 0) {
-+            return true;
-+        }
-+        // immunize leashed entities
-+        if (entity instanceof Mob && ((Mob)entity).getLeashHolder() instanceof Player) {
-+            return true;
-+        }
-+        // Paper end
- 
--        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
-+        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
-+        entity.isTemporarilyActive = false; // Paper
- 
-         // Should this entity tick?
-         if ( !isActive )
-@@ -0,0 +0,0 @@ public class ActivationRange
-             if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
-             {
-                 // Check immunities every 20 ticks.
--                if ( ActivationRange.checkEntityImmunities( entity ) )
--                {
--                    // Triggered some sort of immunity, give 20 full ticks before we check again.
--                    entity.activatedTick = MinecraftServer.currentTick + 20;
-+                // Paper start
-+                int immunity = checkEntityImmunities(entity);
-+                if (immunity >= 0) {
-+                    entity.activatedTick = MinecraftServer.currentTick + immunity;
-+                } else {
-+                    entity.isTemporarilyActive = true;
-                 }
-+                // Paper end
-                 isActive = true;
-             }
-         }
-diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
-+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
-@@ -0,0 +0,0 @@ public class SpigotWorldConfig
-     public int monsterActivationRange = 32;
-     public int raiderActivationRange = 64;
-     public int miscActivationRange = 16;
-+    // Paper start
-+    public int flyingMonsterActivationRange = 32;
-+    public int waterActivationRange = 16;
-+    public int villagerActivationRange = 32;
-+    public int wakeUpInactiveAnimals = 4;
-+    public int wakeUpInactiveAnimalsEvery = 60*20;
-+    public int wakeUpInactiveAnimalsFor = 5*20;
-+    public int wakeUpInactiveMonsters = 8;
-+    public int wakeUpInactiveMonstersEvery = 20*20;
-+    public int wakeUpInactiveMonstersFor = 5*20;
-+    public int wakeUpInactiveVillagers = 4;
-+    public int wakeUpInactiveVillagersEvery = 30*20;
-+    public int wakeUpInactiveVillagersFor = 5*20;
-+    public int wakeUpInactiveFlying = 8;
-+    public int wakeUpInactiveFlyingEvery = 10*20;
-+    public int wakeUpInactiveFlyingFor = 5*20;
-+    public int villagersWorkImmunityAfter = 5*20;
-+    public int villagersWorkImmunityFor = 20;
-+    public boolean villagersActiveForPanic = true;
-+    // Paper end
-     public boolean tickInactiveVillagers = true;
-     public boolean ignoreSpectatorActivation = false;
-     private void activationRange()
-     {
-+        boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper
-         this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange );
-         this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange );
-         this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange );
-         this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange );
-+        // Paper start
-+        this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange );
-+        this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange );
-+        this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange );
-+
-+        this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals);
-+        this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery);
-+        this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor);
-+
-+        this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters);
-+        this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery);
-+        this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor);
-+
-+        this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers);
-+        this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery);
-+        this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor);
-+
-+        this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying);
-+        this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery);
-+        this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor);
-+
-+        this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter );
-+        this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor );
-+        this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic );
-+        // Paper end
-         this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers );
-         this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation );
-         this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation );
diff --git a/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch b/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch
index 8026cceeb7..201ac3ffeb 100644
--- a/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch
+++ b/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch
@@ -13,44 +13,45 @@ If that serting is not enabled, collisions will be ignored for players, since
 movement will load only the chunk the player enters anyways and avoids loading
 massive amounts of surrounding chunks due to large AABB lookups.
 
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
+index f70e77dcce4d94b06efd27273194ce95f4b336ec..0aed1e455dec32c3d53d8fb2f1047e1bf177f171 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -218,6 +218,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      // Paper end - Share random for entities to make them more random
      public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
  
 +    public boolean collisionLoadChunks = false; // Paper
-     private CraftEntity bukkitEntity;
+     private org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
  
-     public CraftEntity getBukkitEntity() {
-diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/BlockCollisions.java
-+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java
-@@ -0,0 +0,0 @@ public class BlockCollisions<T> extends AbstractIterator<T> {
+     public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
+diff --git a/net/minecraft/world/level/BlockCollisions.java b/net/minecraft/world/level/BlockCollisions.java
+index fd2c338db43aad070cc32c24891b40599c544ac9..2861ea4b699d403b1245f8be5a62503d366ded65 100644
+--- a/net/minecraft/world/level/BlockCollisions.java
++++ b/net/minecraft/world/level/BlockCollisions.java
+@@ -80,16 +80,37 @@ public class BlockCollisions<T> extends AbstractIterator<T> {
      @Override
      protected T computeNext() {
          while (this.cursor.advance()) {
 -            int i = this.cursor.nextX();
--            int j = this.cursor.nextY();
--            int k = this.cursor.nextZ();
+-            int i1 = this.cursor.nextY();
+-            int i2 = this.cursor.nextZ();
 +            int i = this.cursor.nextX(); final int x = i; // Paper - OBFHELPER
-+            int j = this.cursor.nextY(); final int y = j; // Paper - OBFHELPER
-+            int k = this.cursor.nextZ(); final int z = k; // Paper - OBFHELPER
-             int l = this.cursor.getNextType();
-             if (l != 3) {
--                BlockGetter blockGetter = this.getChunk(i, k);
--                if (blockGetter != null) {
--                    this.pos.set(i, j, k);
--                    BlockState blockState = blockGetter.getBlockState(this.pos);
--                    if ((!this.onlySuffocatingBlocks || blockState.isSuffocating(blockGetter, this.pos))
++            int i1 = this.cursor.nextY(); final int y = i1; // Paper - OBFHELPER
++            int i2 = this.cursor.nextZ(); final int z = i2; // Paper - OBFHELPER
+             int nextType = this.cursor.getNextType();
+             if (nextType != 3) {
+-                BlockGetter chunk = this.getChunk(i, i2);
+-                if (chunk != null) {
+-                    this.pos.set(i, i1, i2);
+-                    BlockState blockState = chunk.getBlockState(this.pos);
+-                    if ((!this.onlySuffocatingBlocks || blockState.isSuffocating(chunk, this.pos))
 +                // Paper start - ensure we don't load chunks
 +                // BlockGetter blockGetter = this.getChunk(i, k);
 +                if (true) {
-+                    final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null;
-+                    boolean far = source != null && io.papermc.paper.util.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14;
++                    @Nullable final Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null;
++                    final boolean far = source != null && io.papermc.paper.util.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14;
 +                    this.pos.set(x, y, z);
 +                    BlockState blockState;
 +                    if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) {
@@ -72,25 +73,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    }
 +                    if (true // onlySuffocatingBlocks is only true on the client, so we don't care about it here
 +                    // Paper end - ensure we don't load chunks
-                         && (l != 1 || blockState.hasLargeCollisionShape())
-                         && (l != 2 || blockState.is(Blocks.MOVING_PISTON))) {
-                         VoxelShape voxelShape = this.context.getCollisionShape(blockState, this.collisionGetter, this.pos);
-diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java
-+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java
-@@ -0,0 +0,0 @@ public interface CollisionGetter extends BlockGetter {
+                         && (nextType != 1 || blockState.hasLargeCollisionShape())
+                         && (nextType != 2 || blockState.is(Blocks.MOVING_PISTON))) {
+                         VoxelShape collisionShape = this.context.getCollisionShape(blockState, this.collisionGetter, this.pos);
+diff --git a/net/minecraft/world/level/CollisionGetter.java b/net/minecraft/world/level/CollisionGetter.java
+index cb54c3aadd8f3c719d3f7ef1fda4aa517919b7c3..844f76a38884e823a558fe59c421ffd4711f80b4 100644
+--- a/net/minecraft/world/level/CollisionGetter.java
++++ b/net/minecraft/world/level/CollisionGetter.java
+@@ -50,11 +50,13 @@ public interface CollisionGetter extends BlockGetter {
      }
  
-     default boolean noCollision(@Nullable Entity entity, AABB box, boolean checkFluid) {
--        for (VoxelShape voxelShape : checkFluid ? this.getBlockAndLiquidCollisions(entity, box) : this.getBlockCollisions(entity, box)) {
+     default boolean noCollision(@Nullable Entity entity, AABB collisionBox, boolean checkLiquid) {
 +        try { if (entity != null) entity.collisionLoadChunks = true; // Paper
-+            for (VoxelShape voxelShape : checkFluid ? this.getBlockAndLiquidCollisions(entity, box) : this.getBlockCollisions(entity, box)) {
+         for (VoxelShape voxelShape : checkLiquid ? this.getBlockAndLiquidCollisions(entity, collisionBox) : this.getBlockCollisions(entity, collisionBox)) {
              if (!voxelShape.isEmpty()) {
                  return false;
              }
          }
 +        } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
  
-         if (!this.getEntityCollisions(entity, box).isEmpty()) {
+         if (!this.getEntityCollisions(entity, collisionBox).isEmpty()) {
              return false;
diff --git a/paper-server/src/main/java/org/spigotmc/ActivationRange.java b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
index d2d43209ac..554d80ddbf 100644
--- a/paper-server/src/main/java/org/spigotmc/ActivationRange.java
+++ b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
@@ -1,14 +1,22 @@
 package org.spigotmc;
 
+import net.minecraft.core.BlockPos;
 import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerChunkCache;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.entity.FlyingMob;
 import net.minecraft.world.entity.LightningBolt;
 import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.Mob;
 import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.Brain;
 import net.minecraft.world.entity.ambient.AmbientCreature;
 import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.animal.Bee;
 import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.animal.WaterAnimal;
+import net.minecraft.world.entity.animal.horse.Llama;
 import net.minecraft.world.entity.boss.EnderDragonPart;
 import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
 import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
@@ -16,12 +24,13 @@ import net.minecraft.world.entity.boss.wither.WitherBoss;
 import net.minecraft.world.entity.item.ItemEntity;
 import net.minecraft.world.entity.item.PrimedTnt;
 import net.minecraft.world.entity.monster.Creeper;
-import net.minecraft.world.entity.monster.Monster;
-import net.minecraft.world.entity.monster.Slime;
+import net.minecraft.world.entity.monster.Enemy;
+import net.minecraft.world.entity.monster.Pillager;
 import net.minecraft.world.entity.npc.Villager;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.entity.projectile.AbstractArrow;
 import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
+import net.minecraft.world.entity.projectile.EyeOfEnder;
 import net.minecraft.world.entity.projectile.FireworkRocketEntity;
 import net.minecraft.world.entity.projectile.ThrowableProjectile;
 import net.minecraft.world.entity.projectile.ThrownTrident;
@@ -45,6 +54,43 @@ public final class ActivationRange {
 
         AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0);
     }
+    // Paper start
+
+    static net.minecraft.world.entity.schedule.Activity[] VILLAGER_PANIC_IMMUNITIES = {
+        net.minecraft.world.entity.schedule.Activity.HIDE,
+        net.minecraft.world.entity.schedule.Activity.PRE_RAID,
+        net.minecraft.world.entity.schedule.Activity.RAID,
+        net.minecraft.world.entity.schedule.Activity.PANIC
+    };
+
+    private static int checkInactiveWakeup(final Entity entity) {
+        final Level world = entity.level();
+        final SpigotWorldConfig config = world.spigotConfig;
+        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+        if (entity.activationType == ActivationType.VILLAGER) {
+            if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
+                world.wakeupInactiveRemainingVillagers--;
+                return config.wakeUpInactiveVillagersFor;
+            }
+        } else if (entity.activationType == ActivationType.ANIMAL) {
+            if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
+                world.wakeupInactiveRemainingAnimals--;
+                return config.wakeUpInactiveAnimalsFor;
+            }
+        } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
+            if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
+                world.wakeupInactiveRemainingFlying--;
+                return config.wakeUpInactiveFlyingFor;
+            }
+        } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
+            if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
+                world.wakeupInactiveRemainingMonsters--;
+                return config.wakeUpInactiveMonstersFor;
+            }
+        }
+        return -1;
+    }
+    // Paper end
 
     static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0);
 
@@ -56,9 +102,18 @@ public final class ActivationRange {
      * @return group id
      */
     public static ActivationType initializeEntityActivationType(final Entity entity) {
+        if (entity instanceof WaterAnimal) {
+            return ActivationType.WATER;
+        } // Paper
+        else if (entity instanceof Villager) {
+            return ActivationType.VILLAGER;
+        } // Paper
+        else if (entity instanceof FlyingMob && entity instanceof Enemy) {
+            return ActivationType.FLYING_MONSTER;
+        } // Paper - doing & Monster incase Flying no longer includes monster in future
         if (entity instanceof Raider) {
             return ActivationType.RAIDER;
-        } else if (entity instanceof Monster || entity instanceof Slime) {
+        } else if (entity instanceof Enemy) { // Paper - correct monster check
             return ActivationType.MONSTER;
         } else if (entity instanceof PathfinderMob || entity instanceof AmbientCreature) {
             return ActivationType.ANIMAL;
@@ -79,6 +134,10 @@ public final class ActivationRange {
             || (entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0)
             || (entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0)
             || (entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0)
+            || (entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0) // Paper
+            || (entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0) // Paper
+            || (entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0) // Paper
+            || entity instanceof EyeOfEnder // Paper
             || entity instanceof Player
             || entity instanceof ThrowableProjectile
             || entity instanceof EnderDragon
@@ -106,10 +165,25 @@ public final class ActivationRange {
         final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
         final int animalActivationRange = world.spigotConfig.animalActivationRange;
         final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
+        // Paper start
+        final int waterActivationRange = world.spigotConfig.waterActivationRange;
+        final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
+        final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
+        world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
+        world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
+        world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
+        world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
+        final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource();
+        // Paper end
 
         int maxRange = Math.max(monsterActivationRange, animalActivationRange);
         maxRange = Math.max(maxRange, raiderActivationRange);
         maxRange = Math.max(maxRange, miscActivationRange);
+        // Paper start
+        maxRange = Math.max(maxRange, flyingActivationRange);
+        maxRange = Math.max(maxRange, waterActivationRange);
+        maxRange = Math.max(maxRange, villagerActivationRange);
+        // Paper end
         maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange);
 
         for (final Player player : world.players()) {
@@ -118,13 +192,30 @@ public final class ActivationRange {
                 continue;
             }
 
-            ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, 256, maxRange);
-            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, 256, miscActivationRange);
-            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, 256, raiderActivationRange);
-            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, 256, animalActivationRange);
-            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, 256, monsterActivationRange);
+            // Paper start
+            final int worldHeight = world.getHeight();
+            ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
+            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
+            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange);
+            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange);
+            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange);
+            ActivationType.WATER.boundingBox = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange);
+            ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange);
+            ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange);
+            // Paper end
 
-            world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity);
+            // Paper start
+            final java.util.List<Entity> entities = world.getEntities((Entity) null, ActivationRange.maxBB, null);
+            final boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking
+            for (final Entity entity : entities) {
+                // Paper start - Configurable marker ticking
+                if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) {
+                    continue;
+                }
+                // Paper end - Configurable marker ticking
+                ActivationRange.activateEntity(entity);
+            }
+            // Paper end
         }
     }
 
@@ -152,43 +243,102 @@ public final class ActivationRange {
      * @param entity
      * @return
      */
-    public static boolean checkEntityImmunities(final Entity entity) {
-        // quick checks.
-        if (entity.isInWater() || entity.getRemainingFireTicks() > 0) {
-            return true;
+    public static int checkEntityImmunities(final Entity entity) { // Paper - return # of ticks to get immunity
+        // Paper start
+        final SpigotWorldConfig config = entity.level().spigotConfig;
+        final int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
+        if (inactiveWakeUpImmunity > -1) {
+            return inactiveWakeUpImmunity;
         }
-        if (!(entity instanceof final AbstractArrow abstractArrow)) {
-            if (!entity.onGround() || !entity.getPassengers().isEmpty() || entity.isPassenger()) {
-                return true;
+        if (entity.getRemainingFireTicks() > 0) {
+            return 2;
+        }
+        if (entity.activatedImmunityTick >= MinecraftServer.currentTick) {
+            return 1;
+        }
+        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
+        // Paper end
+        // quick checks.
+        if ((entity.activationType != ActivationType.WATER && entity.isInWater() && entity.isPushedByFluid())) // Paper
+        {
+            return 100; // Paper
+        }
+        // Paper start
+        if (!entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D) {
+            return 100;
+        }
+        // Paper end
+        if (!(entity instanceof final AbstractArrow arrow)) {
+            if ((!entity.onGround() && !(entity instanceof FlyingMob))) { // Paper - remove passengers logic
+                return 10; // Paper
             }
-        } else if (!abstractArrow.isInGround()) {
-            return true;
+        } else if (!arrow.isInGround()) {
+            return 1; // Paper
         }
         // special cases.
         if (entity instanceof final LivingEntity living) {
-            if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || !living.activeEffects.isEmpty()) {
-                return true;
+            if (living.onClimbable() || living.jumping || living.hurtTime > 0 || !living.activeEffects.isEmpty() || living.isFreezing()) { // Paper
+                return 1; // Paper
             }
-            if (entity instanceof final PathfinderMob pathfinderMob && pathfinderMob.getTarget() != null) {
-                return true;
+            if (entity instanceof final Mob mob && mob.getTarget() != null) { // Paper
+                return 20; // Paper
             }
-            if (entity instanceof final Villager villager && villager.canBreed()) {
-                return true;
+            // Paper start
+            if (entity instanceof final Bee bee) {
+                final BlockPos movingTarget = bee.getMovingTarget();
+                if (bee.isAngry() ||
+                    (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) ||
+                    (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget))
+                ) {
+                    return 20;
+                }
             }
+            if (entity instanceof final Villager villager) {
+                final Brain<Villager> behaviorController = villager.getBrain();
+
+                if (config.villagersActiveForPanic) {
+                    for (final net.minecraft.world.entity.schedule.Activity activity : VILLAGER_PANIC_IMMUNITIES) {
+                        if (behaviorController.isActive(activity)) {
+                            return 20 * 5;
+                        }
+                    }
+                }
+
+                if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) {
+                    if (behaviorController.isActive(net.minecraft.world.entity.schedule.Activity.WORK)) {
+                        return config.villagersWorkImmunityFor;
+                    }
+                }
+            }
+            if (entity instanceof final Llama llama && llama.inCaravan()) {
+                return 1;
+            }
+            // Paper end
             if (entity instanceof final Animal animal) {
                 if (animal.isBaby() || animal.isInLove()) {
-                    return true;
+                    return 5; // Paper
                 }
                 if (entity instanceof final Sheep sheep && sheep.isSheared()) {
-                    return true;
+                    return 1; // Paper
                 }
             }
             if (entity instanceof final Creeper creeper && creeper.isIgnited()) { // isExplosive
-                return true;
+                return 20; // Paper
             }
+            // Paper start
+            if (entity instanceof final Mob mob && mob.targetSelector.hasTasks()) {
+                return 0;
+            }
+            if (entity instanceof final Pillager pillager) {
+                // TODO:?
+            }
+            // Paper end
         }
         // SPIGOT-6644: Otherwise the target refresh tick will be missed
-        return entity instanceof ExperienceOrb;
+        if (entity instanceof ExperienceOrb) {
+            return 20; // Paper
+        }
+        return -1; // Paper
     }
 
     /**
@@ -202,17 +352,32 @@ public final class ActivationRange {
         if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick
             return true;
         }
+        // Paper start - special case always immunities
+        // immunize brand new entities, dead entities, and portal scenarios
+        if (entity.defaultActivationState || entity.tickCount < 20 * 10 || !entity.isAlive() || (entity.portalProcess != null && !entity.portalProcess.hasExpired()) || entity.portalCooldown > 0) {
+            return true;
+        }
+        // immunize leashed entities
+        if (entity instanceof final Mob mob && mob.getLeashHolder() instanceof Player) {
+            return true;
+        }
+        // Paper end
 
-        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
+        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
+        entity.isTemporarilyActive = false; // Paper
 
         // Should this entity tick?
         if (!isActive) {
             if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
                 // Check immunities every 20 ticks.
-                if (ActivationRange.checkEntityImmunities(entity)) {
-                    // Triggered some sort of immunity, give 20 full ticks before we check again.
-                    entity.activatedTick = MinecraftServer.currentTick + 20;
+                // Paper start
+                final int immunity = checkEntityImmunities(entity);
+                if (immunity >= 0) {
+                    entity.activatedTick = MinecraftServer.currentTick + immunity;
+                } else {
+                    entity.isTemporarilyActive = true;
                 }
+                // Paper end
                 isActive = true;
             }
         }
diff --git a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 2b26324613..2c408fa4ab 100644
--- a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -211,14 +211,60 @@ public class SpigotWorldConfig
     public int monsterActivationRange = 32;
     public int raiderActivationRange = 64;
     public int miscActivationRange = 16;
+    // Paper start
+    public int flyingMonsterActivationRange = 32;
+    public int waterActivationRange = 16;
+    public int villagerActivationRange = 32;
+    public int wakeUpInactiveAnimals = 4;
+    public int wakeUpInactiveAnimalsEvery = 60*20;
+    public int wakeUpInactiveAnimalsFor = 5*20;
+    public int wakeUpInactiveMonsters = 8;
+    public int wakeUpInactiveMonstersEvery = 20*20;
+    public int wakeUpInactiveMonstersFor = 5*20;
+    public int wakeUpInactiveVillagers = 4;
+    public int wakeUpInactiveVillagersEvery = 30*20;
+    public int wakeUpInactiveVillagersFor = 5*20;
+    public int wakeUpInactiveFlying = 8;
+    public int wakeUpInactiveFlyingEvery = 10*20;
+    public int wakeUpInactiveFlyingFor = 5*20;
+    public int villagersWorkImmunityAfter = 5*20;
+    public int villagersWorkImmunityFor = 20;
+    public boolean villagersActiveForPanic = true;
+    // Paper end
     public boolean tickInactiveVillagers = true;
     public boolean ignoreSpectatorActivation = false;
     private void activationRange()
     {
+        boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper
         this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange );
         this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange );
         this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange );
         this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange );
+        // Paper start
+        this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange );
+        this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange );
+        this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange );
+
+        this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals);
+        this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery);
+        this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor);
+
+        this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters);
+        this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery);
+        this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor);
+
+        this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers);
+        this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery);
+        this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor);
+
+        this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying);
+        this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery);
+        this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor);
+
+        this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter );
+        this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor );
+        this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic );
+        // Paper end
         this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers );
         this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation );
         this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation );

From f8cb014d207ce40edf9195e558083cfb87387264 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 14:08:25 +0100
Subject: [PATCH 199/285] Move Aikar's EAR 1 into EAR 2 patch

---
 .../1043-Entity-Activation-Range-2.0.patch    | 649 ++++++++++++++++--
 .../server/level/ServerLevel.java.patch       |  42 +-
 .../world/entity/AgeableMob.java.patch        |  32 +-
 .../world/entity/AreaEffectCloud.java.patch   |  19 -
 .../minecraft/world/entity/Entity.java.patch  |  23 +-
 .../world/entity/LivingEntity.java.patch      |   9 +-
 .../world/entity/item/ItemEntity.java.patch   |  37 +-
 .../world/entity/npc/Villager.java.patch      |  18 -
 .../projectile/AbstractArrow.java.patch       |  12 +-
 .../FireworkRocketEntity.java.patch           |  27 -
 .../entity/activation/ActivationType.java     |  47 ++
 .../java/org/spigotmc/ActivationRange.java    | 387 -----------
 .../main/java/org/spigotmc/TrackingRange.java |   9 +-
 13 files changed, 666 insertions(+), 645 deletions(-)
 create mode 100644 paper-server/src/main/java/io/papermc/paper/entity/activation/ActivationType.java
 delete mode 100644 paper-server/src/main/java/org/spigotmc/ActivationRange.java

diff --git a/feature-patches/1043-Entity-Activation-Range-2.0.patch b/feature-patches/1043-Entity-Activation-Range-2.0.patch
index 2a9fee105b..3767bb01b0 100644
--- a/feature-patches/1043-Entity-Activation-Range-2.0.patch
+++ b/feature-patches/1043-Entity-Activation-Range-2.0.patch
@@ -13,38 +13,499 @@ Adds water Mobs to activation range config and nerfs fish
 Adds flying monsters to control ghast and phantoms
 Adds villagers as separate config
 
-
+diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..bd888ef719b9bfc93bace0b1d0fb771ac659f515
+--- /dev/null
++++ b/io/papermc/paper/entity/activation/ActivationRange.java
+@@ -0,0 +1,318 @@
++package io.papermc.paper.entity.activation;
++
++import net.minecraft.core.BlockPos;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.ExperienceOrb;
++import net.minecraft.world.entity.FlyingMob;
++import net.minecraft.world.entity.LightningBolt;
++import net.minecraft.world.entity.LivingEntity;
++import net.minecraft.world.entity.Mob;
++import net.minecraft.world.entity.ai.Brain;
++import net.minecraft.world.entity.animal.Animal;
++import net.minecraft.world.entity.animal.Bee;
++import net.minecraft.world.entity.animal.Sheep;
++import net.minecraft.world.entity.animal.horse.Llama;
++import net.minecraft.world.entity.boss.EnderDragonPart;
++import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
++import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
++import net.minecraft.world.entity.boss.wither.WitherBoss;
++import net.minecraft.world.entity.item.ItemEntity;
++import net.minecraft.world.entity.item.PrimedTnt;
++import net.minecraft.world.entity.monster.Creeper;
++import net.minecraft.world.entity.monster.Pillager;
++import net.minecraft.world.entity.npc.Villager;
++import net.minecraft.world.entity.player.Player;
++import net.minecraft.world.entity.projectile.AbstractArrow;
++import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
++import net.minecraft.world.entity.projectile.EyeOfEnder;
++import net.minecraft.world.entity.projectile.FireworkRocketEntity;
++import net.minecraft.world.entity.projectile.ThrowableProjectile;
++import net.minecraft.world.entity.projectile.ThrownTrident;
++import net.minecraft.world.entity.schedule.Activity;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.phys.AABB;
++import org.spigotmc.SpigotWorldConfig;
++
++public final class ActivationRange {
++
++    private ActivationRange() {
++    }
++
++    static Activity[] VILLAGER_PANIC_IMMUNITIES = {
++        Activity.HIDE,
++        Activity.PRE_RAID,
++        Activity.RAID,
++        Activity.PANIC
++    };
++
++    private static int checkInactiveWakeup(final Entity entity) {
++        final Level world = entity.level();
++        final SpigotWorldConfig config = world.spigotConfig;
++        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
++        if (entity.activationType == ActivationType.VILLAGER) {
++            if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
++                world.wakeupInactiveRemainingVillagers--;
++                return config.wakeUpInactiveVillagersFor;
++            }
++        } else if (entity.activationType == ActivationType.ANIMAL) {
++            if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
++                world.wakeupInactiveRemainingAnimals--;
++                return config.wakeUpInactiveAnimalsFor;
++            }
++        } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
++            if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
++                world.wakeupInactiveRemainingFlying--;
++                return config.wakeUpInactiveFlyingFor;
++            }
++        } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
++            if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
++                world.wakeupInactiveRemainingMonsters--;
++                return config.wakeUpInactiveMonstersFor;
++            }
++        }
++        return -1;
++    }
++
++    static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0);
++
++    /**
++     * These entities are excluded from Activation range checks.
++     *
++     * @param entity
++     * @param config
++     * @return boolean If it should always tick.
++     */
++    public static boolean initializeEntityActivationState(final Entity entity, final SpigotWorldConfig config) {
++        return (entity.activationType == ActivationType.MISC && config.miscActivationRange == 0)
++            || (entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0)
++            || (entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0)
++            || (entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0)
++            || (entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0)
++            || (entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0)
++            || (entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0)
++            || entity instanceof EyeOfEnder
++            || entity instanceof Player
++            || entity instanceof ThrowableProjectile
++            || entity instanceof EnderDragon
++            || entity instanceof EnderDragonPart
++            || entity instanceof WitherBoss
++            || entity instanceof AbstractHurtingProjectile
++            || entity instanceof LightningBolt
++            || entity instanceof PrimedTnt
++            || entity instanceof net.minecraft.world.entity.item.FallingBlockEntity
++            || entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart
++            || entity instanceof net.minecraft.world.entity.vehicle.AbstractBoat
++            || entity instanceof EndCrystal
++            || entity instanceof FireworkRocketEntity
++            || entity instanceof ThrownTrident;
++    }
++
++    /**
++     * Find what entities are in range of the players in the world and set
++     * active if in range.
++     *
++     * @param world
++     */
++    public static void activateEntities(final Level world) {
++        final int miscActivationRange = world.spigotConfig.miscActivationRange;
++        final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
++        final int animalActivationRange = world.spigotConfig.animalActivationRange;
++        final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
++        final int waterActivationRange = world.spigotConfig.waterActivationRange;
++        final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
++        final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
++        world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
++        world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
++        world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
++        world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
++
++        int maxRange = Math.max(monsterActivationRange, animalActivationRange);
++        maxRange = Math.max(maxRange, raiderActivationRange);
++        maxRange = Math.max(maxRange, miscActivationRange);
++        maxRange = Math.max(maxRange, flyingActivationRange);
++        maxRange = Math.max(maxRange, waterActivationRange);
++        maxRange = Math.max(maxRange, villagerActivationRange);
++        maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange);
++
++        for (final Player player : world.players()) {
++            player.activatedTick = MinecraftServer.currentTick;
++            if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) {
++                continue;
++            }
++
++            final int worldHeight = world.getHeight();
++            ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
++            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
++            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange);
++            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange);
++            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange);
++            ActivationType.WATER.boundingBox = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange);
++            ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange);
++            ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange);
++
++            final java.util.List<Entity> entities = world.getEntities((Entity) null, ActivationRange.maxBB, e -> true);
++            final boolean tickMarkers = world.paperConfig().entities.markers.tick;
++            for (final Entity entity : entities) {
++                if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) {
++                    continue;
++                }
++
++                ActivationRange.activateEntity(entity);
++            }
++        }
++    }
++
++    /**
++     * Tries to activate an entity.
++     *
++     * @param entity
++     */
++    private static void activateEntity(final Entity entity) {
++        if (MinecraftServer.currentTick > entity.activatedTick) {
++            if (entity.defaultActivationState) {
++                entity.activatedTick = MinecraftServer.currentTick;
++                return;
++            }
++            if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) {
++                entity.activatedTick = MinecraftServer.currentTick;
++            }
++        }
++    }
++
++    /**
++     * If an entity is not in range, do some more checks to see if we should
++     * give it a shot.
++     *
++     * @param entity
++     * @return
++     */
++    public static int checkEntityImmunities(final Entity entity) { // return # of ticks to get immunity
++        final SpigotWorldConfig config = entity.level().spigotConfig;
++        final int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
++        if (inactiveWakeUpImmunity > -1) {
++            return inactiveWakeUpImmunity;
++        }
++        if (entity.getRemainingFireTicks() > 0) {
++            return 2;
++        }
++        if (entity.activatedImmunityTick >= MinecraftServer.currentTick) {
++            return 1;
++        }
++        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
++        if ((entity.activationType != ActivationType.WATER && entity.isInWater() && entity.isPushedByFluid())) {
++            return 100;
++        }
++        if (!entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D) {
++            return 100;
++        }
++        if (!(entity instanceof final AbstractArrow arrow)) {
++            if ((!entity.onGround() && !(entity instanceof FlyingMob))) {
++                return 10;
++            }
++        } else if (!arrow.isInGround()) {
++            return 1;
++        }
++        // special cases.
++        if (entity instanceof final LivingEntity living) {
++            if (living.onClimbable() || living.jumping || living.hurtTime > 0 || !living.activeEffects.isEmpty() || living.isFreezing()) {
++                return 1;
++            }
++            if (entity instanceof final Mob mob && mob.getTarget() != null) {
++                return 20;
++            }
++            if (entity instanceof final Bee bee) {
++                final BlockPos movingTarget = bee.getMovingTarget();
++                if (bee.isAngry() ||
++                    (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) ||
++                    (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget))
++                ) {
++                    return 20;
++                }
++            }
++            if (entity instanceof final Villager villager) {
++                final Brain<Villager> behaviorController = villager.getBrain();
++
++                if (config.villagersActiveForPanic) {
++                    for (final Activity activity : VILLAGER_PANIC_IMMUNITIES) {
++                        if (behaviorController.isActive(activity)) {
++                            return 20 * 5;
++                        }
++                    }
++                }
++
++                if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) {
++                    if (behaviorController.isActive(Activity.WORK)) {
++                        return config.villagersWorkImmunityFor;
++                    }
++                }
++            }
++            if (entity instanceof final Llama llama && llama.inCaravan()) {
++                return 1;
++            }
++            if (entity instanceof final Animal animal) {
++                if (animal.isBaby() || animal.isInLove()) {
++                    return 5;
++                }
++                if (entity instanceof final Sheep sheep && sheep.isSheared()) {
++                    return 1;
++                }
++            }
++            if (entity instanceof final Creeper creeper && creeper.isIgnited()) { // isExplosive
++                return 20;
++            }
++            if (entity instanceof final Mob mob && mob.targetSelector.hasTasks()) {
++                return 0;
++            }
++            if (entity instanceof final Pillager pillager) {
++                // TODO:?
++            }
++        }
++        // SPIGOT-6644: Otherwise the target refresh tick will be missed
++        if (entity instanceof ExperienceOrb) {
++            return 20;
++        }
++        return -1;
++    }
++
++    /**
++     * Checks if the entity is active for this tick.
++     *
++     * @param entity
++     * @return
++     */
++    public static boolean checkIfActive(final Entity entity) {
++        // Never safe to skip fireworks or item gravity
++        if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Needed for item gravity, see ItemEntity tick
++            return true;
++        }
++        // special case always immunities
++        // immunize brand-new entities, dead entities, and portal scenarios
++        if (entity.defaultActivationState || entity.tickCount < 20 * 10 || !entity.isAlive() || (entity.portalProcess != null && !entity.portalProcess.hasExpired()) || entity.portalCooldown > 0) {
++            return true;
++        }
++        // immunize leashed entities
++        if (entity instanceof final Mob mob && mob.getLeashHolder() instanceof Player) {
++            return true;
++        }
++
++        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
++        entity.isTemporarilyActive = false;
++
++        // Should this entity tick?
++        if (!isActive) {
++            if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
++                // Check immunities every 20 ticks.
++                final int immunity = checkEntityImmunities(entity);
++                if (immunity >= 0) {
++                    entity.activatedTick = MinecraftServer.currentTick + immunity;
++                } else {
++                    entity.isTemporarilyActive = true;
++                }
++                isActive = true;
++            }
++        }
++        // removed the original's dumb tick skipping for active entities
++        return isActive;
++    }
++}
+diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
+index 9bbcafa8e70f95d5ab6318211a0265acbfc76b1c..f8f145cd9614f7e38d6aa72501fe31837340a8bb 100644
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Queues;
+-import com.google.common.collect.Sets;
+ import com.google.common.collect.ImmutableList.Builder;
+ import com.mojang.datafixers.DataFixer;
+ import com.mojang.logging.LogUtils;
+@@ -19,7 +18,6 @@ import it.unimi.dsi.fastutil.longs.LongIterator;
+ import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
+ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+ import it.unimi.dsi.fastutil.longs.LongSet;
+-import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
+ import java.io.IOException;
+ import java.io.Writer;
+ import java.nio.file.Path;
+@@ -95,7 +93,6 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
+ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+ import net.minecraft.world.level.storage.LevelStorageSource;
+-import net.minecraft.world.phys.Vec3;
+ import org.apache.commons.lang3.mutable.MutableBoolean;
+ import org.slf4j.Logger;
+ 
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 1d7e9492a474c99dff372d6b57f1f195e42d5114..aa5eed48871b5ab67d18213e532271241aae9182 100644
+index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..c2d9f1e6c68f4945e3b9e5b95326e8180fa7a0fb 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -963,10 +963,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -551,6 +551,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+                 profilerFiller.pop();
+             }
+ 
++            io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
+             this.entityTickList
+                 .forEach(
+                     entity -> {
+@@ -960,16 +961,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         entity.tickCount++;
+         profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
          profilerFiller.incrementCounter("tickNonPassenger");
-         // Spigot start
-         final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
--        if (isActive) {
++        final boolean isActive = io.papermc.paper.entity.activation.ActivationRange.checkIfActive(entity); // Paper - EAR 2
 +        if (isActive) { // Paper - EAR 2
          entity.tick();
          entity.postTick(); // CraftBukkit
--        } else {entity.inactiveTick();} // Spigot end
 +        } else {entity.inactiveTick();} // Paper - EAR 2
          profilerFiller.pop();
  
          for (Entity entity1 : entity.getPassengers()) {
+-            this.tickPassenger(entity, entity1);
++            this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
+         }
+     }
+ 
+-    private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
++    private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
+         if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
+             passengerEntity.stopRiding();
+         } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
+@@ -978,12 +982,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+             ProfilerFiller profilerFiller = Profiler.get();
+             profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
+             profilerFiller.incrementCounter("tickPassenger");
++            // Paper start - EAR 2
++            if (isActive) {
+             passengerEntity.rideTick();
+             passengerEntity.postTick(); // CraftBukkit
++            } else {
++                passengerEntity.setDeltaMovement(Vec3.ZERO);
++                passengerEntity.inactiveTick();
++                // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
++                ridingEntity.positionRider(passengerEntity);
++            }
++            // Paper end - EAR 2
+             profilerFiller.pop();
+ 
+             for (Entity entity : passengerEntity.getPassengers()) {
+-                this.tickPassenger(passengerEntity, entity);
++                this.tickPassenger(passengerEntity, entity, isActive); // Paper - EAR 2
+             }
+         }
+     }
+diff --git a/net/minecraft/world/entity/AgeableMob.java b/net/minecraft/world/entity/AgeableMob.java
+index a9f01e616ef6b0d74caf57cd68eb371a4fd30fd5..179f4e4b9b1eb57f78bbb2f9fa34b11ea79b7a88 100644
+--- a/net/minecraft/world/entity/AgeableMob.java
++++ b/net/minecraft/world/entity/AgeableMob.java
+@@ -126,6 +126,23 @@ public abstract class AgeableMob extends PathfinderMob {
+         super.onSyncedDataUpdated(key);
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++        if (this.level().isClientSide || this.ageLocked) { // CraftBukkit
++            this.refreshDimensions();
++        } else {
++            int age = this.getAge();
++            if (age < 0) {
++                this.setAge(++age);
++            } else if (age > 0) {
++                this.setAge(--age);
++            }
++        }
++    }
++    // Paper end - EAR 2
++
+     @Override
+     public void aiStep() {
+         super.aiStep();
+diff --git a/net/minecraft/world/entity/AreaEffectCloud.java b/net/minecraft/world/entity/AreaEffectCloud.java
+index b4a1202a9f43525caf215d2f5c86ad92ea4f6de7..47db6ac3ef23fd0da127cfb5a4d3ba9ebd2ab54d 100644
+--- a/net/minecraft/world/entity/AreaEffectCloud.java
++++ b/net/minecraft/world/entity/AreaEffectCloud.java
+@@ -128,6 +128,16 @@ public class AreaEffectCloud extends Entity implements TraceableEntity {
+         this.duration = duration;
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++        if (this.tickCount >= this.waitTime + this.duration) {
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++        }
++    }
++    // Paper end - EAR 2
++
+     @Override
+     public void tick() {
+         super.tick();
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 0aed1e455dec32c3d53d8fb2f1047e1bf177f171..8c2441a6c7abc3a80426923c1ea42000283ee167 100644
+index 020d05bb60abec10fa37e651c17f600c883af61d..2e5f1dc15ea6a20f8cbdef97ecbf00e5d56603cf 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -383,6 +383,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-     // Spigot end
-     protected int numCollisions = 0; // Paper - Cap entity collisions
-     public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals
-+    public long activatedImmunityTick = Integer.MIN_VALUE; // Paper - EAR
-+    public boolean isTemporarilyActive; // Paper - EAR
-     public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
-     // Paper start - Entity origin API
-     @javax.annotation.Nullable
-@@ -959,6 +961,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -386,6 +386,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     public boolean fixedPose = false; // Paper - Expand Pose API
+     private final int despawnTime; // Paper - entity despawn time limit
+     public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges
++    // Paper start - EAR 2
++    public final boolean defaultActivationState;
++    public long activatedTick = Integer.MIN_VALUE;
++    public boolean isTemporarilyActive;
++    public long activatedImmunityTick = Integer.MIN_VALUE;
++
++    public void inactiveTick() {
++    }
++    // Paper end - EAR 2
+ 
+     public void setOrigin(@javax.annotation.Nonnull org.bukkit.Location location) {
+         this.origin = location.toVector();
+@@ -423,6 +432,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+         this.position = Vec3.ZERO;
+         this.blockPosition = BlockPos.ZERO;
+         this.chunkPosition = ChunkPos.ZERO;
++        // Paper start - EAR 2
++        if (level != null) {
++            this.defaultActivationState = io.papermc.paper.entity.activation.ActivationRange.initializeEntityActivationState(this, level.spigotConfig);
++        } else {
++            this.defaultActivationState = false;
++        }
++        // Paper end - EAR 2
+         SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
+         builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
+         builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
+@@ -946,6 +962,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          } else {
              this.wasOnFire = this.isOnFire();
              if (type == MoverType.PISTON) {
@@ -53,20 +514,39 @@ index 0aed1e455dec32c3d53d8fb2f1047e1bf177f171..8c2441a6c7abc3a80426923c1ea42000
                  movement = this.limitPistonMovement(movement);
                  if (movement.equals(Vec3.ZERO)) {
                      return;
-@@ -972,6 +976,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -959,6 +977,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
                  this.stuckSpeedMultiplier = Vec3.ZERO;
                  this.setDeltaMovement(Vec3.ZERO);
              }
 +            // Paper start - ignore movement changes while inactive.
 +            if (isTemporarilyActive && !(this instanceof ItemEntity) && movement == getDeltaMovement() && type == MoverType.SELF) {
 +                setDeltaMovement(Vec3.ZERO);
-+                gameprofilerfiller.pop();
++                profilerFiller.pop();
 +                return;
 +            }
 +            // Paper end
  
              movement = this.maybeBackOffFromEdge(movement, type);
              Vec3 vec3 = this.collide(movement);
+diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
+index 41ef8c24903e5efceead43796e647824a54193df..9de400977ec33e485e87cdf1cf145588527e1e10 100644
+--- a/net/minecraft/world/entity/LivingEntity.java
++++ b/net/minecraft/world/entity/LivingEntity.java
+@@ -3089,6 +3089,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
+         return false;
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++        ++this.noActionTime; // Above all the floats
++    }
++    // Paper end - EAR 2
++
+     @Override
+     public void tick() {
+         super.tick();
 diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
 index f7d69db61d1293510428ae275e8a50571dde5ddf..1ed07fd23985a6bf8cf8300f74c92b7531a79fc6 100644
 --- a/net/minecraft/world/entity/Mob.java
@@ -105,24 +585,18 @@ index 0caf50ec50f056b83a20bbc6a2fe0144593aef39..af59a700755654eb68d6bf57d0712c4a
          return this.getWalkTargetValue(pos, this.level());
      }
 diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index 88e7e245824b670652878e03a2142a13e97508fe..2730e76228b03b1ec52108f394c20fe003801b02 100644
+index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..eeba224bd575451ba6023df65ef9d9b97f7f1c71 100644
 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
 +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-@@ -22,12 +22,12 @@ public class GoalSelector {
-             return false;
-         }
-     };
-+    private int curRate; // Paper - EAR 2
+@@ -25,6 +25,7 @@ public class GoalSelector {
      private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
      private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
-     private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
-     private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
+     private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
++    private int curRate; // Paper - EAR 2
  
--
      public void addGoal(int priority, Goal goal) {
          this.availableGoals.add(new WrappedGoal(priority, goal));
-     }
-@@ -37,6 +37,22 @@ public class GoalSelector {
+@@ -35,6 +36,22 @@ public class GoalSelector {
          this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal()));
      }
  
@@ -172,20 +646,55 @@ index 789fea258d70e60d38271ebb31270562dc7eb3ab..d0ab3db7bbd2942db19f473474371b20
                              return true;
                          }
                      }
+diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
+index 1c82a41acb8717b2c56498602fd1ecbe6aa58fe5..dcbd35d6bf81d7a0621020710114887b68a7dcc6 100644
+--- a/net/minecraft/world/entity/item/ItemEntity.java
++++ b/net/minecraft/world/entity/item/ItemEntity.java
+@@ -124,6 +124,29 @@ public class ItemEntity extends Entity implements TraceableEntity {
+         return 0.04;
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        super.inactiveTick();
++        if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
++            this.pickupDelay--;
++        }
++        if (this.age != -32768) {
++            this.age++;
++        }
++
++        if (!this.level().isClientSide && this.age >= this.despawnRate) {// Paper - Alternative item-despawn-rate
++            // CraftBukkit start - fire ItemDespawnEvent
++            if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
++                this.age = 0;
++                return;
++            }
++            // CraftBukkit end
++            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
++        }
++    }
++    // Paper end - EAR 2
++
+     @Override
+     public void tick() {
+         if (this.getItem().isEmpty()) {
 diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
-index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9ce712a462 100644
+index 27568a1604d2dd5d46e836bbc25431929e218aa1..2b83262e4a13eae86df82913ce4f3121e3631a43 100644
 --- a/net/minecraft/world/entity/npc/Villager.java
 +++ b/net/minecraft/world/entity/npc/Villager.java
-@@ -269,18 +269,33 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
-     @Override
-     public void inactiveTick() {
-         // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
--        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
--            this.customServerAiStep((ServerLevel) this.level());
-+        // Paper start
+@@ -265,11 +265,35 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+         return this.assignProfessionWhenSpawned;
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
 +        if (this.getUnhappyCounter() > 0) {
 +            this.setUnhappyCounter(this.getUnhappyCounter() - 1);
-         }
++        }
 +        if (this.isEffectiveAi()) {
 +            if (this.level().spigotConfig.tickInactiveVillagers) {
 +                this.customServerAiStep(this.level().getMinecraftWorld());
@@ -194,11 +703,10 @@ index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9c
 +            }
 +        }
 +        maybeDecayGossip();
-+        // Paper end
-         super.inactiveTick();
-     }
-     // Spigot End
- 
++        super.inactiveTick();
++    }
++    // Paper end - EAR 2
++
      @Override
      protected void customServerAiStep(ServerLevel level) {
 +        // Paper start - EAR 2
@@ -213,7 +721,7 @@ index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9c
          profilerFiller.pop();
          if (this.assignProfessionWhenSpawned) {
              this.assignProfessionWhenSpawned = false;
-@@ -304,7 +319,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+@@ -293,7 +317,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
              this.lastTradedPlayer = null;
          }
  
@@ -222,7 +730,7 @@ index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9c
              Raid raidAt = level.getRaidAt(this.blockPosition());
              if (raidAt != null && raidAt.isActive() && !raidAt.isOver()) {
                  level.broadcastEntityEvent(this, (byte)42);
-@@ -314,6 +329,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+@@ -303,6 +327,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
          if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) {
              this.stopTrading();
          }
@@ -230,6 +738,53 @@ index 81a6d31d90dc139e4d99c2b8e609032e8b100b2d..0b73ad491eb6992e1d8fdd7e75264c9c
  
          super.customServerAiStep(level);
      }
+diff --git a/net/minecraft/world/entity/projectile/Arrow.java b/net/minecraft/world/entity/projectile/Arrow.java
+index c1e09e701757a300183b62d343ded03033e63aa7..56574f8ef879159edc0114da09300143a2c79a35 100644
+--- a/net/minecraft/world/entity/projectile/Arrow.java
++++ b/net/minecraft/world/entity/projectile/Arrow.java
+@@ -66,6 +66,16 @@ public class Arrow extends AbstractArrow {
+         builder.define(ID_EFFECT_COLOR, -1);
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        if (this.isInGround()) {
++            this.life++;
++        }
++        super.inactiveTick();
++    }
++    // Paper end
++
+     @Override
+     public void tick() {
+         super.tick();
+diff --git a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+index 7c0862c50b44555fb27ce7dc46f4ec95a3eb0022..774ca9e0b56fd175ae246051de762d0c4256ca58 100644
+--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+@@ -102,6 +102,21 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier {
+         return super.shouldRender(x, y, z) && !this.isAttachedToEntity();
+     }
+ 
++    // Paper start - EAR 2
++    @Override
++    public void inactiveTick() {
++        this.life++;
++        if (this.life > this.lifetime && this.level() instanceof ServerLevel serverLevel) {
++            // CraftBukkit start
++            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++                this.explode(serverLevel);
++            }
++            // CraftBukkit end
++        }
++        super.inactiveTick();
++    }
++    // Paper end - EAR 2
++
+     @Override
+     public void tick() {
+         super.tick();
 diff --git a/net/minecraft/world/entity/vehicle/MinecartHopper.java b/net/minecraft/world/entity/vehicle/MinecartHopper.java
 index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c116aee9b35 100644
 --- a/net/minecraft/world/entity/vehicle/MinecartHopper.java
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index daa7e73901..0eaf3ea736 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -291,14 +291,6 @@
          if (flag) {
              this.resetEmptyTime();
          }
-@@ -385,6 +_,7 @@
-                 profilerFiller.pop();
-             }
- 
-+            org.spigotmc.ActivationRange.activateEntities(this); // Spigot
-             this.entityTickList
-                 .forEach(
-                     entity -> {
 @@ -461,12 +_,12 @@
          int minBlockZ = pos.getMinBlockZ();
          ProfilerFiller profilerFiller = Profiler.get();
@@ -464,52 +456,22 @@
      }
  
      public void resetEmptyTime() {
-@@ -752,15 +_,20 @@
-         entity.tickCount++;
+@@ -753,6 +_,7 @@
          profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
          profilerFiller.incrementCounter("tickNonPassenger");
-+        // Spigot start
-+        final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); // Paper - EAR 2
-+        if (isActive) {
          entity.tick();
 +        entity.postTick(); // CraftBukkit
-+        } else {entity.inactiveTick();} // Spigot end
          profilerFiller.pop();
  
          for (Entity entity1 : entity.getPassengers()) {
--            this.tickPassenger(entity, entity1);
-+            this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
-         }
-     }
- 
--    private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
-+    private void tickPassenger(Entity ridingEntity, Entity passengerEntity, boolean isActive) { // Paper - EAR 2
-         if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
-             passengerEntity.stopRiding();
-         } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
-@@ -769,11 +_,21 @@
-             ProfilerFiller profilerFiller = Profiler.get();
+@@ -770,6 +_,7 @@
              profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
              profilerFiller.incrementCounter("tickPassenger");
-+            // Paper start - EAR 2
-+            if (isActive) {
              passengerEntity.rideTick();
 +            passengerEntity.postTick(); // CraftBukkit
-+            } else {
-+            passengerEntity.setDeltaMovement(Vec3.ZERO);
-+            passengerEntity.inactiveTick();
-+            // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
-+            ridingEntity.positionRider(passengerEntity);
-+            // Paper end - EAR 2
-+            }
              profilerFiller.pop();
  
              for (Entity entity : passengerEntity.getPassengers()) {
--                this.tickPassenger(passengerEntity, entity);
-+                this.tickPassenger(passengerEntity, entity, isActive); // Paper - EAR 2
-             }
-         }
-     }
 @@ -786,6 +_,7 @@
      public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
          ServerChunkCache chunkSource = this.getChunkSource();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
index dcf72a4df6..a0e42a63dd 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AgeableMob.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/AgeableMob.java
 +++ b/net/minecraft/world/entity/AgeableMob.java
-@@ -20,11 +_,37 @@
+@@ -20,6 +_,7 @@
      protected int age;
      protected int forcedAge;
      protected int forcedAgeTimer;
@@ -8,36 +8,6 @@
  
      protected AgeableMob(EntityType<? extends AgeableMob> entityType, Level level) {
          super(entityType, level);
-     }
- 
-+    // Spigot start
-+    @Override
-+    public void inactiveTick()
-+    {
-+        super.inactiveTick();
-+        if ( this.level().isClientSide || this.ageLocked )
-+        { // CraftBukkit
-+            this.refreshDimensions();
-+        } else
-+        {
-+            int i = this.getAge();
-+
-+            if ( i < 0 )
-+            {
-+                ++i;
-+                this.setAge( i );
-+            } else if ( i > 0 )
-+            {
-+                --i;
-+                this.setAge( i );
-+            }
-+        }
-+    }
-+    // Spigot end
-+
-     @Override
-     public SpawnGroupData finalizeSpawn(
-         ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
 @@ -66,6 +_,7 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
index 1f6b41bf6d..adddf98077 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -9,25 +9,6 @@
      @Nullable
      public UUID ownerUUID;
  
-@@ -128,6 +_,18 @@
-         this.duration = duration;
-     }
- 
-+    // Spigot start - copied from below
-+    @Override
-+    public void inactiveTick() {
-+        super.inactiveTick();
-+
-+        if (this.tickCount >= this.waitTime + this.duration) {
-+            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-+            return;
-+        }
-+    }
-+    // Spigot end
-+
-     @Override
-     public void tick() {
-         super.tick();
 @@ -177,7 +_,7 @@
  
      private void serverTick(ServerLevel level) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 7bd2ceef50..b77ca19e9f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -133,7 +133,7 @@
      private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
      private long pistonDeltasGameTime;
      private EntityDimensions dimensions;
-@@ -250,6 +_,68 @@
+@@ -250,6 +_,63 @@
      private final List<Entity.Movement> movementThisTick = new ArrayList<>();
      private final Set<BlockState> blocksInside = new ReferenceArraySet<>();
      private final LongSet visitedBlocks = new LongOpenHashSet();
@@ -153,12 +153,6 @@
 +    // Marks an entity, that it was removed by a plugin via Entity#remove
 +    // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
 +    public boolean pluginRemoved = false;
-+    // Spigot start
-+    public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
-+    public final boolean defaultActivationState;
-+    public long activatedTick = Integer.MIN_VALUE;
-+    public void inactiveTick() { }
-+    // Spigot end
 +    protected int numCollisions = 0; // Paper - Cap entity collisions
 +    public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals
 +    public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
@@ -170,6 +164,7 @@
 +    public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
 +    public boolean fixedPose = false; // Paper - Expand Pose API
 +    private final int despawnTime; // Paper - entity despawn time limit
++    public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges
 +
 +    public void setOrigin(@javax.annotation.Nonnull org.bukkit.Location location) {
 +        this.origin = location.toVector();
@@ -202,20 +197,6 @@
  
      public Entity(EntityType<?> entityType, Level level) {
          this.type = entityType;
-@@ -258,6 +_,13 @@
-         this.position = Vec3.ZERO;
-         this.blockPosition = BlockPos.ZERO;
-         this.chunkPosition = ChunkPos.ZERO;
-+        // Spigot start
-+        if (level != null) {
-+            this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, level.spigotConfig);
-+        } else {
-+            this.defaultActivationState = false;
-+        }
-+        // Spigot end
-         SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
-         builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
-         builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
 @@ -271,6 +_,7 @@
          this.entityData = builder.build();
          this.setPos(0.0, 0.0, 0.0);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index 6f3c797b1f..6a08fbc658 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -39,7 +39,7 @@
  public abstract class LivingEntity extends Entity implements Attackable {
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final String TAG_ACTIVE_EFFECTS = "active_effects";
-@@ -266,11 +_,36 @@
+@@ -266,11 +_,29 @@
          EquipmentSlot.class
      );
      protected float appliedScale = 1.0F;
@@ -59,13 +59,6 @@
 +        return this.getYHeadRot();
 +    }
 +    // CraftBukkit end
-+    // Spigot start
-+    public void inactiveTick()
-+    {
-+        super.inactiveTick();
-+        ++this.noActionTime; // Above all the floats
-+    }
-+    // Spigot end
  
      protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
          super(entityType, level);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 7e9f5a0f27..8700616d07 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -51,15 +51,12 @@
                      f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F;
                  }
  
-@@ -184,11 +_,40 @@
+@@ -184,8 +_,14 @@
                  }
              }
  
 -            if (!this.level().isClientSide && this.age >= 6000) {
 -                this.discard();
--            }
--        }
--    }
 +            if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate
 +                // CraftBukkit start - fire ItemDespawnEvent
 +                if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
@@ -68,35 +65,9 @@
 +                }
 +                // CraftBukkit end
 +                this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-+            }
-+        }
-+    }
-+
-+    // Spigot start - copied from above
-+    @Override
-+    public void inactiveTick() {
-+        if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
-+            --this.pickupDelay;
-+        }
-+        if (this.age != -32768) {
-+            ++this.age;
-+        }
-+
-+        if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - Alternative item-despawn-rate
-+            // CraftBukkit start - fire ItemDespawnEvent
-+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
-+                this.age = 0;
-+                return;
-+            }
-+            // CraftBukkit end
-+            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
-+        }
-+    }
-+    // Spigot end
-+
- 
-     @Override
-     public BlockPos getBlockPosBelowThatAffectsMyMovement() {
+             }
+         }
+     }
 @@ -210,9 +_,18 @@
  
      private void mergeWithNeighbours() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
index 7c58286a0c..a3b4672db7 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch
@@ -15,24 +15,6 @@
  public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA = SynchedEntityData.defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
-@@ -257,6 +_,17 @@
-         return this.assignProfessionWhenSpawned;
-     }
- 
-+    // Spigot Start
-+    @Override
-+    public void inactiveTick() {
-+        // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :(
-+        if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) {
-+            this.customServerAiStep((ServerLevel) this.level());
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
-+
-     @Override
-     protected void customServerAiStep(ServerLevel level) {
-         ProfilerFiller profilerFiller = Profiler.get();
 @@ -275,7 +_,7 @@
                      this.increaseProfessionLevelOnUpdate = false;
                  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
index 23dc1ce9bf..cf61cc49d2 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -8,7 +8,7 @@
  import net.minecraft.world.entity.player.Player;
  import net.minecraft.world.item.Item;
  import net.minecraft.world.item.ItemStack;
-@@ -63,16 +_,26 @@
+@@ -63,16 +_,16 @@
      protected int inGroundTime;
      public AbstractArrow.Pickup pickup = AbstractArrow.Pickup.DISALLOWED;
      public int shakeTime;
@@ -26,16 +26,6 @@
      @Nullable
 -    private ItemStack firedFromWeapon = null;
 +    public ItemStack firedFromWeapon = null; // Paper - private -> public
-+
-+    // Spigot Start
-+    @Override
-+    public void inactiveTick() {
-+        if (this.isInGround()) {
-+            this.life += 1;
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
  
      protected AbstractArrow(EntityType<? extends AbstractArrow> entityType, Level level) {
          super(entityType, level);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
index fb50fc5183..f0049b5ab4 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
@@ -8,33 +8,6 @@
  
      public FireworkRocketEntity(EntityType<? extends FireworkRocketEntity> entityType, Level level) {
          super(entityType, level);
-@@ -84,6 +_,26 @@
-         this.setOwner(shooter);
-     }
- 
-+    // Spigot Start - copied from tick
-+    @Override
-+    public void inactiveTick() {
-+        this.life += 1;
-+
-+        if (this.life > this.lifetime) {
-+            Level world = this.level();
-+
-+            if (world instanceof ServerLevel serverLevel) {
-+                // CraftBukkit start
-+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
-+                    this.explode(serverLevel);
-+                }
-+                // CraftBukkit end
-+            }
-+        }
-+        super.inactiveTick();
-+    }
-+    // Spigot End
-+
-     @Override
-     protected void defineSynchedData(SynchedEntityData.Builder builder) {
-         builder.define(DATA_ID_FIREWORKS_ITEM, getDefaultItem());
 @@ -158,7 +_,7 @@
          }
  
diff --git a/paper-server/src/main/java/io/papermc/paper/entity/activation/ActivationType.java b/paper-server/src/main/java/io/papermc/paper/entity/activation/ActivationType.java
new file mode 100644
index 0000000000..cd43845a08
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/entity/activation/ActivationType.java
@@ -0,0 +1,47 @@
+package io.papermc.paper.entity.activation;
+
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.FlyingMob;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ambient.AmbientCreature;
+import net.minecraft.world.entity.animal.WaterAnimal;
+import net.minecraft.world.entity.monster.Enemy;
+import net.minecraft.world.entity.npc.Villager;
+import net.minecraft.world.entity.raid.Raider;
+import net.minecraft.world.phys.AABB;
+
+public enum ActivationType {
+    WATER,
+    FLYING_MONSTER,
+    VILLAGER,
+    MONSTER,
+    ANIMAL,
+    RAIDER,
+    MISC;
+
+    AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0);
+
+    /**
+     * Returns the activation type for the given entity.
+     *
+     * @param entity entity to get the activation type for
+     * @return activation type
+     */
+    public static ActivationType activationTypeFor(final Entity entity) {
+        if (entity instanceof WaterAnimal) {
+            return ActivationType.WATER;
+        } else if (entity instanceof Villager) {
+            return ActivationType.VILLAGER;
+        } else if (entity instanceof FlyingMob && entity instanceof Enemy) {
+            return ActivationType.FLYING_MONSTER;
+        } else if (entity instanceof Raider) {
+            return ActivationType.RAIDER;
+        } else if (entity instanceof Enemy) {
+            return ActivationType.MONSTER;
+        } else if (entity instanceof PathfinderMob || entity instanceof AmbientCreature) {
+            return ActivationType.ANIMAL;
+        } else {
+            return ActivationType.MISC;
+        }
+    }
+}
diff --git a/paper-server/src/main/java/org/spigotmc/ActivationRange.java b/paper-server/src/main/java/org/spigotmc/ActivationRange.java
deleted file mode 100644
index 554d80ddbf..0000000000
--- a/paper-server/src/main/java/org/spigotmc/ActivationRange.java
+++ /dev/null
@@ -1,387 +0,0 @@
-package org.spigotmc;
-
-import net.minecraft.core.BlockPos;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerChunkCache;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.entity.ExperienceOrb;
-import net.minecraft.world.entity.FlyingMob;
-import net.minecraft.world.entity.LightningBolt;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.entity.Mob;
-import net.minecraft.world.entity.PathfinderMob;
-import net.minecraft.world.entity.ai.Brain;
-import net.minecraft.world.entity.ambient.AmbientCreature;
-import net.minecraft.world.entity.animal.Animal;
-import net.minecraft.world.entity.animal.Bee;
-import net.minecraft.world.entity.animal.Sheep;
-import net.minecraft.world.entity.animal.WaterAnimal;
-import net.minecraft.world.entity.animal.horse.Llama;
-import net.minecraft.world.entity.boss.EnderDragonPart;
-import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
-import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
-import net.minecraft.world.entity.boss.wither.WitherBoss;
-import net.minecraft.world.entity.item.ItemEntity;
-import net.minecraft.world.entity.item.PrimedTnt;
-import net.minecraft.world.entity.monster.Creeper;
-import net.minecraft.world.entity.monster.Enemy;
-import net.minecraft.world.entity.monster.Pillager;
-import net.minecraft.world.entity.npc.Villager;
-import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.entity.projectile.AbstractArrow;
-import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
-import net.minecraft.world.entity.projectile.EyeOfEnder;
-import net.minecraft.world.entity.projectile.FireworkRocketEntity;
-import net.minecraft.world.entity.projectile.ThrowableProjectile;
-import net.minecraft.world.entity.projectile.ThrownTrident;
-import net.minecraft.world.entity.raid.Raider;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.phys.AABB;
-
-public final class ActivationRange {
-
-    private ActivationRange() {
-    }
-
-    public enum ActivationType {
-        WATER, // Paper
-        FLYING_MONSTER, // Paper
-        VILLAGER, // Paper
-        MONSTER,
-        ANIMAL,
-        RAIDER,
-        MISC;
-
-        AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0);
-    }
-    // Paper start
-
-    static net.minecraft.world.entity.schedule.Activity[] VILLAGER_PANIC_IMMUNITIES = {
-        net.minecraft.world.entity.schedule.Activity.HIDE,
-        net.minecraft.world.entity.schedule.Activity.PRE_RAID,
-        net.minecraft.world.entity.schedule.Activity.RAID,
-        net.minecraft.world.entity.schedule.Activity.PANIC
-    };
-
-    private static int checkInactiveWakeup(final Entity entity) {
-        final Level world = entity.level();
-        final SpigotWorldConfig config = world.spigotConfig;
-        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
-        if (entity.activationType == ActivationType.VILLAGER) {
-            if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) {
-                world.wakeupInactiveRemainingVillagers--;
-                return config.wakeUpInactiveVillagersFor;
-            }
-        } else if (entity.activationType == ActivationType.ANIMAL) {
-            if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) {
-                world.wakeupInactiveRemainingAnimals--;
-                return config.wakeUpInactiveAnimalsFor;
-            }
-        } else if (entity.activationType == ActivationType.FLYING_MONSTER) {
-            if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) {
-                world.wakeupInactiveRemainingFlying--;
-                return config.wakeUpInactiveFlyingFor;
-            }
-        } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) {
-            if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) {
-                world.wakeupInactiveRemainingMonsters--;
-                return config.wakeUpInactiveMonstersFor;
-            }
-        }
-        return -1;
-    }
-    // Paper end
-
-    static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0);
-
-    /**
-     * Initializes an entities type on construction to specify what group this
-     * entity is in for activation ranges.
-     *
-     * @param entity
-     * @return group id
-     */
-    public static ActivationType initializeEntityActivationType(final Entity entity) {
-        if (entity instanceof WaterAnimal) {
-            return ActivationType.WATER;
-        } // Paper
-        else if (entity instanceof Villager) {
-            return ActivationType.VILLAGER;
-        } // Paper
-        else if (entity instanceof FlyingMob && entity instanceof Enemy) {
-            return ActivationType.FLYING_MONSTER;
-        } // Paper - doing & Monster incase Flying no longer includes monster in future
-        if (entity instanceof Raider) {
-            return ActivationType.RAIDER;
-        } else if (entity instanceof Enemy) { // Paper - correct monster check
-            return ActivationType.MONSTER;
-        } else if (entity instanceof PathfinderMob || entity instanceof AmbientCreature) {
-            return ActivationType.ANIMAL;
-        } else {
-            return ActivationType.MISC;
-        }
-    }
-
-    /**
-     * These entities are excluded from Activation range checks.
-     *
-     * @param entity
-     * @param config
-     * @return boolean If it should always tick.
-     */
-    public static boolean initializeEntityActivationState(final Entity entity, final SpigotWorldConfig config) {
-        return (entity.activationType == ActivationType.MISC && config.miscActivationRange == 0)
-            || (entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0)
-            || (entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0)
-            || (entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0)
-            || (entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0) // Paper
-            || (entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0) // Paper
-            || (entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0) // Paper
-            || entity instanceof EyeOfEnder // Paper
-            || entity instanceof Player
-            || entity instanceof ThrowableProjectile
-            || entity instanceof EnderDragon
-            || entity instanceof EnderDragonPart
-            || entity instanceof WitherBoss
-            || entity instanceof AbstractHurtingProjectile
-            || entity instanceof LightningBolt
-            || entity instanceof PrimedTnt
-            || entity instanceof net.minecraft.world.entity.item.FallingBlockEntity // Paper - Always tick falling blocks
-            || entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart // Paper
-            || entity instanceof net.minecraft.world.entity.vehicle.AbstractBoat // Paper
-            || entity instanceof EndCrystal
-            || entity instanceof FireworkRocketEntity
-            || entity instanceof ThrownTrident;
-    }
-
-    /**
-     * Find what entities are in range of the players in the world and set
-     * active if in range.
-     *
-     * @param world
-     */
-    public static void activateEntities(final Level world) {
-        final int miscActivationRange = world.spigotConfig.miscActivationRange;
-        final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
-        final int animalActivationRange = world.spigotConfig.animalActivationRange;
-        final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
-        // Paper start
-        final int waterActivationRange = world.spigotConfig.waterActivationRange;
-        final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange;
-        final int villagerActivationRange = world.spigotConfig.villagerActivationRange;
-        world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals);
-        world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers);
-        world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters);
-        world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying);
-        final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource();
-        // Paper end
-
-        int maxRange = Math.max(monsterActivationRange, animalActivationRange);
-        maxRange = Math.max(maxRange, raiderActivationRange);
-        maxRange = Math.max(maxRange, miscActivationRange);
-        // Paper start
-        maxRange = Math.max(maxRange, flyingActivationRange);
-        maxRange = Math.max(maxRange, waterActivationRange);
-        maxRange = Math.max(maxRange, villagerActivationRange);
-        // Paper end
-        maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange);
-
-        for (final Player player : world.players()) {
-            player.activatedTick = MinecraftServer.currentTick;
-            if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) {
-                continue;
-            }
-
-            // Paper start
-            final int worldHeight = world.getHeight();
-            ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
-            ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
-            ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange);
-            ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange);
-            ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange);
-            ActivationType.WATER.boundingBox = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange);
-            ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange);
-            ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange);
-            // Paper end
-
-            // Paper start
-            final java.util.List<Entity> entities = world.getEntities((Entity) null, ActivationRange.maxBB, null);
-            final boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking
-            for (final Entity entity : entities) {
-                // Paper start - Configurable marker ticking
-                if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) {
-                    continue;
-                }
-                // Paper end - Configurable marker ticking
-                ActivationRange.activateEntity(entity);
-            }
-            // Paper end
-        }
-    }
-
-    /**
-     * Tries to activate an entity.
-     *
-     * @param entity
-     */
-    private static void activateEntity(final Entity entity) {
-        if (MinecraftServer.currentTick > entity.activatedTick) {
-            if (entity.defaultActivationState) {
-                entity.activatedTick = MinecraftServer.currentTick;
-                return;
-            }
-            if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) {
-                entity.activatedTick = MinecraftServer.currentTick;
-            }
-        }
-    }
-
-    /**
-     * If an entity is not in range, do some more checks to see if we should
-     * give it a shot.
-     *
-     * @param entity
-     * @return
-     */
-    public static int checkEntityImmunities(final Entity entity) { // Paper - return # of ticks to get immunity
-        // Paper start
-        final SpigotWorldConfig config = entity.level().spigotConfig;
-        final int inactiveWakeUpImmunity = checkInactiveWakeup(entity);
-        if (inactiveWakeUpImmunity > -1) {
-            return inactiveWakeUpImmunity;
-        }
-        if (entity.getRemainingFireTicks() > 0) {
-            return 2;
-        }
-        if (entity.activatedImmunityTick >= MinecraftServer.currentTick) {
-            return 1;
-        }
-        final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick;
-        // Paper end
-        // quick checks.
-        if ((entity.activationType != ActivationType.WATER && entity.isInWater() && entity.isPushedByFluid())) // Paper
-        {
-            return 100; // Paper
-        }
-        // Paper start
-        if (!entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D) {
-            return 100;
-        }
-        // Paper end
-        if (!(entity instanceof final AbstractArrow arrow)) {
-            if ((!entity.onGround() && !(entity instanceof FlyingMob))) { // Paper - remove passengers logic
-                return 10; // Paper
-            }
-        } else if (!arrow.isInGround()) {
-            return 1; // Paper
-        }
-        // special cases.
-        if (entity instanceof final LivingEntity living) {
-            if (living.onClimbable() || living.jumping || living.hurtTime > 0 || !living.activeEffects.isEmpty() || living.isFreezing()) { // Paper
-                return 1; // Paper
-            }
-            if (entity instanceof final Mob mob && mob.getTarget() != null) { // Paper
-                return 20; // Paper
-            }
-            // Paper start
-            if (entity instanceof final Bee bee) {
-                final BlockPos movingTarget = bee.getMovingTarget();
-                if (bee.isAngry() ||
-                    (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) ||
-                    (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget))
-                ) {
-                    return 20;
-                }
-            }
-            if (entity instanceof final Villager villager) {
-                final Brain<Villager> behaviorController = villager.getBrain();
-
-                if (config.villagersActiveForPanic) {
-                    for (final net.minecraft.world.entity.schedule.Activity activity : VILLAGER_PANIC_IMMUNITIES) {
-                        if (behaviorController.isActive(activity)) {
-                            return 20 * 5;
-                        }
-                    }
-                }
-
-                if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) {
-                    if (behaviorController.isActive(net.minecraft.world.entity.schedule.Activity.WORK)) {
-                        return config.villagersWorkImmunityFor;
-                    }
-                }
-            }
-            if (entity instanceof final Llama llama && llama.inCaravan()) {
-                return 1;
-            }
-            // Paper end
-            if (entity instanceof final Animal animal) {
-                if (animal.isBaby() || animal.isInLove()) {
-                    return 5; // Paper
-                }
-                if (entity instanceof final Sheep sheep && sheep.isSheared()) {
-                    return 1; // Paper
-                }
-            }
-            if (entity instanceof final Creeper creeper && creeper.isIgnited()) { // isExplosive
-                return 20; // Paper
-            }
-            // Paper start
-            if (entity instanceof final Mob mob && mob.targetSelector.hasTasks()) {
-                return 0;
-            }
-            if (entity instanceof final Pillager pillager) {
-                // TODO:?
-            }
-            // Paper end
-        }
-        // SPIGOT-6644: Otherwise the target refresh tick will be missed
-        if (entity instanceof ExperienceOrb) {
-            return 20; // Paper
-        }
-        return -1; // Paper
-    }
-
-    /**
-     * Checks if the entity is active for this tick.
-     *
-     * @param entity
-     * @return
-     */
-    public static boolean checkIfActive(final Entity entity) {
-        // Never safe to skip fireworks or item gravity
-        if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick
-            return true;
-        }
-        // Paper start - special case always immunities
-        // immunize brand new entities, dead entities, and portal scenarios
-        if (entity.defaultActivationState || entity.tickCount < 20 * 10 || !entity.isAlive() || (entity.portalProcess != null && !entity.portalProcess.hasExpired()) || entity.portalCooldown > 0) {
-            return true;
-        }
-        // immunize leashed entities
-        if (entity instanceof final Mob mob && mob.getLeashHolder() instanceof Player) {
-            return true;
-        }
-        // Paper end
-
-        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick;
-        entity.isTemporarilyActive = false; // Paper
-
-        // Should this entity tick?
-        if (!isActive) {
-            if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
-                // Check immunities every 20 ticks.
-                // Paper start
-                final int immunity = checkEntityImmunities(entity);
-                if (immunity >= 0) {
-                    entity.activatedTick = MinecraftServer.currentTick + immunity;
-                } else {
-                    entity.isTemporarilyActive = true;
-                }
-                // Paper end
-                isActive = true;
-            }
-        }
-        // Paper - remove dumb tick skipping for active entities
-        return isActive;
-    }
-}
diff --git a/paper-server/src/main/java/org/spigotmc/TrackingRange.java b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
index 039f8abeb5..4835431493 100644
--- a/paper-server/src/main/java/org/spigotmc/TrackingRange.java
+++ b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
@@ -1,5 +1,6 @@
 package org.spigotmc;
 
+import net.minecraft.server.level.ServerLevel;
 import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.world.entity.Display;
 import net.minecraft.world.entity.Entity;
@@ -25,11 +26,12 @@ public final class TrackingRange {
         if (defaultRange == 0) {
             return defaultRange;
         }
+
         final SpigotWorldConfig config = entity.level().spigotConfig;
         if (entity instanceof ServerPlayer) {
             return config.playerTrackingRange;
-            // Paper start - Simplify and set water mobs to animal tracking range
         }
+
         switch (entity.activationType) {
             case RAIDER:
             case MONSTER:
@@ -41,14 +43,15 @@ public final class TrackingRange {
                 return config.animalTrackingRange;
             case MISC:
         }
+
         if (entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb) {
-        // Paper end
             return config.miscTrackingRange;
         } else if (entity instanceof Display) {
             return config.displayTrackingRange;
         } else {
             if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) {
-                return ((net.minecraft.server.level.ServerLevel) (entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt
+                // Exempt ender dragon
+                return ((ServerLevel) entity.getCommandSenderWorld()).getChunkSource().chunkMap.serverViewDistance;
             }
             return config.otherTrackingRange;
         }

From 002b02c2736490532c6ff12b5e8fda17d23a659d Mon Sep 17 00:00:00 2001
From: stonar96 <minecraft.stonar96@gmail.com>
Date: Mon, 16 Dec 2024 15:04:45 +0100
Subject: [PATCH 200/285] Move anti xray classes to source

---
 .../paper/antixray/BitStorageReader.java      |  51 ++
 .../paper/antixray/BitStorageWriter.java      |  79 ++
 .../antixray/ChunkPacketBlockController.java  |  45 ++
 .../ChunkPacketBlockControllerAntiXray.java   | 676 ++++++++++++++++++
 .../paper/antixray/ChunkPacketInfo.java       |  80 +++
 .../antixray/ChunkPacketInfoAntiXray.java     |  29 +
 6 files changed, 960 insertions(+)
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/BitStorageReader.java
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockController.java
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java
 create mode 100644 paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfoAntiXray.java

diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageReader.java b/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageReader.java
new file mode 100644
index 0000000000..c27703775c
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageReader.java
@@ -0,0 +1,51 @@
+package io.papermc.paper.antixray;
+
+public final class BitStorageReader {
+
+    private byte[] buffer;
+    private int bits;
+    private int mask;
+    private int longInBufferIndex;
+    private int bitInLongIndex;
+    private long current;
+
+    public void setBuffer(byte[] buffer) {
+        this.buffer = buffer;
+    }
+
+    public void setBits(int bits) {
+        this.bits = bits;
+        mask = (1 << bits) - 1;
+    }
+
+    public void setIndex(int index) {
+        longInBufferIndex = index;
+        bitInLongIndex = 0;
+        init();
+    }
+
+    private void init() {
+        if (buffer.length > longInBufferIndex + 7) {
+            current = ((((long) buffer[longInBufferIndex]) << 56)
+                | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
+                | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
+                | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
+                | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
+                | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
+                | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
+                | (((long) buffer[longInBufferIndex + 7] & 0xff)));
+        }
+    }
+
+    public int read() {
+        if (bitInLongIndex + bits > 64) {
+            bitInLongIndex = 0;
+            longInBufferIndex += 8;
+            init();
+        }
+
+        int value = (int) (current >>> bitInLongIndex) & mask;
+        bitInLongIndex += bits;
+        return value;
+    }
+}
diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java b/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java
new file mode 100644
index 0000000000..83412e0dda
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java
@@ -0,0 +1,79 @@
+package io.papermc.paper.antixray;
+
+public final class BitStorageWriter {
+
+    private byte[] buffer;
+    private int bits;
+    private long mask;
+    private int longInBufferIndex;
+    private int bitInLongIndex;
+    private long current;
+    private boolean dirty;
+
+    public void setBuffer(byte[] buffer) {
+        this.buffer = buffer;
+    }
+
+    public void setBits(int bits) {
+        this.bits = bits;
+        mask = (1L << bits) - 1;
+    }
+
+    public void setIndex(int index) {
+        longInBufferIndex = index;
+        bitInLongIndex = 0;
+        init();
+    }
+
+    private void init() {
+        if (buffer.length > longInBufferIndex + 7) {
+            current = ((((long) buffer[longInBufferIndex]) << 56)
+                | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
+                | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
+                | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
+                | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
+                | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
+                | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
+                | (((long) buffer[longInBufferIndex + 7] & 0xff)));
+        }
+
+        dirty = false;
+    }
+
+    public void flush() {
+        if (dirty && buffer.length > longInBufferIndex + 7) {
+            buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff);
+            buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff);
+            buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff);
+            buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff);
+            buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff);
+            buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff);
+            buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff);
+            buffer[longInBufferIndex + 7] = (byte) (current & 0xff);
+        }
+    }
+
+    public void write(int value) {
+        if (bitInLongIndex + bits > 64) {
+            flush();
+            bitInLongIndex = 0;
+            longInBufferIndex += 8;
+            init();
+        }
+
+        current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
+        dirty = true;
+        bitInLongIndex += bits;
+    }
+
+    public void skip() {
+        bitInLongIndex += bits;
+
+        if (bitInLongIndex > 64) {
+            flush();
+            bitInLongIndex = bits;
+            longInBufferIndex += 8;
+            init();
+        }
+    }
+}
diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockController.java b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockController.java
new file mode 100644
index 0000000000..69b553747b
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockController.java
@@ -0,0 +1,45 @@
+package io.papermc.paper.antixray;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ServerPlayerGameMode;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+public class ChunkPacketBlockController {
+
+    public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController();
+
+    protected ChunkPacketBlockController() {
+
+    }
+
+    public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) {
+        return null;
+    }
+
+    public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
+        return false;
+    }
+
+    public ChunkPacketInfo<BlockState> getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
+        return null;
+    }
+
+    public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
+        chunkPacket.setReady(true);
+    }
+
+    public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
+
+    }
+
+    public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
+
+    }
+}
diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java
new file mode 100644
index 0000000000..ee2d3a54d7
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java
@@ -0,0 +1,676 @@
+package io.papermc.paper.antixray;
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import io.papermc.paper.configuration.type.EngineMode;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.IntSupplier;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ServerPlayerGameMode;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.biome.Biomes;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.EntityBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.EmptyLevelChunk;
+import net.minecraft.world.level.chunk.GlobalPalette;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.MissingPaletteEntryException;
+import net.minecraft.world.level.chunk.Palette;
+import org.bukkit.Bukkit;
+
+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController {
+
+    private static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
+    private static final LevelChunkSection EMPTY_SECTION = null;
+    private final Executor executor;
+    private final EngineMode engineMode;
+    private final int maxBlockHeight;
+    private final int updateRadius;
+    private final boolean usePermission;
+    private final BlockState[] presetBlockStates;
+    private final BlockState[] presetBlockStatesFull;
+    private final BlockState[] presetBlockStatesStone;
+    private final BlockState[] presetBlockStatesDeepslate;
+    private final BlockState[] presetBlockStatesNetherrack;
+    private final BlockState[] presetBlockStatesEndStone;
+    private final int[] presetBlockStateBitsGlobal;
+    private final int[] presetBlockStateBitsStoneGlobal;
+    private final int[] presetBlockStateBitsDeepslateGlobal;
+    private final int[] presetBlockStateBitsNetherrackGlobal;
+    private final int[] presetBlockStateBitsEndStoneGlobal;
+    private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
+    private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
+    private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION};
+    private final int maxBlockHeightUpdatePosition;
+
+    public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) {
+        this.executor = executor;
+        WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray;
+        engineMode = paperWorldConfig.engineMode;
+        maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4;
+        updateRadius = paperWorldConfig.updateRadius;
+        usePermission = paperWorldConfig.usePermission;
+        List<Block> toObfuscate;
+
+        if (engineMode == EngineMode.HIDE) {
+            toObfuscate = paperWorldConfig.hiddenBlocks;
+            presetBlockStates = null;
+            presetBlockStatesFull = null;
+            presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()};
+            presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()};
+            presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()};
+            presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()};
+            presetBlockStateBitsGlobal = null;
+            presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())};
+            presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())};
+            presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())};
+            presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())};
+        } else {
+            toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks);
+            List<BlockState> presetBlockStateList = new LinkedList<>();
+
+            for (Block block : paperWorldConfig.hiddenBlocks) {
+
+                if (!(block instanceof EntityBlock)) {
+                    toObfuscate.add(block);
+                    presetBlockStateList.add(block.defaultBlockState());
+                }
+            }
+
+            // The doc of the LinkedHashSet(Collection<? extends E>) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation
+            Set<BlockState> presetBlockStateSet = new LinkedHashSet<>();
+            // Therefore addAll(Collection<? extends E>) is used, which guarantees this order in the doc
+            presetBlockStateSet.addAll(presetBlockStateList);
+            presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]);
+            presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]);
+            presetBlockStatesStone = null;
+            presetBlockStatesDeepslate = null;
+            presetBlockStatesNetherrack = null;
+            presetBlockStatesEndStone = null;
+            presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length];
+
+            for (int i = 0; i < presetBlockStatesFull.length; i++) {
+                presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]);
+            }
+
+            presetBlockStateBitsStoneGlobal = null;
+            presetBlockStateBitsDeepslateGlobal = null;
+            presetBlockStateBitsNetherrackGlobal = null;
+            presetBlockStateBitsEndStoneGlobal = null;
+        }
+
+        for (Block block : toObfuscate) {
+
+            // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
+            if (block != null && !block.defaultBlockState().isAir()) {
+                // Replace all block states of a specified block
+                for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
+                    obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
+                }
+            }
+        }
+
+        EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS));
+        BlockPos zeroPos = new BlockPos(0, 0, 0);
+
+        for (int i = 0; i < solidGlobal.length; i++) {
+            BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i);
+
+            if (blockState != null) {
+                solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos)
+                    && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState();
+                // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used
+                // shulker box checks TE.
+            }
+        }
+
+        maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1;
+    }
+
+    private int getPresetBlockStatesFullLength() {
+        return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length;
+    }
+
+    @Override
+    public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) {
+        // Return the block states to be added to the paletted containers so that they can be used for obfuscation
+        int bottomBlockY = chunkSectionY << 4;
+
+        if (bottomBlockY < maxBlockHeight) {
+            if (engineMode == EngineMode.HIDE) {
+                return switch (level.getWorld().getEnvironment()) {
+                    case NETHER -> presetBlockStatesNetherrack;
+                    case THE_END -> presetBlockStatesEndStone;
+                    default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone;
+                };
+            }
+
+            return presetBlockStates;
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
+        return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass");
+    }
+
+    @Override
+    public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
+        // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later
+        return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this);
+    }
+
+    @Override
+    public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
+        if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) {
+            chunkPacket.setReady(true);
+            return;
+        }
+
+        if (!Bukkit.isPrimaryThread()) {
+            // Plugins?
+            MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
+            return;
+        }
+
+        LevelChunk chunk = chunkPacketInfo.getChunk();
+        int x = chunk.getPos().x;
+        int z = chunk.getPos().z;
+        Level level = chunk.getLevel();
+        ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1));
+        executor.execute((Runnable) chunkPacketInfo);
+    }
+
+    // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal)
+    // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
+    private final ThreadLocal<int[]> presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]);
+    private static final ThreadLocal<boolean[]> SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+    private static final ThreadLocal<boolean[]> OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+    // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
+    private static final ThreadLocal<boolean[][]> CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]);
+    private static final ThreadLocal<boolean[][]> NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
+    private static final ThreadLocal<boolean[][]> NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
+
+    public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
+        int[] presetBlockStateBits = this.presetBlockStateBits.get();
+        boolean[] solid = SOLID.get();
+        boolean[] obfuscate = OBFUSCATE.get();
+        boolean[][] current = CURRENT.get();
+        boolean[][] next = NEXT.get();
+        boolean[][] nextNext = NEXT_NEXT.get();
+        // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
+        BitStorageReader bitStorageReader = new BitStorageReader();
+        BitStorageWriter bitStorageWriter = new BitStorageWriter();
+        LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4];
+        LevelChunk chunk = chunkPacketInfoAntiXray.getChunk();
+        Level level = chunk.getLevel();
+        int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSectionY(), chunk.getSectionsCount()) - 1;
+        boolean[] solidTemp = null;
+        boolean[] obfuscateTemp = null;
+        bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer());
+        bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer());
+        int numberOfBlocks = presetBlockStateBits.length;
+        // Keep the lambda expressions as simple as possible. They are used very frequently.
+        LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() {
+            // engine-mode: 3
+            private int state;
+            private int next;
+
+            {
+                while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
+            }
+
+            @Override
+            public void nextLayer() {
+                // https://en.wikipedia.org/wiki/Xorshift
+                state ^= state << 13;
+                state ^= state >>> 17;
+                state ^= state << 5;
+                // https://www.pcg-random.org/posts/bounded-rands.html
+                next = (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
+            }
+
+            @Override
+            public int getAsInt() {
+                return next;
+            }
+        } : new LayeredIntSupplier() {
+            // engine-mode: 2
+            private int state;
+
+            {
+                while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
+            }
+
+            @Override
+            public int getAsInt() {
+                // https://en.wikipedia.org/wiki/Xorshift
+                state ^= state << 13;
+                state ^= state >>> 17;
+                state ^= state << 5;
+                // https://www.pcg-random.org/posts/bounded-rands.html
+                return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
+            }
+        };
+
+        for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) {
+            if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) {
+                int[] presetBlockStateBitsTemp;
+
+                if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) {
+                    if (engineMode == EngineMode.HIDE) {
+                        presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) {
+                            case NETHER -> presetBlockStateBitsNetherrackGlobal;
+                            case THE_END -> presetBlockStateBitsEndStoneGlobal;
+                            default -> chunkSectionIndex + chunk.getMinSectionY() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal;
+                        };
+                    } else {
+                        presetBlockStateBitsTemp = presetBlockStateBitsGlobal;
+                    }
+                } else {
+                    // If it's presetBlockStates, use this.presetBlockStatesFull instead
+                    BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex);
+                    presetBlockStateBitsTemp = presetBlockStateBits;
+
+                    for (int i = 0; i < presetBlockStateBitsTemp.length; i++) {
+                        // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible
+                        // For more details see the comments in the readPalette method
+                        presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]);
+                    }
+                }
+
+                bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
+
+                // Check if the chunk section below was not obfuscated
+                if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) {
+                    // If so, initialize some stuff
+                    bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
+                    bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
+                    solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal);
+                    obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal);
+                    // Read the blocks of the upper layer of the chunk section below if it exists
+                    LevelChunkSection belowChunkSection = null;
+                    boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION;
+
+                    for (int z = 0; z < 16; z++) {
+                        for (int x = 0; x < 16; x++) {
+                            current[z][x] = true;
+                            next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z);
+                        }
+                    }
+
+                    // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section
+                    bitStorageWriter.setBits(0);
+                    obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random);
+                }
+
+                bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
+                nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex];
+                nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex];
+                nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex];
+                nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex];
+
+                // Obfuscate all layers of the current chunk section except the upper one
+                for (int y = 0; y < 15; y++) {
+                    boolean[][] temp = current;
+                    current = next;
+                    next = nextNext;
+                    nextNext = temp;
+                    random.nextLayer();
+                    obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+                }
+
+                // Check if the chunk section above doesn't need obfuscation
+                if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) {
+                    // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists
+                    LevelChunkSection aboveChunkSection;
+
+                    if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) {
+                        boolean[][] temp = current;
+                        current = next;
+                        next = nextNext;
+                        nextNext = temp;
+
+                        for (int z = 0; z < 16; z++) {
+                            for (int x = 0; x < 16; x++) {
+                                if (isTransparent(aboveChunkSection, x, 0, z)) {
+                                    current[z][x] = true;
+                                }
+                            }
+                        }
+
+                        // There is nothing to read anymore
+                        bitStorageReader.setBits(0);
+                        solid[0] = true;
+                        random.nextLayer();
+                        obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+                    }
+                } else {
+                    // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section
+                    bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1));
+                    bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1));
+                    solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal);
+                    obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal);
+                    boolean[][] temp = current;
+                    current = next;
+                    next = nextNext;
+                    nextNext = temp;
+                    random.nextLayer();
+                    obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+                }
+
+                bitStorageWriter.flush();
+            }
+        }
+
+        chunkPacketInfoAntiXray.getChunkPacket().setReady(true);
+    }
+
+    private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) {
+        // First block of first line
+        int bits = bitStorageReader.read();
+
+        if (nextNext[0][0] = !solid[bits]) {
+            bitStorageWriter.skip();
+            next[0][1] = true;
+            next[1][0] = true;
+        } else {
+            if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) {
+                bitStorageWriter.skip();
+            } else {
+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+            }
+        }
+
+        if (!obfuscate[bits]) {
+            next[0][0] = true;
+        }
+
+        // First line
+        for (int x = 1; x < 15; x++) {
+            bits = bitStorageReader.read();
+
+            if (nextNext[0][x] = !solid[bits]) {
+                bitStorageWriter.skip();
+                next[0][x - 1] = true;
+                next[0][x + 1] = true;
+                next[1][x] = true;
+            } else {
+                if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) {
+                    bitStorageWriter.skip();
+                } else {
+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+                }
+            }
+
+            if (!obfuscate[bits]) {
+                next[0][x] = true;
+            }
+        }
+
+        // Last block of first line
+        bits = bitStorageReader.read();
+
+        if (nextNext[0][15] = !solid[bits]) {
+            bitStorageWriter.skip();
+            next[0][14] = true;
+            next[1][15] = true;
+        } else {
+            if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) {
+                bitStorageWriter.skip();
+            } else {
+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+            }
+        }
+
+        if (!obfuscate[bits]) {
+            next[0][15] = true;
+        }
+
+        // All inner lines
+        for (int z = 1; z < 15; z++) {
+            // First block
+            bits = bitStorageReader.read();
+
+            if (nextNext[z][0] = !solid[bits]) {
+                bitStorageWriter.skip();
+                next[z][1] = true;
+                next[z - 1][0] = true;
+                next[z + 1][0] = true;
+            } else {
+                if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) {
+                    bitStorageWriter.skip();
+                } else {
+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+                }
+            }
+
+            if (!obfuscate[bits]) {
+                next[z][0] = true;
+            }
+
+            // All inner blocks
+            for (int x = 1; x < 15; x++) {
+                bits = bitStorageReader.read();
+
+                if (nextNext[z][x] = !solid[bits]) {
+                    bitStorageWriter.skip();
+                    next[z][x - 1] = true;
+                    next[z][x + 1] = true;
+                    next[z - 1][x] = true;
+                    next[z + 1][x] = true;
+                } else {
+                    if (current[z][x]) {
+                        bitStorageWriter.skip();
+                    } else {
+                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+                    }
+                }
+
+                if (!obfuscate[bits]) {
+                    next[z][x] = true;
+                }
+            }
+
+            // Last block
+            bits = bitStorageReader.read();
+
+            if (nextNext[z][15] = !solid[bits]) {
+                bitStorageWriter.skip();
+                next[z][14] = true;
+                next[z - 1][15] = true;
+                next[z + 1][15] = true;
+            } else {
+                if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) {
+                    bitStorageWriter.skip();
+                } else {
+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+                }
+            }
+
+            if (!obfuscate[bits]) {
+                next[z][15] = true;
+            }
+        }
+
+        // First block of last line
+        bits = bitStorageReader.read();
+
+        if (nextNext[15][0] = !solid[bits]) {
+            bitStorageWriter.skip();
+            next[15][1] = true;
+            next[14][0] = true;
+        } else {
+            if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) {
+                bitStorageWriter.skip();
+            } else {
+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+            }
+        }
+
+        if (!obfuscate[bits]) {
+            next[15][0] = true;
+        }
+
+        // Last line
+        for (int x = 1; x < 15; x++) {
+            bits = bitStorageReader.read();
+
+            if (nextNext[15][x] = !solid[bits]) {
+                bitStorageWriter.skip();
+                next[15][x - 1] = true;
+                next[15][x + 1] = true;
+                next[14][x] = true;
+            } else {
+                if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) {
+                    bitStorageWriter.skip();
+                } else {
+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+                }
+            }
+
+            if (!obfuscate[bits]) {
+                next[15][x] = true;
+            }
+        }
+
+        // Last block of last line
+        bits = bitStorageReader.read();
+
+        if (nextNext[15][15] = !solid[bits]) {
+            bitStorageWriter.skip();
+            next[15][14] = true;
+            next[14][15] = true;
+        } else {
+            if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) {
+                bitStorageWriter.skip();
+            } else {
+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+            }
+        }
+
+        if (!obfuscate[bits]) {
+            next[15][15] = true;
+        }
+    }
+
+    private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) {
+        if (chunkSection == EMPTY_SECTION) {
+            return true;
+        }
+
+        try {
+            return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))];
+        } catch (MissingPaletteEntryException e) {
+            // Race condition / visibility issue / no happens-before relationship
+            // We don't care and treat the block as transparent
+            // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur
+            return true;
+        }
+    }
+
+    private boolean[] readPalette(Palette<BlockState> palette, boolean[] temp, boolean[] global) {
+        if (palette instanceof GlobalPalette) {
+            return global;
+        }
+
+        try {
+            for (int i = 0; i < palette.getSize(); i++) {
+                temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))];
+            }
+        } catch (MissingPaletteEntryException e) {
+            // Race condition / visibility issue / no happens-before relationship
+            // We don't care because we at least see the state as it was when the chunk packet was created
+            // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here
+            // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data
+        }
+
+        return temp;
+    }
+
+    @Override
+    public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
+        if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) {
+            updateNearbyBlocks(level, blockPos);
+        }
+    }
+
+    @Override
+    public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
+        if (blockPos.getY() <= maxBlockHeightUpdatePosition) {
+            updateNearbyBlocks(serverPlayerGameMode.level, blockPos);
+        }
+    }
+
+    private void updateNearbyBlocks(Level level, BlockPos blockPos) {
+        if (updateRadius >= 2) {
+            BlockPos temp = blockPos.west();
+            updateBlock(level, temp);
+            updateBlock(level, temp.west());
+            updateBlock(level, temp.below());
+            updateBlock(level, temp.above());
+            updateBlock(level, temp.north());
+            updateBlock(level, temp.south());
+            updateBlock(level, temp = blockPos.east());
+            updateBlock(level, temp.east());
+            updateBlock(level, temp.below());
+            updateBlock(level, temp.above());
+            updateBlock(level, temp.north());
+            updateBlock(level, temp.south());
+            updateBlock(level, temp = blockPos.below());
+            updateBlock(level, temp.below());
+            updateBlock(level, temp.north());
+            updateBlock(level, temp.south());
+            updateBlock(level, temp = blockPos.above());
+            updateBlock(level, temp.above());
+            updateBlock(level, temp.north());
+            updateBlock(level, temp.south());
+            updateBlock(level, temp = blockPos.north());
+            updateBlock(level, temp.north());
+            updateBlock(level, temp = blockPos.south());
+            updateBlock(level, temp.south());
+        } else if (updateRadius == 1) {
+            updateBlock(level, blockPos.west());
+            updateBlock(level, blockPos.east());
+            updateBlock(level, blockPos.below());
+            updateBlock(level, blockPos.above());
+            updateBlock(level, blockPos.north());
+            updateBlock(level, blockPos.south());
+        } else {
+            // Do nothing if updateRadius <= 0 (test mode)
+        }
+    }
+
+    private void updateBlock(Level level, BlockPos blockPos) {
+        BlockState blockState = level.getBlockStateIfLoaded(blockPos);
+
+        if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) {
+            ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
+        }
+    }
+
+    @FunctionalInterface
+    private interface LayeredIntSupplier extends IntSupplier {
+        default void nextLayer() {
+
+        }
+    }
+}
diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java
new file mode 100644
index 0000000000..a33a4d45d4
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java
@@ -0,0 +1,80 @@
+package io.papermc.paper.antixray;
+
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.Palette;
+
+public class ChunkPacketInfo<T> {
+
+    private final ClientboundLevelChunkWithLightPacket chunkPacket;
+    private final LevelChunk chunk;
+    private final int[] bits;
+    private final Object[] palettes;
+    private final int[] indexes;
+    private final Object[][] presetValues;
+    private byte[] buffer;
+
+    public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
+        this.chunkPacket = chunkPacket;
+        this.chunk = chunk;
+        int sections = chunk.getSectionsCount();
+        bits = new int[sections];
+        palettes = new Object[sections];
+        indexes = new int[sections];
+        presetValues = new Object[sections][];
+    }
+
+    public ClientboundLevelChunkWithLightPacket getChunkPacket() {
+        return chunkPacket;
+    }
+
+    public LevelChunk getChunk() {
+        return chunk;
+    }
+
+    public byte[] getBuffer() {
+        return buffer;
+    }
+
+    public void setBuffer(byte[] buffer) {
+        this.buffer = buffer;
+    }
+
+    public int getBits(int chunkSectionIndex) {
+        return bits[chunkSectionIndex];
+    }
+
+    public void setBits(int chunkSectionIndex, int bits) {
+        this.bits[chunkSectionIndex] = bits;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Palette<T> getPalette(int chunkSectionIndex) {
+        return (Palette<T>) palettes[chunkSectionIndex];
+    }
+
+    public void setPalette(int chunkSectionIndex, Palette<T> palette) {
+        palettes[chunkSectionIndex] = palette;
+    }
+
+    public int getIndex(int chunkSectionIndex) {
+        return indexes[chunkSectionIndex];
+    }
+
+    public void setIndex(int chunkSectionIndex, int index) {
+        indexes[chunkSectionIndex] = index;
+    }
+
+    @SuppressWarnings("unchecked")
+    public T[] getPresetValues(int chunkSectionIndex) {
+        return (T[]) presetValues[chunkSectionIndex];
+    }
+
+    public void setPresetValues(int chunkSectionIndex, T[] presetValues) {
+        this.presetValues[chunkSectionIndex] = presetValues;
+    }
+
+    public boolean isWritten(int chunkSectionIndex) {
+        return bits[chunkSectionIndex] != 0;
+    }
+}
diff --git a/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfoAntiXray.java b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfoAntiXray.java
new file mode 100644
index 0000000000..7d8dff55bf
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/antixray/ChunkPacketInfoAntiXray.java
@@ -0,0 +1,29 @@
+package io.papermc.paper.antixray;
+
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo<BlockState> implements Runnable {
+
+    private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray;
+    private LevelChunk[] nearbyChunks;
+
+    public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) {
+        super(chunkPacket, chunk);
+        this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray;
+    }
+
+    public LevelChunk[] getNearbyChunks() {
+        return nearbyChunks;
+    }
+
+    public void setNearbyChunks(LevelChunk... nearbyChunks) {
+        this.nearbyChunks = nearbyChunks;
+    }
+
+    @Override
+    public void run() {
+        chunkPacketBlockControllerAntiXray.obfuscate(this);
+    }
+}

From 79ff0d4e3322f843d797605abb337cad568c5d04 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 15:13:42 +0100
Subject: [PATCH 201/285] Update anti-xray patch

---
 feature-patches/1044-Anti-Xray.patch | 1642 +++++---------------------
 1 file changed, 325 insertions(+), 1317 deletions(-)

diff --git a/feature-patches/1044-Anti-Xray.patch b/feature-patches/1044-Anti-Xray.patch
index f002f23549..6b16ff833e 100644
--- a/feature-patches/1044-Anti-Xray.patch
+++ b/feature-patches/1044-Anti-Xray.patch
@@ -4,1007 +4,11 @@ Date: Thu, 25 Nov 2021 13:27:51 +0100
 Subject: [PATCH] Anti-Xray
 
 
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+public final class BitStorageReader {
-+
-+    private byte[] buffer;
-+    private int bits;
-+    private int mask;
-+    private int longInBufferIndex;
-+    private int bitInLongIndex;
-+    private long current;
-+
-+    public void setBuffer(byte[] buffer) {
-+        this.buffer = buffer;
-+    }
-+
-+    public void setBits(int bits) {
-+        this.bits = bits;
-+        mask = (1 << bits) - 1;
-+    }
-+
-+    public void setIndex(int index) {
-+        longInBufferIndex = index;
-+        bitInLongIndex = 0;
-+        init();
-+    }
-+
-+    private void init() {
-+        if (buffer.length > longInBufferIndex + 7) {
-+            current = ((((long) buffer[longInBufferIndex]) << 56)
-+                | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
-+                | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
-+                | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
-+                | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
-+                | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
-+                | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
-+                | (((long) buffer[longInBufferIndex + 7] & 0xff)));
-+        }
-+    }
-+
-+    public int read() {
-+        if (bitInLongIndex + bits > 64) {
-+            bitInLongIndex = 0;
-+            longInBufferIndex += 8;
-+            init();
-+        }
-+
-+        int value = (int) (current >>> bitInLongIndex) & mask;
-+        bitInLongIndex += bits;
-+        return value;
-+    }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+public final class BitStorageWriter {
-+
-+    private byte[] buffer;
-+    private int bits;
-+    private long mask;
-+    private int longInBufferIndex;
-+    private int bitInLongIndex;
-+    private long current;
-+    private boolean dirty;
-+
-+    public void setBuffer(byte[] buffer) {
-+        this.buffer = buffer;
-+    }
-+
-+    public void setBits(int bits) {
-+        this.bits = bits;
-+        mask = (1L << bits) - 1;
-+    }
-+
-+    public void setIndex(int index) {
-+        longInBufferIndex = index;
-+        bitInLongIndex = 0;
-+        init();
-+    }
-+
-+    private void init() {
-+        if (buffer.length > longInBufferIndex + 7) {
-+            current = ((((long) buffer[longInBufferIndex]) << 56)
-+                | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
-+                | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
-+                | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
-+                | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
-+                | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
-+                | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
-+                | (((long) buffer[longInBufferIndex + 7] & 0xff)));
-+        }
-+
-+        dirty = false;
-+    }
-+
-+    public void flush() {
-+        if (dirty && buffer.length > longInBufferIndex + 7) {
-+            buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff);
-+            buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff);
-+            buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff);
-+            buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff);
-+            buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff);
-+            buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff);
-+            buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff);
-+            buffer[longInBufferIndex + 7] = (byte) (current & 0xff);
-+        }
-+    }
-+
-+    public void write(int value) {
-+        if (bitInLongIndex + bits > 64) {
-+            flush();
-+            bitInLongIndex = 0;
-+            longInBufferIndex += 8;
-+            init();
-+        }
-+
-+        current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
-+        dirty = true;
-+        bitInLongIndex += bits;
-+    }
-+
-+    public void skip() {
-+        bitInLongIndex += bits;
-+
-+        if (bitInLongIndex > 64) {
-+            flush();
-+            bitInLongIndex = bits;
-+            longInBufferIndex += 8;
-+            init();
-+        }
-+    }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.core.Direction;
-+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
-+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.ServerPlayerGameMode;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+
-+public class ChunkPacketBlockController {
-+
-+    public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController();
-+
-+    protected ChunkPacketBlockController() {
-+
-+    }
-+
-+    public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) {
-+        return null;
-+    }
-+
-+    public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
-+        return false;
-+    }
-+
-+    public ChunkPacketInfo<BlockState> getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
-+        return null;
-+    }
-+
-+    public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
-+        chunkPacket.setReady(true);
-+    }
-+
-+    public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
-+
-+    }
-+
-+    public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
-+
-+    }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+import io.papermc.paper.configuration.WorldConfiguration;
-+import io.papermc.paper.configuration.type.EngineMode;
-+import java.util.ArrayList;
-+import java.util.LinkedHashSet;
-+import java.util.LinkedList;
-+import java.util.List;
-+import java.util.Set;
-+import java.util.concurrent.Executor;
-+import java.util.concurrent.ThreadLocalRandom;
-+import java.util.function.IntSupplier;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.core.Direction;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
-+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.ServerPlayerGameMode;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.biome.Biomes;
-+import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.EntityBlock;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.EmptyLevelChunk;
-+import net.minecraft.world.level.chunk.GlobalPalette;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.chunk.LevelChunkSection;
-+import net.minecraft.world.level.chunk.MissingPaletteEntryException;
-+import net.minecraft.world.level.chunk.Palette;
-+import org.bukkit.Bukkit;
-+
-+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController {
-+
-+    private static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
-+    private static final LevelChunkSection EMPTY_SECTION = null;
-+    private final Executor executor;
-+    private final EngineMode engineMode;
-+    private final int maxBlockHeight;
-+    private final int updateRadius;
-+    private final boolean usePermission;
-+    private final BlockState[] presetBlockStates;
-+    private final BlockState[] presetBlockStatesFull;
-+    private final BlockState[] presetBlockStatesStone;
-+    private final BlockState[] presetBlockStatesDeepslate;
-+    private final BlockState[] presetBlockStatesNetherrack;
-+    private final BlockState[] presetBlockStatesEndStone;
-+    private final int[] presetBlockStateBitsGlobal;
-+    private final int[] presetBlockStateBitsStoneGlobal;
-+    private final int[] presetBlockStateBitsDeepslateGlobal;
-+    private final int[] presetBlockStateBitsNetherrackGlobal;
-+    private final int[] presetBlockStateBitsEndStoneGlobal;
-+    private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
-+    private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
-+    private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION};
-+    private final int maxBlockHeightUpdatePosition;
-+
-+    public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) {
-+        this.executor = executor;
-+        WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray;
-+        engineMode = paperWorldConfig.engineMode;
-+        maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4;
-+        updateRadius = paperWorldConfig.updateRadius;
-+        usePermission = paperWorldConfig.usePermission;
-+        List<Block> toObfuscate;
-+
-+        if (engineMode == EngineMode.HIDE) {
-+            toObfuscate = paperWorldConfig.hiddenBlocks;
-+            presetBlockStates = null;
-+            presetBlockStatesFull = null;
-+            presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()};
-+            presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()};
-+            presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()};
-+            presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()};
-+            presetBlockStateBitsGlobal = null;
-+            presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())};
-+            presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())};
-+            presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())};
-+            presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())};
-+        } else {
-+            toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks);
-+            List<BlockState> presetBlockStateList = new LinkedList<>();
-+
-+            for (Block block : paperWorldConfig.hiddenBlocks) {
-+
-+                if (!(block instanceof EntityBlock)) {
-+                    toObfuscate.add(block);
-+                    presetBlockStateList.add(block.defaultBlockState());
-+                }
-+            }
-+
-+            // The doc of the LinkedHashSet(Collection<? extends E>) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation
-+            Set<BlockState> presetBlockStateSet = new LinkedHashSet<>();
-+            // Therefore addAll(Collection<? extends E>) is used, which guarantees this order in the doc
-+            presetBlockStateSet.addAll(presetBlockStateList);
-+            presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]);
-+            presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]);
-+            presetBlockStatesStone = null;
-+            presetBlockStatesDeepslate = null;
-+            presetBlockStatesNetherrack = null;
-+            presetBlockStatesEndStone = null;
-+            presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length];
-+
-+            for (int i = 0; i < presetBlockStatesFull.length; i++) {
-+                presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]);
-+            }
-+
-+            presetBlockStateBitsStoneGlobal = null;
-+            presetBlockStateBitsDeepslateGlobal = null;
-+            presetBlockStateBitsNetherrackGlobal = null;
-+            presetBlockStateBitsEndStoneGlobal = null;
-+        }
-+
-+        for (Block block : toObfuscate) {
-+
-+            // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
-+            if (block != null && !block.defaultBlockState().isAir()) {
-+                // Replace all block states of a specified block
-+                for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
-+                    obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
-+                }
-+            }
-+        }
-+
-+        EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS));
-+        BlockPos zeroPos = new BlockPos(0, 0, 0);
-+
-+        for (int i = 0; i < solidGlobal.length; i++) {
-+            BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i);
-+
-+            if (blockState != null) {
-+                solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos)
-+                    && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState();
-+                // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used
-+                // shulker box checks TE.
-+            }
-+        }
-+
-+        maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1;
-+    }
-+
-+    private int getPresetBlockStatesFullLength() {
-+        return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length;
-+    }
-+
-+    @Override
-+    public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) {
-+        // Return the block states to be added to the paletted containers so that they can be used for obfuscation
-+        int bottomBlockY = chunkSectionY << 4;
-+
-+        if (bottomBlockY < maxBlockHeight) {
-+            if (engineMode == EngineMode.HIDE) {
-+                return switch (level.getWorld().getEnvironment()) {
-+                    case NETHER -> presetBlockStatesNetherrack;
-+                    case THE_END -> presetBlockStatesEndStone;
-+                    default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone;
-+                };
-+            }
-+
-+            return presetBlockStates;
-+        }
-+
-+        return null;
-+    }
-+
-+    @Override
-+    public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
-+        return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass");
-+    }
-+
-+    @Override
-+    public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
-+        // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later
-+        return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this);
-+    }
-+
-+    @Override
-+    public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
-+        if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) {
-+            chunkPacket.setReady(true);
-+            return;
-+        }
-+
-+        if (!Bukkit.isPrimaryThread()) {
-+            // Plugins?
-+            MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
-+            return;
-+        }
-+
-+        LevelChunk chunk = chunkPacketInfo.getChunk();
-+        int x = chunk.getPos().x;
-+        int z = chunk.getPos().z;
-+        Level level = chunk.getLevel();
-+        ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1));
-+        executor.execute((Runnable) chunkPacketInfo);
-+    }
-+
-+    // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal)
-+    // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
-+    private final ThreadLocal<int[]> presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]);
-+    private static final ThreadLocal<boolean[]> SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
-+    private static final ThreadLocal<boolean[]> OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
-+    // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
-+    private static final ThreadLocal<boolean[][]> CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]);
-+    private static final ThreadLocal<boolean[][]> NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
-+    private static final ThreadLocal<boolean[][]> NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
-+
-+    public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
-+        int[] presetBlockStateBits = this.presetBlockStateBits.get();
-+        boolean[] solid = SOLID.get();
-+        boolean[] obfuscate = OBFUSCATE.get();
-+        boolean[][] current = CURRENT.get();
-+        boolean[][] next = NEXT.get();
-+        boolean[][] nextNext = NEXT_NEXT.get();
-+        // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
-+        BitStorageReader bitStorageReader = new BitStorageReader();
-+        BitStorageWriter bitStorageWriter = new BitStorageWriter();
-+        LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4];
-+        LevelChunk chunk = chunkPacketInfoAntiXray.getChunk();
-+        Level level = chunk.getLevel();
-+        int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSectionY(), chunk.getSectionsCount()) - 1;
-+        boolean[] solidTemp = null;
-+        boolean[] obfuscateTemp = null;
-+        bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer());
-+        bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer());
-+        int numberOfBlocks = presetBlockStateBits.length;
-+        // Keep the lambda expressions as simple as possible. They are used very frequently.
-+        LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() {
-+            // engine-mode: 3
-+            private int state;
-+            private int next;
-+
-+            {
-+                while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
-+            }
-+
-+            @Override
-+            public void nextLayer() {
-+                // https://en.wikipedia.org/wiki/Xorshift
-+                state ^= state << 13;
-+                state ^= state >>> 17;
-+                state ^= state << 5;
-+                // https://www.pcg-random.org/posts/bounded-rands.html
-+                next = (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
-+            }
-+
-+            @Override
-+            public int getAsInt() {
-+                return next;
-+            }
-+        } : new LayeredIntSupplier() {
-+            // engine-mode: 2
-+            private int state;
-+
-+            {
-+                while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
-+            }
-+
-+            @Override
-+            public int getAsInt() {
-+                // https://en.wikipedia.org/wiki/Xorshift
-+                state ^= state << 13;
-+                state ^= state >>> 17;
-+                state ^= state << 5;
-+                // https://www.pcg-random.org/posts/bounded-rands.html
-+                return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
-+            }
-+        };
-+
-+        for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) {
-+            if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) {
-+                int[] presetBlockStateBitsTemp;
-+
-+                if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) {
-+                    if (engineMode == EngineMode.HIDE) {
-+                        presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) {
-+                            case NETHER -> presetBlockStateBitsNetherrackGlobal;
-+                            case THE_END -> presetBlockStateBitsEndStoneGlobal;
-+                            default -> chunkSectionIndex + chunk.getMinSectionY() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal;
-+                        };
-+                    } else {
-+                        presetBlockStateBitsTemp = presetBlockStateBitsGlobal;
-+                    }
-+                } else {
-+                    // If it's presetBlockStates, use this.presetBlockStatesFull instead
-+                    BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex);
-+                    presetBlockStateBitsTemp = presetBlockStateBits;
-+
-+                    for (int i = 0; i < presetBlockStateBitsTemp.length; i++) {
-+                        // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible
-+                        // For more details see the comments in the readPalette method
-+                        presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]);
-+                    }
-+                }
-+
-+                bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
-+
-+                // Check if the chunk section below was not obfuscated
-+                if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) {
-+                    // If so, initialize some stuff
-+                    bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
-+                    bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
-+                    solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal);
-+                    obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal);
-+                    // Read the blocks of the upper layer of the chunk section below if it exists
-+                    LevelChunkSection belowChunkSection = null;
-+                    boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION;
-+
-+                    for (int z = 0; z < 16; z++) {
-+                        for (int x = 0; x < 16; x++) {
-+                            current[z][x] = true;
-+                            next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z);
-+                        }
-+                    }
-+
-+                    // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section
-+                    bitStorageWriter.setBits(0);
-+                    obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random);
-+                }
-+
-+                bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
-+                nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex];
-+                nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex];
-+                nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex];
-+                nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex];
-+
-+                // Obfuscate all layers of the current chunk section except the upper one
-+                for (int y = 0; y < 15; y++) {
-+                    boolean[][] temp = current;
-+                    current = next;
-+                    next = nextNext;
-+                    nextNext = temp;
-+                    random.nextLayer();
-+                    obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
-+                }
-+
-+                // Check if the chunk section above doesn't need obfuscation
-+                if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) {
-+                    // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists
-+                    LevelChunkSection aboveChunkSection;
-+
-+                    if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) {
-+                        boolean[][] temp = current;
-+                        current = next;
-+                        next = nextNext;
-+                        nextNext = temp;
-+
-+                        for (int z = 0; z < 16; z++) {
-+                            for (int x = 0; x < 16; x++) {
-+                                if (isTransparent(aboveChunkSection, x, 0, z)) {
-+                                    current[z][x] = true;
-+                                }
-+                            }
-+                        }
-+
-+                        // There is nothing to read anymore
-+                        bitStorageReader.setBits(0);
-+                        solid[0] = true;
-+                        random.nextLayer();
-+                        obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
-+                    }
-+                } else {
-+                    // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section
-+                    bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1));
-+                    bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1));
-+                    solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal);
-+                    obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal);
-+                    boolean[][] temp = current;
-+                    current = next;
-+                    next = nextNext;
-+                    nextNext = temp;
-+                    random.nextLayer();
-+                    obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
-+                }
-+
-+                bitStorageWriter.flush();
-+            }
-+        }
-+
-+        chunkPacketInfoAntiXray.getChunkPacket().setReady(true);
-+    }
-+
-+    private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) {
-+        // First block of first line
-+        int bits = bitStorageReader.read();
-+
-+        if (nextNext[0][0] = !solid[bits]) {
-+            bitStorageWriter.skip();
-+            next[0][1] = true;
-+            next[1][0] = true;
-+        } else {
-+            if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) {
-+                bitStorageWriter.skip();
-+            } else {
-+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+            }
-+        }
-+
-+        if (!obfuscate[bits]) {
-+            next[0][0] = true;
-+        }
-+
-+        // First line
-+        for (int x = 1; x < 15; x++) {
-+            bits = bitStorageReader.read();
-+
-+            if (nextNext[0][x] = !solid[bits]) {
-+                bitStorageWriter.skip();
-+                next[0][x - 1] = true;
-+                next[0][x + 1] = true;
-+                next[1][x] = true;
-+            } else {
-+                if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) {
-+                    bitStorageWriter.skip();
-+                } else {
-+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+                }
-+            }
-+
-+            if (!obfuscate[bits]) {
-+                next[0][x] = true;
-+            }
-+        }
-+
-+        // Last block of first line
-+        bits = bitStorageReader.read();
-+
-+        if (nextNext[0][15] = !solid[bits]) {
-+            bitStorageWriter.skip();
-+            next[0][14] = true;
-+            next[1][15] = true;
-+        } else {
-+            if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) {
-+                bitStorageWriter.skip();
-+            } else {
-+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+            }
-+        }
-+
-+        if (!obfuscate[bits]) {
-+            next[0][15] = true;
-+        }
-+
-+        // All inner lines
-+        for (int z = 1; z < 15; z++) {
-+            // First block
-+            bits = bitStorageReader.read();
-+
-+            if (nextNext[z][0] = !solid[bits]) {
-+                bitStorageWriter.skip();
-+                next[z][1] = true;
-+                next[z - 1][0] = true;
-+                next[z + 1][0] = true;
-+            } else {
-+                if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) {
-+                    bitStorageWriter.skip();
-+                } else {
-+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+                }
-+            }
-+
-+            if (!obfuscate[bits]) {
-+                next[z][0] = true;
-+            }
-+
-+            // All inner blocks
-+            for (int x = 1; x < 15; x++) {
-+                bits = bitStorageReader.read();
-+
-+                if (nextNext[z][x] = !solid[bits]) {
-+                    bitStorageWriter.skip();
-+                    next[z][x - 1] = true;
-+                    next[z][x + 1] = true;
-+                    next[z - 1][x] = true;
-+                    next[z + 1][x] = true;
-+                } else {
-+                    if (current[z][x]) {
-+                        bitStorageWriter.skip();
-+                    } else {
-+                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+                    }
-+                }
-+
-+                if (!obfuscate[bits]) {
-+                    next[z][x] = true;
-+                }
-+            }
-+
-+            // Last block
-+            bits = bitStorageReader.read();
-+
-+            if (nextNext[z][15] = !solid[bits]) {
-+                bitStorageWriter.skip();
-+                next[z][14] = true;
-+                next[z - 1][15] = true;
-+                next[z + 1][15] = true;
-+            } else {
-+                if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) {
-+                    bitStorageWriter.skip();
-+                } else {
-+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+                }
-+            }
-+
-+            if (!obfuscate[bits]) {
-+                next[z][15] = true;
-+            }
-+        }
-+
-+        // First block of last line
-+        bits = bitStorageReader.read();
-+
-+        if (nextNext[15][0] = !solid[bits]) {
-+            bitStorageWriter.skip();
-+            next[15][1] = true;
-+            next[14][0] = true;
-+        } else {
-+            if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) {
-+                bitStorageWriter.skip();
-+            } else {
-+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+            }
-+        }
-+
-+        if (!obfuscate[bits]) {
-+            next[15][0] = true;
-+        }
-+
-+        // Last line
-+        for (int x = 1; x < 15; x++) {
-+            bits = bitStorageReader.read();
-+
-+            if (nextNext[15][x] = !solid[bits]) {
-+                bitStorageWriter.skip();
-+                next[15][x - 1] = true;
-+                next[15][x + 1] = true;
-+                next[14][x] = true;
-+            } else {
-+                if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) {
-+                    bitStorageWriter.skip();
-+                } else {
-+                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+                }
-+            }
-+
-+            if (!obfuscate[bits]) {
-+                next[15][x] = true;
-+            }
-+        }
-+
-+        // Last block of last line
-+        bits = bitStorageReader.read();
-+
-+        if (nextNext[15][15] = !solid[bits]) {
-+            bitStorageWriter.skip();
-+            next[15][14] = true;
-+            next[14][15] = true;
-+        } else {
-+            if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) {
-+                bitStorageWriter.skip();
-+            } else {
-+                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
-+            }
-+        }
-+
-+        if (!obfuscate[bits]) {
-+            next[15][15] = true;
-+        }
-+    }
-+
-+    private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) {
-+        if (chunkSection == EMPTY_SECTION) {
-+            return true;
-+        }
-+
-+        try {
-+            return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))];
-+        } catch (MissingPaletteEntryException e) {
-+            // Race condition / visibility issue / no happens-before relationship
-+            // We don't care and treat the block as transparent
-+            // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur
-+            return true;
-+        }
-+    }
-+
-+    private boolean[] readPalette(Palette<BlockState> palette, boolean[] temp, boolean[] global) {
-+        if (palette instanceof GlobalPalette) {
-+            return global;
-+        }
-+
-+        try {
-+            for (int i = 0; i < palette.getSize(); i++) {
-+                temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))];
-+            }
-+        } catch (MissingPaletteEntryException e) {
-+            // Race condition / visibility issue / no happens-before relationship
-+            // We don't care because we at least see the state as it was when the chunk packet was created
-+            // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here
-+            // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data
-+        }
-+
-+        return temp;
-+    }
-+
-+    @Override
-+    public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
-+        if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) {
-+            updateNearbyBlocks(level, blockPos);
-+        }
-+    }
-+
-+    @Override
-+    public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) {
-+        if (blockPos.getY() <= maxBlockHeightUpdatePosition) {
-+            updateNearbyBlocks(serverPlayerGameMode.level, blockPos);
-+        }
-+    }
-+
-+    private void updateNearbyBlocks(Level level, BlockPos blockPos) {
-+        if (updateRadius >= 2) {
-+            BlockPos temp = blockPos.west();
-+            updateBlock(level, temp);
-+            updateBlock(level, temp.west());
-+            updateBlock(level, temp.below());
-+            updateBlock(level, temp.above());
-+            updateBlock(level, temp.north());
-+            updateBlock(level, temp.south());
-+            updateBlock(level, temp = blockPos.east());
-+            updateBlock(level, temp.east());
-+            updateBlock(level, temp.below());
-+            updateBlock(level, temp.above());
-+            updateBlock(level, temp.north());
-+            updateBlock(level, temp.south());
-+            updateBlock(level, temp = blockPos.below());
-+            updateBlock(level, temp.below());
-+            updateBlock(level, temp.north());
-+            updateBlock(level, temp.south());
-+            updateBlock(level, temp = blockPos.above());
-+            updateBlock(level, temp.above());
-+            updateBlock(level, temp.north());
-+            updateBlock(level, temp.south());
-+            updateBlock(level, temp = blockPos.north());
-+            updateBlock(level, temp.north());
-+            updateBlock(level, temp = blockPos.south());
-+            updateBlock(level, temp.south());
-+        } else if (updateRadius == 1) {
-+            updateBlock(level, blockPos.west());
-+            updateBlock(level, blockPos.east());
-+            updateBlock(level, blockPos.below());
-+            updateBlock(level, blockPos.above());
-+            updateBlock(level, blockPos.north());
-+            updateBlock(level, blockPos.south());
-+        } else {
-+            // Do nothing if updateRadius <= 0 (test mode)
-+        }
-+    }
-+
-+    private void updateBlock(Level level, BlockPos blockPos) {
-+        BlockState blockState = level.getBlockStateIfLoaded(blockPos);
-+
-+        if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) {
-+            ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
-+        }
-+    }
-+
-+    @FunctionalInterface
-+    private interface LayeredIntSupplier extends IntSupplier {
-+        default void nextLayer() {
-+
-+        }
-+    }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.chunk.Palette;
-+
-+public class ChunkPacketInfo<T> {
-+
-+    private final ClientboundLevelChunkWithLightPacket chunkPacket;
-+    private final LevelChunk chunk;
-+    private final int[] bits;
-+    private final Object[] palettes;
-+    private final int[] indexes;
-+    private final Object[][] presetValues;
-+    private byte[] buffer;
-+
-+    public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
-+        this.chunkPacket = chunkPacket;
-+        this.chunk = chunk;
-+        int sections = chunk.getSectionsCount();
-+        bits = new int[sections];
-+        palettes = new Object[sections];
-+        indexes = new int[sections];
-+        presetValues = new Object[sections][];
-+    }
-+
-+    public ClientboundLevelChunkWithLightPacket getChunkPacket() {
-+        return chunkPacket;
-+    }
-+
-+    public LevelChunk getChunk() {
-+        return chunk;
-+    }
-+
-+    public byte[] getBuffer() {
-+        return buffer;
-+    }
-+
-+    public void setBuffer(byte[] buffer) {
-+        this.buffer = buffer;
-+    }
-+
-+    public int getBits(int chunkSectionIndex) {
-+        return bits[chunkSectionIndex];
-+    }
-+
-+    public void setBits(int chunkSectionIndex, int bits) {
-+        this.bits[chunkSectionIndex] = bits;
-+    }
-+
-+    @SuppressWarnings("unchecked")
-+    public Palette<T> getPalette(int chunkSectionIndex) {
-+        return (Palette<T>) palettes[chunkSectionIndex];
-+    }
-+
-+    public void setPalette(int chunkSectionIndex, Palette<T> palette) {
-+        palettes[chunkSectionIndex] = palette;
-+    }
-+
-+    public int getIndex(int chunkSectionIndex) {
-+        return indexes[chunkSectionIndex];
-+    }
-+
-+    public void setIndex(int chunkSectionIndex, int index) {
-+        indexes[chunkSectionIndex] = index;
-+    }
-+
-+    @SuppressWarnings("unchecked")
-+    public T[] getPresetValues(int chunkSectionIndex) {
-+        return (T[]) presetValues[chunkSectionIndex];
-+    }
-+
-+    public void setPresetValues(int chunkSectionIndex, T[] presetValues) {
-+        this.presetValues[chunkSectionIndex] = presetValues;
-+    }
-+
-+    public boolean isWritten(int chunkSectionIndex) {
-+        return bits[chunkSectionIndex] != 0;
-+    }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.antixray;
-+
-+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+
-+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo<BlockState> implements Runnable {
-+
-+    private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray;
-+    private LevelChunk[] nearbyChunks;
-+
-+    public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) {
-+        super(chunkPacket, chunk);
-+        this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray;
-+    }
-+
-+    public LevelChunk[] getNearbyChunks() {
-+        return nearbyChunks;
-+    }
-+
-+    public void setNearbyChunks(LevelChunk... nearbyChunks) {
-+        this.nearbyChunks = nearbyChunks;
-+    }
-+
-+    @Override
-+    public void run() {
-+        chunkPacketBlockControllerAntiXray.obfuscate(this);
-+    }
-+}
-diff --git a/src/main/java/io/papermc/paper/FeatureHooks.java b/src/main/java/io/papermc/paper/FeatureHooks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/io/papermc/paper/FeatureHooks.java
-+++ b/src/main/java/io/papermc/paper/FeatureHooks.java
-@@ -0,0 +0,0 @@ import it.unimi.dsi.fastutil.longs.LongSets;
+diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
+index aad9d9687dffb872a12ba0ba39d674895b7474e7..764daee7cd619c56314bcea9a4c35702afcb262d 100644
+--- a/io/papermc/paper/FeatureHooks.java
++++ b/io/papermc/paper/FeatureHooks.java
+@@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.longs.LongSets;
  import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
  import it.unimi.dsi.fastutil.objects.ObjectSet;
  import it.unimi.dsi.fastutil.objects.ObjectSets;
@@ -1012,7 +16,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
-@@ -0,0 +0,0 @@ public final class FeatureHooks {
+@@ -35,20 +36,25 @@ public final class FeatureHooks {
      }
  
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
@@ -1042,84 +46,90 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
-@@ -0,0 +0,0 @@ public record ClientboundChunksBiomesPacket(List<ClientboundChunksBiomesPacket.C
+@@ -74,4 +80,4 @@ public final class FeatureHooks {
+     public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
+         return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
+     }
+-}
+\ No newline at end of file
++}
+diff --git a/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java b/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
+index d4872b7f4e9591b3b1c67406312905851303f521..cb41460e94161675e2ab43f4b1b5286ee38e2e13 100644
+--- a/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
+@@ -70,8 +70,10 @@ public record ClientboundChunksBiomesPacket(List<ClientboundChunksBiomesPacket.C
          }
  
-         public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) {
+         public static void extractChunkData(FriendlyByteBuf buffer, LevelChunk chunk) {
 +            int chunkSectionIndex = 0; // Paper - Anti-Xray
              for (LevelChunkSection levelChunkSection : chunk.getSections()) {
--                levelChunkSection.getBiomes().write(buf);
-+                levelChunkSection.getBiomes().write(buf, null, chunkSectionIndex); // Paper - Anti-Xray
+-                levelChunkSection.getBiomes().write(buffer);
++                levelChunkSection.getBiomes().write(buffer, null, chunkSectionIndex); // Paper - Anti-Xray
 +                chunkSectionIndex++; // Paper - Anti-Xray
              }
          }
  
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacketData {
+diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+index 5d1943d37dfad0c12e77179f0866851532d983e9..3aea76690bc3e35758d3bf274777130af17d8a0f 100644
+--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+@@ -28,7 +28,13 @@ public class ClientboundLevelChunkPacketData {
      private final byte[] buffer;
      private final List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData;
  
--    public ClientboundLevelChunkPacketData(LevelChunk chunk) {
 +    // Paper start - Anti-Xray - Add chunk packet info
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); }
-+    public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public ClientboundLevelChunkPacketData(LevelChunk levelChunk) {
++        this(levelChunk, null);
++    }
++    public ClientboundLevelChunkPacketData(LevelChunk levelChunk, io.papermc.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
 +        // Paper end
          this.heightmaps = new CompoundTag();
  
-         for (Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacketData {
+         for (Entry<Heightmap.Types, Heightmap> entry : levelChunk.getHeightmaps()) {
+@@ -38,7 +44,11 @@ public class ClientboundLevelChunkPacketData {
          }
  
-         this.buffer = new byte[calculateChunkSize(chunk)];
--        extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk);
-+
+         this.buffer = new byte[calculateChunkSize(levelChunk)];
+-        extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk);
 +        // Paper start - Anti-Xray - Add chunk packet info
 +        if (chunkPacketInfo != null) {
 +            chunkPacketInfo.setBuffer(this.buffer);
 +        }
-+
-+        extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo);
-+        // Paper end
++        extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk, chunkPacketInfo);
          this.blockEntitiesData = Lists.newArrayList();
  
-         for (Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) {
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacketData {
+         for (Entry<BlockPos, BlockEntity> entryx : levelChunk.getBlockEntities().entrySet()) {
+@@ -85,9 +95,17 @@ public class ClientboundLevelChunkPacketData {
          return byteBuf;
      }
  
--    public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) {
 +    // Paper start - Anti-Xray - Add chunk packet info
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); }
-+    public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public static void extractChunkData(FriendlyByteBuf buffer, LevelChunk chunk) {
++        ClientboundLevelChunkPacketData.extractChunkData(buffer, chunk, null);
++    }
++    public static void extractChunkData(FriendlyByteBuf buffer, LevelChunk chunk, io.papermc.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
 +        int chunkSectionIndex = 0;
-+
          for (LevelChunkSection levelChunkSection : chunk.getSections()) {
--            levelChunkSection.write(buf);
-+            levelChunkSection.write(buf, chunkPacketInfo, chunkSectionIndex);
+-            levelChunkSection.write(buffer);
++            levelChunkSection.write(buffer, chunkPacketInfo, chunkSectionIndex);
 +            chunkSectionIndex++;
-+            // Paper end
++            // Paper end  - Anti-Xray - Add chunk packet info
          }
      }
  
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-@@ -0,0 +0,0 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
+diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+index aadf2dccb996e422cacf8bb510cc642e69ee4972..d2d21fe8d7275b01454e09be252d7dd7710cdc2d 100644
+--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+@@ -18,13 +18,31 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
      private final int z;
      private final ClientboundLevelChunkPacketData chunkData;
      private final ClientboundLightUpdatePacketData lightData;
-+    // Paper start - Async-Anti-Xray - Ready flag for the connection
++    // Paper start - Async-Anti-Xray - Ready flag for the connection, add chunk packet info
 +    private volatile boolean ready;
  
--    public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits) {
 +    @Override
 +    public boolean isReady() {
 +        return this.ready;
@@ -1128,267 +138,268 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public void setReady(boolean ready) {
 +        this.ready = ready;
 +    }
-+    // Paper end
 +
-+    // Paper start - Anti-Xray - Add chunk packet info
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits) { this(chunk, lightProvider, skyBits, blockBits, true); }
-+    public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean modifyBlocks) {
-         ChunkPos chunkPos = chunk.getPos();
-         this.x = chunkPos.x;
-         this.z = chunkPos.z;
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight) {
++        this(chunk, lightEngine, skyLight, blockLight, true);
++    }
++    public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight, boolean modifyBlocks) {
++        // Paper end - Anti-Xray
+         ChunkPos pos = chunk.getPos();
+         this.x = pos.x;
+         this.z = pos.z;
 -        this.chunkData = new ClientboundLevelChunkPacketData(chunk);
-+        com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null;
-+        this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo);
-+        // Paper end
-         this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits);
++        io.papermc.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; // Paper - Ant-Xray
++        this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); // Paper - Anti-Xray
+         this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight);
 +        chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
      }
  
-     private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buf) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+     private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index c2d9f1e6c68f4945e3b9e5b95326e8180fa7a0fb..bb43dff0d55af335ed54f3a8843633b20b7ee63b 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -348,7 +348,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit
+     ) {
+         // CraftBukkit start
+-        super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules()))); // Paper - create paper world configs
++        super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs; Async-Anti-Xray: Pass executor
+         this.pvpMode = server.isPvpAllowed();
+         this.levelStorageAccess = levelStorageAccess;
+         this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
+diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
+index d6a493de667bfe97b722efe40d1530bdb666bb94..bb352ad40da33a9d411836d4fab2664e226a6e38 100644
+--- a/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -28,7 +28,7 @@ import org.slf4j.Logger;
  
-     // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
-     public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
--        super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs
-+        super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules())), executor); // Paper - create paper world configs; Async-Anti-Xray: Pass executor
-         this.pvpMode = minecraftserver.isPvpAllowed();
-         this.convertable = convertable_conversionsession;
-         this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.player.PlayerInteractEvent;
  public class ServerPlayerGameMode {
- 
      private static final Logger LOGGER = LogUtils.getLogger();
 -    protected ServerLevel level;
 +    public ServerLevel level; // Paper - Anti-Xray - protected -> public
      protected final ServerPlayer player;
-     private GameType gameModeForPlayer;
+     private GameType gameModeForPlayer = GameType.DEFAULT_MODE;
      @Nullable
-@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
+@@ -312,6 +312,7 @@ public class ServerPlayerGameMode {
+                 org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
              }
- 
          }
-+
-+        this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray
++        this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, face, maxBuildHeight, sequence); // Paper - Anti-Xray
      }
  
-     public void destroyAndAck(BlockPos pos, int sequence, String reason) {
-diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java
-+++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java
-@@ -0,0 +0,0 @@ public class PlayerChunkSender {
+     public void destroyAndAck(BlockPos pos, int sequence, String message) {
+diff --git a/net/minecraft/server/network/PlayerChunkSender.java b/net/minecraft/server/network/PlayerChunkSender.java
+index 342bc843c384761e883de861044f4f8930ae8763..14878690a88fd4de3e2c127086607e6c819c636c 100644
+--- a/net/minecraft/server/network/PlayerChunkSender.java
++++ b/net/minecraft/server/network/PlayerChunkSender.java
+@@ -78,8 +78,11 @@ public class PlayerChunkSender {
          }
      }
  
--    private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) {
--        handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null));
-+    public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - public
-+        // Paper start - Anti-Xray
-+        final boolean shouldModify = world.chunkPacketBlockController.shouldModify(handler.player, chunk);
-+        handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null, shouldModify));
+-    private static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
+-        packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null));
++    // Paper start - Anti-Xray
++    public static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
++        final boolean shouldModify = level.chunkPacketBlockController.shouldModify(packetListener.player, chunk);
++        packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify));
 +        // Paper end - Anti-Xray
          // Paper start - PlayerChunkLoadEvent
          if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
-             new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent();
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+             new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
+index 2580b249d9a024400e295ca5beab551b3571ceb3..d227714de0fe13544779fae6cf0e9ff6af5469c7 100644
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -404,7 +404,7 @@ public abstract class PlayerList {
                      .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
              player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
-                     new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains),
--                    worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null)
-+                    worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, true) // Paper - Anti-Xray
+                     new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, player.chunkPosition(), plains),
+-                    serverLevel.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null)
++                    serverLevel.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, true) // Paper - Anti-Xray
              );
          }
          // Paper end - Send empty chunk
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index 051a86e8a723aeb86ffa06e3cd5fab102a808cde..2c2eae0c8656055940c6e1457fc77a20bb58dd8e 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -168,6 +168,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      }
      // Paper end - add paper world config
  
-+    public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
++    public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
      public static BlockPos lastPhysicsProblem; // Spigot
      private org.spigotmc.TickLimiter entityLimiter;
      private org.spigotmc.TickLimiter tileLimiter;
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- 
-     public abstract ResourceKey<LevelStem> getTypeKey();
- 
--    protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config
-+    protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config & Anti-Xray
-         this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+@@ -214,7 +215,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+         org.bukkit.generator.BiomeProvider biomeProvider, // CraftBukkit
+         org.bukkit.World.Environment env, // CraftBukkit
+         java.util.function.Function<org.spigotmc.SpigotWorldConfig, // Spigot - create per world config
+-        io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator // Paper - create paper world config
++        io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
++        java.util.concurrent.Executor executor // Paper - Anti-Xray
+     ) {
+         this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot
          this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
-         this.generator = gen;
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -295,6 +297,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          // CraftBukkit end
          this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
          this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
-+        this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
++        this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
      }
  
      // Paper start - Cancel hit for vanished players
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -495,6 +498,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
              // CraftBukkit end
  
-             BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
-+            this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray
+             BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
++            this.chunkPacketBlockController.onBlockChange(this, pos, state, blockState, flags, recursionLeft); // Paper - Anti-Xray
  
-             if (iblockdata1 == null) {
+             if (blockState == null) {
                  // CraftBukkit start - remove blockstate if failed (or the same)
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
+index f68f3f5e8ef39a0dc371e75110227a39791c04c8..254ca42e08148915b96ce77310a46e09de84ef53 100644
+--- a/net/minecraft/world/level/chunk/ChunkAccess.java
++++ b/net/minecraft/world/level/chunk/ChunkAccess.java
+@@ -114,14 +114,14 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
              }
          }
  
--        ChunkAccess.replaceMissingSections(biomeRegistry, this.sections);
+-        replaceMissingSections(biomeRegistry, this.sections);
 +        this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method
-         // CraftBukkit start
-         this.biomeRegistry = biomeRegistry;
+         this.biomeRegistry = biomeRegistry; // Craftbukkit
      }
-     public final Registry<Biome> biomeRegistry;
-     // CraftBukkit end
  
--    private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) {
-+    private void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) { // Paper - Anti-Xray - make it a non-static method
-         for (int i = 0; i < sectionArray.length; ++i) {
-             if (sectionArray[i] == null) {
--                sectionArray[i] = new LevelChunkSection(biomeRegistry);
-+                sectionArray[i] = new LevelChunkSection(biomeRegistry, this.levelHeightAccessor instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) this.levelHeightAccessor : null, this.chunkPos, this.levelHeightAccessor.getSectionYFromSectionIndex(i)); // Paper - Anti-Xray - Add parameters
+-    private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
++    private void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) { // Paper - Anti-Xray - make it a non-static method
+         for (int i = 0; i < sections.length; i++) {
+             if (sections[i] == null) {
+-                sections[i] = new LevelChunkSection(biomeRegistry);
++                sections[i] = new LevelChunkSection(biomeRegistry, this.levelHeightAccessor instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) this.levelHeightAccessor : null, this.chunkPos, this.levelHeightAccessor.getSectionYFromSectionIndex(i)); // Paper - Anti-Xray - Add parameters
              }
          }
- 
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
      }
+diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
+index b3cc671f33b2c8c5f3131afffc6ee9d7b83dd3bc..d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba 100644
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -109,7 +109,7 @@ public class LevelChunk extends ChunkAccess {
+         @Nullable LevelChunk.PostLoadProcessor postLoad,
+         @Nullable BlendingData blendingData
+     ) {
+-        super(pos, data, level, level.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData);
++        super(pos, data, level, net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry
+         this.level = (net.minecraft.server.level.ServerLevel) level; // CraftBukkit - type
+         this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
  
-     public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) {
--        super(pos, upgradeData, world, world.registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData);
-+        super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry
-         this.tickersInLevel = Maps.newHashMap();
-         this.unsavedListener = (chunkcoordintpair1) -> {
-         };
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java
+index baa9f3e2e6e45c250930658e82bad70a3a292b05..fc21c3268c4b4fda2933d71f0913db28e3796653 100644
+--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -38,9 +38,15 @@ public class LevelChunkSection {
          this.recalcBlockCounts();
      }
  
--    public LevelChunkSection(Registry<Biome> biomeRegistry) {
++    // Paper start - Anti-Xray - Add parameters
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public LevelChunkSection(Registry<Biome> biomeRegistry) {
 -        this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
 -        this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
-+    // Paper start - Anti-Xray - Add parameters
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(Registry<Biome> biomeRegistry) { this(biomeRegistry, null, null, 0); }
++        this(biomeRegistry, null, null, 0);
++    }
 +    public LevelChunkSection(Registry<Biome> biomeRegistry, net.minecraft.world.level.Level level, net.minecraft.world.level.ChunkPos chunkPos, int chunkSectionY) {
-+    // Paper end
++        // Paper end - Anti-Xray
 +        this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, chunkPos, chunkSectionY)); // Paper - Anti-Xray - Add preset block states
 +        this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
      }
  
      public BlockState getBlockState(int x, int y, int z) {
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
-         this.biomes = datapaletteblock;
+@@ -168,10 +174,16 @@ public class LevelChunkSection {
+         this.biomes = palettedContainer;
      }
  
--    public void write(FriendlyByteBuf buf) {
 +    // Paper start - Anti-Xray - Add chunk packet info
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); }
-+    public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo, int chunkSectionIndex) {
-         buf.writeShort(this.nonEmptyBlockCount);
--        this.states.write(buf);
--        this.biomes.write(buf);
-+        this.states.write(buf, chunkPacketInfo, chunkSectionIndex);
-+        this.biomes.write(buf, null, chunkSectionIndex);
-+        // Paper end
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public void write(FriendlyByteBuf buffer) {
++        this.write(buffer, null, 0);
++    }
++    public void write(FriendlyByteBuf buffer, io.papermc.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo, int chunkSectionIndex) {
+         buffer.writeShort(this.nonEmptyBlockCount);
+-        this.states.write(buffer);
+-        this.biomes.write(buffer);
++        this.states.write(buffer, chunkPacketInfo, chunkSectionIndex);
++        this.biomes.write(buffer, null, chunkSectionIndex);
++        // Paper end - Anti-Xray
      }
  
      public int getSerializedSize() {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java
+index e8ec28ce3fe13561b45c4654e174776d9d2d7b71..a6028a54c75de068515e95913b21160ab4326985 100644
+--- a/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -28,6 +28,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      private static final int MIN_PALETTE_BITS = 0;
-     private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
+     private final PaletteResize<T> dummyPaletteResize = (bits, objectAdded) -> 0;
      public final IdMap<T> registry;
 +    private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
      private volatile PalettedContainer.Data<T> data;
      private final PalettedContainer.Strategy strategy;
-     // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-         // this.threadingDetector.checkAndUnlock(); // Paper - disable this
+     //private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+@@ -40,13 +41,21 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         // this.threadingDetector.checkAndUnlock(); // Paper - disable this - use proper synchronization
      }
  
--    public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) {
--        PalettedContainerRO.Unpacker<T, PalettedContainer<T>> unpacker = PalettedContainer::unpack;
 +    // Paper start - Anti-Xray - Add preset values
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); }
-+    public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) {
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> registry, Codec<T> codec, PalettedContainer.Strategy strategy, T value) {
+-        PalettedContainerRO.Unpacker<T, PalettedContainer<T>> unpacker = PalettedContainer::unpack;
++        return PalettedContainer.codecRW(registry, codec, strategy, value, null);
++    }
++    public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> registry, Codec<T> codec, PalettedContainer.Strategy strategy, T value, T @org.jetbrains.annotations.Nullable [] presetValues) {
 +        PalettedContainerRO.Unpacker<T, PalettedContainer<T>> unpacker = (idListx, paletteProviderx, serialized) -> {
-+            return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues);
++            return unpack(idListx, paletteProviderx, serialized, value, presetValues);
 +        };
 +        // Paper end
-         return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker);
+         return codec(registry, codec, strategy, value, unpacker);
      }
  
-     public static <T> Codec<PalettedContainerRO<T>> codecRO(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) {
-         PalettedContainerRO.Unpacker<T, PalettedContainerRO<T>> unpacker = (idListx, paletteProviderx, serialized) -> unpack(
--                    idListx, paletteProviderx, serialized
-+                    idListx, paletteProviderx, serialized, defaultValue, null // Paper - Anti-Xray - Add preset values
-                 )
-                 .map(result -> (PalettedContainerRO<T>)result);
-         return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker);
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+     public static <T> Codec<PalettedContainerRO<T>> codecRO(IdMap<T> registry, Codec<T> codec, PalettedContainer.Strategy strategy, T value) {
+-        PalettedContainerRO.Unpacker<T, PalettedContainerRO<T>> unpacker = (registry1, strategy1, packedData) -> unpack(registry1, strategy1, packedData)
++        PalettedContainerRO.Unpacker<T, PalettedContainerRO<T>> unpacker = (registry1, strategy1, packedData) -> unpack(registry1, strategy1, packedData, value, null) // Paper - Anti-Xray - Add preset values
+             .map(container -> (PalettedContainerRO<T>)container);
+         return codec(registry, codec, strategy, value, unpacker);
+     }
+@@ -66,27 +75,66 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              );
      }
  
 +    // Paper start - Anti-Xray - Add preset values
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); }
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
++    public PalettedContainer(IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Configuration<T> configuration, BitStorage storage, List<T> values) {
++        this(registry, strategy, configuration, storage, values, null, null);
++    }
      public PalettedContainer(
-         IdMap<T> idList,
-         PalettedContainer.Strategy paletteProvider,
-         PalettedContainer.Configuration<T> dataProvider,
-         BitStorage storage,
--        List<T> paletteEntries
-+        List<T> paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues
+-        IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Configuration<T> configuration, BitStorage storage, List<T> values
++        IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Configuration<T> configuration, BitStorage storage, List<T> values, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues
      ) {
 +        this.presetValues = presetValues;
-         this.registry = idList;
-         this.strategy = paletteProvider;
-         this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries));
-+
-+        if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) {
+         this.registry = registry;
+         this.strategy = strategy;
+         this.data = new PalettedContainer.Data<>(configuration, storage, configuration.factory().create(configuration.bits(), registry, this, values));
++        if (presetValues != null && (configuration.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : configuration.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) {
 +            // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us
 +            // We readd this here but in a smarter way than it was before
-+            int maxSize = 1 << dataProvider.bits();
++            int maxSize = 1 << configuration.bits();
 +
 +            for (T presetValue : presetValues) {
 +                if (this.data.palette.getSize() >= maxSize) {
-+                    java.util.Set<T> allValues = new java.util.HashSet<>(paletteEntries);
++                    java.util.Set<T> allValues = new java.util.HashSet<>(values);
 +                    allValues.addAll(Arrays.asList(presetValues));
 +                    int newBits = Mth.ceillog2(allValues.size());
 +
-+                    if (newBits > dataProvider.bits()) {
++                    if (newBits > configuration.bits()) {
 +                        this.onResize(newBits, null);
 +                    }
 +
@@ -1401,57 +412,56 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end
      }
  
--    private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data) {
+-    private PalettedContainer(IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Data<T> data) {
 +    // Paper start - Anti-Xray - Add preset values
-+    private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data, T @org.jetbrains.annotations.Nullable [] presetValues) {
++    private PalettedContainer(IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Data<T> data, T @org.jetbrains.annotations.Nullable [] presetValues) {
 +        this.presetValues = presetValues;
-+        // Paper end
-         this.registry = idList;
-         this.strategy = paletteProvider;
++        // Paper end - Anti-Xray
+         this.registry = registry;
+         this.strategy = strategy;
          this.data = data;
      }
  
--    private PalettedContainer(PalettedContainer<T> container) {
-+    private PalettedContainer(PalettedContainer<T> container, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values
+-    private PalettedContainer(PalettedContainer<T> other) {
++    private PalettedContainer(PalettedContainer<T> other, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values
 +        this.presetValues = presetValues; // Paper - Anti-Xray - Add preset values
-         this.registry = container.registry;
-         this.strategy = container.strategy;
-         this.data = container.data.copy(this);
+         this.registry = other.registry;
+         this.strategy = other.strategy;
+         this.data = other.data.copy(this);
      }
  
--    public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) {
 +    // Paper start - Anti-Xray - Add preset values
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); }
-+    public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) {
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public PalettedContainer(IdMap<T> registry, T palette, PalettedContainer.Strategy strategy) {
++        this(registry, palette, strategy, null);
++    }
++    public PalettedContainer(IdMap<T> registry, T palette, PalettedContainer.Strategy strategy, T @org.jetbrains.annotations.Nullable [] presetValues) {
 +        this.presetValues = presetValues;
-+        // Paper end
-         this.strategy = paletteProvider;
-         this.registry = idList;
++        // Paper end - Anti-Xray
+         this.strategy = strategy;
+         this.registry = registry;
          this.data = this.createOrReuseData(null, 0);
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -101,11 +149,30 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      @Override
-     public synchronized int onResize(int newBits, T object) { // Paper - synchronize
+     public synchronized int onResize(int bits, T objectAdded) { // Paper - synchronize
          PalettedContainer.Data<T> data = this.data;
-+
 +        // Paper start - Anti-Xray - Add preset values
-+        if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) {
++        if (this.presetValues != null && objectAdded != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) {
 +            int duplicates = 0;
 +            List<T> presetValues = Arrays.asList(this.presetValues);
-+            duplicates += presetValues.contains(object) ? 1 : 0;
++            duplicates += presetValues.contains(objectAdded) ? 1 : 0;
 +            duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0;
-+            newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates);
++            bits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << bits)) + presetValues.size() - duplicates);
 +        }
-+
-         PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits);
-         data2.copyFrom(data.palette, data.storage);
-         this.data = data2;
--        return data2.palette.idFor(object);
++        // Paper end - Anti-Xray
+         PalettedContainer.Data<T> data1 = this.createOrReuseData(data, bits);
+         data1.copyFrom(data.palette, data.storage);
+         this.data = data1;
+-        return data1.palette.idFor(objectAdded);
++        // Paper start - Anti-Xray
 +        this.addPresetValues();
-+        return object == null ? -1 : data2.palette.idFor(object);
-+        // Paper end
++        return objectAdded == null ? -1 : data1.palette.idFor(objectAdded);
 +    }
-+
-+    // Paper start - Anti-Xray - Add preset values
 +    private void addPresetValues() {
 +        if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) {
 +            for (T presetValue : this.presetValues) {
@@ -1459,13 +469,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        }
      }
-+    // Paper end
++    // Paper end - Anti-Xray
  
-     public T getAndSet(int x, int y, int z, T value) {
+     public synchronized T getAndSet(int x, int y, int z, T state) { // Paper - synchronize
          this.acquire();
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-             data.palette.read(buf);
-             buf.readLongArray(data.storage.getRaw());
+@@ -172,24 +239,35 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+             data.palette.read(buffer);
+             buffer.readLongArray(data.storage.getRaw());
              this.data = data;
 +            this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server)
          } finally {
@@ -1474,42 +484,44 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 +    // Paper start - Anti-Xray; Add chunk packet info
-+    @Override
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); }
      @Override
--    public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
-+    public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) { // Paper - Synchronize
+-    public synchronized void write(FriendlyByteBuf buffer) { // Paper - synchronize
++    @Deprecated @io.papermc.paper.annotation.DoNotUse
++    public void write(FriendlyByteBuf buffer) {
++        this.write(buffer, null, 0);
++    }
++    @Override
++    public synchronized void write(FriendlyByteBuf buffer, @Nullable io.papermc.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) { // Paper - Synchronize
          this.acquire();
  
          try {
--            this.data.write(buf);
-+            this.data.write(buf, chunkPacketInfo, chunkSectionIndex);
-+
+-            this.data.write(buffer);
++            this.data.write(buffer, chunkPacketInfo, chunkSectionIndex);
 +            if (chunkPacketInfo != null) {
 +                chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues);
 +            }
-+            // Paper end
++            // Paper end - Anti-Xray
          } finally {
              this.release();
          }
      }
  
      private static <T> DataResult<PalettedContainer<T>> unpack(
--        IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData<T> serialized
-+        IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData<T> serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues // Paper - Anti-Xray - Add preset values
+-        IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainerRO.PackedData<T> packedData
++        IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainerRO.PackedData<T> packedData, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues // Paper - Anti-Xray - Add preset values
      ) {
-         List<T> list = serialized.paletteEntries();
-         int i = paletteProvider.size();
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         List<T> list = packedData.paletteEntries();
+         int size = strategy.size();
+@@ -222,7 +300,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              }
          }
  
--        return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list));
-+        return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values
+-        return DataResult.success(new PalettedContainer<>(registry, strategy, configuration, bitStorage, list));
++        return DataResult.success(new PalettedContainer<>(registry, strategy, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -280,12 +358,12 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
  
      @Override
      public PalettedContainer<T> copy() {
@@ -1524,100 +536,96 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -324,9 +402,16 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              return 1 + this.palette.getSerializedSize() + VarInt.getByteSize(this.storage.getRaw().length) + this.storage.getRaw().length * 8;
          }
  
--        public void write(FriendlyByteBuf buf) {
+-        public void write(FriendlyByteBuf buffer) {
 +        // Paper start - Anti-Xray - Add chunk packet info
-+        public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) {
-             buf.writeByte(this.storage.getBits());
-             this.palette.write(buf);
-+
++        public void write(FriendlyByteBuf buffer, @Nullable io.papermc.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) {
+             buffer.writeByte(this.storage.getBits());
+             this.palette.write(buffer);
 +            if (chunkPacketInfo != null) {
 +                chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits());
 +                chunkPacketInfo.setPalette(chunkSectionIndex, this.palette);
-+                chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + VarInt.getByteSize(this.storage.getRaw().length));
++                chunkPacketInfo.setIndex(chunkSectionIndex, buffer.writerIndex() + VarInt.getByteSize(this.storage.getRaw().length));
 +            }
 +            // Paper end
-+
-             buf.writeLongArray(this.storage.getRaw());
+             buffer.writeLongArray(this.storage.getRaw());
          }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java
-@@ -0,0 +0,0 @@ public interface PalettedContainerRO<T> {
+diff --git a/net/minecraft/world/level/chunk/PalettedContainerRO.java b/net/minecraft/world/level/chunk/PalettedContainerRO.java
+index bfbb1a2bb4abbb369a24f2f01439e9ea3e16794b..8d6ed8be4d93f7d4e6ea80c351020d88ee98aa4d 100644
+--- a/net/minecraft/world/level/chunk/PalettedContainerRO.java
++++ b/net/minecraft/world/level/chunk/PalettedContainerRO.java
+@@ -14,7 +14,10 @@ public interface PalettedContainerRO<T> {
  
-     void getAll(Consumer<T> action);
+     void getAll(Consumer<T> consumer);
  
--    void write(FriendlyByteBuf buf);
+-    void write(FriendlyByteBuf buffer);
 +    // Paper start - Anti-Xray - Add chunk packet info
-+    @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf);
-+    void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex);
++    @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buffer);
++    void write(FriendlyByteBuf buffer, @javax.annotation.Nullable io.papermc.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex);
 +    // Paper end
  
      int getSerializedSize();
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -0,0 +0,0 @@ import org.slf4j.Logger;
- // CraftBukkit - persistentDataContainer
- public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) {
- 
--    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
-+    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper start - Anti-Xray
+diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+index 37437a86d74291fab1de9495008aafb15dfadce0..cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d 100644
+--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+@@ -94,7 +94,7 @@ public record SerializableChunkData(
+     , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer
+ ) {
+     public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(
+-        Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()
++        Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null // Paper - Anti-Xray
+     );
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final String TAG_UPGRADE_DATA = "UpgradeData";
-     private static final String BLOCK_TICKS_TAG = "block_ticks";
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -128,6 +128,7 @@ public record SerializableChunkData(
  
      @Nullable
-     public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) {
-+        net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) world; // Paper - Anti-Xray This is is seemingly only called from ChunkMap, where, we have a server level. We'll fight this later if needed.
-         if (!nbt.contains("Status", 8)) {
+     public static SerializableChunkData parse(LevelHeightAccessor levelHeightAccessor, RegistryAccess registries, CompoundTag tag) {
++        net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) levelHeightAccessor; // Paper - Anti-Xray This is is seemingly only called from ChunkMap, where, we have a server level. We'll fight this later if needed.
+         if (!tag.contains("Status", 8)) {
              return null;
          } else {
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-             Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
+@@ -212,18 +213,21 @@ public record SerializableChunkData(
+             Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(registry); // CraftBukkit - read/write
  
-             for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
--                CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1);
-+                CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1); final CompoundTag sectionData = nbttagcompound3; // Paper - Anti-Xray - OBFHELPER
-                 byte b0 = nbttagcompound3.getByte("Y");
-                 LevelChunkSection chunksection;
- 
-                 if (b0 >= world.getMinSectionY() && b0 <= world.getMaxSectionY()) {
-                     PalettedContainer datapaletteblock;
+             for (int i2 = 0; i2 < list7.size(); i2++) {
+-                CompoundTag compound2 = list7.getCompound(i2);
++                CompoundTag compound2 = list7.getCompound(i2); final CompoundTag sectionData = compound2; // Paper - Anti-Xray - OBFHELPER
+                 int _byte = compound2.getByte("Y");
+                 LevelChunkSection levelChunkSection;
+                 if (_byte >= levelHeightAccessor.getMinSectionY() && _byte <= levelHeightAccessor.getMaxSectionY()) {
 +                    // Paper start - Anti-Xray - Add preset block states
-+                    BlockState[] presetBlockStates = serverLevel.chunkPacketBlockController.getPresetBlockStates(serverLevel, chunkcoordintpair, b0);
-+
- 
-                     if (nbttagcompound3.contains("block_states", 10)) {
--                        datapaletteblock = (PalettedContainer) SerializableChunkData.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("block_states")).promotePartial((s1) -> {
++                    BlockState[] presetBlockStates = serverLevel.chunkPacketBlockController.getPresetBlockStates(serverLevel, chunkPos, _byte);
+                     PalettedContainer<BlockState> palettedContainer;
+                     if (compound2.contains("block_states", 10)) {
+-                        palettedContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, compound2.getCompound("block_states"))
 +                        Codec<PalettedContainer<BlockState>> blockStateCodec = presetBlockStates == null ? BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); // Paper - Anti-Xray
-+                        datapaletteblock = blockStateCodec.parse(NbtOps.INSTANCE, sectionData.getCompound("block_states")).promotePartial((s1) -> { // Paper - Anti-Xray
-                             logErrors(chunkcoordintpair, b0, s1);
-                         }).getOrThrow(SerializableChunkData.ChunkReadException::new);
++                        palettedContainer = blockStateCodec.parse(NbtOps.INSTANCE, sectionData.getCompound("block_states")) // Paper - Anti-Xray
+                             .promotePartial(string -> logErrors(chunkPos, _byte, string))
+                             .getOrThrow(SerializableChunkData.ChunkReadException::new);
                      } else {
--                        datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
-+                        datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); // Paper - Anti-Xray
+                         palettedContainer = new PalettedContainer<>(
+-                            Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES
++                            Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates // Paper - Anti-Xray
+                         );
                      }
  
-                     PalettedContainer object; // CraftBukkit - read/write
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-                             logErrors(chunkcoordintpair, b0, s1);
-                         }).getOrThrow(SerializableChunkData.ChunkReadException::new);
+@@ -234,7 +238,7 @@ public record SerializableChunkData(
+                             .getOrThrow(SerializableChunkData.ChunkReadException::new);
                      } else {
--                        object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
-+                        object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null);  // Paper - Anti-Xray - Add preset biomes
+                         palettedContainerRo = new PalettedContainer<>(
+-                            registry.asHolderIdMap(), registry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES
++                            registry.asHolderIdMap(), registry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null // Paper - Anti-Xray - Add preset biomes
+                         );
                      }
  
-                     chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -414,7 +418,7 @@ public record SerializableChunkData(
  
      // CraftBukkit start - read/write
      private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {

From 273ced9170293fbc0f39648b16a741e760856f93 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 15:17:46 +0100
Subject: [PATCH 202/285] Apply some feature patches

---
 ...WorldBounds-and-getBlockState-for-inlin.patch |  4 ++--
 ...in-item-frames-performance-and-bug-fixe.patch |  6 ++++--
 ...ork-Manager-and-add-advanced-packet-sup.patch |  0
 .../0004-Allow-Saving-of-Oversized-Chunks.patch  |  0
 .../0005-Entity-Activation-Range-2.0.patch       |  4 ++--
 .../patches/features/0006-Anti-Xray.patch        |  6 +++---
 ...Velocity-compression-and-cipher-natives.patch |  0
 ...8-Optimize-Collision-to-not-load-chunks.patch |  3 +--
 ...e-GoalSelector-Goal.Flag-Set-operations.patch | 15 +++++++--------
 .../0010-Optimize-Voxel-Shape-Merging.patch      |  0
 ...tity-type-tags-suggestions-in-selectors.patch |  0
 ...ndle-Oversized-block-entities-in-chunks.patch | 16 ++++++++--------
 ...3-Check-distance-in-entity-interactions.patch |  8 ++++----
 .../0014-optimize-dirt-and-snow-spreading.patch  |  0
 ...mise-getChunkAt-calls-for-loaded-chunks.patch |  2 +-
 15 files changed, 32 insertions(+), 32 deletions(-)
 rename {feature-patches => paper-server/patches/features}/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch (96%)
 rename feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch => paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch (96%)
 rename feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch => paper-server/patches/features/0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch (100%)
 rename feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch => paper-server/patches/features/0004-Allow-Saving-of-Oversized-Chunks.patch (100%)
 rename feature-patches/1043-Entity-Activation-Range-2.0.patch => paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch (99%)
 rename feature-patches/1044-Anti-Xray.patch => paper-server/patches/features/0006-Anti-Xray.patch (99%)
 rename feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch => paper-server/patches/features/0007-Use-Velocity-compression-and-cipher-natives.patch (100%)
 rename feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch => paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch (98%)
 rename feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch => paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch (95%)
 rename feature-patches/1049-Optimize-Voxel-Shape-Merging.patch => paper-server/patches/features/0010-Optimize-Voxel-Shape-Merging.patch (100%)
 rename feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch => paper-server/patches/features/0011-Fix-entity-type-tags-suggestions-in-selectors.patch (100%)
 rename feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch => paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch (85%)
 rename feature-patches/1055-Check-distance-in-entity-interactions.patch => paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch (93%)
 rename feature-patches/1056-optimize-dirt-and-snow-spreading.patch => paper-server/patches/features/0014-optimize-dirt-and-snow-spreading.patch (100%)
 rename feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch => paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch (96%)

diff --git a/feature-patches/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/paper-server/patches/features/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
similarity index 96%
rename from feature-patches/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
rename to paper-server/patches/features/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
index 51f8cb9c22..4f1d53e0e3 100644
--- a/feature-patches/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
+++ b/paper-server/patches/features/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
@@ -29,10 +29,10 @@ index 03e2178430849d26c9826517e34ad069c94fc00a..11555ce7159ca6c8ddfe9691f86d3720
          this.x = x;
          this.y = y;
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index da264246f9d1909980c281248e64a522806b36f4..d3a22634c5e76cde78011aa6d40f67370763f83e 100644
+index e0239091729f6be138c037951fd5c138497ee358..691fee2e2097244126f4fac0f5d00bf6916b9766 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -351,7 +351,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -350,7 +350,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      // Paper end
  
      public boolean isInWorldBounds(BlockPos pos) {
diff --git a/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
similarity index 96%
rename from feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
rename to paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index 8cd9865ac4..a98c97715f 100644
--- a/feature-patches/1039-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -1,3 +1,5 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <aikar@aikar.co>
 Date: Fri, 29 Apr 2016 20:02:00 -0400
 Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes
 
@@ -11,10 +13,10 @@ custom renderers are in use, defaulting to the much simpler Vanilla system.
 Additionally, numerous issues to player position tracking on maps has been fixed.
 
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 91a1330d7308c9faadaf773d056493e1df5dcd1e..1d7e9492a474c99dff372d6b57f1f195e42d5114 100644
+index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..cce78d73e8adafd66d0f3ffb3fabb5e6c025c7df 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2296,7 +2296,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2282,7 +2282,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                          }
  
                          map.carriedByPlayers.remove(player);
diff --git a/feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/paper-server/patches/features/0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
similarity index 100%
rename from feature-patches/1040-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
rename to paper-server/patches/features/0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
diff --git a/feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch b/paper-server/patches/features/0004-Allow-Saving-of-Oversized-Chunks.patch
similarity index 100%
rename from feature-patches/1041-Allow-Saving-of-Oversized-Chunks.patch
rename to paper-server/patches/features/0004-Allow-Saving-of-Oversized-Chunks.patch
diff --git a/feature-patches/1043-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
similarity index 99%
rename from feature-patches/1043-Entity-Activation-Range-2.0.patch
rename to paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
index 3767bb01b0..322c566db9 100644
--- a/feature-patches/1043-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
@@ -366,7 +366,7 @@ index 9bbcafa8e70f95d5ab6318211a0265acbfc76b1c..f8f145cd9614f7e38d6aa72501fe3183
  import org.slf4j.Logger;
  
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..c2d9f1e6c68f4945e3b9e5b95326e8180fa7a0fb 100644
+index cce78d73e8adafd66d0f3ffb3fabb5e6c025c7df..a4b523ac1926895ccc87464892fa81753ae8f73c 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -551,6 +551,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -824,7 +824,7 @@ index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c11
 +
  }
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index e0239091729f6be138c037951fd5c138497ee358..051a86e8a723aeb86ffa06e3cd5fab102a808cde 100644
+index 691fee2e2097244126f4fac0f5d00bf6916b9766..25fb8a91bd3012da383711d58cc25cbada510f56 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -153,6 +153,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
diff --git a/feature-patches/1044-Anti-Xray.patch b/paper-server/patches/features/0006-Anti-Xray.patch
similarity index 99%
rename from feature-patches/1044-Anti-Xray.patch
rename to paper-server/patches/features/0006-Anti-Xray.patch
index 6b16ff833e..30ae5f9c6d 100644
--- a/feature-patches/1044-Anti-Xray.patch
+++ b/paper-server/patches/features/0006-Anti-Xray.patch
@@ -157,7 +157,7 @@ index aadf2dccb996e422cacf8bb510cc642e69ee4972..d2d21fe8d7275b01454e09be252d7dd7
  
      private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index c2d9f1e6c68f4945e3b9e5b95326e8180fa7a0fb..bb43dff0d55af335ed54f3a8843633b20b7ee63b 100644
+index a4b523ac1926895ccc87464892fa81753ae8f73c..ca9427a7eae9a66f4f1ccedda7b1def7ac2a88da 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -348,7 +348,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -222,7 +222,7 @@ index 2580b249d9a024400e295ca5beab551b3571ceb3..d227714de0fe13544779fae6cf0e9ff6
          }
          // Paper end - Send empty chunk
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 051a86e8a723aeb86ffa06e3cd5fab102a808cde..2c2eae0c8656055940c6e1457fc77a20bb58dd8e 100644
+index 25fb8a91bd3012da383711d58cc25cbada510f56..872c3b8826f436b15f6ab0a3619692c5202eadc3 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -168,6 +168,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@@ -260,7 +260,7 @@ index 051a86e8a723aeb86ffa06e3cd5fab102a808cde..2c2eae0c8656055940c6e1457fc77a20
              if (blockState == null) {
                  // CraftBukkit start - remove blockstate if failed (or the same)
 diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
-index f68f3f5e8ef39a0dc371e75110227a39791c04c8..254ca42e08148915b96ce77310a46e09de84ef53 100644
+index 12d9b532e466ec4e46920d409b5f1b3ae60b80f8..bc688ad1097ef4159dfc5f96d963a9fa63262e20 100644
 --- a/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/net/minecraft/world/level/chunk/ChunkAccess.java
 @@ -114,14 +114,14 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
diff --git a/feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch b/paper-server/patches/features/0007-Use-Velocity-compression-and-cipher-natives.patch
similarity index 100%
rename from feature-patches/1045-Use-Velocity-compression-and-cipher-natives.patch
rename to paper-server/patches/features/0007-Use-Velocity-compression-and-cipher-natives.patch
diff --git a/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch b/paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch
similarity index 98%
rename from feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch
rename to paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch
index 201ac3ffeb..940badfeb9 100644
--- a/feature-patches/1046-Optimize-Collision-to-not-load-chunks.patch
+++ b/paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch
@@ -13,9 +13,8 @@ If that serting is not enabled, collisions will be ignored for players, since
 movement will load only the chunk the player enters anyways and avoids loading
 massive amounts of surrounding chunks due to large AABB lookups.
 
-
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index f70e77dcce4d94b06efd27273194ce95f4b336ec..0aed1e455dec32c3d53d8fb2f1047e1bf177f171 100644
+index 2e5f1dc15ea6a20f8cbdef97ecbf00e5d56603cf..5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -218,6 +218,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
diff --git a/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
similarity index 95%
rename from feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
rename to paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
index a550caaf22..74942c0f7e 100644
--- a/feature-patches/1047-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
+++ b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
@@ -69,21 +69,20 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
  
      // Paper end - Mob Goal API
 diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..88e7e245824b670652878e03a2142a13e97508fe 100644
+index eeba224bd575451ba6023df65ef9d9b97f7f1c71..0846ad1f26ee73d72c77b53579a00c321d696b73 100644
 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
 +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-@@ -24,7 +24,9 @@ public class GoalSelector {
+@@ -24,7 +24,8 @@ public class GoalSelector {
      };
      private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
      private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
 -    private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
 +    private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
 +    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
-+
+     private int curRate; // Paper - EAR 2
  
      public void addGoal(int priority, Goal goal) {
-         this.availableGoals.add(new WrappedGoal(priority, goal));
-@@ -45,18 +47,18 @@ public class GoalSelector {
+@@ -62,18 +63,18 @@ public class GoalSelector {
          this.availableGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal);
      }
  
@@ -111,7 +110,7 @@ index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..88e7e245824b670652878e03a2142a13
              if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) {
                  return false;
              }
-@@ -70,7 +72,7 @@ public class GoalSelector {
+@@ -87,7 +88,7 @@ public class GoalSelector {
          profilerFiller.push("goalCleanup");
  
          for (WrappedGoal wrappedGoal : this.availableGoals) {
@@ -120,7 +119,7 @@ index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..88e7e245824b670652878e03a2142a13
                  wrappedGoal.stop();
              }
          }
-@@ -80,11 +82,14 @@ public class GoalSelector {
+@@ -97,11 +98,14 @@ public class GoalSelector {
          profilerFiller.push("goalUpdate");
  
          for (WrappedGoal wrappedGoalx : this.availableGoals) {
@@ -140,7 +139,7 @@ index 9338e63cc28413f5559bb0122ef5e04a84bd51d1..88e7e245824b670652878e03a2142a13
                      WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL);
                      wrappedGoal1.stop();
                      this.lockedFlags.put(flag, wrappedGoalx);
-@@ -116,11 +121,11 @@ public class GoalSelector {
+@@ -133,11 +137,11 @@ public class GoalSelector {
      }
  
      public void disableControlFlag(Goal.Flag flag) {
diff --git a/feature-patches/1049-Optimize-Voxel-Shape-Merging.patch b/paper-server/patches/features/0010-Optimize-Voxel-Shape-Merging.patch
similarity index 100%
rename from feature-patches/1049-Optimize-Voxel-Shape-Merging.patch
rename to paper-server/patches/features/0010-Optimize-Voxel-Shape-Merging.patch
diff --git a/feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch b/paper-server/patches/features/0011-Fix-entity-type-tags-suggestions-in-selectors.patch
similarity index 100%
rename from feature-patches/1053-Fix-entity-type-tags-suggestions-in-selectors.patch
rename to paper-server/patches/features/0011-Fix-entity-type-tags-suggestions-in-selectors.patch
diff --git a/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch b/paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch
similarity index 85%
rename from feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch
rename to paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch
index 127068aa94..3d61143e0e 100644
--- a/feature-patches/1054-Handle-Oversized-block-entities-in-chunks.patch
+++ b/paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch
@@ -9,7 +9,7 @@ creating too large of a packet to sed.
 Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
 
 diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
-index 5d1943d37dfad0c12e77179f0866851532d983e9..0a7e6c639d5125a135a43476bbb2ef2d682f743c 100644
+index 3aea76690bc3e35758d3bf274777130af17d8a0f..9e321ef1c3d5803519b243685f4ee598dc0cf640 100644
 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
 +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
 @@ -27,6 +27,14 @@ public class ClientboundLevelChunkPacketData {
@@ -25,11 +25,11 @@ index 5d1943d37dfad0c12e77179f0866851532d983e9..0a7e6c639d5125a135a43476bbb2ef2d
 +    }
 +    // Paper end - Handle oversized block entities in chunks
  
-     public ClientboundLevelChunkPacketData(LevelChunk levelChunk) {
-         this.heightmaps = new CompoundTag();
-@@ -40,8 +48,18 @@ public class ClientboundLevelChunkPacketData {
-         this.buffer = new byte[calculateChunkSize(levelChunk)];
-         extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk);
+     // Paper start - Anti-Xray - Add chunk packet info
+     @Deprecated @io.papermc.paper.annotation.DoNotUse
+@@ -50,8 +58,18 @@ public class ClientboundLevelChunkPacketData {
+         }
+         extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk, chunkPacketInfo);
          this.blockEntitiesData = Lists.newArrayList();
 +        int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks
  
@@ -47,10 +47,10 @@ index 5d1943d37dfad0c12e77179f0866851532d983e9..0a7e6c639d5125a135a43476bbb2ef2d
          }
      }
 diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index aadf2dccb996e422cacf8bb510cc642e69ee4972..5288de783481b7e932017c679b9eaa715b8826c6 100644
+index d2d21fe8d7275b01454e09be252d7dd7710cdc2d..5eef540242413df3ed136aa8837866a94cc285b3 100644
 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-@@ -66,4 +66,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
+@@ -84,4 +84,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
      public ClientboundLightUpdatePacketData getLightData() {
          return this.lightData;
      }
diff --git a/feature-patches/1055-Check-distance-in-entity-interactions.patch b/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
similarity index 93%
rename from feature-patches/1055-Check-distance-in-entity-interactions.patch
rename to paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
index 6572f8d99e..ed5541da75 100644
--- a/feature-patches/1055-Check-distance-in-entity-interactions.patch
+++ b/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
@@ -17,10 +17,10 @@ index 60952bd49a89b8d6247d0c8bac837e5b3d586a76..fe84fe69a2a9ed95ec45a9e5af6e6f5a
      public static <K, V> Collector<Entry<? extends K, ? extends V>, ?, Map<K, V>> toMap() {
          return Collectors.toMap(Entry::getKey, Entry::getValue);
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 88f8d462728231627c3ee7557518a2e04b4fd199..e118fd567427064c6ad6637f874ed146e67b2ee8 100644
+index 9de400977ec33e485e87cdf1cf145588527e1e10..c83aeaf4e50dd7290c608dfe260a3bd2404b6004 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -1392,7 +1392,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -1385,7 +1385,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  this.hurtCurrentlyUsedShield(amount);
                  f1 = amount;
                  amount = 0.0F;
@@ -29,7 +29,7 @@ index 88f8d462728231627c3ee7557518a2e04b4fd199..e118fd567427064c6ad6637f874ed146
                      this.blockUsingShield(livingEntity);
                  }
  
-@@ -1477,6 +1477,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -1470,6 +1470,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
                          d = damageSource.getSourcePosition().x() - this.getX();
                          d1 = damageSource.getSourcePosition().z() - this.getZ();
                      }
@@ -44,7 +44,7 @@ index 88f8d462728231627c3ee7557518a2e04b4fd199..e118fd567427064c6ad6637f874ed146
  
                      this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
                      if (!flag) {
-@@ -2352,7 +2360,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -2345,7 +2353,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
                  Entity entity = damageSource.getDirectEntity();
  
diff --git a/feature-patches/1056-optimize-dirt-and-snow-spreading.patch b/paper-server/patches/features/0014-optimize-dirt-and-snow-spreading.patch
similarity index 100%
rename from feature-patches/1056-optimize-dirt-and-snow-spreading.patch
rename to paper-server/patches/features/0014-optimize-dirt-and-snow-spreading.patch
diff --git a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch
similarity index 96%
rename from feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
rename to paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch
index bb441c5742..5d23cbf347 100644
--- a/feature-patches/1057-Optimise-getChunkAt-calls-for-loaded-chunks.patch
+++ b/paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch
@@ -7,7 +7,7 @@ bypass the need to get a player chunk, then get the either,
 then unwrap it...
 
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index d310e7489fc4ecede8deef59241444769d87b0a1..f268808cb250e374f2d5652b20eece011e87dcfb 100644
+index d310e7489fc4ecede8deef59241444769d87b0a1..796b5f8541b0cf84482ab2b5a60adde544d43593 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -218,6 +218,12 @@ public class ServerChunkCache extends ChunkSource {

From 9c973e84bc1bd1d098ccd898d298866f8162f73f Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 15:44:33 +0100
Subject: [PATCH 203/285] Some fixes

---
 build-data/paper.at                           |  1 +
 .../patches/features/0006-Anti-Xray.patch     | 26 +++++++------------
 ...oalSelector-Goal.Flag-Set-operations.patch |  4 +--
 ...tboundLevelChunkWithLightPacket.java.patch | 14 ++++++++++
 4 files changed, 27 insertions(+), 18 deletions(-)
 create mode 100644 paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java.patch

diff --git a/build-data/paper.at b/build-data/paper.at
index 04c669edaa..3c12c9553d 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -101,6 +101,7 @@ public net.minecraft.server.level.ServerPlayer seenCredits
 public net.minecraft.server.level.ServerPlayer triggerDimensionChangeTriggers(Lnet/minecraft/server/level/ServerLevel;)V
 public net.minecraft.server.level.ServerPlayer wardenSpawnTracker
 public net.minecraft.server.level.ServerPlayer$RespawnPosAngle
+public net.minecraft.server.level.ServerPlayerGameMode level
 public net.minecraft.server.level.Ticket key
 public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z
 public net.minecraft.server.network.ServerLoginPacketListenerImpl connection
diff --git a/paper-server/patches/features/0006-Anti-Xray.patch b/paper-server/patches/features/0006-Anti-Xray.patch
index 30ae5f9c6d..b322872f20 100644
--- a/paper-server/patches/features/0006-Anti-Xray.patch
+++ b/paper-server/patches/features/0006-Anti-Xray.patch
@@ -120,25 +120,28 @@ index 5d1943d37dfad0c12e77179f0866851532d983e9..3aea76690bc3e35758d3bf274777130a
      }
  
 diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index aadf2dccb996e422cacf8bb510cc642e69ee4972..d2d21fe8d7275b01454e09be252d7dd7710cdc2d 100644
+index 3a384175f8e7f204234bbaf3081bdc20c47a0d4b..5699bc15eba92e22433a20cb8326b59f2ebd3036 100644
 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-@@ -18,13 +18,31 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
+@@ -18,18 +18,31 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
      private final int z;
      private final ClientboundLevelChunkPacketData chunkData;
      private final ClientboundLightUpdatePacketData lightData;
+-    // Paper start - Anti-Xray
 +    // Paper start - Async-Anti-Xray - Ready flag for the connection, add chunk packet info
 +    private volatile boolean ready;
- 
++
 +    @Override
 +    public boolean isReady() {
 +        return this.ready;
 +    }
 +
-+    public void setReady(boolean ready) {
+     public void setReady(final boolean ready) {
+-        // Empty hook, updated by feature patch
 +        this.ready = ready;
-+    }
-+
+     }
+-    // Paper end - Anti-Xray
+ 
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse
      public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight) {
 +        this(chunk, lightEngine, skyLight, blockLight, true);
@@ -170,18 +173,9 @@ index a4b523ac1926895ccc87464892fa81753ae8f73c..ca9427a7eae9a66f4f1ccedda7b1def7
          this.levelStorageAccess = levelStorageAccess;
          this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index d6a493de667bfe97b722efe40d1530bdb666bb94..bb352ad40da33a9d411836d4fab2664e226a6e38 100644
+index 732a4f20bade67c57a4f85142849752b72e349ee..6176f0738aa1a18df5d7d4d49fd6961e3f2eb736 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -28,7 +28,7 @@ import org.slf4j.Logger;
- 
- public class ServerPlayerGameMode {
-     private static final Logger LOGGER = LogUtils.getLogger();
--    protected ServerLevel level;
-+    public ServerLevel level; // Paper - Anti-Xray - protected -> public
-     protected final ServerPlayer player;
-     private GameType gameModeForPlayer = GameType.DEFAULT_MODE;
-     @Nullable
 @@ -312,6 +312,7 @@ public class ServerPlayerGameMode {
                  org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
              }
diff --git a/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
index 74942c0f7e..2c4bd4a04c 100644
--- a/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
+++ b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
@@ -69,7 +69,7 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
  
      // Paper end - Mob Goal API
 diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index eeba224bd575451ba6023df65ef9d9b97f7f1c71..0846ad1f26ee73d72c77b53579a00c321d696b73 100644
+index eeba224bd575451ba6023df65ef9d9b97f7f1c71..20f0c1a444f40b28fe4de9ebc9eb40243c1f22a0 100644
 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
 +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
 @@ -24,7 +24,8 @@ public class GoalSelector {
@@ -96,7 +96,7 @@ index eeba224bd575451ba6023df65ef9d9b97f7f1c71..0846ad1f26ee73d72c77b53579a00c32
 -        return false;
 +    // Paper start - Perf: optimize goal types
 +    private static boolean goalContainsAnyFlags(WrappedGoal goal, ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> flags) {
-+        return goal.getFlags().hasCommonElements(controls);
++        return goal.getFlags().hasCommonElements(flags);
      }
  
      private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map<Goal.Flag, WrappedGoal> flag) {
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java.patch
new file mode 100644
index 0000000000..edcd2a2d5d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+@@ -18,6 +_,11 @@
+     private final int z;
+     private final ClientboundLevelChunkPacketData chunkData;
+     private final ClientboundLightUpdatePacketData lightData;
++    // Paper start - Anti-Xray
++    public void setReady(final boolean ready) {
++        // Empty hook, updated by feature patch
++    }
++    // Paper end - Anti-Xray
+ 
+     public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight) {
+         ChunkPos pos = chunk.getPos();

From 783b3b70e66919667d1c996cf23588473327ed0f Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Mon, 16 Dec 2024 17:29:46 +0100
Subject: [PATCH 204/285] readd dropped event in InteractWithDoor

---
 ...heck-distance-in-entity-interactions.patch |  4 +-
 .../server/level/ServerPlayer.java.patch      |  2 +-
 .../world/CompoundContainer.java.patch        | 16 ++---
 .../net/minecraft/world/Container.java.patch  |  6 +-
 .../world/SimpleContainer.java.patch          |  8 +--
 .../entity/ai/behavior/Behavior.java.patch    | 18 +++--
 .../ai/behavior/GoToWantedItem.java.patch     |  4 +-
 .../ai/behavior/InteractWithDoor.java.patch   | 15 ++++-
 .../animal/horse/AbstractHorse.java.patch     |  8 +--
 .../world/entity/player/Inventory.java.patch  | 10 +--
 .../entity/vehicle/AbstractBoat.java.patch    | 22 ++----
 .../vehicle/AbstractChestBoat.java.patch      | 11 ++-
 .../vehicle/AbstractMinecart.java.patch       |  5 +-
 .../AbstractMinecartContainer.java.patch      | 67 ++++++++++---------
 .../entity/vehicle/MinecartTNT.java.patch     |  2 +-
 .../inventory/MerchantContainer.java.patch    |  8 +--
 .../inventory/ResultContainer.java.patch      |  4 +-
 .../TransientCraftingContainer.java.patch     |  8 +--
 .../world/level/block/DoorBlock.java.patch    | 17 +++--
 .../AbstractFurnaceBlockEntity.java.patch     |  8 +--
 .../block/entity/BarrelBlockEntity.java.patch |  8 +--
 .../entity/BrewingStandBlockEntity.java.patch |  8 +--
 .../block/entity/ChestBlockEntity.java.patch  |  8 +--
 .../ChiseledBookShelfBlockEntity.java.patch   |  8 +--
 .../entity/CrafterBlockEntity.java.patch      |  8 +--
 .../entity/DecoratedPotBlockEntity.java.patch |  8 +--
 .../entity/DispenserBlockEntity.java.patch    |  8 +--
 .../block/entity/HopperBlockEntity.java.patch |  8 +--
 .../entity/JukeboxBlockEntity.java.patch      |  8 +--
 .../entity/LecternBlockEntity.java.patch      |  8 +--
 .../entity/ShulkerBoxBlockEntity.java.patch   |  8 +--
 .../PaperInventoryCustomHolderContainer.java  |  8 +--
 .../inventory/CraftInventoryCustom.java       |  8 +--
 33 files changed, 175 insertions(+), 172 deletions(-)

diff --git a/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch b/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
index ed5541da75..6e558c931a 100644
--- a/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
+++ b/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
@@ -54,10 +54,10 @@ index 9de400977ec33e485e87cdf1cf145588527e1e10..c83aeaf4e50dd7290c608dfe260a3bd2
                  }
              }
 diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-index 5cd65e94ac7830aaa2a64057fc2a81478b55ea41..b9cb86717d7e6c05eb97f3b1bbf1d0111a0ba6ed 100644
+index 3bdb3b0984d0fee21b2c094e1d4c1f917ab68f92..54a4bf2f7df87b4a694187ade81ba158f83f0246 100644
 --- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-@@ -641,7 +641,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
+@@ -638,7 +638,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
              this.waterLevel = this.getY(1.0);
              double d2 = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
              if (this.level().noCollision(this, this.getBoundingBox().move(0.0, d2 - this.getY(), 0.0))) {
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index debc79d7cc..64c374439b 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -881,7 +881,7 @@
 +        {
 +            {
 +                Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at, force).ifRight((unit) -> {
-+    // CraftBukkit end
++                    // CraftBukkit end
                      this.awardStat(Stats.SLEEP_IN_BED);
                      CriteriaTriggers.SLEPT_IN_BED.trigger(this);
                  });
diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
index 54adbbf786..adc2c80c8e 100644
--- a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
@@ -15,16 +15,16 @@
 +        return result;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.container1.onOpen(who);
-+        this.container2.onOpen(who);
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.container1.onOpen(player);
++        this.container2.onOpen(player);
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.container1.onClose(who);
-+        this.container2.onClose(who);
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.container1.onClose(player);
++        this.container2.onClose(player);
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/Container.java.patch b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
index ac7f19ed19..1250bede66 100644
--- a/paper-server/patches/sources/net/minecraft/world/Container.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
@@ -19,9 +19,9 @@
 +    // Paper start
 +    java.util.List<ItemStack> getContents();
 +
-+    void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who);
++    void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player);
 +
-+    void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who);
++    void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player);
 +
 +    java.util.List<org.bukkit.entity.HumanEntity> getViewers();
 +
@@ -31,6 +31,6 @@
 +
 +    org.bukkit.Location getLocation();
 +
-+    int MAX_STACK = 99;
++    int MAX_STACK = Item.ABSOLUTE_MAX_STACK_SIZE;
 +    // Paper end
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
index 314fec3e3b..7fe6062581 100644
--- a/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/SimpleContainer.java.patch
@@ -13,12 +13,12 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
index d205203f19..5088503a08 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/Behavior.java.patch
@@ -1,12 +1,10 @@
 --- a/net/minecraft/world/entity/ai/behavior/Behavior.java
 +++ b/net/minecraft/world/entity/ai/behavior/Behavior.java
-@@ -14,6 +_,9 @@
+@@ -14,6 +_,7 @@
      private long endTimestamp;
      private final int minDuration;
      private final int maxDuration;
-+    // Paper start - configurable behavior tick rate and timings
-+    private final String configKey;
-+    // Paper end - configurable behavior tick rate and timings
++    private final String configKey; // Paper - configurable behavior tick rate and timings
  
      public Behavior(Map<MemoryModuleType<?>, MemoryStatus> entryCondition) {
          this(entryCondition, 60);
@@ -29,12 +27,12 @@
  
      @Override
      public final boolean tryStart(ServerLevel level, E owner, long gameTime) {
-+       // Paper start - configurable behavior tick rate and timings
-+       int tickRate = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.behavior.get(owner.getType(), this.configKey), -1);
-+       if (tickRate > -1 && gameTime < this.endTimestamp + tickRate) {
-+           return false;
-+       }
-+       // Paper end - configurable behavior tick rate and timings
++        // Paper start - configurable behavior tick rate and timings
++        int tickRate = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.behavior.get(owner.getType(), this.configKey), -1);
++        if (tickRate > -1 && gameTime < this.endTimestamp + tickRate) {
++            return false;
++        }
++        // Paper end - configurable behavior tick rate and timings
          if (this.hasRequiredMemories(owner) && this.checkExtraStartConditions(level, owner)) {
              this.status = Behavior.Status.RUNNING;
              int i = this.minDuration + level.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
index ca1948e8a9..b4c97cad9b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
@@ -11,12 +11,12 @@
 +                                    if (event.isCancelled()) {
 +                                        return false;
 +                                    }
-+                                    if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem)) { // Paper - only erase allay memory on non-item targets
++                                    if (!(event.getTarget() instanceof org.bukkit.craftbukkit.entity.CraftItem targetItem)) { // Paper - only erase allay memory on non-item targets
 +                                        nearestVisibleWantedItem.erase();
 +                                        return false; // Paper - only erase allay memory on non-item targets
 +                                    }
 +
-+                                    itemEntity = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
++                                    itemEntity = targetItem.getHandle();
 +                                }
 +                                // CraftBukkit end
                                  WalkTarget walkTarget1 = new WalkTarget(new EntityTracker(itemEntity, false), speedModifier, 0);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
index 8f8cc15760..05862afb29 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
@@ -9,7 +9,20 @@
 +                                    if (!event.callEvent()) {
 +                                        return false;
 +                                    }
-+                                    // CraftBukkit end
++                                    // CraftBukkit end - entities opening doors
                                      doorBlock.setOpen(entity, level, blockState, blockPos, true);
                                  }
  
+@@ -69,6 +_,12 @@
+                             if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+                                 DoorBlock doorBlock1 = (DoorBlock)blockState1.getBlock();
+                                 if (!doorBlock1.isOpen(blockState1)) {
++                                    // CraftBukkit start - entities opening doors
++                                    org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), blockPos1));
++                                    if (!event.callEvent()) {
++                                        return false;
++                                    }
++                                    // CraftBukkit end - entities opening doors
+                                     doorBlock1.setOpen(entity, level, blockState1, blockPos1, true);
+                                     optional = rememberDoorToClose(doorsToClose, optional, level, blockPos1);
+                                 }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
index 1b2858a398..19135714d8 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -33,13 +33,13 @@
 +        }
 +
 +        @Override
-+        public void onOpen(CraftHumanEntity who) {
-+            this.transaction.add(who);
++        public void onOpen(CraftHumanEntity player) {
++            this.transaction.add(player);
 +        }
 +
 +        @Override
-+        public void onClose(CraftHumanEntity who) {
-+            this.transaction.remove(who);
++        public void onClose(CraftHumanEntity player) {
++            this.transaction.remove(player);
 +        }
 +
 +        @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
index ca6987132b..5a84bf452f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
@@ -5,7 +5,7 @@
      private int timesChanged;
  
 +    // CraftBukkit start - add fields and methods
-+    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
 +    public List<ItemStack> getContents() {
@@ -21,12 +21,12 @@
 +        return this.armor;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
index 20b17c550f..a0e7f6a45f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-@@ -83,6 +_,14 @@
+@@ -83,6 +_,15 @@
      private Leashable.LeashData leashData;
      private final Supplier<Item> dropItem;
  
@@ -10,6 +10,7 @@
 +    public double occupiedDeceleration = 0.2D;
 +    public double unoccupiedDeceleration = -1;
 +    public boolean landBoats = false;
++    private org.bukkit.Location lastLocation;
 +    // CraftBukkit end
 +
      public AbstractBoat(EntityType<? extends AbstractBoat> entityType, Level level, Supplier<Item> dropItem) {
@@ -55,30 +56,19 @@
              super.push(entity);
          }
      }
-@@ -243,6 +_,7 @@
-         return this.getDirection().getClockWise();
-     }
- 
-+    private org.bukkit.Location lastLocation; // CraftBukkit
-     @Override
-     public void tick() {
-         this.oldStatus = this.status;
-@@ -283,6 +_,21 @@
+@@ -283,6 +_,18 @@
              this.setDeltaMovement(Vec3.ZERO);
          }
  
 +        // CraftBukkit start
-+        org.bukkit.Server server = this.level().getCraftServer();
-+        org.bukkit.World bworld = this.level().getWorld();
-+
-+        org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
++        org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.position(), this.level().getWorld(), this.getYRot(), this.getXRot());
 +        org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
 +
-+        server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++        new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle).callEvent();
 +
 +        if (this.lastLocation != null && !this.lastLocation.equals(to)) {
 +            org.bukkit.event.vehicle.VehicleMoveEvent event = new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, this.lastLocation, to);
-+            server.getPluginManager().callEvent(event);
++            event.callEvent();
 +        }
 +        this.lastLocation = vehicle.getLocation();
 +        // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
index 3dc8fe5278..9fd4787f99 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractChestBoat.java.patch
@@ -40,12 +40,11 @@
              return null;
          } else {
              this.unpackLootTable(playerInventory.player);
-@@ -198,4 +_,59 @@
+@@ -198,4 +_,58 @@
      public void stopOpen(Player player) {
          this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of(player));
      }
 +
-+
 +    // Paper start - LootTable API
 +    final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
 +
@@ -64,13 +63,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
index ea248c3955..35881f5e7a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
@@ -28,8 +28,8 @@
 +        if (!collides) {
 +            return false;
 +        }
-+        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), entity.getBukkitEntity());
 +
++        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), entity.getBukkitEntity());
 +        return collisionEvent.callEvent();
 +        // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player
      }
@@ -141,7 +141,7 @@
                      double d = entity.getX() - this.getX();
                      double d1 = entity.getZ() - this.getZ();
                      double d2 = d * d + d1 * d1;
-@@ -602,4 +_,27 @@
+@@ -602,4 +_,28 @@
      public boolean isFurnace() {
          return false;
      }
@@ -167,5 +167,6 @@
 +        this.derailedZ = derailed.getZ();
 +    }
 +    // CraftBukkit end
++
 +    public net.minecraft.world.item.Item publicGetDropItem() { return getDropItem(); } // Paper - api to get boat and minecart material - expose public drop item
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
index 5a1e4c8f55..5289f0b57c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
 +++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
-@@ -21,11 +_,59 @@
+@@ -21,10 +_,11 @@
  import net.minecraft.world.phys.Vec3;
  
  public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
@@ -9,15 +9,42 @@
      @Nullable
      public ResourceKey<LootTable> lootTable;
      public long lootTableSeed;
++    private final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper - LootTable API
  
-+    // Paper start - LootTable API
-+    final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
+     protected AbstractMinecartContainer(EntityType<?> entityType, Level level) {
+         super(entityType, level);
+@@ -72,11 +_,18 @@
+ 
+     @Override
+     public void remove(Entity.RemovalReason reason) {
++        // CraftBukkit start - add Bukkit remove cause
++        this.remove(reason, null);
++    }
 +
 +    @Override
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++        // CraftBukkit end  - add Bukkit remove cause
+         if (!this.level().isClientSide && reason.shouldDestroy()) {
+             Containers.dropContents(this.level(), this, this);
+         }
+ 
+-        super.remove(reason);
++        super.remove(reason, cause); // CraftBukkit - add Bukkit remove cause
+     }
+ 
+     @Override
+@@ -164,4 +_,50 @@
+     public void clearItemStacks() {
+         this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
+     }
++
++    // Paper start - LootTable API
++    @Override
 +    public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
 +        return this.lootableData;
 +    }
 +    // Paper end - LootTable API
++
 +    // CraftBukkit start
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
@@ -26,12 +53,12 @@
 +        return this.itemStacks;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
@@ -56,28 +83,4 @@
 +        return this.getBukkitEntity().getLocation();
 +    }
 +    // CraftBukkit end
-+
-+
-     protected AbstractMinecartContainer(EntityType<?> entityType, Level level) {
-         super(entityType, level);
-     }
-@@ -72,11 +_,18 @@
- 
-     @Override
-     public void remove(Entity.RemovalReason reason) {
-+        // CraftBukkit start - add Bukkit remove cause
-+        this.remove(reason, null);
-+    }
-+
-+    @Override
-+    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
-+        // CraftBukkit end  - add Bukkit remove cause
-         if (!this.level().isClientSide && reason.shouldDestroy()) {
-             Containers.dropContents(this.level(), this, this);
-         }
- 
--        super.remove(reason);
-+        super.remove(reason, cause);  // CraftBukkit - add Bukkit remove cause
-     }
- 
-     @Override
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
index 8cfbf8de7d..0f23c30d40 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
@@ -28,7 +28,7 @@
 +            // CraftBukkit start
 +            org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(
 +                this.getBukkitEntity(),
-+                (float) ((double) this.explosionPowerBase + (double) this.explosionSpeedFactor * this.random.nextDouble() * 1.5D * min),
++                (float) (this.explosionPowerBase + this.explosionSpeedFactor * this.random.nextDouble() * 1.5 * min),
 +                this.isIncendiary
 +            );
 +            if (!event.callEvent()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
index a460afb9b4..c41d046425 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
@@ -12,12 +12,12 @@
 +        return this.itemStacks;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +        this.merchant.setTradingPlayer((Player) null); // SPIGOT-4860
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
index da8f31ffe7..a384ac95a8 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ResultContainer.java.patch
@@ -21,8 +21,8 @@
 +    }
 +
 +    // Don't need a transaction; the InventoryCrafting keeps track of it for us
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {}
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {}
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {}
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {}
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return new java.util.ArrayList<>();
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
index bc3a488239..898f5850c4 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -15,16 +15,16 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    public org.bukkit.event.inventory.InventoryType getInvType() {
 +        return this.items.size() == 4 ? org.bukkit.event.inventory.InventoryType.CRAFTING : org.bukkit.event.inventory.InventoryType.WORKBENCH;
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
index 87099f1713..03420b6376 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DoorBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/DoorBlock.java
 +++ b/net/minecraft/world/level/block/DoorBlock.java
-@@ -229,9 +_,23 @@
+@@ -229,9 +_,22 @@
  
      @Override
      protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
@@ -9,21 +9,20 @@
 -        if (!this.defaultBlockState().is(neighborBlock) && flag != state.getValue(POWERED)) {
 +        // CraftBukkit start
 +        BlockPos otherHalf = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
-+        org.bukkit.World bworld = level.getWorld();
-+        org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
-+        org.bukkit.block.Block blockTop = bworld.getBlockAt(otherHalf.getX(), otherHalf.getY(), otherHalf.getZ());
++        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++        org.bukkit.block.Block blockTop = org.bukkit.craftbukkit.block.CraftBlock.at(level, otherHalf);
 +
 +        int power = bukkitBlock.getBlockPower();
 +        int powerTop = blockTop.getBlockPower();
 +        if (powerTop > power) power = powerTop;
-+        int oldPower = (Boolean) state.getValue(DoorBlock.POWERED) ? 15 : 0;
++        int oldPower = state.getValue(DoorBlock.POWERED) ? net.minecraft.world.level.redstone.Redstone.SIGNAL_MAX : net.minecraft.world.level.redstone.Redstone.SIGNAL_MIN;
 +
 +        if (oldPower == 0 ^ power == 0) {
-+            org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, power);
-+            level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++            org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, power);
++            event.callEvent();
 +
-+            boolean flag = eventRedstone.getNewCurrent() > 0;
-+        // CraftBukkit end
++            boolean flag = event.getNewCurrent() > 0;
++            // CraftBukkit end
              if (flag != state.getValue(OPEN)) {
                  this.playSound(null, level, pos, flag);
                  level.gameEvent(null, flag ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
index 5b5640d1f8..092b7d392d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -22,12 +22,12 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
index 24372abac2..a7e3cbcde9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -14,13 +14,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
index e470fbe622..93f629fdbf 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -39,12 +39,12 @@
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
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
index d3845d7cfa..32a967c2c3 100644
--- 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
@@ -12,12 +12,12 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index 6248b82627..2c8981a4cb 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -14,13 +14,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
index 537c7fdff2..edc04acff8 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
@@ -14,13 +14,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
index 0b6a7181a9..08a08744a5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
@@ -15,13 +15,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
index 1951b2f1e9..1448e2338d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
@@ -12,12 +12,12 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
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
index ebb7abcb76..4c305f64e5 100644
--- 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
@@ -12,12 +12,12 @@
 +        return this.items;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
index 53cf63280b..7ef85bb6b9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -16,13 +16,13 @@
 +    }
 +
 +    @Override
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
 +    @Override
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
index 3ce197ece6..d3be2ca424 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -17,13 +17,13 @@
 +        }
 +
 +        @Override
-+        public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+            this.transaction.add(who);
++        public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++            this.transaction.add(player);
 +        }
 +
 +        @Override
-+        public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+            this.transaction.remove(who);
++        public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++            this.transaction.remove(player);
 +        }
 +
 +        @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
index 297c0cd136..dba115b265 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
@@ -13,12 +13,12 @@
 +        return this.itemStacks;
 +    }
 +
-+    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.add(who);
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.add(player);
 +    }
 +
-+    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
-+        this.transaction.remove(who);
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++        this.transaction.remove(player);
 +    }
 +
 +    public List<org.bukkit.entity.HumanEntity> getViewers() {
diff --git a/paper-server/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java b/paper-server/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java
index 224d4b2cc4..f3ef363497 100644
--- a/paper-server/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java
+++ b/paper-server/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java
@@ -105,13 +105,13 @@ public final class PaperInventoryCustomHolderContainer implements Container {
     }
 
     @Override
-    public void onOpen(CraftHumanEntity who) {
-        this.delegate.onOpen(who);
+    public void onOpen(CraftHumanEntity player) {
+        this.delegate.onOpen(player);
     }
 
     @Override
-    public void onClose(CraftHumanEntity who) {
-        this.delegate.onClose(who);
+    public void onClose(CraftHumanEntity player) {
+        this.delegate.onClose(player);
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
index da1c1fe0fa..393e44cd3e 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
@@ -194,13 +194,13 @@ public class CraftInventoryCustom extends CraftInventory {
         }
 
         @Override
-        public void onOpen(CraftHumanEntity who) {
-            this.viewers.add(who);
+        public void onOpen(CraftHumanEntity player) {
+            this.viewers.add(player);
         }
 
         @Override
-        public void onClose(CraftHumanEntity who) {
-            this.viewers.remove(who);
+        public void onClose(CraftHumanEntity player) {
+            this.viewers.remove(player);
         }
 
         @Override

From 48be22a63ec1e323a99605478aa72150280e7a28 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 08:29:06 -0800
Subject: [PATCH 205/285] Use forkjoin thread pool for background executor

ForkJoin thread pools are specially handled with CompletableFuture.
Specifically, join()/get() calls will allow work stealing from other
fork join threads.

This fixes a deadlock where the worldgen worker is waiting on the
I/O worker.
---
 .../sources/net/minecraft/Util.java.patch     | 24 +++++++------------
 1 file changed, 9 insertions(+), 15 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/Util.java.patch b/paper-server/patches/sources/net/minecraft/Util.java.patch
index bcf7f18858..946139d552 100644
--- a/paper-server/patches/sources/net/minecraft/Util.java.patch
+++ b/paper-server/patches/sources/net/minecraft/Util.java.patch
@@ -37,7 +37,7 @@
      }
  
      public static long getEpochMillis() {
-@@ -146,15 +_,17 @@
+@@ -146,9 +_,10 @@
          return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now());
      }
  
@@ -50,22 +50,16 @@
          if (i <= 0) {
              directExecutorService = MoreExecutors.newDirectExecutorService();
          } else {
-             AtomicInteger atomicInteger = new AtomicInteger(1);
--            directExecutorService = new ForkJoinPool(i, forkJoinPool -> {
--                final String string = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
-+            directExecutorService = Executors.newFixedThreadPool(i, target -> new io.papermc.paper.util.ServerWorkerThread(target, name, priorityModifier));
-+        }
-+        /*  final String string = "Worker-" + name + "-" + atomicInteger.getAndIncrement();
-                 ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(forkJoinPool) {
-                     @Override
-                     protected void onStart() {
-@@ -176,13 +_,27 @@
+@@ -173,16 +_,30 @@
+                         super.onTermination(throwOnTermination);
+                     }
+                 };
++                forkJoinWorkerThread.setPriority(Thread.NORM_PRIORITY + priorityModifier); // Paper - Deprioritize over main
                  forkJoinWorkerThread.setName(string);
                  return forkJoinWorkerThread;
-             }, Util::onThreadException, true);
--        }
-+        }*/
-+        // Paper end
+-            }, Util::onThreadException, true);
++            }, Util::onThreadException, true, 0, Integer.MAX_VALUE, 1, null, 365, TimeUnit.DAYS); // Paper - do not expire threads
+         }
  
          return new TracingExecutor(directExecutorService);
      }

From 9e8a2de45b690a45351117615782975496f52947 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Mon, 16 Dec 2024 18:07:41 +0100
Subject: [PATCH 206/285] readd bukkit extra data to entity tags

---
 ...item-frames-performance-and-bug-fixe.patch |  4 +--
 ...oalSelector-Goal.Flag-Set-operations.patch | 32 +++++++++----------
 .../server/level/ServerPlayer.java.patch      | 12 +++++--
 .../PrepareRamNearestTarget.java.patch        | 10 +-----
 .../ai/behavior/WorkAtComposter.java.patch    |  4 +--
 .../world/entity/ai/goal/Goal.java.patch      |  4 +--
 .../ai/goal/target/TargetGoal.java.patch      |  2 +-
 .../world/entity/ai/sensing/Sensor.java.patch | 12 +++----
 8 files changed, 38 insertions(+), 42 deletions(-)

diff --git a/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index a98c97715f..a7533a2d46 100644
--- a/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -28,10 +28,10 @@ index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..cce78d73e8adafd66d0f3ffb3fabb5e6
                  }
              }
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index e3a4cf9cd03670705391f4dc68f193d7cee656e0..f9c9485a051f5fa6d508b8a194c9f17657e1a0f4 100644
+index cfd30aa774d3bb3049ff9331a623624c6a13f774..940509d1f31aedf20b8f5b9192c34ad004875728 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -2654,6 +2654,14 @@ public class ServerPlayer extends Player {
+@@ -2655,6 +2655,14 @@ public class ServerPlayer extends Player {
                  this.awardStat(Stats.DROP);
              }
  
diff --git a/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
index 2c4bd4a04c..8d68059b74 100644
--- a/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
+++ b/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
@@ -7,7 +7,7 @@ Optimise the stream.anyMatch statement to move to a bitset
 where we can replace the call with a single bitwise operation.
 
 diff --git a/net/minecraft/world/entity/ai/goal/Goal.java b/net/minecraft/world/entity/ai/goal/Goal.java
-index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16efa2db8d24 100644
+index d4f8387254a65b25fc808932a069298d0f8da091..5f5bf0e710ecff09a571091e5a923332be70cb74 100644
 --- a/net/minecraft/world/entity/ai/goal/Goal.java
 +++ b/net/minecraft/world/entity/ai/goal/Goal.java
 @@ -7,7 +7,15 @@ import net.minecraft.world.entity.Entity;
@@ -15,15 +15,15 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
  
  public abstract class Goal {
 -    private final EnumSet<Goal.Flag> flags = EnumSet.noneOf(Goal.Flag.class);
-+    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
++    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector
 +
-+    // Paper start - remove streams from pathfindergoalselector; make sure types are not empty
++    // Paper start - remove streams from GoalSelector; make sure types are not empty
 +    protected Goal() {
 +        if (this.goalTypes.size() == 0) {
 +            this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
 +        }
 +    }
-+    // Paper end - remove streams from pathfindergoalselector
++    // Paper end - remove streams from GoalSelector
  
      public abstract boolean canUse();
  
@@ -33,13 +33,13 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
      public void setFlags(EnumSet<Goal.Flag> flagSet) {
 -        this.flags.clear();
 -        this.flags.addAll(flagSet);
-+        // Paper start - remove streams from pathfindergoalselector
++        // Paper start - remove streams from GoalSelector
 +        this.goalTypes.clear();
 +        this.goalTypes.addAllUnchecked(flagSet);
 +        if (this.goalTypes.size() == 0) {
 +            this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR);
 +        }
-+        // Paper end - remove streams from pathfindergoalselector;
++        // Paper end - remove streams from GoalSelector
      }
  
      @Override
@@ -49,10 +49,10 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
  
 -    public EnumSet<Goal.Flag> getFlags() {
 -        return this.flags;
-+    // Paper start - remove streams from pathfindergoalselector
++    // Paper start - remove streams from GoalSelector
 +    public ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() {
 +        return this.goalTypes;
-+        // Paper end - remove streams from pathfindergoalselector
++        // Paper end - remove streams from GoalSelector
      }
  
  
@@ -66,10 +66,10 @@ index e99836c4073d8b25eddd3b4c784dbdb1f0c4f2b1..a2d788a656b2a5767c8a00bbcaca16ef
 -        this.flags.add(flag);
 +        this.goalTypes.addUnchecked(flag);
      }
- 
      // Paper end - Mob Goal API
+ 
 diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java
-index eeba224bd575451ba6023df65ef9d9b97f7f1c71..20f0c1a444f40b28fe4de9ebc9eb40243c1f22a0 100644
+index eeba224bd575451ba6023df65ef9d9b97f7f1c71..a927c2790c8ab9ccaa7161b970e10b0b44817dd8 100644
 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java
 +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java
 @@ -24,7 +24,8 @@ public class GoalSelector {
@@ -77,8 +77,8 @@ index eeba224bd575451ba6023df65ef9d9b97f7f1c71..20f0c1a444f40b28fe4de9ebc9eb4024
      private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap<>(Goal.Flag.class);
      private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
 -    private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
-+    private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector
-+    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector
++    private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from GoalSelector
++    private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<net.minecraft.world.entity.ai.goal.Goal.Flag> goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector
      private int curRate; // Paper - EAR 2
  
      public void addGoal(int priority, Goal goal) {
@@ -144,17 +144,17 @@ index eeba224bd575451ba6023df65ef9d9b97f7f1c71..20f0c1a444f40b28fe4de9ebc9eb4024
  
      public void disableControlFlag(Goal.Flag flag) {
 -        this.disabledFlags.add(flag);
-+        this.goalTypes.addUnchecked(flag); // Paper - remove streams from pathfindergoalselector
++        this.goalTypes.addUnchecked(flag); // Paper - remove streams from GoalSelector
      }
  
      public void enableControlFlag(Goal.Flag flag) {
 -        this.disabledFlags.remove(flag);
-+        this.goalTypes.removeUnchecked(flag); // Paper - remove streams from pathfindergoalselector
++        this.goalTypes.removeUnchecked(flag); // Paper - remove streams from GoalSelector
      }
  
      public void setControlFlag(Goal.Flag flag, boolean enabled) {
 diff --git a/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/net/minecraft/world/entity/ai/goal/WrappedGoal.java
-index 4bdbd323b642ed3422948fe24780be8b503602dc..5aaad796d2e2f7b57b1fd911a1498cdf235c36be 100644
+index 4bdbd323b642ed3422948fe24780be8b503602dc..2c2ab6a1df9d3d23773e44ce4041cc1c21b55163 100644
 --- a/net/minecraft/world/entity/ai/goal/WrappedGoal.java
 +++ b/net/minecraft/world/entity/ai/goal/WrappedGoal.java
 @@ -69,7 +69,7 @@ public class WrappedGoal extends Goal {
@@ -162,7 +162,7 @@ index 4bdbd323b642ed3422948fe24780be8b503602dc..5aaad796d2e2f7b57b1fd911a1498cdf
  
      @Override
 -    public EnumSet<Goal.Flag> getFlags() {
-+    public ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() { // Paper - remove streams from pathfindergoalselector
++    public ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<Goal.Flag> getFlags() { // Paper - remove streams from GoalSelector
          return this.goal.getFlags();
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index 64c374439b..0a308ad258 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -5,7 +5,7 @@
      private boolean disconnected;
      private int requestedViewDistance = 2;
 -    public String language = "en_us";
-+    public String language = null; // CraftBukkit - default  // Paper - default to null
++    public String language = null; // Paper - default to null
 +    public java.util.Locale adventure$locale = java.util.Locale.US; // Paper
      @Nullable
      private Vec3 startingToFallPosition;
@@ -106,7 +106,7 @@
          this.advancements = server.getPlayerList().getPlayerAdvancements(this);
 -        this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F);
 -        this.updateOptions(clientInformation);
-+        // this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F);  // Paper - Don't move existing players to world spawn
++        // this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn
 +        this.updateOptionsNoEvents(clientInformation); // Paper - don't call options events on login
          this.object = null;
 +
@@ -196,6 +196,14 @@
          if (compound.contains("SpawnX", 99) && compound.contains("SpawnY", 99) && compound.contains("SpawnZ", 99)) {
              this.respawnPosition = new BlockPos(compound.getInt("SpawnX"), compound.getInt("SpawnY"), compound.getInt("SpawnZ"));
              this.respawnForced = compound.getBoolean("SpawnForced");
+@@ -475,6 +_,7 @@
+                 .resultOrPartial(LOGGER::error)
+                 .ifPresent(spawnDimension -> compound.put("SpawnDimension", spawnDimension));
+         }
++        this.getBukkitEntity().setExtraData(compound); // CraftBukkit
+ 
+         compound.putBoolean("spawn_extra_particles_on_fall", this.spawnExtraParticlesOnFall);
+         if (this.raidOmenPosition != null) {
 @@ -490,7 +_,18 @@
      private void saveParentVehicle(CompoundTag tag) {
          Entity rootVehicle = this.getRootVehicle();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
index 239d6f934e..6df8611d59 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
@@ -1,20 +1,12 @@
 --- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
 +++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
-@@ -10,6 +_,7 @@
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundSource;
- import net.minecraft.util.Mth;
 @@ -75,6 +_,16 @@
              .flatMap(
                  nearestVisibleLivingEntities -> nearestVisibleLivingEntities.findClosest(livingEntity -> this.ramTargeting.test(level, entity, livingEntity))
              )
 +            // CraftBukkit start
 +            .map((entityliving) -> {
-+                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof net.minecraft.server.level.ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
 +                if (event.isCancelled() || event.getTarget() == null) {
 +                    return null;
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
index 3556764a3b..9f783335fb 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java.patch
@@ -4,9 +4,9 @@
                  inventory.removeItemType(Items.WHEAT, i3);
                  ItemStack itemStack = inventory.addItem(new ItemStack(Items.BREAD, min));
                  if (!itemStack.isEmpty()) {
-+                    villager.forceDrops = true; // Paper - Add missing forceDrop toggle
++                    villager.forceDrops = true; // Paper - Add missing forceDrop toggles
                      villager.spawnAtLocation(level, itemStack, 0.5F);
-+                    villager.forceDrops = false; // Paper - Add missing forceDrop toggle
++                    villager.forceDrops = false; // Paper - Add missing forceDrop toggles
                  }
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
index 34a28aaf49..8760a911ad 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/Goal.java.patch
@@ -13,8 +13,8 @@
 +    public void addFlag(final Goal.Flag flag) {
 +        this.flags.add(flag);
 +    }
-+
 +    // Paper end - Mob Goal API
++
      protected int adjustedTickDelay(int adjustment) {
          return this.requiresUpdateEveryTick() ? adjustment : reducedTickDelay(adjustment);
      }
@@ -25,7 +25,7 @@
 +    // Paper start - Mob goal api
 +    private com.destroystokyo.paper.entity.ai.PaperVanillaGoal<?> vanillaGoal;
 +    public <T extends org.bukkit.entity.Mob> com.destroystokyo.paper.entity.ai.Goal<T> asPaperVanillaGoal() {
-+        if(this.vanillaGoal == null) {
++        if (this.vanillaGoal == null) {
 +            this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this);
 +        }
 +        //noinspection unchecked
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
index 9973960f95..68b320d4df 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/target/TargetGoal.java.patch
@@ -14,7 +14,7 @@
      @Override
      public void stop() {
 -        this.mob.setTarget(null);
-+        this.mob.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
++        this.mob.setTarget(null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
          this.targetMob = null;
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
index a052e1e5a2..c74a750263 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/sensing/Sensor.java.patch
@@ -1,12 +1,10 @@
 --- a/net/minecraft/world/entity/ai/sensing/Sensor.java
 +++ b/net/minecraft/world/entity/ai/sensing/Sensor.java
-@@ -29,8 +_,19 @@
+@@ -29,8 +_,17 @@
          .ignoreInvisibilityTesting();
      private final int scanRate;
      private long timeToTick;
-+    // Paper start - configurable sensor tick rate and timings
-+    private final String configKey;
-+    // Paper end
++    private final String configKey; // Paper - configurable sensor tick rate and timings
  
      public Sensor(int scanRate) {
 +        // Paper start - configurable sensor tick rate and timings
@@ -20,14 +18,12 @@
          this.scanRate = scanRate;
          this.timeToTick = RANDOM.nextInt(scanRate);
      }
-@@ -41,7 +_,9 @@
+@@ -41,7 +_,7 @@
  
      public final void tick(ServerLevel level, E entity) {
          if (--this.timeToTick <= 0L) {
 -            this.timeToTick = this.scanRate;
-+            // Paper start - configurable sensor tick rate and timings
-+            this.timeToTick = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate);
-+            // Paper end
++            this.timeToTick = java.util.Objects.requireNonNullElse(level.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); // Paper - configurable sensor tick rate and timings
              this.updateTargetingConditionRanges(entity);
              this.doTick(level, entity);
          }

From 9539f74ff0a17cdd2082de77b018a639454f337f Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 08:36:33 -0800
Subject: [PATCH 207/285] Move rewrite dataconverter system patch

---
 .../patches/features/0016-Rewrite-dataconverter-system.patch      | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename feature-patches/1058-Rewrite-dataconverter-system.patch => paper-server/patches/features/0016-Rewrite-dataconverter-system.patch (100%)

diff --git a/feature-patches/1058-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
similarity index 100%
rename from feature-patches/1058-Rewrite-dataconverter-system.patch
rename to paper-server/patches/features/0016-Rewrite-dataconverter-system.patch

From df3be3f436334e6a73391138728299b3203bc980 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 08:47:02 -0800
Subject: [PATCH 208/285] Fix patch line numbers and indices

---
 .../0016-Rewrite-dataconverter-system.patch   | 1256 ++++++++---------
 1 file changed, 628 insertions(+), 628 deletions(-)

diff --git a/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
index f1790227a0..63a36045f0 100644
--- a/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
@@ -8,10 +8,10 @@ for details.
 
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,54 @@
 +package ca.spottedleaf.dataconverter.converters;
 +
 +import java.util.Comparator;
@@ -68,10 +68,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
 +public interface DataHook<T, R> {
@@ -83,10 +83,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
 +public abstract class DataType<T, R> {
@@ -96,10 +96,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ca55b3f7e7208e629e88d4c7bfa9517384a26fef
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
 +import ca.spottedleaf.dataconverter.types.MapType;
@@ -111,10 +111,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a27d3d41109271834b6c37fa22d4b80d9e4b88c8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,79 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -196,10 +196,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..344c8c4f3207b6c8b565e5ad6db2470a272b77c3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,447 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -649,10 +649,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..94da5d6d2f43dae07cfc6750b23689fd4a175d2a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,568 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
 +@SuppressWarnings("unused")
@@ -1223,10 +1223,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.advancements;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1257,10 +1257,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.advancements;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1305,10 +1305,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f227c0565a0c475fcb06991b485507d50bbd2ad0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,60 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1371,10 +1371,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1b871c78e77015d0216a0ecc61aa05689ccfab10
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,57 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1434,10 +1434,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f64b7a1999f9f81ed752626f46803174a9889e9d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1485,10 +1485,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7b47879a7c2e8c21fae43bf5247585c716d75565
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.blockname;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1555,10 +1555,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d4cd5362e77eb71cb8eb45ffcc73185e01be1157
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -1626,10 +1626,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1016 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -2648,10 +2648,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..084c67a46bc5ec7f5a4bef3216805a87b32c83d0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -2686,10 +2686,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cd190605a2c3d8631f85a74a634f7951eec6f0b1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,304 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.custom;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -2996,10 +2996,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6684915d6c0c44328a9296dc3ceb530e69482083
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3040,10 +3040,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3090,10 +3090,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3133,10 +3133,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,371 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3510,10 +3510,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3546,10 +3546,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -3576,10 +3576,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4f4f4cb6037c2a46ffcf427f5812164bbb98b8b7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1829 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +import ca.spottedleaf.dataconverter.types.MapType;
@@ -5411,10 +5411,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..86f6aa3e3fa886976809f350fc5eb16f6a026ed9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,533 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -5950,10 +5950,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bcc586cb68148fd960dd685eecce853169a92ed5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +public final class HelperSpawnEggNameV105 {
@@ -6033,10 +6033,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..28dcc6f1425a46c6c76dd16a67aeab0ec72d1d6a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,106 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -6145,10 +6145,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemname;
 +
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename;
@@ -6169,10 +6169,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..06596b56a1f89900e5f23f7f4a12bd1d5d02b7c8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -6213,10 +6213,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,460 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -6679,10 +6679,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4fa31e40b0a6f571a853299b4e242de921ccbda0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,87 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -6772,10 +6772,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2d29d89cc45866822189a62bffbe1a8fe57c477b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1245 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
@@ -8023,10 +8023,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4c537b661b7a28193add3267ec2d639add49423b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.leveldat;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8075,10 +8075,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.options;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8109,10 +8109,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2cf90187ea8bc54b06cebd54ae2582ca66d91132
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,270 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.particle;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -8385,10 +8385,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,53 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.poi;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8444,10 +8444,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,53 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.poi;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8503,10 +8503,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.recipe;
 +
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename;
@@ -8527,10 +8527,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,66 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.stats;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8599,10 +8599,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..891be75bf5c4af56e839c88b26f0a828554ae5c4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,321 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.stats;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8926,10 +8926,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ab05dda0cc2083418443d0dee23ccc0a6f754ea0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.tileentity;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -8966,10 +8966,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dfa750bdaef7d7b6dadbc5665c1461f7e6df08ca
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,128 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -9100,10 +9100,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b093a9eeeea3f7c1c220485b7144d22c6fd504a0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,166 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -9272,10 +9272,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..075574f33476882ddc6787e3b8bac8643a414eb0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,129 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -9407,10 +9407,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d42bff4fec99eb0b19d132794f4e3306b6dddb0f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,335 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
 +import ca.spottedleaf.dataconverter.minecraft.versions.*;
@@ -9748,10 +9748,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,86 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -9840,10 +9840,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f7dced8a47ebdd262ae815ff9bc453312343ce49
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.hooks;
 +
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataHook;
@@ -9875,10 +9875,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.hooks;
 +
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataHook;
@@ -9901,10 +9901,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..17ded002b5546de8be4a5238c20ccfda460a98bb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,82 @@
 +package ca.spottedleaf.dataconverter.minecraft.util;
 +
 +import com.google.gson.JsonElement;
@@ -9989,10 +9989,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..91b1d0be9d697a4fa8bc5b448b329df1f5deabc4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,161 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10156,10 +10156,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..32d54d5960088b547b3ca09bff28b0752dddd77c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10207,10 +10207,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..00bb3cff8f3d220d65a18f9b82b4b5361588b109
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,86 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10299,10 +10299,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4f35484ed524dbf09cf9e8b1bb999fc98ec0bb0f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -10348,10 +10348,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..189b682da7eacea118610e466e8648675fccf776
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10403,10 +10403,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..fa9b11b46f0fbcaabcaed02a7fc3f5af3337ec27
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,83 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10492,10 +10492,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e9d288c41c40d96ac7c6b605babc436d6a5796f3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10541,10 +10541,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ba9487bc35bedfd7261d4a4fd9476de070f65f33
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10593,10 +10593,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5df0c8da6415a4651e5678a170bc8ff32dd66337
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10640,10 +10640,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b089fc93b88c5a7b4bb1eb0e105120b5393de1b1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10684,10 +10684,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0c69cf9b419049dc5338abb408fa3f0390e4e353
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10754,10 +10754,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..41ceef54e202420616ad57e9f9c200457c7d2848
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,101 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10861,10 +10861,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7b7d02eac9e121c45b557b664e156327d182c015
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -10907,10 +10907,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b735165f9b296730b77339875255aa982e18a40a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,176 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11089,10 +11089,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b003819eb395039dca8141179b57632e90db1d4d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,62 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11157,10 +11157,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..90889dddd8a510fe69c47413f5fe3ed4a756fedb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -11180,10 +11180,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0e198bef171c92d53725d338bb793b1e269f2997
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11221,10 +11221,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bf6f57bc84785622aea35dc70872db6d4d9516a1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11251,10 +11251,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2f6a43d858645baeb3c69959479b6835dd7bd7a8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,513 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11770,10 +11770,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..47682ffbc10805a4cba73dca43198e52c0ce63df
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11813,10 +11813,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..95822caa64d6c8a780bb120bedd2728355d26b84
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,87 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11906,10 +11906,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bf64be7255b02461d218a821ac9b36ba5bc83b13
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -11957,10 +11957,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d870aaca4ff623c71604f889c2e667bfe50fe696
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,142 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12105,10 +12105,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..af9c6ee26580eb10bf8426f5b61c26df63a910a6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12137,10 +12137,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2bf1baee2321b3cb584ab6355f43263d6c8ec0be
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12174,10 +12174,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..99f0f34cc14639ed8ed73b847f74cdc607607af8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12214,10 +12214,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2ae50eea847671f3995688901c79caf520440d7a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12242,10 +12242,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7180c1168bffb9fe70d18fe7414a5372518413a8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12293,10 +12293,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..56d9babebba8b8ba6be07ea413e9c04ffea84023
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12329,10 +12329,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cdbb9379f66aa6edc05c5e6cb2bdeae97f1ea38b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,75 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12410,10 +12410,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a9e42da41064ea293a71dbf2d681a857b2e1812e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12455,10 +12455,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..884049818efdf273443fb3d1c2d7250564fbdbf7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12488,10 +12488,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..907a5e2a26ee046e292508e1f06d5f26d10af8c1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12587,10 +12587,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1e99de15732bdd283835a9531f76e29ddab91f46
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -12623,10 +12623,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1259216b43434d0f7c7be10a081fd05057c253cf
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,151 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12780,10 +12780,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b72fe109aa8c60425c00aad234d60a1c70dda60b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,88 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -12874,10 +12874,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..10349a70b865b19cca471a16548fd49910a2b0e7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,370 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -13250,10 +13250,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..fae8cf61c9900544cdecd223f72e1311c8a1cfb1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -13279,10 +13279,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dbfb51b74c54a9a479de49ecb295854fc69aef64
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -13363,10 +13363,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cd07718649f0e2ca66f1ec3b0aba81611333ba09
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -13446,10 +13446,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ce87995961605c80f24371c9c64706ae76e3edea
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,219 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -13671,10 +13671,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dfc9d1e89983c73e06ce3c8a22c29f49af4a935c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,111 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -13788,10 +13788,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6bcc0de5987db4d9ac28fabefbb58c28f2065d96
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,68 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -13862,10 +13862,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d2093732e06ddccdd8a34bbfcaee6ede3aae96d0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -13896,10 +13896,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f198495e1bad7a1cb84f41c1ea96b1d0e7943c9e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,110 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14012,10 +14012,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..810a838edeea95bb5d0b4b351e65417b762fc45c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14059,10 +14059,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7f65def5a0f48af268183d9c3b74937924b47b75
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14101,10 +14101,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9e1a3af9fb261e585542495f189f898eaa6d9263
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14125,10 +14125,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..aeae0c62efa1e189fe4b0da585c8a2a101bb5ede
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14159,10 +14159,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ad12a97fe28b6f05973f0927245c944dcf184c46
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14211,10 +14211,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2066f320d774319bec84007ca7ed137eb78d91d1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14259,10 +14259,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a4bd2c65fe5a4b4d3e430e5c7eee79435afac4ee
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14299,10 +14299,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dbf3215a781555d048077565851884eeb48402b1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14325,10 +14325,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ede4d0bfc0fe0e4a3a6fb906037a4c964baac6e6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14347,10 +14347,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..02204cd67dc614e95f2ab95ed413ce62baec296f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14402,10 +14402,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a965a5941e3624db725a4f101405357df11598c8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14436,10 +14436,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f8b5f5818ed4e839b62777a5d5e9baf70b12a6f0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14467,10 +14467,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f97f21e12af1e02aacc1591a88b5da3d7e3f4cfa
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14538,10 +14538,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..fe2d58caf2371f1c430dea209210357f36392a96
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,75 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14619,10 +14619,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7f2db47a58baf1851abb9269b13fb08d4740081a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14655,10 +14655,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f1f7cd60d3fb1d7d3de92091681932607b452d25
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14694,10 +14694,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cc377819db8182b466b92aba9a9c0d2c483f941d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14734,10 +14734,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0ae698a80e81a1648bb90149d9f0effdec8e777c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -14759,10 +14759,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ddebd1ea2eec5e469d4857503965084d78afce19
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14802,10 +14802,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..70d3ab9fe12fab7282edc18938faa94a34d3decb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14849,10 +14849,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..19b0a1197cdf5988f21ba332883b65df646ff0c1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14894,10 +14894,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c7887c54c85dd7a198aa5c1597c02b2d6887bf71
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -14926,10 +14926,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8654f8c7f759720e1e1dd8ae94656699f151c407
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15025,10 +15025,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4b1b9b55e2491bd98efddfb28e2aa1074140a1c2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15060,10 +15060,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..023d8b9aa7d95c674847d9c5dbe0061adcbdc4d3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15105,10 +15105,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cec032b20e834a8c6c8901e6fb2d127d7c80b353
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,51 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -15162,10 +15162,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c9a23cf055353ee49f07263ea01161de2c035138
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15217,10 +15217,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7439d0e948f144d93a1fa7b57c2b478a54835d6d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -15251,10 +15251,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..20904d3e18b317a2f7e5d6063fcf94dda27b5768
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15288,10 +15288,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8297fe9ab7007399847f3e7ac84519f0dec08576
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15327,10 +15327,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f2be8817fe733ae30729952a2aae13d2396b8111
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15398,10 +15398,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..540ae9aab0acdfbd3800db0468c52e973cb8d93f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -15422,10 +15422,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..994960d0e67ed0af48d33e9a3db5d1757d85eac5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,73 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15501,10 +15501,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9342d9efeb1980c7cb67bf0620d12bd9f71165ee
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15555,10 +15555,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f9e9d88e4cca15d2d4fdcbc0dbcae4c35c02284a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -15588,10 +15588,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b948564d01726d9891a0733896b3e5cec937bd6d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -15627,10 +15627,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a640878469c7ea155cde1cca728b15f2a4bacd73
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,97 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -15730,10 +15730,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dcd2b1689bbd845238c86cea9dae0c5153d01499
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,590 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16326,10 +16326,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..99f65d84ffaa75db3d2b4568c92d85d3ef20b77f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16369,10 +16369,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..35eccf43fd7e31071a9d64883212cddf021ae861
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16440,10 +16440,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7cb7106037b18c0cf8ddff1f9ba25d4f987a6326
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -16464,10 +16464,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9a4d47d78596e2275745673f31f772f0252f2cda
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -16488,10 +16488,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7777d83d63dc177f0bac72290ed2e5c3cbd028be
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -16535,10 +16535,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..157f4b1673f7b71942949d979890b30a5f9e2ca3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,123 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16664,10 +16664,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e7197d098b3d6269d3a4fd9be0432d85f0504dfd
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -16700,10 +16700,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4e54a4ee0c14109609d8d8f1bc6c0c5dabf4fb07
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16731,10 +16731,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9306ab25feae6315e48aeeb71de960bdf62bcf76
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,63 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16800,10 +16800,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f8d493674380d53398c853899da76024c9d84984
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16848,10 +16848,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c0f6135fff38100c1955d64ee3f4ff984308e503
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16888,10 +16888,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..99d1df6362b290fdaa65385168ff6588647a8056
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -16937,10 +16937,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f64f2c2d6051b7e7024a0ebc42c1dd8dc6434cf9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,346 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -17289,10 +17289,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9cfecd222ef41fdb4f31517a0821d7532386285f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,103 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17398,10 +17398,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9e6c7dc40d509cf424976831382425ab7eceb024
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17426,10 +17426,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f019774923bf08fc0f7dc7cafd5fb66fdd7427f8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17509,10 +17509,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..137a530c1b979e7257b77f405885aa9f4d376c11
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -17563,10 +17563,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e50fbc38dbf9198c0c652b506e50780eca368bb0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17587,10 +17587,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..140bfff947e540452f3794eda2f1e2122f8d3f27
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17611,10 +17611,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7ec79da7e8871d6beca05a25c70d8c6811531faa
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -17655,10 +17655,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..87cbe1c717635908a30c57028346a1abeb21e6a6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17688,10 +17688,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0c996642f561d2471a506a34f5efe6dae5cd1fb3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17710,10 +17710,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1f1685cb0e1427e88dc5c970b0cb58aae0393396
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17734,10 +17734,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..15a7bf7b7ea883d7a3cee9183b92e12838efd690
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17762,10 +17762,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..39ffcec6e78229dd62abfd42c1ac64c3ccccc6dc
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17813,10 +17813,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bb87bcedfc2ed8d19b266e925beca0f54b50a0ab
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17848,10 +17848,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a242e8e9a7a7c80c00ec0d64542b3d7dc3103e24
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17870,10 +17870,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ce568002e54924e001c12271f0bde7183bc23c61
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17918,10 +17918,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e6a2f29b20aa6d7cd431fc63c2d8ed70dc9a2ab8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -17946,10 +17946,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dc1604f48a9f15721e709f2e128210085520c15e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,205 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -18157,10 +18157,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cc89ca8a01c2589c807be2a7560bcc6051417379
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -18198,10 +18198,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3c5fc48f39c08249a61199c7f72dddee65fd98af
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -18226,10 +18226,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0967c8c794869a972c1283cab6b3f3cef1d77aec
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -18253,10 +18253,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e0d6b2f6b00e0bfce205efa889de1765ef22793a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -18284,10 +18284,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cd00c9398791967be6dd10f7183c61902431b27a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -18306,10 +18306,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1b692e4866d99c89705289ad1f386f467382b1c7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,71 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -18383,10 +18383,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..21d1617d222d0b82b1c5222a0ef1a1fa9da02ab8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,929 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19318,10 +19318,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4c58b3b53d8526cbde6cf1e8c90cabdaceaf2a03
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19355,10 +19355,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..356963228d884a0a74e6d7c9922b4ced627bbfb0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,62 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -19423,10 +19423,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bd8117a101d308e59251f927feb0692a6b22f547
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,210 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19639,10 +19639,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..03b3d8e2b97a346a45e6c57cb07474baa3bb6096
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19723,10 +19723,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..28a7596b62f8a918f342e2d06eda050a7f2bad0b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,111 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19840,10 +19840,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e32224267d53d82ba15942141a5cb7a19eb380f2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -19869,10 +19869,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..224ee1d9a3ba68e5a617c2c0846be47feef0bed1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19906,10 +19906,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..eddfdecffcaf5e4cd7e5c3a79864816ffbaacae1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,58 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -19970,10 +19970,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ce083fca547170bb1e1014e868beaf535e940fc3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,209 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20185,10 +20185,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..06fe7dd2580cd8eaad9e0c7de8d0e27287d1b0a9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20231,10 +20231,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a0e89f59c75f6d34480f4b8c4f8fa013dddc5d52
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20260,10 +20260,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..fb108cb7f50755b52f537a888ca155aa9db39b3a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20287,10 +20287,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..79768b25a32a5333f8cb6ec6e8c422478a6891df
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20309,10 +20309,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e6e70ff4a28446fd8c4c663e0e44791ec4e5ac0a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20338,10 +20338,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6a096226995e89285054b4ab35ed3e14ae4da694
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20386,10 +20386,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f06412a417ba12111c9e8f30b747ed3ad6dcbcb6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,54 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20446,10 +20446,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b296229502491b54f6352ee1f9db0023296b36ec
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20476,10 +20476,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2752dfd1a7ff896e2ed736846980da5adde6e657
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20507,10 +20507,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b1cfe038364e12d542d93c3108887173b2e05262
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20538,10 +20538,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d0677d68b393da9b151c7b2add2fbbd8608e315f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20568,10 +20568,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9b9ef34db7dbae8574c4bb3d474592e7991441d5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20618,10 +20618,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c70d6dc72d1d913904b71640fe3476c644449ee2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,63 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20687,10 +20687,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0a1ef2e55a1f9cd6381a2c6fdc04f81ee190ba81
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20722,10 +20722,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..04b84c8466d4fa8f2ad21aae2de44273c05495b6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20763,10 +20763,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..db9eb946638447445649f4576b3698c0774e44bb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20789,10 +20789,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..87053c0c1de258770e7630830307ab484915aad8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20811,10 +20811,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..85270a36c5f75b1c6be49e461b302c3339c95750
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -20835,10 +20835,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0096664e25dca8d690c6154324f97efdb1ada723
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20871,10 +20871,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0a4a1f690f568b8977e9b2caaf7fba15cb3307a5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20900,10 +20900,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..53827b9b8999e7b284f3df0f41c98dbbc7d69c1c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,84 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -20990,10 +20990,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2120a5928446f2597fb261d5d6e91c3c9700cb22
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21015,10 +21015,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6a8d3f6fd18d941e5b0b18fc5208b7fe1f9fd724
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21041,10 +21041,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7051d4f01b6f43f3d435d21d65b83ba702ffda41
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21066,10 +21066,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..75a3cbc8e6749abd4bceff710d2f7c3ca6df9d70
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,15 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21087,10 +21087,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..30c23572a5989f0bd6bff6e424ee59c84bc8d8f1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,47 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21140,10 +21140,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5c09f745e5200393cf4ecdcb5b42466c2e2d94d9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,96 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21242,10 +21242,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..914df7885582a1fde398e755dbfaf00e19ce16b3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21277,10 +21277,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2cf41561b4a229ba4d60540f85ba0a8946bb9753
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21300,10 +21300,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5db4a5222bf80f01c128d97ec849b89003837beb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21355,10 +21355,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6f447d59677be4630b53e948419a0ae2a3414315
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21389,10 +21389,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9e7f34a40280a7704c4a4d1c03a7dda8e030aff5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21418,10 +21418,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ea97d596bfb4b9c6b9b7d0534604e042a775ea78
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21462,10 +21462,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e8a1fcd9e67b151a360e11089289154a14dde27c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21506,10 +21506,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5c64ec5b9bdcc279bc1b86e6bb0b877003b213cb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21605,10 +21605,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..685021e87236c5b7ce5ee0b5422d7bf856ea6652
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21643,10 +21643,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a5b91dc5eb4e9206cbb4489ce14195d7517ef4e0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,58 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21707,10 +21707,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..39b4bd2d0bb36ef242467e89010ef5fc1490de9b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,245 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -21958,10 +21958,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f9e3eb71b268f7bc1940f0199fddfa1ac27401b8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -21980,10 +21980,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..855a95b4ef686fa9d2cefdef5664290528e4dc60
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22019,10 +22019,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..603467a7a2b6da93181a0a32eedb30d1614b0069
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22089,10 +22089,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ccda8d0f7c0a284fd4f91622dc33b832b4a95c45
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22132,10 +22132,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1fc62f9cadb990790420376d5c80b14775b71a48
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22163,10 +22163,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0c34d445825e8b49249af852b1f2f09c8b5a2574
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,14 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22183,10 +22183,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a40397feb5962bd5f4a44cc85bb359f5f99ff03a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22212,10 +22212,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7ff5e2f1a386d75b6d0d6fc3160f2241bf74b262
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22242,10 +22242,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a76916cdb7cf91b8ba5461524472b3e455f02885
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,72 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22320,10 +22320,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..78a10f89218eb0edf121f88978b4fe13e1b1bf44
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,82 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22408,10 +22408,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c45a1a77adbb5dc5ba8c3dae0bb480450520c731
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22455,10 +22455,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f0c0748b003648e5fe06d0b6dd1b21948ac0e54a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22509,10 +22509,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..920d7734d883d74e8334102b22cabce24a79db7e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,131 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -22646,10 +22646,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c4cc52620afb728533efe988bf2066ffc947f2d6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22673,10 +22673,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f50b81d931a1908d405bb72e0679983a742d5223
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,14 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -22693,10 +22693,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a1a4659538c8f678319ddc7d61b400051c8a4953
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,340 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23039,10 +23039,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c45dda60ed8da6802181f7f169a5b97f591b00ee
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23123,10 +23123,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..26e27331223bc5671db49bb730a754597815b8cc
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,153 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23282,10 +23282,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f752bb2fca2e4cd438c0540460912d4bc2c6f25e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23325,10 +23325,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f097881401855137f5d4ac25ba1468e635a702b5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23367,10 +23367,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2a6d144c2f074403bde8a62377ca6986c0c12a84
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -23397,10 +23397,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..632c8008484e844d962405c6ef8fb9f09fc6c977
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -23425,10 +23425,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1cd426cf78d62d428406caa319cc5c8649e7f36c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23465,10 +23465,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..74c13c46390e4533a9eb2c8ae5d9846db55efa94
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,244 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23715,10 +23715,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d65e05285ef238aa8c6d660aa42fcd69e07d9430
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23767,10 +23767,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..45b141a651d954554fcca68f36c0b3344328d902
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -23814,10 +23814,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b0949ac2035662ba1c943b4bfab2f19e985e6864
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23853,10 +23853,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0047a20dab2ffd6b39a8bcb8ed9f3878f20e31c2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,128 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -23987,10 +23987,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..630263a61b5db4207c1a5051e3e2249ab3dd3957
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,112 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24105,10 +24105,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..85eb8f37f89faed8b366c1d1c850b028bfcb2164
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24147,10 +24147,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..855c5a99951996ffe4eabb24a69321043cce41d7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,143 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24296,10 +24296,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..817682bb5830242eca25cc1939ed2bda9f1c460b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24367,10 +24367,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b85673d792d4b1c317d312ba607a0d30c2f57ea9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24395,10 +24395,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3b0855353f40e8ce54b86305152aa35af9154c6f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24422,10 +24422,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..22eae4d39c3887ef4991fd21856c32c43c543f88
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24455,10 +24455,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4e89460386bbc75b8380835b5df3ca821d6a9c82
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24485,10 +24485,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c4c6e75b8aae973fc4e4ac9f6e03ecbb5a38ef99
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24531,10 +24531,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c1a74d545333224d9e8c79667bf42b2617fbe346
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24581,10 +24581,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c8eb7ba000310d1165c63fb9eef3787872f299bb
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24609,10 +24609,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9119204ef25d78b04c5afc58965df56725ac7079
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24651,10 +24651,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8b4041d3d3a4a001bf06eaedbddad1b297122b12
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24674,10 +24674,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7d09c4218d0db8119d1681bf95900be830557fa3
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,69 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24749,10 +24749,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a7a4d6446b7765ac485af82df660aafab05955bf
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -24773,10 +24773,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7f88b435378305a3a66e1e54b85afd9b019513ee
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24824,10 +24824,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3faf2c3265600141003355771f38a7879e0f769a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24853,10 +24853,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3b65108c6a1ac469bb8f81a933b6475f3ea9f63f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24891,10 +24891,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..55f00e218f04e1e095ccc7d62282d87d7eb8f8c7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -24938,10 +24938,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c0d74b4822be60c637f26b2ef1e172fdf9e89d01
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,56 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25000,10 +25000,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cc593df4a09d6cb93196d8cfb34ebac43e61ebbe
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,67 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25073,10 +25073,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e6777f58d7d4722cabd30fa495cee054f58b3e48
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,440 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25519,10 +25519,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e0efac6a303d4c9623e03acdf07f89c2cacc9f04
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,221 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -25746,10 +25746,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..81a2006d5e2059df0979c6380a16255767bcd89a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,59 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25811,10 +25811,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f4ebe856d03d9837214e9a1c93f1b1e79aa7bb08
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25856,10 +25856,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c6b6038255e16bd15873bb7fe596b721fcec365e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25891,10 +25891,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..68810919e168f36de160033aa659060487d94bd8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25961,10 +25961,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dc9fba23654262b1489e4f8056a7f4b222ab1179
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
@@ -25994,10 +25994,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0d59cb380e625bb2658216d4a6cb8faebdd147c5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -26021,10 +26021,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f0e26849e28ce7ce362927ec81b281e51bd1e591
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,363 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
@@ -26390,10 +26390,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,11 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.block_name;
 +
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
@@ -26407,10 +26407,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..64fc063748d4839d787a773d2c7258dcffc6bc21
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.game_event;
 +
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker;
@@ -26439,10 +26439,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..20c8efdb746c9d3b9d87bf991dc44e11e1ea697c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataType;
@@ -26483,10 +26483,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4205546d7b2c4a07d23a017004989875b7beb3c6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataType;
@@ -26523,10 +26523,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4feedd9e48c3a85bd75b9c0a3b09c91fa9532a93
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,183 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
@@ -26712,10 +26712,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,11 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.item_name;
 +
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
@@ -26729,10 +26729,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack;
 +
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
@@ -26747,10 +26747,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack;
 +
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
@@ -26765,10 +26765,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity;
 +
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
@@ -26783,10 +26783,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,272 @@
 +package ca.spottedleaf.dataconverter.types;
 +
 +public interface ListType {
@@ -27061,10 +27061,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b8dad91ad3b8692448134c4f12cf9853dc06fccc
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,223 @@
 +package ca.spottedleaf.dataconverter.types;
 +
 +import java.util.Set;
@@ -27290,10 +27290,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,72 @@
 +package ca.spottedleaf.dataconverter.types;
 +
 +public enum ObjectType {
@@ -27368,10 +27368,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.types;
 +
 +public interface TypeUtil {
@@ -27383,10 +27383,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,15 @@
 +package ca.spottedleaf.dataconverter.types;
 +
 +import ca.spottedleaf.dataconverter.types.json.JsonTypeCompressedUtil;
@@ -27404,10 +27404,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,415 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -27825,10 +27825,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..b6ad4623894454675f4be52ecdb4655d6623b385
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,474 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -28305,10 +28305,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -28329,10 +28329,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,81 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -28416,10 +28416,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,440 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
 +import ca.spottedleaf.dataconverter.types.ObjectType;
@@ -28862,10 +28862,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..01b6796c6ac168a82f41cf4fddbd32a1c8a86484
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,454 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -29322,10 +29322,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
 +import ca.spottedleaf.dataconverter.types.ListType;
@@ -29346,10 +29346,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..40da70d5cf584a9730f9fe81c355cf8513fba475
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,592 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCDataConverter;
@@ -29944,10 +29944,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import it.unimi.dsi.fastutil.ints.Int2IntFunction;
@@ -30027,10 +30027,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,74 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import java.util.Arrays;
@@ -30107,10 +30107,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4bbf38c812feeb30d2aa5f3fcf482bfcbed79d05
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,239 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +public final class IntegerUtil {
@@ -30352,10 +30352,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,76 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import it.unimi.dsi.fastutil.longs.Long2IntFunction;
@@ -30434,10 +30434,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,76 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import java.util.Arrays;
@@ -30516,10 +30516,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98ea57055c7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.util;
 +
 +import ca.spottedleaf.dataconverter.types.MapType;
@@ -30560,10 +30560,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 834c5ce238c7adb0164a6282582d709348ef96cc..11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2 100644
 --- a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -0,0 +0,0 @@ public final class PaperHooks implements PlatformHooks {
+@@ -203,6 +203,43 @@ public final class PaperHooks implements PlatformHooks {
      @Override
      public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
                                    final int fromVersion, final int toVersion) {
@@ -30608,10 +30608,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
          ).getValue();
 diff --git a/src/main/java/net/minecraft/data/structures/StructureUpdater.java b/src/main/java/net/minecraft/data/structures/StructureUpdater.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 96aea6d8cb68dd033c31cbde9d73ee490f320501..c51d71dd24cd28c22cda83cc3128c414ebd71a54 100644
 --- a/src/main/java/net/minecraft/data/structures/StructureUpdater.java
 +++ b/src/main/java/net/minecraft/data/structures/StructureUpdater.java
-@@ -0,0 +0,0 @@ public class StructureUpdater implements SnbtToNbt.Filter {
+@@ -27,7 +27,7 @@ public class StructureUpdater implements SnbtToNbt.Filter {
              LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 4173, name);
          }
  
@@ -30621,10 +30621,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return structureTemplate.save(new CompoundTag());
      }
 diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ccbd76a7d1a3c29759aec86c5780cab6d244915d..807d05097f7313361eadb600187421d25e294413 100644
 --- a/src/main/java/net/minecraft/server/MinecraftServer.java
 +++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -331,6 +331,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
@@ -30633,10 +30633,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Thread thread = new Thread(() -> {
              ((MinecraftServer) atomicreference.get()).runServer();
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 7d5e2e6e96ea9017334dddade54a9dcb37518642..092f7b6bba4e1291f76c2c09155f33803e93eb04 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -86,7 +86,7 @@ public class ChunkStorage implements AutoCloseable {
          } else {
              try {
                  // CraftBukkit start
@@ -30645,7 +30645,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      CompoundTag level = nbttagcompound.getCompound("Level");
                      if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
                          ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -98,7 +98,7 @@ public class ChunkStorage implements AutoCloseable {
                  // CraftBukkit end
  
                  if (i < 1493) {
@@ -30654,7 +30654,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
                          LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
  
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -116,7 +116,7 @@ public class ChunkStorage implements AutoCloseable {
                  // Spigot end
  
                  ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
@@ -30664,10 +30664,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  if (stopBelowZero) {
                      nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e0e843f4f69013379ed70cb63d9b4f72163b828b..578d270d5b7efb9ac8f5dde539170f6021e2b786 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-@@ -0,0 +0,0 @@ public class SimpleRegionStorage implements AutoCloseable {
+@@ -32,13 +32,30 @@ public class SimpleRegionStorage implements AutoCloseable {
          return this.worker.store(pos, nbt);
      }
  
@@ -30702,10 +30702,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public CompletableFuture<Void> synchronize(boolean sync) {
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 5f354b333a39b873915bedd57b647355ae5bdf56..c3586281c9594769593a6027ea0a78f7c76c0262 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 +++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-@@ -0,0 +0,0 @@ public class StructureCheck {
+@@ -151,7 +151,7 @@ public class StructureCheck {
  
                  CompoundTag compoundTag2;
                  try {
@@ -30715,10 +30715,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      LOGGER.warn("Failed to partially datafix chunk {}", pos, var12);
                      return StructureCheckResult.CHUNK_LOAD_NEEDED;
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 05a76f9d18638f10218161450470f07524b723ac..3ab22c384bb8a7772d389977a61d0e28975fdb79 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
 +++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
-@@ -0,0 +0,0 @@ public class StructureTemplateManager {
+@@ -245,7 +245,7 @@ public class StructureTemplateManager {
      public StructureTemplate readStructure(CompoundTag nbt) {
          StructureTemplate structureTemplate = new StructureTemplate();
          int i = NbtUtils.getDataVersion(nbt, 500);
@@ -30728,10 +30728,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 79397b3c76e4b9d2ee03dfa16c2daf4f71ae8b4d..cdca5ae69991cc068bfbc0686b5defb3604a5440 100644
 --- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
 +++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-@@ -0,0 +0,0 @@ public class LevelStorageSource {
+@@ -277,12 +277,21 @@ public class LevelStorageSource {
      static Dynamic<?> readLevelDataTagFixed(Path path, DataFixer dataFixer) throws IOException {
          CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(path);
          CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Data");
@@ -30756,10 +30756,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return DataFixTypes.WORLD_GEN_SETTINGS.updateToCurrentVersion(dataFixer, dynamic1, i);
          });
 diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b54a3741cd3ba615c83c98985cb4b3c4c586ed7a..b148cf247acdd36f856d0495cde4cc5ad32b5a2f 100644
 --- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
 +++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
-@@ -0,0 +0,0 @@ public class PlayerDataStorage {
+@@ -137,7 +137,7 @@ public class PlayerDataStorage {
          }).map((nbttagcompound) -> {
              int i = NbtUtils.getDataVersion(nbttagcompound, -1);
  
@@ -30769,10 +30769,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return nbttagcompound;
          });
 diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 0e8f4710c0ce05e8cd42cbbc9fedc05bf8585a19..15892c7769caa15f3d52a1ee2147cf9615aa0e25 100644
 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
 +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
+@@ -523,7 +523,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
  
          net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
          final int dataVersion = compound.getInt("DataVersion");
@@ -30781,7 +30781,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow());
      }
  
-@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
+@@ -552,7 +552,10 @@ public final class CraftMagicNumbers implements UnsafeValues {
  
          final int dataVersion = data.get("DataVersion").getAsInt();
          final int currentVersion = org.bukkit.craftbukkit.util.CraftMagicNumbers.INSTANCE.getDataVersion();
@@ -30793,7 +30793,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          com.mojang.serialization.DynamicOps<com.google.gson.JsonElement> ops = MinecraftServer.getServer().registryAccess().createSerializationContext(com.mojang.serialization.JsonOps.INSTANCE);
          return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.CODEC.parse(ops, data).getOrThrow(IllegalArgumentException::new));
      }
-@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
+@@ -574,7 +577,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
  
          net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
          int dataVersion = compound.getInt("DataVersion");

From 7d29c678f771fe49cbb852fbde2b92974042357d Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 09:24:34 -0800
Subject: [PATCH 209/285] Add in DataConverter

---
 .../features/0001-Add-PaperHooks.patch        |  252 +++
 ...dBounds-and-getBlockState-for-inlin.patch} |    0
 ...tem-frames-performance-and-bug-fixe.patch} |    0
 ...Manager-and-add-advanced-packet-sup.patch} |    0
 ...05-Allow-Saving-of-Oversized-Chunks.patch} |    0
 ...=> 0006-Entity-Activation-Range-2.0.patch} |    0
 ...6-Anti-Xray.patch => 0007-Anti-Xray.patch} |    0
 ...city-compression-and-cipher-natives.patch} |    0
 ...timize-Collision-to-not-load-chunks.patch} |    0
 ...alSelector-Goal.Flag-Set-operations.patch} |    0
 ...> 0011-Optimize-Voxel-Shape-Merging.patch} |    0
 ...-type-tags-suggestions-in-selectors.patch} |    0
 ...-Oversized-block-entities-in-chunks.patch} |    2 +-
 ...eck-distance-in-entity-interactions.patch} |    2 +-
 ...15-optimize-dirt-and-snow-spreading.patch} |    0
 ...-getChunkAt-calls-for-loaded-chunks.patch} |    0
 ...> 0017-Rewrite-dataconverter-system.patch} | 1445 ++++++++---------
 .../moonrise/paper/PaperHooks.java            |  240 ---
 .../craftbukkit/util/CraftMagicNumbers.java   |    4 +-
 19 files changed, 954 insertions(+), 991 deletions(-)
 create mode 100644 paper-server/patches/features/0001-Add-PaperHooks.patch
 rename paper-server/patches/features/{0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch => 0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch} (100%)
 rename paper-server/patches/features/{0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch => 0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch} (100%)
 rename paper-server/patches/features/{0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch => 0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch} (100%)
 rename paper-server/patches/features/{0004-Allow-Saving-of-Oversized-Chunks.patch => 0005-Allow-Saving-of-Oversized-Chunks.patch} (100%)
 rename paper-server/patches/features/{0005-Entity-Activation-Range-2.0.patch => 0006-Entity-Activation-Range-2.0.patch} (100%)
 rename paper-server/patches/features/{0006-Anti-Xray.patch => 0007-Anti-Xray.patch} (100%)
 rename paper-server/patches/features/{0007-Use-Velocity-compression-and-cipher-natives.patch => 0008-Use-Velocity-compression-and-cipher-natives.patch} (100%)
 rename paper-server/patches/features/{0008-Optimize-Collision-to-not-load-chunks.patch => 0009-Optimize-Collision-to-not-load-chunks.patch} (100%)
 rename paper-server/patches/features/{0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch => 0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch} (100%)
 rename paper-server/patches/features/{0010-Optimize-Voxel-Shape-Merging.patch => 0011-Optimize-Voxel-Shape-Merging.patch} (100%)
 rename paper-server/patches/features/{0011-Fix-entity-type-tags-suggestions-in-selectors.patch => 0012-Fix-entity-type-tags-suggestions-in-selectors.patch} (100%)
 rename paper-server/patches/features/{0012-Handle-Oversized-block-entities-in-chunks.patch => 0013-Handle-Oversized-block-entities-in-chunks.patch} (97%)
 rename paper-server/patches/features/{0013-Check-distance-in-entity-interactions.patch => 0014-Check-distance-in-entity-interactions.patch} (97%)
 rename paper-server/patches/features/{0014-optimize-dirt-and-snow-spreading.patch => 0015-optimize-dirt-and-snow-spreading.patch} (100%)
 rename paper-server/patches/features/{0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch => 0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch} (100%)
 rename paper-server/patches/features/{0016-Rewrite-dataconverter-system.patch => 0017-Rewrite-dataconverter-system.patch} (95%)
 delete mode 100644 paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java

diff --git a/paper-server/patches/features/0001-Add-PaperHooks.patch b/paper-server/patches/features/0001-Add-PaperHooks.patch
new file mode 100644
index 0000000000..db8dd7f311
--- /dev/null
+++ b/paper-server/patches/features/0001-Add-PaperHooks.patch
@@ -0,0 +1,252 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
+Date: Mon, 16 Dec 2024 09:03:35 -0800
+Subject: [PATCH] Add PaperHooks
+
+
+diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..834c5ce238c7adb0164a6282582d709348ef96cc
+--- /dev/null
++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+@@ -0,0 +1,240 @@
++package ca.spottedleaf.moonrise.paper;
++
++import ca.spottedleaf.moonrise.common.PlatformHooks;
++import com.mojang.datafixers.DSL;
++import com.mojang.datafixers.DataFixer;
++import com.mojang.serialization.Dynamic;
++import java.util.Collection;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.GenerationChunkHolder;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.boss.EnderDragonPart;
++import net.minecraft.world.level.BlockGetter;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.ProtoChunk;
++import net.minecraft.world.level.chunk.storage.SerializableChunkData;
++import net.minecraft.world.level.entity.EntityTypeTest;
++import net.minecraft.world.phys.AABB;
++import java.util.List;
++import java.util.function.Predicate;
++
++public final class PaperHooks implements PlatformHooks {
++
++    @Override
++    public String getBrand() {
++        return "Paper";
++    }
++
++    @Override
++    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
++        return blockState.getLightEmission();
++    }
++
++    @Override
++    public Predicate<BlockState> maybeHasLightEmission() {
++        return (final BlockState state) -> {
++            return state.getLightEmission() != 0;
++        };
++    }
++
++    @Override
++    public boolean hasCurrentlyLoadingChunk() {
++        return false;
++    }
++
++    @Override
++    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
++        return null;
++    }
++
++    @Override
++    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
++
++    }
++
++    @Override
++    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
++
++    }
++
++    @Override
++    public boolean allowAsyncTicketUpdates() {
++        return true;
++    }
++
++    @Override
++    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
++
++    }
++
++    @Override
++    public void chunkUnloadFromWorld(final LevelChunk chunk) {
++
++    }
++
++    @Override
++    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
++
++    }
++
++    @Override
++    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
++        final Collection<EnderDragonPart> parts = world.dragonParts();
++        if (parts.isEmpty()) {
++            return;
++        }
++
++        for (final EnderDragonPart part : parts) {
++            if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
++                into.add(part);
++            }
++        }
++    }
++
++    @Override
++    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
++        if (into.size() >= maxCount) {
++            // fix neoforge issue: do not add if list is already full
++            return;
++        }
++
++        final Collection<EnderDragonPart> parts = world.dragonParts();
++        if (parts.isEmpty()) {
++            return;
++        }
++        for (final EnderDragonPart part : parts) {
++            if (!part.getBoundingBox().intersects(boundingBox)) {
++                continue;
++            }
++            final T casted = (T)entityTypeTest.tryCast(part);
++            if (casted != null && (predicate == null || predicate.test(casted))) {
++                into.add(casted);
++                if (into.size() >= maxCount) {
++                    break;
++                }
++            }
++        }
++    }
++
++    @Override
++    public void entityMove(final Entity entity, final long oldSection, final long newSection) {
++
++    }
++
++    @Override
++    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
++        return true;
++    }
++
++    @Override
++    public boolean configFixMC224294() {
++        return true;
++    }
++
++    @Override
++    public boolean configAutoConfigSendDistance() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
++    }
++
++    @Override
++    public double configPlayerMaxLoadRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
++    }
++
++    @Override
++    public double configPlayerMaxGenRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
++    }
++
++    @Override
++    public double configPlayerMaxSendRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
++    }
++
++    @Override
++    public int configPlayerMaxConcurrentLoads() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
++    }
++
++    @Override
++    public int configPlayerMaxConcurrentGens() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
++    }
++
++    @Override
++    public long configAutoSaveInterval(final ServerLevel world) {
++        return world.paperConfig().chunks.autoSaveInterval.value();
++    }
++
++    @Override
++    public int configMaxAutoSavePerTick(final ServerLevel world) {
++        return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
++    }
++
++    @Override
++    public boolean configFixMC159283() {
++        return true;
++    }
++
++    @Override
++    public boolean forceNoSave(final ChunkAccess chunk) {
++        return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
++    }
++
++    @Override
++    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
++                                  final int fromVersion, final int toVersion) {
++        return (CompoundTag)dataFixer.update(
++            type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
++        ).getValue();
++    }
++
++    @Override
++    public boolean hasMainChunkLoadHook() {
++        return false;
++    }
++
++    @Override
++    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
++
++    }
++
++    @Override
++    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
++        return entities;
++    }
++
++    @Override
++    public void unloadEntity(final Entity entity) {
++        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
++    }
++
++    @Override
++    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
++        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
++    }
++
++    @Override
++    public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
++        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
++    }
++}
diff --git a/paper-server/patches/features/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
similarity index 100%
rename from paper-server/patches/features/0001-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
rename to paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
diff --git a/paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
similarity index 100%
rename from paper-server/patches/features/0002-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
rename to paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
diff --git a/paper-server/patches/features/0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
similarity index 100%
rename from paper-server/patches/features/0003-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
rename to paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
diff --git a/paper-server/patches/features/0004-Allow-Saving-of-Oversized-Chunks.patch b/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
similarity index 100%
rename from paper-server/patches/features/0004-Allow-Saving-of-Oversized-Chunks.patch
rename to paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
diff --git a/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
similarity index 100%
rename from paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
rename to paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
diff --git a/paper-server/patches/features/0006-Anti-Xray.patch b/paper-server/patches/features/0007-Anti-Xray.patch
similarity index 100%
rename from paper-server/patches/features/0006-Anti-Xray.patch
rename to paper-server/patches/features/0007-Anti-Xray.patch
diff --git a/paper-server/patches/features/0007-Use-Velocity-compression-and-cipher-natives.patch b/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
similarity index 100%
rename from paper-server/patches/features/0007-Use-Velocity-compression-and-cipher-natives.patch
rename to paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
diff --git a/paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch b/paper-server/patches/features/0009-Optimize-Collision-to-not-load-chunks.patch
similarity index 100%
rename from paper-server/patches/features/0008-Optimize-Collision-to-not-load-chunks.patch
rename to paper-server/patches/features/0009-Optimize-Collision-to-not-load-chunks.patch
diff --git a/paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/paper-server/patches/features/0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
similarity index 100%
rename from paper-server/patches/features/0009-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
rename to paper-server/patches/features/0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
diff --git a/paper-server/patches/features/0010-Optimize-Voxel-Shape-Merging.patch b/paper-server/patches/features/0011-Optimize-Voxel-Shape-Merging.patch
similarity index 100%
rename from paper-server/patches/features/0010-Optimize-Voxel-Shape-Merging.patch
rename to paper-server/patches/features/0011-Optimize-Voxel-Shape-Merging.patch
diff --git a/paper-server/patches/features/0011-Fix-entity-type-tags-suggestions-in-selectors.patch b/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
similarity index 100%
rename from paper-server/patches/features/0011-Fix-entity-type-tags-suggestions-in-selectors.patch
rename to paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
diff --git a/paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch b/paper-server/patches/features/0013-Handle-Oversized-block-entities-in-chunks.patch
similarity index 97%
rename from paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch
rename to paper-server/patches/features/0013-Handle-Oversized-block-entities-in-chunks.patch
index 3d61143e0e..3ae9000bc3 100644
--- a/paper-server/patches/features/0012-Handle-Oversized-block-entities-in-chunks.patch
+++ b/paper-server/patches/features/0013-Handle-Oversized-block-entities-in-chunks.patch
@@ -47,7 +47,7 @@ index 3aea76690bc3e35758d3bf274777130af17d8a0f..9e321ef1c3d5803519b243685f4ee598
          }
      }
 diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index d2d21fe8d7275b01454e09be252d7dd7710cdc2d..5eef540242413df3ed136aa8837866a94cc285b3 100644
+index 5699bc15eba92e22433a20cb8326b59f2ebd3036..8578d1f78ddd1bb75f3230f04bfaa35af9f5f822 100644
 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
 @@ -84,4 +84,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
diff --git a/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch b/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
similarity index 97%
rename from paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
rename to paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
index 6e558c931a..45b999e2f7 100644
--- a/paper-server/patches/features/0013-Check-distance-in-entity-interactions.patch
+++ b/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Check distance in entity interactions
 
 
 diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java
-index 60952bd49a89b8d6247d0c8bac837e5b3d586a76..fe84fe69a2a9ed95ec45a9e5af6e6f5a5a74edda 100644
+index ae1d53cefb9cede1c93cb8b22122a4a2d2d9a40c..80a7a85e1a03a1ca406259207e1ae3b909b3284f 100644
 --- a/net/minecraft/Util.java
 +++ b/net/minecraft/Util.java
 @@ -130,6 +130,7 @@ public class Util {
diff --git a/paper-server/patches/features/0014-optimize-dirt-and-snow-spreading.patch b/paper-server/patches/features/0015-optimize-dirt-and-snow-spreading.patch
similarity index 100%
rename from paper-server/patches/features/0014-optimize-dirt-and-snow-spreading.patch
rename to paper-server/patches/features/0015-optimize-dirt-and-snow-spreading.patch
diff --git a/paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/paper-server/patches/features/0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch
similarity index 100%
rename from paper-server/patches/features/0015-Optimise-getChunkAt-calls-for-loaded-chunks.patch
rename to paper-server/patches/features/0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch
diff --git a/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0017-Rewrite-dataconverter-system.patch
similarity index 95%
rename from paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
rename to paper-server/patches/features/0017-Rewrite-dataconverter-system.patch
index 63a36045f0..844c0f9320 100644
--- a/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0017-Rewrite-dataconverter-system.patch
@@ -6,11 +6,11 @@ Subject: [PATCH] Rewrite dataconverter system
 Please see https://github.com/PaperMC/DataConverter
 for details.
 
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java
+diff --git a/ca/spottedleaf/dataconverter/converters/DataConverter.java b/ca/spottedleaf/dataconverter/converters/DataConverter.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java
++++ b/ca/spottedleaf/dataconverter/converters/DataConverter.java
 @@ -0,0 +1,54 @@
 +package ca.spottedleaf.dataconverter.converters;
 +
@@ -66,11 +66,11 @@ index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525
 +        return getVersion(encoded) + "." + getStep(encoded);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
+diff --git a/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
++++ b/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
@@ -81,11 +81,11 @@ index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced7
 +    public R postHook(final T data, final long fromVersion, final long toVersion);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
+diff --git a/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
++++ b/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
@@ -94,11 +94,11 @@ index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e993672
 +    public abstract R convert(final T data, final long fromVersion, final long toVersion);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
+diff --git a/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ca55b3f7e7208e629e88d4c7bfa9517384a26fef
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
++++ b/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.converters.datatypes;
 +
@@ -109,11 +109,11 @@ index 0000000000000000000000000000000000000000..ca55b3f7e7208e629e88d4c7bfa95173
 +    public T walk(final T data, final long fromVersion, final long toVersion);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a27d3d41109271834b6c37fa22d4b80d9e4b88c8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
++++ b/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
 @@ -0,0 +1,79 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
@@ -194,11 +194,11 @@ index 0000000000000000000000000000000000000000..a27d3d41109271834b6c37fa22d4b80d
 +
 +    private MCDataConverter() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..344c8c4f3207b6c8b565e5ad6db2470a272b77c3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
++++ b/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
 @@ -0,0 +1,447 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
@@ -647,11 +647,11 @@ index 0000000000000000000000000000000000000000..344c8c4f3207b6c8b565e5ad6db2470a
 +
 +    private MCVersionRegistry() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..94da5d6d2f43dae07cfc6750b23689fd4a175d2a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
++++ b/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
 @@ -0,0 +1,568 @@
 +package ca.spottedleaf.dataconverter.minecraft;
 +
@@ -1221,11 +1221,11 @@ index 0000000000000000000000000000000000000000..94da5d6d2f43dae07cfc6750b23689fd
 +
 +    private MCVersions() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.advancements;
 +
@@ -1255,11 +1255,11 @@ index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a7
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java
 @@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.advancements;
 +
@@ -1303,11 +1303,11 @@ index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f227c0565a0c475fcb06991b485507d50bbd2ad0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
 @@ -0,0 +1,60 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
@@ -1369,11 +1369,11 @@ index 0000000000000000000000000000000000000000..f227c0565a0c475fcb06991b485507d5
 +
 +    private ConverterAbstractAttributesRename() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1b871c78e77015d0216a0ecc61aa05689ccfab10
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractOldAttributesRename.java
 @@ -0,0 +1,57 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
@@ -1432,11 +1432,11 @@ index 0000000000000000000000000000000000000000..1b871c78e77015d0216a0ecc61aa0568
 +
 +    private ConverterAbstractOldAttributesRename() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f64b7a1999f9f81ed752626f46803174a9889e9d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterEntityAttributesBaseValueUpdater.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
 +
@@ -1483,11 +1483,11 @@ index 0000000000000000000000000000000000000000..f64b7a1999f9f81ed752626f46803174
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7b47879a7c2e8c21fae43bf5247585c716d75565
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
 @@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.blockname;
 +
@@ -1553,11 +1553,11 @@ index 0000000000000000000000000000000000000000..7b47879a7c2e8c21fae43bf5247585c7
 +        });
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d4cd5362e77eb71cb8eb45ffcc73185e01be1157
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java
 @@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
@@ -1624,11 +1624,11 @@ index 0000000000000000000000000000000000000000..d4cd5362e77eb71cb8eb45ffcc73185e
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java
 @@ -0,0 +1,1016 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
@@ -2646,11 +2646,11 @@ index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..084c67a46bc5ec7f5a4bef3216805a87b32c83d0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java
 @@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.chunk;
 +
@@ -2684,11 +2684,11 @@ index 0000000000000000000000000000000000000000..084c67a46bc5ec7f5a4bef3216805a87
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java b/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cd190605a2c3d8631f85a74a634f7951eec6f0b1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
 @@ -0,0 +1,304 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.custom;
 +
@@ -2994,11 +2994,11 @@ index 0000000000000000000000000000000000000000..cd190605a2c3d8631f85a74a634f7951
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", signTileConverter);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6684915d6c0c44328a9296dc3ceb530e69482083
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
@@ -3038,11 +3038,11 @@ index 0000000000000000000000000000000000000000..6684915d6c0c44328a9296dc3ceb530e
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java
 @@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
@@ -3088,11 +3088,11 @@ index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
@@ -3131,11 +3131,11 @@ index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c8540920
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java
 @@ -0,0 +1,371 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.entity;
 +
@@ -3508,11 +3508,11 @@ index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -3544,11 +3544,11 @@ index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa975
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -3574,11 +3574,11 @@ index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e140
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4f4f4cb6037c2a46ffcf427f5812164bbb98b8b7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java
 @@ -0,0 +1,1829 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -5409,11 +5409,11 @@ index 0000000000000000000000000000000000000000..4f4f4cb6037c2a46ffcf427f5812164b
 +        finalizeMaps();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..86f6aa3e3fa886976809f350fc5eb16f6a026ed9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java
 @@ -0,0 +1,533 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -5948,11 +5948,11 @@ index 0000000000000000000000000000000000000000..86f6aa3e3fa886976809f350fc5eb16f
 +        return POTION_NAMES[id & 127];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bcc586cb68148fd960dd685eecce853169a92ed5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java
 @@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -6031,11 +6031,11 @@ index 0000000000000000000000000000000000000000..bcc586cb68148fd960dd685eecce8531
 +        return ID_TO_STRING[id & 255];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..28dcc6f1425a46c6c76dd16a67aeab0ec72d1d6a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java
 @@ -0,0 +1,106 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
@@ -6143,11 +6143,11 @@ index 0000000000000000000000000000000000000000..28dcc6f1425a46c6c76dd16a67aeab0e
 +
 +    private RenameHelper() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemname;
 +
@@ -6167,11 +6167,11 @@ index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9d
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..06596b56a1f89900e5f23f7f4a12bd1d5d02b7c8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
@@ -6211,11 +6211,11 @@ index 0000000000000000000000000000000000000000..06596b56a1f89900e5f23f7f4a12bd1d
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
 @@ -0,0 +1,460 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
@@ -6677,11 +6677,11 @@ index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4fa31e40b0a6f571a853299b4e242de921ccbda0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java
 @@ -0,0 +1,87 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
@@ -6770,11 +6770,11 @@ index 0000000000000000000000000000000000000000..4fa31e40b0a6f571a853299b4e242de9
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2d29d89cc45866822189a62bffbe1a8fe57c477b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
 @@ -0,0 +1,1245 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
 +
@@ -8021,11 +8021,11 @@ index 0000000000000000000000000000000000000000..2d29d89cc45866822189a62bffbe1a8f
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4c537b661b7a28193add3267ec2d639add49423b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
 @@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.leveldat;
 +
@@ -8073,11 +8073,11 @@ index 0000000000000000000000000000000000000000..4c537b661b7a28193add3267ec2d639a
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.options;
 +
@@ -8107,11 +8107,11 @@ index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java b/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2cf90187ea8bc54b06cebd54ae2582ca66d91132
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
 @@ -0,0 +1,270 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.particle;
 +
@@ -8383,11 +8383,11 @@ index 0000000000000000000000000000000000000000..2cf90187ea8bc54b06cebd54ae2582ca
 +
 +    private ConverterParticleToNBT() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
 @@ -0,0 +1,53 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.poi;
 +
@@ -8442,11 +8442,11 @@ index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b
 +        });
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java
 @@ -0,0 +1,53 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.poi;
 +
@@ -8501,11 +8501,11 @@ index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed7
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.recipe;
 +
@@ -8525,11 +8525,11 @@ index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269
 +        ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.RECIPE, renamer);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java
 @@ -0,0 +1,66 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.stats;
 +
@@ -8597,11 +8597,11 @@ index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e
 +        });
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..891be75bf5c4af56e839c88b26f0a828554ae5c4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java
 @@ -0,0 +1,321 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.stats;
 +
@@ -8924,11 +8924,11 @@ index 0000000000000000000000000000000000000000..891be75bf5c4af56e839c88b26f0a828
 +        };
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java b/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ab05dda0cc2083418443d0dee23ccc0a6f754ea0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
++++ b/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.tileentity;
 +
@@ -8964,11 +8964,11 @@ index 0000000000000000000000000000000000000000..ab05dda0cc2083418443d0dee23ccc0a
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java b/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dfa750bdaef7d7b6dadbc5665c1461f7e6df08ca
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
++++ b/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
 @@ -0,0 +1,128 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
@@ -9098,11 +9098,11 @@ index 0000000000000000000000000000000000000000..dfa750bdaef7d7b6dadbc5665c1461f7
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b093a9eeeea3f7c1c220485b7144d22c6fd504a0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
++++ b/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
 @@ -0,0 +1,166 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
@@ -9270,11 +9270,11 @@ index 0000000000000000000000000000000000000000..b093a9eeeea3f7c1c220485b7144d22c
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..075574f33476882ddc6787e3b8bac8643a414eb0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
++++ b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java
 @@ -0,0 +1,129 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
@@ -9405,11 +9405,11 @@ index 0000000000000000000000000000000000000000..075574f33476882ddc6787e3b8bac864
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d42bff4fec99eb0b19d132794f4e3306b6dddb0f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
++++ b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java
 @@ -0,0 +1,335 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
@@ -9746,11 +9746,11 @@ index 0000000000000000000000000000000000000000..d42bff4fec99eb0b19d132794f4e3306
 +
 +    private MCTypeRegistry() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
++++ b/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java
 @@ -0,0 +1,86 @@
 +package ca.spottedleaf.dataconverter.minecraft.datatypes;
 +
@@ -9838,11 +9838,11 @@ index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f7dced8a47ebdd262ae815ff9bc453312343ce49
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
++++ b/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.hooks;
 +
@@ -9873,11 +9873,11 @@ index 0000000000000000000000000000000000000000..f7dced8a47ebdd262ae815ff9bc45331
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
++++ b/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java
 @@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.hooks;
 +
@@ -9899,11 +9899,11 @@ index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243a
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java b/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..17ded002b5546de8be4a5238c20ccfda460a98bb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
++++ b/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java
 @@ -0,0 +1,82 @@
 +package ca.spottedleaf.dataconverter.minecraft.util;
 +
@@ -9987,11 +9987,11 @@ index 0000000000000000000000000000000000000000..17ded002b5546de8be4a5238c20ccfda
 +
 +    private ComponentUtils() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..91b1d0be9d697a4fa8bc5b448b329df1f5deabc4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
 @@ -0,0 +1,161 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10154,11 +10154,11 @@ index 0000000000000000000000000000000000000000..91b1d0be9d697a4fa8bc5b448b329df1
 +
 +    private V100() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..32d54d5960088b547b3ca09bff28b0752dddd77c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10205,11 +10205,11 @@ index 0000000000000000000000000000000000000000..32d54d5960088b547b3ca09bff28b075
 +
 +    private V101() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..00bb3cff8f3d220d65a18f9b82b4b5361588b109
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
 @@ -0,0 +1,86 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10297,11 +10297,11 @@ index 0000000000000000000000000000000000000000..00bb3cff8f3d220d65a18f9b82b4b536
 +
 +    private V102() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4f35484ed524dbf09cf9e8b1bb999fc98ec0bb0f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
 @@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10346,11 +10346,11 @@ index 0000000000000000000000000000000000000000..4f35484ed524dbf09cf9e8b1bb999fc9
 +
 +    private V1022() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..189b682da7eacea118610e466e8648675fccf776
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
 @@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10401,11 +10401,11 @@ index 0000000000000000000000000000000000000000..189b682da7eacea118610e466e864867
 +
 +    private V105() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..fa9b11b46f0fbcaabcaed02a7fc3f5af3337ec27
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V106.java
 @@ -0,0 +1,83 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10490,11 +10490,11 @@ index 0000000000000000000000000000000000000000..fa9b11b46f0fbcaabcaed02a7fc3f5af
 +
 +    private V106() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e9d288c41c40d96ac7c6b605babc436d6a5796f3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
 @@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10539,11 +10539,11 @@ index 0000000000000000000000000000000000000000..e9d288c41c40d96ac7c6b605babc436d
 +
 +    private V107() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ba9487bc35bedfd7261d4a4fd9476de070f65f33
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
 @@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10591,11 +10591,11 @@ index 0000000000000000000000000000000000000000..ba9487bc35bedfd7261d4a4fd9476de0
 +
 +    private V108() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5df0c8da6415a4651e5678a170bc8ff32dd66337
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10638,11 +10638,11 @@ index 0000000000000000000000000000000000000000..5df0c8da6415a4651e5678a170bc8ff3
 +
 +    private V109() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b089fc93b88c5a7b4bb1eb0e105120b5393de1b1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10682,11 +10682,11 @@ index 0000000000000000000000000000000000000000..b089fc93b88c5a7b4bb1eb0e105120b5
 +
 +    private V110() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0c69cf9b419049dc5338abb408fa3f0390e4e353
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
 @@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10752,11 +10752,11 @@ index 0000000000000000000000000000000000000000..0c69cf9b419049dc5338abb408fa3f03
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..41ceef54e202420616ad57e9f9c200457c7d2848
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
 @@ -0,0 +1,101 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10859,11 +10859,11 @@ index 0000000000000000000000000000000000000000..41ceef54e202420616ad57e9f9c20045
 +
 +    private V1125() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7b7d02eac9e121c45b557b664e156327d182c015
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
 @@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -10905,11 +10905,11 @@ index 0000000000000000000000000000000000000000..7b7d02eac9e121c45b557b664e156327
 +
 +    private V113() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b735165f9b296730b77339875255aa982e18a40a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
 @@ -0,0 +1,176 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11087,11 +11087,11 @@ index 0000000000000000000000000000000000000000..b735165f9b296730b77339875255aa98
 +
 +    private V1344() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b003819eb395039dca8141179b57632e90db1d4d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
 @@ -0,0 +1,62 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11155,11 +11155,11 @@ index 0000000000000000000000000000000000000000..b003819eb395039dca8141179b57632e
 +
 +    private V135() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..90889dddd8a510fe69c47413f5fe3ed4a756fedb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
 @@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11178,11 +11178,11 @@ index 0000000000000000000000000000000000000000..90889dddd8a510fe69c47413f5fe3ed4
 +
 +    private V143() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0e198bef171c92d53725d338bb793b1e269f2997
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
 @@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11219,11 +11219,11 @@ index 0000000000000000000000000000000000000000..0e198bef171c92d53725d338bb793b1e
 +
 +    private V1446() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bf6f57bc84785622aea35dc70872db6d4d9516a1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11249,11 +11249,11 @@ index 0000000000000000000000000000000000000000..bf6f57bc84785622aea35dc70872db6d
 +
 +    private V1450() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2f6a43d858645baeb3c69959479b6835dd7bd7a8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
 @@ -0,0 +1,513 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11768,11 +11768,11 @@ index 0000000000000000000000000000000000000000..2f6a43d858645baeb3c69959479b6835
 +
 +    private V1451() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..47682ffbc10805a4cba73dca43198e52c0ce63df
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11811,11 +11811,11 @@ index 0000000000000000000000000000000000000000..47682ffbc10805a4cba73dca43198e52
 +
 +    private V1456() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..95822caa64d6c8a780bb120bedd2728355d26b84
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
 @@ -0,0 +1,87 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11904,11 +11904,11 @@ index 0000000000000000000000000000000000000000..95822caa64d6c8a780bb120bedd27283
 +
 +    private V1458() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bf64be7255b02461d218a821ac9b36ba5bc83b13
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -11955,11 +11955,11 @@ index 0000000000000000000000000000000000000000..bf64be7255b02461d218a821ac9b36ba
 +
 +    private V1460() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d870aaca4ff623c71604f889c2e667bfe50fe696
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
 @@ -0,0 +1,142 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12103,11 +12103,11 @@ index 0000000000000000000000000000000000000000..d870aaca4ff623c71604f889c2e667bf
 +
 +    private V1466() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..af9c6ee26580eb10bf8426f5b61c26df63a910a6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
 @@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12135,11 +12135,11 @@ index 0000000000000000000000000000000000000000..af9c6ee26580eb10bf8426f5b61c26df
 +
 +    private V147() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2bf1baee2321b3cb584ab6355f43263d6c8ec0be
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
 @@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12172,11 +12172,11 @@ index 0000000000000000000000000000000000000000..2bf1baee2321b3cb584ab6355f43263d
 +
 +    private V1470() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..99f0f34cc14639ed8ed73b847f74cdc607607af8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12212,11 +12212,11 @@ index 0000000000000000000000000000000000000000..99f0f34cc14639ed8ed73b847f74cdc6
 +
 +    private V1474() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2ae50eea847671f3995688901c79caf520440d7a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12240,11 +12240,11 @@ index 0000000000000000000000000000000000000000..2ae50eea847671f3995688901c79caf5
 +
 +    private V1475() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7180c1168bffb9fe70d18fe7414a5372518413a8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12291,11 +12291,11 @@ index 0000000000000000000000000000000000000000..7180c1168bffb9fe70d18fe7414a5372
 +
 +    private V1480() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..56d9babebba8b8ba6be07ea413e9c04ffea84023
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12327,11 +12327,11 @@ index 0000000000000000000000000000000000000000..56d9babebba8b8ba6be07ea413e9c04f
 +
 +    private V1483() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cdbb9379f66aa6edc05c5e6cb2bdeae97f1ea38b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
 @@ -0,0 +1,75 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12408,11 +12408,11 @@ index 0000000000000000000000000000000000000000..cdbb9379f66aa6edc05c5e6cb2bdeae9
 +
 +    private V1484() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a9e42da41064ea293a71dbf2d681a857b2e1812e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12453,11 +12453,11 @@ index 0000000000000000000000000000000000000000..a9e42da41064ea293a71dbf2d681a857
 +
 +    private V1486() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..884049818efdf273443fb3d1c2d7250564fbdbf7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
 @@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12486,11 +12486,11 @@ index 0000000000000000000000000000000000000000..884049818efdf273443fb3d1c2d72505
 +
 +    private V1487() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..907a5e2a26ee046e292508e1f06d5f26d10af8c1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
 @@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12585,11 +12585,11 @@ index 0000000000000000000000000000000000000000..907a5e2a26ee046e292508e1f06d5f26
 +
 +    private V1488() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1e99de15732bdd283835a9531f76e29ddab91f46
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12621,11 +12621,11 @@ index 0000000000000000000000000000000000000000..1e99de15732bdd283835a9531f76e29d
 +
 +    private V1490() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1259216b43434d0f7c7be10a081fd05057c253cf
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
 @@ -0,0 +1,151 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12778,11 +12778,11 @@ index 0000000000000000000000000000000000000000..1259216b43434d0f7c7be10a081fd050
 +
 +    private V1492() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b72fe109aa8c60425c00aad234d60a1c70dda60b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
 @@ -0,0 +1,88 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -12872,11 +12872,11 @@ index 0000000000000000000000000000000000000000..b72fe109aa8c60425c00aad234d60a1c
 +
 +    private V1494() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..10349a70b865b19cca471a16548fd49910a2b0e7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
 @@ -0,0 +1,370 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13248,11 +13248,11 @@ index 0000000000000000000000000000000000000000..10349a70b865b19cca471a16548fd499
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..fae8cf61c9900544cdecd223f72e1311c8a1cfb1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13277,11 +13277,11 @@ index 0000000000000000000000000000000000000000..fae8cf61c9900544cdecd223f72e1311
 +
 +    private V1500() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dbfb51b74c54a9a479de49ecb295854fc69aef64
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
 @@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13361,11 +13361,11 @@ index 0000000000000000000000000000000000000000..dbfb51b74c54a9a479de49ecb295854f
 +
 +    private V1501() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cd07718649f0e2ca66f1ec3b0aba81611333ba09
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
 @@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13444,11 +13444,11 @@ index 0000000000000000000000000000000000000000..cd07718649f0e2ca66f1ec3b0aba8161
 +
 +    private V1502() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ce87995961605c80f24371c9c64706ae76e3edea
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
 @@ -0,0 +1,219 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13669,11 +13669,11 @@ index 0000000000000000000000000000000000000000..ce87995961605c80f24371c9c64706ae
 +
 +    private V1506() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dfc9d1e89983c73e06ce3c8a22c29f49af4a935c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
 @@ -0,0 +1,111 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13786,11 +13786,11 @@ index 0000000000000000000000000000000000000000..dfc9d1e89983c73e06ce3c8a22c29f49
 +
 +    private V1510() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6bcc0de5987db4d9ac28fabefbb58c28f2065d96
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
 @@ -0,0 +1,68 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13860,11 +13860,11 @@ index 0000000000000000000000000000000000000000..6bcc0de5987db4d9ac28fabefbb58c28
 +
 +    private V1514() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d2093732e06ddccdd8a34bbfcaee6ede3aae96d0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -13894,11 +13894,11 @@ index 0000000000000000000000000000000000000000..d2093732e06ddccdd8a34bbfcaee6ede
 +
 +    private V1515() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f198495e1bad7a1cb84f41c1ea96b1d0e7943c9e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
 @@ -0,0 +1,110 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14010,11 +14010,11 @@ index 0000000000000000000000000000000000000000..f198495e1bad7a1cb84f41c1ea96b1d0
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..810a838edeea95bb5d0b4b351e65417b762fc45c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V165.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14057,11 +14057,11 @@ index 0000000000000000000000000000000000000000..810a838edeea95bb5d0b4b351e65417b
 +
 +    private V165() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7f65def5a0f48af268183d9c3b74937924b47b75
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
 @@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14099,11 +14099,11 @@ index 0000000000000000000000000000000000000000..7f65def5a0f48af268183d9c3b749379
 +
 +    private V1800() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9e1a3af9fb261e585542495f189f898eaa6d9263
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14123,11 +14123,11 @@ index 0000000000000000000000000000000000000000..9e1a3af9fb261e585542495f189f898e
 +
 +    private V1801() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..aeae0c62efa1e189fe4b0da585c8a2a101bb5ede
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14157,11 +14157,11 @@ index 0000000000000000000000000000000000000000..aeae0c62efa1e189fe4b0da585c8a2a1
 +
 +    private V1802() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ad12a97fe28b6f05973f0927245c944dcf184c46
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
 @@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14209,11 +14209,11 @@ index 0000000000000000000000000000000000000000..ad12a97fe28b6f05973f0927245c944d
 +
 +    private V1803() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2066f320d774319bec84007ca7ed137eb78d91d1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
 @@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14257,11 +14257,11 @@ index 0000000000000000000000000000000000000000..2066f320d774319bec84007ca7ed137e
 +
 +    private V1904() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a4bd2c65fe5a4b4d3e430e5c7eee79435afac4ee
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14297,11 +14297,11 @@ index 0000000000000000000000000000000000000000..a4bd2c65fe5a4b4d3e430e5c7eee7943
 +
 +    private V1905() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dbf3215a781555d048077565851884eeb48402b1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
 @@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14323,11 +14323,11 @@ index 0000000000000000000000000000000000000000..dbf3215a781555d048077565851884ee
 +
 +    private V1906() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ede4d0bfc0fe0e4a3a6fb906037a4c964baac6e6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14345,11 +14345,11 @@ index 0000000000000000000000000000000000000000..ede4d0bfc0fe0e4a3a6fb906037a4c96
 +
 +    private V1909() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..02204cd67dc614e95f2ab95ed413ce62baec296f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
 @@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14400,11 +14400,11 @@ index 0000000000000000000000000000000000000000..02204cd67dc614e95f2ab95ed413ce62
 +
 +    private V1911() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a965a5941e3624db725a4f101405357df11598c8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14434,11 +14434,11 @@ index 0000000000000000000000000000000000000000..a965a5941e3624db725a4f101405357d
 +
 +    private V1914() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f8b5f5818ed4e839b62777a5d5e9baf70b12a6f0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14465,11 +14465,11 @@ index 0000000000000000000000000000000000000000..f8b5f5818ed4e839b62777a5d5e9baf7
 +
 +    private V1917() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f97f21e12af1e02aacc1591a88b5da3d7e3f4cfa
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
 @@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14536,11 +14536,11 @@ index 0000000000000000000000000000000000000000..f97f21e12af1e02aacc1591a88b5da3d
 +
 +    private V1918() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..fe2d58caf2371f1c430dea209210357f36392a96
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
 @@ -0,0 +1,75 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14617,11 +14617,11 @@ index 0000000000000000000000000000000000000000..fe2d58caf2371f1c430dea209210357f
 +
 +    private V1920() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7f2db47a58baf1851abb9269b13fb08d4740081a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14653,11 +14653,11 @@ index 0000000000000000000000000000000000000000..7f2db47a58baf1851abb9269b13fb08d
 +
 +    private V1925() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f1f7cd60d3fb1d7d3de92091681932607b452d25
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14692,11 +14692,11 @@ index 0000000000000000000000000000000000000000..f1f7cd60d3fb1d7d3de9209168193260
 +
 +    private V1928() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cc377819db8182b466b92aba9a9c0d2c483f941d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14732,11 +14732,11 @@ index 0000000000000000000000000000000000000000..cc377819db8182b466b92aba9a9c0d2c
 +
 +    private V1929() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0ae698a80e81a1648bb90149d9f0effdec8e777c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
 @@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14757,11 +14757,11 @@ index 0000000000000000000000000000000000000000..0ae698a80e81a1648bb90149d9f0effd
 +    private V1931() {}
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ddebd1ea2eec5e469d4857503965084d78afce19
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14800,11 +14800,11 @@ index 0000000000000000000000000000000000000000..ddebd1ea2eec5e469d4857503965084d
 +
 +    private V1936() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..70d3ab9fe12fab7282edc18938faa94a34d3decb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14847,11 +14847,11 @@ index 0000000000000000000000000000000000000000..70d3ab9fe12fab7282edc18938faa94a
 +
 +    private V1946() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..19b0a1197cdf5988f21ba332883b65df646ff0c1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14892,11 +14892,11 @@ index 0000000000000000000000000000000000000000..19b0a1197cdf5988f21ba332883b65df
 +
 +    private V1948() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c7887c54c85dd7a198aa5c1597c02b2d6887bf71
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
 @@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -14924,11 +14924,11 @@ index 0000000000000000000000000000000000000000..c7887c54c85dd7a198aa5c1597c02b2d
 +
 +    private V1953() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8654f8c7f759720e1e1dd8ae94656699f151c407
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
 @@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15023,11 +15023,11 @@ index 0000000000000000000000000000000000000000..8654f8c7f759720e1e1dd8ae94656699
 +
 +    private V1955() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4b1b9b55e2491bd98efddfb28e2aa1074140a1c2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15058,11 +15058,11 @@ index 0000000000000000000000000000000000000000..4b1b9b55e2491bd98efddfb28e2aa107
 +
 +    private V1961() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..023d8b9aa7d95c674847d9c5dbe0061adcbdc4d3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15103,11 +15103,11 @@ index 0000000000000000000000000000000000000000..023d8b9aa7d95c674847d9c5dbe0061a
 +
 +    private V1963() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cec032b20e834a8c6c8901e6fb2d127d7c80b353
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
 @@ -0,0 +1,51 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15160,11 +15160,11 @@ index 0000000000000000000000000000000000000000..cec032b20e834a8c6c8901e6fb2d127d
 +
 +    private V2100() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c9a23cf055353ee49f07263ea01161de2c035138
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
 @@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15215,11 +15215,11 @@ index 0000000000000000000000000000000000000000..c9a23cf055353ee49f07263ea01161de
 +
 +    private V2202() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7439d0e948f144d93a1fa7b57c2b478a54835d6d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15249,11 +15249,11 @@ index 0000000000000000000000000000000000000000..7439d0e948f144d93a1fa7b57c2b478a
 +
 +    private V2209() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..20904d3e18b317a2f7e5d6063fcf94dda27b5768
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
 @@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15286,11 +15286,11 @@ index 0000000000000000000000000000000000000000..20904d3e18b317a2f7e5d6063fcf94dd
 +
 +    private V2211() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8297fe9ab7007399847f3e7ac84519f0dec08576
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15325,11 +15325,11 @@ index 0000000000000000000000000000000000000000..8297fe9ab7007399847f3e7ac84519f0
 +
 +    private V2218() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f2be8817fe733ae30729952a2aae13d2396b8111
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
 @@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15396,11 +15396,11 @@ index 0000000000000000000000000000000000000000..f2be8817fe733ae30729952a2aae13d2
 +
 +    private V2501() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..540ae9aab0acdfbd3800db0468c52e973cb8d93f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15420,11 +15420,11 @@ index 0000000000000000000000000000000000000000..540ae9aab0acdfbd3800db0468c52e97
 +
 +    private V2502() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..994960d0e67ed0af48d33e9a3db5d1757d85eac5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
 @@ -0,0 +1,73 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15499,11 +15499,11 @@ index 0000000000000000000000000000000000000000..994960d0e67ed0af48d33e9a3db5d175
 +
 +    private V2503() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9342d9efeb1980c7cb67bf0620d12bd9f71165ee
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
 @@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15553,11 +15553,11 @@ index 0000000000000000000000000000000000000000..9342d9efeb1980c7cb67bf0620d12bd9
 +
 +    private V2505() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f9e9d88e4cca15d2d4fdcbc0dbcae4c35c02284a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
 @@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15586,11 +15586,11 @@ index 0000000000000000000000000000000000000000..f9e9d88e4cca15d2d4fdcbc0dbcae4c3
 +
 +    private V2508() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b948564d01726d9891a0733896b3e5cec937bd6d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15625,11 +15625,11 @@ index 0000000000000000000000000000000000000000..b948564d01726d9891a0733896b3e5ce
 +
 +    private V2509() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a640878469c7ea155cde1cca728b15f2a4bacd73
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
 @@ -0,0 +1,97 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -15728,11 +15728,11 @@ index 0000000000000000000000000000000000000000..a640878469c7ea155cde1cca728b15f2
 +
 +    private V2511() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dcd2b1689bbd845238c86cea9dae0c5153d01499
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
 @@ -0,0 +1,590 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16324,11 +16324,11 @@ index 0000000000000000000000000000000000000000..dcd2b1689bbd845238c86cea9dae0c51
 +
 +    private V2514() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..99f65d84ffaa75db3d2b4568c92d85d3ef20b77f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16367,11 +16367,11 @@ index 0000000000000000000000000000000000000000..99f65d84ffaa75db3d2b4568c92d85d3
 +
 +    private V2516() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..35eccf43fd7e31071a9d64883212cddf021ae861
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
 @@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16438,11 +16438,11 @@ index 0000000000000000000000000000000000000000..35eccf43fd7e31071a9d64883212cddf
 +
 +    private V2518() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7cb7106037b18c0cf8ddff1f9ba25d4f987a6326
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16462,11 +16462,11 @@ index 0000000000000000000000000000000000000000..7cb7106037b18c0cf8ddff1f9ba25d4f
 +
 +    private V2519() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9a4d47d78596e2275745673f31f772f0252f2cda
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16486,11 +16486,11 @@ index 0000000000000000000000000000000000000000..9a4d47d78596e2275745673f31f772f0
 +
 +    private V2522() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7777d83d63dc177f0bac72290ed2e5c3cbd028be
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16533,11 +16533,11 @@ index 0000000000000000000000000000000000000000..7777d83d63dc177f0bac72290ed2e5c3
 +
 +    private V2523() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..157f4b1673f7b71942949d979890b30a5f9e2ca3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
 @@ -0,0 +1,123 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16662,11 +16662,11 @@ index 0000000000000000000000000000000000000000..157f4b1673f7b71942949d979890b30a
 +
 +    private V2527() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e7197d098b3d6269d3a4fd9be0432d85f0504dfd
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16698,11 +16698,11 @@ index 0000000000000000000000000000000000000000..e7197d098b3d6269d3a4fd9be0432d85
 +
 +    private V2528() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4e54a4ee0c14109609d8d8f1bc6c0c5dabf4fb07
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16729,11 +16729,11 @@ index 0000000000000000000000000000000000000000..4e54a4ee0c14109609d8d8f1bc6c0c5d
 +
 +    private V2529() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9306ab25feae6315e48aeeb71de960bdf62bcf76
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
 @@ -0,0 +1,63 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16798,11 +16798,11 @@ index 0000000000000000000000000000000000000000..9306ab25feae6315e48aeeb71de960bd
 +
 +    private V2531() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f8d493674380d53398c853899da76024c9d84984
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
 @@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16846,11 +16846,11 @@ index 0000000000000000000000000000000000000000..f8d493674380d53398c853899da76024
 +
 +    private V2533() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c0f6135fff38100c1955d64ee3f4ff984308e503
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16886,11 +16886,11 @@ index 0000000000000000000000000000000000000000..c0f6135fff38100c1955d64ee3f4ff98
 +
 +    private V2535() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..99d1df6362b290fdaa65385168ff6588647a8056
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
 @@ -0,0 +1,43 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -16935,11 +16935,11 @@ index 0000000000000000000000000000000000000000..99d1df6362b290fdaa65385168ff6588
 +
 +    private V2538() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f64f2c2d6051b7e7024a0ebc42c1dd8dc6434cf9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
 @@ -0,0 +1,346 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17287,11 +17287,11 @@ index 0000000000000000000000000000000000000000..f64f2c2d6051b7e7024a0ebc42c1dd8d
 +
 +    private V2550() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9cfecd222ef41fdb4f31517a0821d7532386285f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
 @@ -0,0 +1,103 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17396,11 +17396,11 @@ index 0000000000000000000000000000000000000000..9cfecd222ef41fdb4f31517a0821d753
 +
 +    private V2551() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9e6c7dc40d509cf424976831382425ab7eceb024
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17424,11 +17424,11 @@ index 0000000000000000000000000000000000000000..9e6c7dc40d509cf424976831382425ab
 +
 +    private V2552() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f019774923bf08fc0f7dc7cafd5fb66fdd7427f8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
 @@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17507,11 +17507,11 @@ index 0000000000000000000000000000000000000000..f019774923bf08fc0f7dc7cafd5fb66f
 +
 +    private V2553() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..137a530c1b979e7257b77f405885aa9f4d376c11
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
 @@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17561,11 +17561,11 @@ index 0000000000000000000000000000000000000000..137a530c1b979e7257b77f405885aa9f
 +
 +    private V2558() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e50fbc38dbf9198c0c652b506e50780eca368bb0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17585,11 +17585,11 @@ index 0000000000000000000000000000000000000000..e50fbc38dbf9198c0c652b506e50780e
 +
 +    private V2568() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..140bfff947e540452f3794eda2f1e2122f8d3f27
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17609,11 +17609,11 @@ index 0000000000000000000000000000000000000000..140bfff947e540452f3794eda2f1e212
 +
 +    private V2671() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7ec79da7e8871d6beca05a25c70d8c6811531faa
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17653,11 +17653,11 @@ index 0000000000000000000000000000000000000000..7ec79da7e8871d6beca05a25c70d8c68
 +
 +    private V2679() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..87cbe1c717635908a30c57028346a1abeb21e6a6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
 @@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17686,11 +17686,11 @@ index 0000000000000000000000000000000000000000..87cbe1c717635908a30c57028346a1ab
 +
 +    private V2680() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0c996642f561d2471a506a34f5efe6dae5cd1fb3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17708,11 +17708,11 @@ index 0000000000000000000000000000000000000000..0c996642f561d2471a506a34f5efe6da
 +
 +    private V2684() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1f1685cb0e1427e88dc5c970b0cb58aae0393396
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17732,11 +17732,11 @@ index 0000000000000000000000000000000000000000..1f1685cb0e1427e88dc5c970b0cb58aa
 +
 +    private V2686() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..15a7bf7b7ea883d7a3cee9183b92e12838efd690
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17760,11 +17760,11 @@ index 0000000000000000000000000000000000000000..15a7bf7b7ea883d7a3cee9183b92e128
 +
 +    private V2688() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..39ffcec6e78229dd62abfd42c1ac64c3ccccc6dc
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17811,11 +17811,11 @@ index 0000000000000000000000000000000000000000..39ffcec6e78229dd62abfd42c1ac64c3
 +
 +    private V2690() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bb87bcedfc2ed8d19b266e925beca0f54b50a0ab
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17846,11 +17846,11 @@ index 0000000000000000000000000000000000000000..bb87bcedfc2ed8d19b266e925beca0f5
 +
 +    private V2691() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a242e8e9a7a7c80c00ec0d64542b3d7dc3103e24
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17868,11 +17868,11 @@ index 0000000000000000000000000000000000000000..a242e8e9a7a7c80c00ec0d64542b3d7d
 +
 +    private V2693() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ce568002e54924e001c12271f0bde7183bc23c61
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
 @@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17916,11 +17916,11 @@ index 0000000000000000000000000000000000000000..ce568002e54924e001c12271f0bde718
 +
 +    private V2696() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e6a2f29b20aa6d7cd431fc63c2d8ed70dc9a2ab8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -17944,11 +17944,11 @@ index 0000000000000000000000000000000000000000..e6a2f29b20aa6d7cd431fc63c2d8ed70
 +
 +    private V2700() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dc1604f48a9f15721e709f2e128210085520c15e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
 @@ -0,0 +1,205 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18155,11 +18155,11 @@ index 0000000000000000000000000000000000000000..dc1604f48a9f15721e709f2e12821008
 +
 +    private V2701() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cc89ca8a01c2589c807be2a7560bcc6051417379
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
 @@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18196,11 +18196,11 @@ index 0000000000000000000000000000000000000000..cc89ca8a01c2589c807be2a7560bcc60
 +
 +    private V2702() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3c5fc48f39c08249a61199c7f72dddee65fd98af
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18224,11 +18224,11 @@ index 0000000000000000000000000000000000000000..3c5fc48f39c08249a61199c7f72dddee
 +
 +    private V2707() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0967c8c794869a972c1283cab6b3f3cef1d77aec
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18251,11 +18251,11 @@ index 0000000000000000000000000000000000000000..0967c8c794869a972c1283cab6b3f3ce
 +
 +    private V2710() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e0d6b2f6b00e0bfce205efa889de1765ef22793a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18282,11 +18282,11 @@ index 0000000000000000000000000000000000000000..e0d6b2f6b00e0bfce205efa889de1765
 +
 +    private V2717() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cd00c9398791967be6dd10f7183c61902431b27a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18304,11 +18304,11 @@ index 0000000000000000000000000000000000000000..cd00c9398791967be6dd10f7183c6190
 +
 +    private V2825() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1b692e4866d99c89705289ad1f386f467382b1c7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
 @@ -0,0 +1,71 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -18381,11 +18381,11 @@ index 0000000000000000000000000000000000000000..1b692e4866d99c89705289ad1f386f46
 +
 +    private V2831() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..21d1617d222d0b82b1c5222a0ef1a1fa9da02ab8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
 @@ -0,0 +1,929 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19316,11 +19316,11 @@ index 0000000000000000000000000000000000000000..21d1617d222d0b82b1c5222a0ef1a1fa
 +
 +    private V2832() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4c58b3b53d8526cbde6cf1e8c90cabdaceaf2a03
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
 @@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19353,11 +19353,11 @@ index 0000000000000000000000000000000000000000..4c58b3b53d8526cbde6cf1e8c90cabda
 +
 +    private V2833() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..356963228d884a0a74e6d7c9922b4ced627bbfb0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
 @@ -0,0 +1,62 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19421,11 +19421,11 @@ index 0000000000000000000000000000000000000000..356963228d884a0a74e6d7c9922b4ced
 +
 +    private V2838() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bd8117a101d308e59251f927feb0692a6b22f547
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
 @@ -0,0 +1,210 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19637,11 +19637,11 @@ index 0000000000000000000000000000000000000000..bd8117a101d308e59251f927feb0692a
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..03b3d8e2b97a346a45e6c57cb07474baa3bb6096
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java
 @@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19721,11 +19721,11 @@ index 0000000000000000000000000000000000000000..03b3d8e2b97a346a45e6c57cb07474ba
 +
 +    private V2842() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..28a7596b62f8a918f342e2d06eda050a7f2bad0b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
 @@ -0,0 +1,111 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19838,11 +19838,11 @@ index 0000000000000000000000000000000000000000..28a7596b62f8a918f342e2d06eda050a
 +
 +    private V2843() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e32224267d53d82ba15942141a5cb7a19eb380f2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19867,11 +19867,11 @@ index 0000000000000000000000000000000000000000..e32224267d53d82ba15942141a5cb7a1
 +
 +    private V2846() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..224ee1d9a3ba68e5a617c2c0846be47feef0bed1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
 @@ -0,0 +1,31 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19904,11 +19904,11 @@ index 0000000000000000000000000000000000000000..224ee1d9a3ba68e5a617c2c0846be47f
 +
 +    private V2852() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..eddfdecffcaf5e4cd7e5c3a79864816ffbaacae1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
 @@ -0,0 +1,58 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -19968,11 +19968,11 @@ index 0000000000000000000000000000000000000000..eddfdecffcaf5e4cd7e5c3a79864816f
 +
 +    private V2967() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ce083fca547170bb1e1014e868beaf535e940fc3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
 @@ -0,0 +1,209 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20183,11 +20183,11 @@ index 0000000000000000000000000000000000000000..ce083fca547170bb1e1014e868beaf53
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..06fe7dd2580cd8eaad9e0c7de8d0e27287d1b0a9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java
 @@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20229,11 +20229,11 @@ index 0000000000000000000000000000000000000000..06fe7dd2580cd8eaad9e0c7de8d0e272
 +
 +    private V3077() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a0e89f59c75f6d34480f4b8c4f8fa013dddc5d52
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20258,11 +20258,11 @@ index 0000000000000000000000000000000000000000..a0e89f59c75f6d34480f4b8c4f8fa013
 +
 +    private V3078() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..fb108cb7f50755b52f537a888ca155aa9db39b3a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20285,11 +20285,11 @@ index 0000000000000000000000000000000000000000..fb108cb7f50755b52f537a888ca155aa
 +
 +    private V3081() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..79768b25a32a5333f8cb6ec6e8c422478a6891df
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20307,11 +20307,11 @@ index 0000000000000000000000000000000000000000..79768b25a32a5333f8cb6ec6e8c42247
 +
 +    private V3082() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e6e70ff4a28446fd8c4c663e0e44791ec4e5ac0a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20336,11 +20336,11 @@ index 0000000000000000000000000000000000000000..e6e70ff4a28446fd8c4c663e0e44791e
 +
 +    private V3083() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6a096226995e89285054b4ab35ed3e14ae4da694
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
 @@ -0,0 +1,42 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20384,11 +20384,11 @@ index 0000000000000000000000000000000000000000..6a096226995e89285054b4ab35ed3e14
 +
 +    private V3084() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f06412a417ba12111c9e8f30b747ed3ad6dcbcb6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
 @@ -0,0 +1,54 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20444,11 +20444,11 @@ index 0000000000000000000000000000000000000000..f06412a417ba12111c9e8f30b747ed3a
 +
 +    private V3086() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b296229502491b54f6352ee1f9db0023296b36ec
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20474,11 +20474,11 @@ index 0000000000000000000000000000000000000000..b296229502491b54f6352ee1f9db0023
 +
 +    private V3087() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2752dfd1a7ff896e2ed736846980da5adde6e657
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20505,11 +20505,11 @@ index 0000000000000000000000000000000000000000..2752dfd1a7ff896e2ed736846980da5a
 +
 +    private V3088() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b1cfe038364e12d542d93c3108887173b2e05262
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20536,11 +20536,11 @@ index 0000000000000000000000000000000000000000..b1cfe038364e12d542d93c3108887173
 +
 +    private V3090() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d0677d68b393da9b151c7b2add2fbbd8608e315f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20566,11 +20566,11 @@ index 0000000000000000000000000000000000000000..d0677d68b393da9b151c7b2add2fbbd8
 +
 +    private V3093() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9b9ef34db7dbae8574c4bb3d474592e7991441d5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
 @@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20616,11 +20616,11 @@ index 0000000000000000000000000000000000000000..9b9ef34db7dbae8574c4bb3d474592e7
 +
 +    private V3094() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c70d6dc72d1d913904b71640fe3476c644449ee2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
 @@ -0,0 +1,63 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20685,11 +20685,11 @@ index 0000000000000000000000000000000000000000..c70d6dc72d1d913904b71640fe3476c6
 +
 +    private V3097() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0a1ef2e55a1f9cd6381a2c6fdc04f81ee190ba81
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20720,11 +20720,11 @@ index 0000000000000000000000000000000000000000..0a1ef2e55a1f9cd6381a2c6fdc04f81e
 +
 +    private V3108() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..04b84c8466d4fa8f2ad21aae2de44273c05495b6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
 @@ -0,0 +1,35 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20761,11 +20761,11 @@ index 0000000000000000000000000000000000000000..04b84c8466d4fa8f2ad21aae2de44273
 +
 +    private V3201() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..db9eb946638447445649f4576b3698c0774e44bb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
 @@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20787,11 +20787,11 @@ index 0000000000000000000000000000000000000000..db9eb946638447445649f4576b3698c0
 +
 +    private V3203() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..87053c0c1de258770e7630830307ab484915aad8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20809,11 +20809,11 @@ index 0000000000000000000000000000000000000000..87053c0c1de258770e7630830307ab48
 +
 +    private V3204() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..85270a36c5f75b1c6be49e461b302c3339c95750
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20833,11 +20833,11 @@ index 0000000000000000000000000000000000000000..85270a36c5f75b1c6be49e461b302c33
 +
 +    private V3209() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0096664e25dca8d690c6154324f97efdb1ada723
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
 @@ -0,0 +1,30 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20869,11 +20869,11 @@ index 0000000000000000000000000000000000000000..0096664e25dca8d690c6154324f97efd
 +
 +    private V3214() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0a4a1f690f568b8977e9b2caaf7fba15cb3307a5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20898,11 +20898,11 @@ index 0000000000000000000000000000000000000000..0a4a1f690f568b8977e9b2caaf7fba15
 +
 +    private V3319() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..53827b9b8999e7b284f3df0f41c98dbbc7d69c1c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
 @@ -0,0 +1,84 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -20988,11 +20988,11 @@ index 0000000000000000000000000000000000000000..53827b9b8999e7b284f3df0f41c98dbb
 +
 +    private V3322() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2120a5928446f2597fb261d5d6e91c3c9700cb22
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
 @@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21013,11 +21013,11 @@ index 0000000000000000000000000000000000000000..2120a5928446f2597fb261d5d6e91c3c
 +
 +    private V3325() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6a8d3f6fd18d941e5b0b18fc5208b7fe1f9fd724
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
 @@ -0,0 +1,20 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21039,11 +21039,11 @@ index 0000000000000000000000000000000000000000..6a8d3f6fd18d941e5b0b18fc5208b7fe
 +
 +    private V3326() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7051d4f01b6f43f3d435d21d65b83ba702ffda41
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
 @@ -0,0 +1,19 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21064,11 +21064,11 @@ index 0000000000000000000000000000000000000000..7051d4f01b6f43f3d435d21d65b83ba7
 +
 +    private V3327() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..75a3cbc8e6749abd4bceff710d2f7c3ca6df9d70
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
 @@ -0,0 +1,15 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21085,11 +21085,11 @@ index 0000000000000000000000000000000000000000..75a3cbc8e6749abd4bceff710d2f7c3c
 +
 +    private V3328() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..30c23572a5989f0bd6bff6e424ee59c84bc8d8f1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
 @@ -0,0 +1,47 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21138,11 +21138,11 @@ index 0000000000000000000000000000000000000000..30c23572a5989f0bd6bff6e424ee59c8
 +
 +    private V3438() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5c09f745e5200393cf4ecdcb5b42466c2e2d94d9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
 @@ -0,0 +1,96 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21240,11 +21240,11 @@ index 0000000000000000000000000000000000000000..5c09f745e5200393cf4ecdcb5b42466c
 +
 +    private V3439() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..914df7885582a1fde398e755dbfaf00e19ce16b3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21275,11 +21275,11 @@ index 0000000000000000000000000000000000000000..914df7885582a1fde398e755dbfaf00e
 +
 +    private V3440() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2cf41561b4a229ba4d60540f85ba0a8946bb9753
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
 @@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21298,11 +21298,11 @@ index 0000000000000000000000000000000000000000..2cf41561b4a229ba4d60540f85ba0a89
 +
 +    private V3441() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5db4a5222bf80f01c128d97ec849b89003837beb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
 @@ -0,0 +1,49 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21353,11 +21353,11 @@ index 0000000000000000000000000000000000000000..5db4a5222bf80f01c128d97ec849b890
 +
 +    private V3447() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6f447d59677be4630b53e948419a0ae2a3414315
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21387,11 +21387,11 @@ index 0000000000000000000000000000000000000000..6f447d59677be4630b53e948419a0ae2
 +
 +    private V3448() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9e7f34a40280a7704c4a4d1c03a7dda8e030aff5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21416,11 +21416,11 @@ index 0000000000000000000000000000000000000000..9e7f34a40280a7704c4a4d1c03a7dda8
 +
 +    private V3450() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ea97d596bfb4b9c6b9b7d0534604e042a775ea78
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21460,11 +21460,11 @@ index 0000000000000000000000000000000000000000..ea97d596bfb4b9c6b9b7d0534604e042
 +
 +    private V3451() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e8a1fcd9e67b151a360e11089289154a14dde27c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21504,11 +21504,11 @@ index 0000000000000000000000000000000000000000..e8a1fcd9e67b151a360e11089289154a
 +
 +    private V3459() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5c64ec5b9bdcc279bc1b86e6bb0b877003b213cb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
 @@ -0,0 +1,93 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21603,11 +21603,11 @@ index 0000000000000000000000000000000000000000..5c64ec5b9bdcc279bc1b86e6bb0b8770
 +
 +    private V3564() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..685021e87236c5b7ce5ee0b5422d7bf856ea6652
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
 @@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21641,11 +21641,11 @@ index 0000000000000000000000000000000000000000..685021e87236c5b7ce5ee0b5422d7bf8
 +
 +    private V3565() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a5b91dc5eb4e9206cbb4489ce14195d7517ef4e0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
 @@ -0,0 +1,58 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21705,11 +21705,11 @@ index 0000000000000000000000000000000000000000..a5b91dc5eb4e9206cbb4489ce14195d7
 +
 +    private V3566() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..39b4bd2d0bb36ef242467e89010ef5fc1490de9b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
 @@ -0,0 +1,245 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21956,11 +21956,11 @@ index 0000000000000000000000000000000000000000..39b4bd2d0bb36ef242467e89010ef5fc
 +
 +    private V3568() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f9e3eb71b268f7bc1940f0199fddfa1ac27401b8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -21978,11 +21978,11 @@ index 0000000000000000000000000000000000000000..f9e3eb71b268f7bc1940f0199fddfa1a
 +
 +    private V3682() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..855a95b4ef686fa9d2cefdef5664290528e4dc60
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22017,11 +22017,11 @@ index 0000000000000000000000000000000000000000..855a95b4ef686fa9d2cefdef56642905
 +
 +    private V3683() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..603467a7a2b6da93181a0a32eedb30d1614b0069
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
 @@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22087,11 +22087,11 @@ index 0000000000000000000000000000000000000000..603467a7a2b6da93181a0a32eedb30d1
 +
 +    private V3685() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ccda8d0f7c0a284fd4f91622dc33b832b4a95c45
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22130,11 +22130,11 @@ index 0000000000000000000000000000000000000000..ccda8d0f7c0a284fd4f91622dc33b832
 +
 +    private V3689() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1fc62f9cadb990790420376d5c80b14775b71a48
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
 @@ -0,0 +1,25 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22161,11 +22161,11 @@ index 0000000000000000000000000000000000000000..1fc62f9cadb990790420376d5c80b147
 +
 +    private V3692() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0c34d445825e8b49249af852b1f2f09c8b5a2574
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
 @@ -0,0 +1,14 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22181,11 +22181,11 @@ index 0000000000000000000000000000000000000000..0c34d445825e8b49249af852b1f2f09c
 +
 +    private V3799() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a40397feb5962bd5f4a44cc85bb359f5f99ff03a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22210,11 +22210,11 @@ index 0000000000000000000000000000000000000000..a40397feb5962bd5f4a44cc85bb359f5
 +
 +    private V3800() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7ff5e2f1a386d75b6d0d6fc3160f2241bf74b262
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22240,11 +22240,11 @@ index 0000000000000000000000000000000000000000..7ff5e2f1a386d75b6d0d6fc3160f2241
 +
 +    private V3803() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a76916cdb7cf91b8ba5461524472b3e455f02885
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
 @@ -0,0 +1,72 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22318,11 +22318,11 @@ index 0000000000000000000000000000000000000000..a76916cdb7cf91b8ba5461524472b3e4
 +
 +    private V3807() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..78a10f89218eb0edf121f88978b4fe13e1b1bf44
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
 @@ -0,0 +1,82 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22406,11 +22406,11 @@ index 0000000000000000000000000000000000000000..78a10f89218eb0edf121f88978b4fe13
 +
 +    private V3808() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c45a1a77adbb5dc5ba8c3dae0bb480450520c731
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22453,11 +22453,11 @@ index 0000000000000000000000000000000000000000..c45a1a77adbb5dc5ba8c3dae0bb48045
 +
 +    private V3809() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f0c0748b003648e5fe06d0b6dd1b21948ac0e54a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
 @@ -0,0 +1,48 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22507,11 +22507,11 @@ index 0000000000000000000000000000000000000000..f0c0748b003648e5fe06d0b6dd1b2194
 +
 +    private V3812() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..920d7734d883d74e8334102b22cabce24a79db7e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
 @@ -0,0 +1,131 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22644,11 +22644,11 @@ index 0000000000000000000000000000000000000000..920d7734d883d74e8334102b22cabce2
 +    
 +    private static record RenamePair(String from, String to) {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c4cc52620afb728533efe988bf2066ffc947f2d6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22671,11 +22671,11 @@ index 0000000000000000000000000000000000000000..c4cc52620afb728533efe988bf2066ff
 +
 +    private V3814() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f50b81d931a1908d405bb72e0679983a742d5223
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
 @@ -0,0 +1,14 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -22691,11 +22691,11 @@ index 0000000000000000000000000000000000000000..f50b81d931a1908d405bb72e0679983a
 +
 +    private V3816() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a1a4659538c8f678319ddc7d61b400051c8a4953
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
 @@ -0,0 +1,340 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23037,11 +23037,11 @@ index 0000000000000000000000000000000000000000..a1a4659538c8f678319ddc7d61b40005
 +
 +    private V3818() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c45dda60ed8da6802181f7f169a5b97f591b00ee
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
 @@ -0,0 +1,78 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23121,11 +23121,11 @@ index 0000000000000000000000000000000000000000..c45dda60ed8da6802181f7f169a5b97f
 +
 +    private V3820() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..26e27331223bc5671db49bb730a754597815b8cc
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
 @@ -0,0 +1,153 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23280,11 +23280,11 @@ index 0000000000000000000000000000000000000000..26e27331223bc5671db49bb730a75459
 +
 +    private V3825() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f752bb2fca2e4cd438c0540460912d4bc2c6f25e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23323,11 +23323,11 @@ index 0000000000000000000000000000000000000000..f752bb2fca2e4cd438c0540460912d4b
 +
 +    private V3828() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f097881401855137f5d4ac25ba1468e635a702b5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
 @@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23365,11 +23365,11 @@ index 0000000000000000000000000000000000000000..f097881401855137f5d4ac25ba1468e6
 +
 +    private V3833() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2a6d144c2f074403bde8a62377ca6986c0c12a84
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3938.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23395,11 +23395,11 @@ index 0000000000000000000000000000000000000000..2a6d144c2f074403bde8a62377ca6986
 +
 +    private V3938() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..632c8008484e844d962405c6ef8fb9f09fc6c977
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3939.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23423,11 +23423,11 @@ index 0000000000000000000000000000000000000000..632c8008484e844d962405c6ef8fb9f0
 +
 +    private V3939() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1cd426cf78d62d428406caa319cc5c8649e7f36c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3943.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23463,11 +23463,11 @@ index 0000000000000000000000000000000000000000..1cd426cf78d62d428406caa319cc5c86
 +
 +    private V3943() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java b/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..74c13c46390e4533a9eb2c8ae5d9846db55efa94
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V3945.java
 @@ -0,0 +1,244 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23713,11 +23713,11 @@ index 0000000000000000000000000000000000000000..74c13c46390e4533a9eb2c8ae5d9846d
 +
 +    private V3945() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d65e05285ef238aa8c6d660aa42fcd69e07d9430
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4054.java
 @@ -0,0 +1,46 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23765,11 +23765,11 @@ index 0000000000000000000000000000000000000000..d65e05285ef238aa8c6d660aa42fcd69
 +
 +    private V4054() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..45b141a651d954554fcca68f36c0b3344328d902
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4055.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23812,11 +23812,11 @@ index 0000000000000000000000000000000000000000..45b141a651d954554fcca68f36c0b334
 +
 +    private V4055() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b0949ac2035662ba1c943b4bfab2f19e985e6864
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4057.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23851,11 +23851,11 @@ index 0000000000000000000000000000000000000000..b0949ac2035662ba1c943b4bfab2f19e
 +
 +    private V4057() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0047a20dab2ffd6b39a8bcb8ed9f3878f20e31c2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4059.java
 @@ -0,0 +1,128 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -23985,11 +23985,11 @@ index 0000000000000000000000000000000000000000..0047a20dab2ffd6b39a8bcb8ed9f3878
 +
 +    private V4059() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..630263a61b5db4207c1a5051e3e2249ab3dd3957
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4061.java
 @@ -0,0 +1,112 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24103,11 +24103,11 @@ index 0000000000000000000000000000000000000000..630263a61b5db4207c1a5051e3e2249a
 +
 +    private V4061() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..85eb8f37f89faed8b366c1d1c850b028bfcb2164
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4064.java
 @@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24145,11 +24145,11 @@ index 0000000000000000000000000000000000000000..85eb8f37f89faed8b366c1d1c850b028
 +
 +    private V4064() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..855c5a99951996ffe4eabb24a69321043cce41d7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4067.java
 @@ -0,0 +1,143 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24294,11 +24294,11 @@ index 0000000000000000000000000000000000000000..855c5a99951996ffe4eabb24a6932104
 +
 +    private V4067() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..817682bb5830242eca25cc1939ed2bda9f1c460b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4068.java
 @@ -0,0 +1,65 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24365,11 +24365,11 @@ index 0000000000000000000000000000000000000000..817682bb5830242eca25cc1939ed2bda
 +
 +    private V4068() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b85673d792d4b1c317d312ba607a0d30c2f57ea9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4070.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24393,11 +24393,11 @@ index 0000000000000000000000000000000000000000..b85673d792d4b1c317d312ba607a0d30
 +
 +    private V4070() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3b0855353f40e8ce54b86305152aa35af9154c6f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4071.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24420,11 +24420,11 @@ index 0000000000000000000000000000000000000000..3b0855353f40e8ce54b86305152aa35a
 +
 +    private V4071() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..22eae4d39c3887ef4991fd21856c32c43c543f88
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4081.java
 @@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24453,11 +24453,11 @@ index 0000000000000000000000000000000000000000..22eae4d39c3887ef4991fd21856c32c4
 +
 +    private V4081() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4e89460386bbc75b8380835b5df3ca821d6a9c82
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4173.java
 @@ -0,0 +1,24 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24483,11 +24483,11 @@ index 0000000000000000000000000000000000000000..4e89460386bbc75b8380835b5df3ca82
 +
 +    private V4173() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c4c6e75b8aae973fc4e4ac9f6e03ecbb5a38ef99
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4175.java
 @@ -0,0 +1,40 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24529,11 +24529,11 @@ index 0000000000000000000000000000000000000000..c4c6e75b8aae973fc4e4ac9f6e03ecbb
 +
 +    private V4175() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c1a74d545333224d9e8c79667bf42b2617fbe346
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4176.java
 @@ -0,0 +1,44 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24579,11 +24579,11 @@ index 0000000000000000000000000000000000000000..c1a74d545333224d9e8c79667bf42b26
 +
 +    private V4176() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c8eb7ba000310d1165c63fb9eef3787872f299bb
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4180.java
 @@ -0,0 +1,22 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24607,11 +24607,11 @@ index 0000000000000000000000000000000000000000..c8eb7ba000310d1165c63fb9eef37878
 +
 +    private V4180() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9119204ef25d78b04c5afc58965df56725ac7079
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4181.java
 @@ -0,0 +1,36 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24649,11 +24649,11 @@ index 0000000000000000000000000000000000000000..9119204ef25d78b04c5afc58965df567
 +
 +    private V4181() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8b4041d3d3a4a001bf06eaedbddad1b297122b12
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4185.java
 @@ -0,0 +1,17 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24672,11 +24672,11 @@ index 0000000000000000000000000000000000000000..8b4041d3d3a4a001bf06eaedbddad1b2
 +
 +    private V4185() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7d09c4218d0db8119d1681bf95900be830557fa3
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4187.java
 @@ -0,0 +1,69 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24747,11 +24747,11 @@ index 0000000000000000000000000000000000000000..7d09c4218d0db8119d1681bf95900be8
 +
 +    private V4187() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a7a4d6446b7765ac485af82df660aafab05955bf
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24771,11 +24771,11 @@ index 0000000000000000000000000000000000000000..a7a4d6446b7765ac485af82df660aafa
 +
 +    private V501() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7f88b435378305a3a66e1e54b85afd9b019513ee
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24822,11 +24822,11 @@ index 0000000000000000000000000000000000000000..7f88b435378305a3a66e1e54b85afd9b
 +
 +    private V502() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3faf2c3265600141003355771f38a7879e0f769a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
 @@ -0,0 +1,23 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24851,11 +24851,11 @@ index 0000000000000000000000000000000000000000..3faf2c3265600141003355771f38a787
 +
 +    private V505() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3b65108c6a1ac469bb8f81a933b6475f3ea9f63f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
 @@ -0,0 +1,32 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24889,11 +24889,11 @@ index 0000000000000000000000000000000000000000..3b65108c6a1ac469bb8f81a933b6475f
 +
 +    private V700() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..55f00e218f04e1e095ccc7d62282d87d7eb8f8c7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V701.java
 @@ -0,0 +1,41 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24936,11 +24936,11 @@ index 0000000000000000000000000000000000000000..55f00e218f04e1e095ccc7d62282d87d
 +
 +    private V701() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c0d74b4822be60c637f26b2ef1e172fdf9e89d01
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
 @@ -0,0 +1,56 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -24998,11 +24998,11 @@ index 0000000000000000000000000000000000000000..c0d74b4822be60c637f26b2ef1e172fd
 +
 +    private V702() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cc593df4a09d6cb93196d8cfb34ebac43e61ebbe
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
 @@ -0,0 +1,67 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25071,11 +25071,11 @@ index 0000000000000000000000000000000000000000..cc593df4a09d6cb93196d8cfb34ebac4
 +
 +    private V703() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e6777f58d7d4722cabd30fa495cee054f58b3e48
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
 @@ -0,0 +1,440 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25517,11 +25517,11 @@ index 0000000000000000000000000000000000000000..e6777f58d7d4722cabd30fa495cee054
 +
 +    private V704() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e0efac6a303d4c9623e03acdf07f89c2cacc9f04
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V705.java
 @@ -0,0 +1,221 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25744,11 +25744,11 @@ index 0000000000000000000000000000000000000000..e0efac6a303d4c9623e03acdf07f89c2
 +
 +    private V705() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..81a2006d5e2059df0979c6380a16255767bcd89a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
 @@ -0,0 +1,59 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25809,11 +25809,11 @@ index 0000000000000000000000000000000000000000..81a2006d5e2059df0979c6380a162557
 +
 +    private V804() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f4ebe856d03d9837214e9a1c93f1b1e79aa7bb08
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25854,11 +25854,11 @@ index 0000000000000000000000000000000000000000..f4ebe856d03d9837214e9a1c93f1b1e7
 +
 +    private V806() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c6b6038255e16bd15873bb7fe596b721fcec365e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25889,11 +25889,11 @@ index 0000000000000000000000000000000000000000..c6b6038255e16bd15873bb7fe596b721
 +
 +    private V808() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..68810919e168f36de160033aa659060487d94bd8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
 @@ -0,0 +1,64 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25959,11 +25959,11 @@ index 0000000000000000000000000000000000000000..68810919e168f36de160033aa6590604
 +
 +    private V813() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dc9fba23654262b1489e4f8056a7f4b222ab1179
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V816.java
 @@ -0,0 +1,27 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -25992,11 +25992,11 @@ index 0000000000000000000000000000000000000000..dc9fba23654262b1489e4f8056a7f4b2
 +
 +    private V816() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0d59cb380e625bb2658216d4a6cb8faebdd147c5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -26019,11 +26019,11 @@ index 0000000000000000000000000000000000000000..0d59cb380e625bb2658216d4a6cb8fae
 +
 +    private V820() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f0e26849e28ce7ce362927ec81b281e51bd1e591
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
 @@ -0,0 +1,363 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
@@ -26388,11 +26388,11 @@ index 0000000000000000000000000000000000000000..f0e26849e28ce7ce362927ec81b281e5
 +
 +    private V99() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
 @@ -0,0 +1,11 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.block_name;
 +
@@ -26405,11 +26405,11 @@ index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0
 +        super(MCTypeRegistry.BLOCK_NAME, paths);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..64fc063748d4839d787a773d2c7258dcffc6bc21
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java
 @@ -0,0 +1,26 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.game_event;
 +
@@ -26437,11 +26437,11 @@ index 0000000000000000000000000000000000000000..64fc063748d4839d787a773d2c7258dc
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..20c8efdb746c9d3b9d87bf991dc44e11e1ea697c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java
 @@ -0,0 +1,38 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
@@ -26481,11 +26481,11 @@ index 0000000000000000000000000000000000000000..20c8efdb746c9d3b9d87bf991dc44e11
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4205546d7b2c4a07d23a017004989875b7beb3c6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java
 @@ -0,0 +1,34 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
@@ -26521,11 +26521,11 @@ index 0000000000000000000000000000000000000000..4205546d7b2c4a07d23a017004989875
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4feedd9e48c3a85bd75b9c0a3b09c91fa9532a93
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java
 @@ -0,0 +1,183 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.generic;
 +
@@ -26710,11 +26710,11 @@ index 0000000000000000000000000000000000000000..4feedd9e48c3a85bd75b9c0a3b09c91f
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java
 @@ -0,0 +1,11 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.item_name;
 +
@@ -26727,11 +26727,11 @@ index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daa
 +        super(MCTypeRegistry.ITEM_NAME, paths);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack;
 +
@@ -26745,11 +26745,11 @@ index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d
 +        super(MCTypeRegistry.ITEM_STACK, paths);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack;
 +
@@ -26763,11 +26763,11 @@ index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b
 +        super(MCTypeRegistry.ITEM_STACK, paths);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
+diff --git a/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
++++ b/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity;
 +
@@ -26781,11 +26781,11 @@ index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818
 +        super(MCTypeRegistry.TILE_ENTITY, paths);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java
+diff --git a/ca/spottedleaf/dataconverter/types/ListType.java b/ca/spottedleaf/dataconverter/types/ListType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java
++++ b/ca/spottedleaf/dataconverter/types/ListType.java
 @@ -0,0 +1,272 @@
 +package ca.spottedleaf.dataconverter.types;
 +
@@ -27059,11 +27059,11 @@ index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b
 +    public void addString(final int index, final String string);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java
+diff --git a/ca/spottedleaf/dataconverter/types/MapType.java b/ca/spottedleaf/dataconverter/types/MapType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b8dad91ad3b8692448134c4f12cf9853dc06fccc
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java
++++ b/ca/spottedleaf/dataconverter/types/MapType.java
 @@ -0,0 +1,223 @@
 +package ca.spottedleaf.dataconverter.types;
 +
@@ -27288,11 +27288,11 @@ index 0000000000000000000000000000000000000000..b8dad91ad3b8692448134c4f12cf9853
 +        throw new IllegalArgumentException("Object " + value + " is not a valid type!");
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java
+diff --git a/ca/spottedleaf/dataconverter/types/ObjectType.java b/ca/spottedleaf/dataconverter/types/ObjectType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java
++++ b/ca/spottedleaf/dataconverter/types/ObjectType.java
 @@ -0,0 +1,72 @@
 +package ca.spottedleaf.dataconverter.types;
 +
@@ -27366,11 +27366,11 @@ index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891
 +        return null;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java
+diff --git a/ca/spottedleaf/dataconverter/types/TypeUtil.java b/ca/spottedleaf/dataconverter/types/TypeUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java
++++ b/ca/spottedleaf/dataconverter/types/TypeUtil.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.dataconverter.types;
 +
@@ -27381,11 +27381,11 @@ index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82
 +    public <K> MapType<K> createEmptyMap();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java
+diff --git a/ca/spottedleaf/dataconverter/types/Types.java b/ca/spottedleaf/dataconverter/types/Types.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java
++++ b/ca/spottedleaf/dataconverter/types/Types.java
 @@ -0,0 +1,15 @@
 +package ca.spottedleaf.dataconverter.types;
 +
@@ -27402,11 +27402,11 @@ index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155
 +    // why does this exist
 +    public static final TypeUtil JSON_COMPRESSED = new JsonTypeCompressedUtil();
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java
+diff --git a/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/ca/spottedleaf/dataconverter/types/json/JsonListType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java
++++ b/ca/spottedleaf/dataconverter/types/json/JsonListType.java
 @@ -0,0 +1,415 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
@@ -27823,11 +27823,11 @@ index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b2
 +        throw new UnsupportedOperationException();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
+diff --git a/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..b6ad4623894454675f4be52ecdb4655d6623b385
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
++++ b/ca/spottedleaf/dataconverter/types/json/JsonMapType.java
 @@ -0,0 +1,474 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
@@ -28303,11 +28303,11 @@ index 0000000000000000000000000000000000000000..b6ad4623894454675f4be52ecdb4655d
 +        this.map.addProperty(key, val);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
+diff --git a/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
++++ b/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
@@ -28327,11 +28327,11 @@ index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78
 +        return new JsonMapType(true);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
+diff --git a/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
++++ b/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java
 @@ -0,0 +1,81 @@
 +package ca.spottedleaf.dataconverter.types.json;
 +
@@ -28414,11 +28414,11 @@ index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
+diff --git a/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
++++ b/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java
 @@ -0,0 +1,440 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
@@ -28860,11 +28860,11 @@ index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c
 +        this.list.add(index, StringTag.valueOf(string));
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
+diff --git a/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..01b6796c6ac168a82f41cf4fddbd32a1c8a86484
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
++++ b/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java
 @@ -0,0 +1,454 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
@@ -29320,11 +29320,11 @@ index 0000000000000000000000000000000000000000..01b6796c6ac168a82f41cf4fddbd32a1
 +        this.map.putString(key, val);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
+diff --git a/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
++++ b/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.dataconverter.types.nbt;
 +
@@ -29344,11 +29344,11 @@ index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd
 +        return new NBTMapType();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
+diff --git a/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java b/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..40da70d5cf584a9730f9fe81c355cf8513fba475
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
++++ b/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
 @@ -0,0 +1,592 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -29942,11 +29942,11 @@ index 0000000000000000000000000000000000000000..40da70d5cf584a9730f9fe81c355cf85
 +        );
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
+diff --git a/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
++++ b/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
 @@ -0,0 +1,77 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30025,11 +30025,11 @@ index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8
 +        return this.val[index];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
+diff --git a/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
++++ b/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java
 @@ -0,0 +1,74 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30105,11 +30105,11 @@ index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd
 +        return this.val[index];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java
+diff --git a/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/ca/spottedleaf/dataconverter/util/IntegerUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4bbf38c812feeb30d2aa5f3fcf482bfcbed79d05
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java
++++ b/ca/spottedleaf/dataconverter/util/IntegerUtil.java
 @@ -0,0 +1,239 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30350,11 +30350,11 @@ index 0000000000000000000000000000000000000000..4bbf38c812feeb30d2aa5f3fcf482bfc
 +        throw new RuntimeException();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
+diff --git a/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
++++ b/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java
 @@ -0,0 +1,76 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30432,11 +30432,11 @@ index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d86
 +        return this.val[index];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
+diff --git a/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
++++ b/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java
 @@ -0,0 +1,76 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30514,11 +30514,11 @@ index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c
 +        return this.val[index];
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
+diff --git a/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98ea57055c7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
++++ b/ca/spottedleaf/dataconverter/util/NamespaceUtil.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.dataconverter.util;
 +
@@ -30559,10 +30559,10 @@ index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98e
 +        return correct.equals(value) ? null : correct;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 index 834c5ce238c7adb0164a6282582d709348ef96cc..11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2 100644
---- a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 @@ -203,6 +203,43 @@ public final class PaperHooks implements PlatformHooks {
      @Override
      public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
@@ -30607,68 +30607,68 @@ index 834c5ce238c7adb0164a6282582d709348ef96cc..11cfe9cc29666ce3a6a40281069fb9eb
          return (CompoundTag)dataFixer.update(
              type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
          ).getValue();
-diff --git a/src/main/java/net/minecraft/data/structures/StructureUpdater.java b/src/main/java/net/minecraft/data/structures/StructureUpdater.java
-index 96aea6d8cb68dd033c31cbde9d73ee490f320501..c51d71dd24cd28c22cda83cc3128c414ebd71a54 100644
---- a/src/main/java/net/minecraft/data/structures/StructureUpdater.java
-+++ b/src/main/java/net/minecraft/data/structures/StructureUpdater.java
+diff --git a/net/minecraft/data/structures/StructureUpdater.java b/net/minecraft/data/structures/StructureUpdater.java
+index 1110ca4075a1bbaa46b66686435dab91b275c945..c2218630c3074c8b3f82364e37503b12bd0a0d74 100644
+--- a/net/minecraft/data/structures/StructureUpdater.java
++++ b/net/minecraft/data/structures/StructureUpdater.java
 @@ -27,7 +27,7 @@ public class StructureUpdater implements SnbtToNbt.Filter {
-             LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 4173, name);
+             LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", dataVersion, 4173, structureLocationPath);
          }
  
--        CompoundTag compoundTag = DataFixTypes.STRUCTURE.updateToCurrentVersion(DataFixers.getDataFixer(), nbt, i);
-+        CompoundTag compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper
+-        CompoundTag compoundTag = DataFixTypes.STRUCTURE.updateToCurrentVersion(DataFixers.getDataFixer(), tag, dataVersion);
++        CompoundTag compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, tag, dataVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper
          structureTemplate.load(BuiltInRegistries.BLOCK, compoundTag);
          return structureTemplate.save(new CompoundTag());
      }
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index ccbd76a7d1a3c29759aec86c5780cab6d244915d..807d05097f7313361eadb600187421d25e294413 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -331,6 +331,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index d450d4af96716caff4b29a84d1d83ec4010854f0..646c2f2b617ed706021c83c9fc4492860dfdd4e9 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
  
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
+     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
 +        ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-         AtomicReference<S> atomicreference = new AtomicReference();
-         Thread thread = new Thread(() -> {
-             ((MinecraftServer) atomicreference.get()).runServer();
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-index 7d5e2e6e96ea9017334dddade54a9dcb37518642..092f7b6bba4e1291f76c2c09155f33803e93eb04 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -86,7 +86,7 @@ public class ChunkStorage implements AutoCloseable {
+         AtomicReference<S> atomicReference = new AtomicReference<>();
+         Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> atomicReference.get().runServer(), "Server thread");
+         thread.setUncaughtExceptionHandler((thread1, exception) -> LOGGER.error("Uncaught exception in server thread", exception));
+diff --git a/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+index 1b931e68634e72c3465a99aa29aa53009163046b..80bc7ad9ad076968d06279dedd845d5946cf2501 100644
+--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+@@ -85,7 +85,7 @@ public class ChunkStorage implements AutoCloseable {
          } else {
              try {
                  // CraftBukkit start
--                if (i < 1466) {
-+                if (false && i < 1466) { // Paper - no longer needed, data converter system / DFU handles it now
-                     CompoundTag level = nbttagcompound.getCompound("Level");
+-                if (version < 1466) {
++                if (false && version < 1466) { // Paper - no longer needed, data converter system / DFU handles it now
+                     CompoundTag level = chunkData.getCompound("Level");
                      if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
-                         ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
-@@ -98,7 +98,7 @@ public class ChunkStorage implements AutoCloseable {
+                         net.minecraft.server.level.ServerChunkCache cps = (generatoraccess == null) ? null : ((net.minecraft.server.level.ServerLevel) generatoraccess).getChunkSource();
+@@ -96,7 +96,7 @@ public class ChunkStorage implements AutoCloseable {
+                 }
                  // CraftBukkit end
- 
-                 if (i < 1493) {
--                    nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
-+                    nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter
-                     if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
-                         LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
- 
-@@ -116,7 +116,7 @@ public class ChunkStorage implements AutoCloseable {
+                 if (version < 1493) {
+-                    chunkData = DataFixTypes.CHUNK.update(this.fixerUpper, chunkData, version, 1493);
++                    chunkData = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, chunkData, version, 1493); // Paper - replace chunk converter
+                     if (chunkData.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+                         LegacyStructureDataHandler legacyStructureHandler = this.getLegacyStructureHandler(levelKey, storage);
+                         chunkData = legacyStructureHandler.updateFromLegacy(chunkData);
+@@ -113,7 +113,7 @@ public class ChunkStorage implements AutoCloseable {
                  // Spigot end
  
-                 ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
--                nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
-+                nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
+                 injectDatafixingContext(chunkData, levelKey, chunkGeneratorKey);
+-                chunkData = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, chunkData, Math.max(1493, version));
++                chunkData = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, chunkData, Math.max(1493, version), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
                  // Spigot start
                  if (stopBelowZero) {
-                     nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-index e0e843f4f69013379ed70cb63d9b4f72163b828b..578d270d5b7efb9ac8f5dde539170f6021e2b786 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+                     chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN).toString());
+diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+index 6be673172548c1382c7402ec4e1ec6ef51f702d3..41ddaceb7485626b1f2ee258c2142eb3114c106e 100644
+--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
++++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 @@ -32,13 +32,30 @@ public class SimpleRegionStorage implements AutoCloseable {
-         return this.worker.store(pos, nbt);
+         return this.worker.store(chunkPos, data);
      }
  
 +    // Paper start - rewrite data conversion system
@@ -30683,122 +30683,73 @@ index e0e843f4f69013379ed70cb63d9b4f72163b828b..578d270d5b7efb9ac8f5dde539170f60
 +    }
 +    // Paper end - rewrite data conversion system
 +
-     public CompoundTag upgradeChunkTag(CompoundTag nbt, int oldVersion) {
--        int i = NbtUtils.getDataVersion(nbt, oldVersion);
--        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, nbt, i);
+     public CompoundTag upgradeChunkTag(CompoundTag tag, int version) {
+-        int dataVersion = NbtUtils.getDataVersion(tag, version);
+-        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, tag, dataVersion);
 +        // Paper start - rewrite data conversion system
-+        final int dataVer = NbtUtils.getDataVersion(nbt, oldVersion);
-+        return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), nbt, dataVer, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
++        final int dataVer = NbtUtils.getDataVersion(tag, version);
++        return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), tag, dataVer, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
 +        // Paper end - rewrite data conversion system
      }
  
-     public Dynamic<Tag> upgradeChunkTag(Dynamic<Tag> nbt, int oldVersion) {
--        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, nbt, oldVersion);
+     public Dynamic<Tag> upgradeChunkTag(Dynamic<Tag> tag, int version) {
+-        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, tag, version);
 +        // Paper start - rewrite data conversion system
-+        final CompoundTag converted = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), (CompoundTag)nbt.getValue(), oldVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
-+        return new Dynamic<>(net.minecraft.nbt.NbtOps.INSTANCE, converted);
++        final CompoundTag converted = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), (CompoundTag)tag.getValue(), version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
++        return new Dynamic<>(tag.getOps(), converted);
 +        // Paper end - rewrite data conversion system
      }
  
-     public CompletableFuture<Void> synchronize(boolean sync) {
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-index 5f354b333a39b873915bedd57b647355ae5bdf56..c3586281c9594769593a6027ea0a78f7c76c0262 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+     public CompletableFuture<Void> synchronize(boolean flushStorage) {
+diff --git a/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+index b348d06b261b23eef02c7b14b3010669de9a1b7e..06b54c0bec4031689d5c2da5cfea4ef28dbd16bc 100644
+--- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java
++++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 @@ -151,7 +151,7 @@ public class StructureCheck {
  
-                 CompoundTag compoundTag2;
+                 CompoundTag compoundTag1;
                  try {
--                    compoundTag2 = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, compoundTag, i);
-+                    compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
+-                    compoundTag1 = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, compoundTag, version);
++                    compoundTag1 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
                  } catch (Exception var12) {
-                     LOGGER.warn("Failed to partially datafix chunk {}", pos, var12);
+                     LOGGER.warn("Failed to partially datafix chunk {}", chunkPos, var12);
                      return StructureCheckResult.CHUNK_LOAD_NEEDED;
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
-index 05a76f9d18638f10218161450470f07524b723ac..3ab22c384bb8a7772d389977a61d0e28975fdb79 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
+diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
+index 408ba448c2f127e27e30bfcc6f35f0bdcf86d298..80533af16a59e9ad7e38d1c37b213529a4ecf5b8 100644
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
 @@ -245,7 +245,7 @@ public class StructureTemplateManager {
      public StructureTemplate readStructure(CompoundTag nbt) {
          StructureTemplate structureTemplate = new StructureTemplate();
-         int i = NbtUtils.getDataVersion(nbt, 500);
--        structureTemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, nbt, i));
-+        structureTemplate.load(this.blockLookup, ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, SharedConstants.getCurrentVersion().getDataVersion().getVersion())); // Paper
+         int dataVersion = NbtUtils.getDataVersion(nbt, 500);
+-        structureTemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, nbt, dataVersion));
++        structureTemplate.load(this.blockLookup, ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, dataVersion, SharedConstants.getCurrentVersion().getDataVersion().getVersion())); // Paper - rewrite data conversion system
          return structureTemplate;
      }
  
-diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-index 79397b3c76e4b9d2ee03dfa16c2daf4f71ae8b4d..cdca5ae69991cc068bfbc0686b5defb3604a5440 100644
---- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-+++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-@@ -277,12 +277,21 @@ public class LevelStorageSource {
-     static Dynamic<?> readLevelDataTagFixed(Path path, DataFixer dataFixer) throws IOException {
-         CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(path);
-         CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Data");
--        int i = NbtUtils.getDataVersion(nbttagcompound1, -1);
-+        int i = NbtUtils.getDataVersion(nbttagcompound1, -1); final int version = i; // Paper - obfuscation helpers
-         Dynamic<?> dynamic = DataFixTypes.LEVEL.updateToCurrentVersion(dataFixer, new Dynamic(NbtOps.INSTANCE, nbttagcompound1), i);
- 
-+        // Paper start - replace data conversion system
-         dynamic = dynamic.update("Player", (dynamic1) -> {
--            return DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, i);
-+            return new Dynamic<>(
-+                NbtOps.INSTANCE,
-+                ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(
-+                    ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER,
-+                    (net.minecraft.nbt.CompoundTag)dynamic1.getValue(),
-+                    version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()
-+                )
-+            );
-         });
-+        // Paper end - replace data conversion system
-         dynamic = dynamic.update("WorldGenSettings", (dynamic1) -> {
-             return DataFixTypes.WORLD_GEN_SETTINGS.updateToCurrentVersion(dataFixer, dynamic1, i);
-         });
-diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
-index b54a3741cd3ba615c83c98985cb4b3c4c586ed7a..b148cf247acdd36f856d0495cde4cc5ad32b5a2f 100644
---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
-+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
-@@ -137,7 +137,7 @@ public class PlayerDataStorage {
-         }).map((nbttagcompound) -> {
-             int i = NbtUtils.getDataVersion(nbttagcompound, -1);
- 
--            nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i);
-+            nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - rewrite data conversion system
-             // entityhuman.load(nbttagcompound); // CraftBukkit - handled above
-             return nbttagcompound;
-         });
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-index 0e8f4710c0ce05e8cd42cbbc9fedc05bf8585a19..15892c7769caa15f3d52a1ee2147cf9615aa0e25 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-@@ -523,7 +523,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
- 
-         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
-         final int dataVersion = compound.getInt("DataVersion");
--        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
-+        compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, this.getDataVersion()); // Paper - replace data conversion system
-         return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow());
+diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
+index ed6ea17e9cf3498591f2acd232a48107564b774b..117e8a155937ed5312d544b6de748206c255c84f 100644
+--- a/net/minecraft/world/level/storage/LevelStorageSource.java
++++ b/net/minecraft/world/level/storage/LevelStorageSource.java
+@@ -227,7 +227,7 @@ public class LevelStorageSource {
+         CompoundTag compound = levelDataTagRaw.getCompound("Data");
+         int dataVersion = NbtUtils.getDataVersion(compound, -1);
+         Dynamic<?> dynamic = DataFixTypes.LEVEL.updateToCurrentVersion(dataFixer, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion);
+-        dynamic = dynamic.update("Player", dynamic1 -> DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, dataVersion));
++        dynamic = dynamic.update("Player", dynamic1 -> new Dynamic(dynamic1.getOps(), ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, (net.minecraft.nbt.CompoundTag)dynamic1.getValue(), dataVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()))); // Paper - replace data conversion system
+         return dynamic.update("WorldGenSettings", dynamic1 -> DataFixTypes.WORLD_GEN_SETTINGS.updateToCurrentVersion(dataFixer, dynamic1, dataVersion));
      }
  
-@@ -552,7 +552,10 @@ public final class CraftMagicNumbers implements UnsafeValues {
+diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
+index 6ac8f16500e44069b84d862d5f0e5c5cbc07387e..5682f6d601cbf8a7eb0d76eafd52095435252579 100644
+--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
+@@ -115,7 +115,7 @@ public class PlayerDataStorage {
  
-         final int dataVersion = data.get("DataVersion").getAsInt();
-         final int currentVersion = org.bukkit.craftbukkit.util.CraftMagicNumbers.INSTANCE.getDataVersion();
--        data = (com.google.gson.JsonObject) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(com.mojang.serialization.JsonOps.INSTANCE, data), dataVersion, currentVersion).getValue();
-+        data = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertJson(
-+            ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK,
-+            data, false, dataVersion, currentVersion
-+        );
-         com.mojang.serialization.DynamicOps<com.google.gson.JsonElement> ops = MinecraftServer.getServer().registryAccess().createSerializationContext(com.mojang.serialization.JsonOps.INSTANCE);
-         return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.CODEC.parse(ops, data).getOrThrow(IllegalArgumentException::new));
-     }
-@@ -574,7 +577,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
- 
-         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
-         int dataVersion = compound.getInt("DataVersion");
--        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
-+        compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, this.getDataVersion());
-         if (!preserveUUID) {
-             // Generate a new UUID so we don't have to worry about deserializing the same entity twice
-             compound.remove("UUID");
+         return optional.or(() -> this.load(name, uuid, ".dat_old")).map(compoundTag -> { // CraftBukkit
+             int dataVersion = NbtUtils.getDataVersion(compoundTag, -1);
+-            compoundTag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundTag, dataVersion);
++            compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, compoundTag, dataVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - rewrite data conversion system
+             // player.load(compoundTag); // CraftBukkit - handled above
+             return compoundTag;
+         });
diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
deleted file mode 100644
index 834c5ce238..0000000000
--- a/paper-server/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package ca.spottedleaf.moonrise.paper;
-
-import ca.spottedleaf.moonrise.common.PlatformHooks;
-import com.mojang.datafixers.DSL;
-import com.mojang.datafixers.DataFixer;
-import com.mojang.serialization.Dynamic;
-import java.util.Collection;
-import net.minecraft.core.BlockPos;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.NbtOps;
-import net.minecraft.server.level.ChunkHolder;
-import net.minecraft.server.level.GenerationChunkHolder;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.entity.boss.EnderDragonPart;
-import net.minecraft.world.level.BlockGetter;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.chunk.ChunkAccess;
-import net.minecraft.world.level.chunk.LevelChunk;
-import net.minecraft.world.level.chunk.ProtoChunk;
-import net.minecraft.world.level.chunk.storage.SerializableChunkData;
-import net.minecraft.world.level.entity.EntityTypeTest;
-import net.minecraft.world.phys.AABB;
-import java.util.List;
-import java.util.function.Predicate;
-
-public final class PaperHooks implements PlatformHooks {
-
-    @Override
-    public String getBrand() {
-        return "Paper";
-    }
-
-    @Override
-    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
-        return blockState.getLightEmission();
-    }
-
-    @Override
-    public Predicate<BlockState> maybeHasLightEmission() {
-        return (final BlockState state) -> {
-            return state.getLightEmission() != 0;
-        };
-    }
-
-    @Override
-    public boolean hasCurrentlyLoadingChunk() {
-        return false;
-    }
-
-    @Override
-    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
-        return null;
-    }
-
-    @Override
-    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
-
-    }
-
-    @Override
-    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
-
-    }
-
-    @Override
-    public boolean allowAsyncTicketUpdates() {
-        return true;
-    }
-
-    @Override
-    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
-
-    }
-
-    @Override
-    public void chunkUnloadFromWorld(final LevelChunk chunk) {
-
-    }
-
-    @Override
-    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
-
-    }
-
-    @Override
-    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
-
-    }
-
-    @Override
-    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
-
-    }
-
-    @Override
-    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
-        final Collection<EnderDragonPart> parts = world.dragonParts();
-        if (parts.isEmpty()) {
-            return;
-        }
-
-        for (final EnderDragonPart part : parts) {
-            if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
-                into.add(part);
-            }
-        }
-    }
-
-    @Override
-    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
-        if (into.size() >= maxCount) {
-            // fix neoforge issue: do not add if list is already full
-            return;
-        }
-
-        final Collection<EnderDragonPart> parts = world.dragonParts();
-        if (parts.isEmpty()) {
-            return;
-        }
-        for (final EnderDragonPart part : parts) {
-            if (!part.getBoundingBox().intersects(boundingBox)) {
-                continue;
-            }
-            final T casted = (T)entityTypeTest.tryCast(part);
-            if (casted != null && (predicate == null || predicate.test(casted))) {
-                into.add(casted);
-                if (into.size() >= maxCount) {
-                    break;
-                }
-            }
-        }
-    }
-
-    @Override
-    public void entityMove(final Entity entity, final long oldSection, final long newSection) {
-
-    }
-
-    @Override
-    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
-        return true;
-    }
-
-    @Override
-    public boolean configFixMC224294() {
-        return true;
-    }
-
-    @Override
-    public boolean configAutoConfigSendDistance() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
-    }
-
-    @Override
-    public double configPlayerMaxLoadRate() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
-    }
-
-    @Override
-    public double configPlayerMaxGenRate() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
-    }
-
-    @Override
-    public double configPlayerMaxSendRate() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
-    }
-
-    @Override
-    public int configPlayerMaxConcurrentLoads() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
-    }
-
-    @Override
-    public int configPlayerMaxConcurrentGens() {
-        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
-    }
-
-    @Override
-    public long configAutoSaveInterval(final ServerLevel world) {
-        return world.paperConfig().chunks.autoSaveInterval.value();
-    }
-
-    @Override
-    public int configMaxAutoSavePerTick(final ServerLevel world) {
-        return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
-    }
-
-    @Override
-    public boolean configFixMC159283() {
-        return true;
-    }
-
-    @Override
-    public boolean forceNoSave(final ChunkAccess chunk) {
-        return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
-    }
-
-    @Override
-    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
-                                  final int fromVersion, final int toVersion) {
-        return (CompoundTag)dataFixer.update(
-            type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
-        ).getValue();
-    }
-
-    @Override
-    public boolean hasMainChunkLoadHook() {
-        return false;
-    }
-
-    @Override
-    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
-
-    }
-
-    @Override
-    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
-        return entities;
-    }
-
-    @Override
-    public void unloadEntity(final Entity entity) {
-        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
-    }
-
-    @Override
-    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
-        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
-    }
-
-    @Override
-    public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
-        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
-    }
-}
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 0e8f4710c0..b6665e1875 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -523,7 +523,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
 
         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
         final int dataVersion = compound.getInt("DataVersion");
-        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
+        compound = ca.spottedleaf.moonrise.common.PlatformHooks.get().convertNBT(References.ITEM_STACK, MinecraftServer.getServer().fixerUpper, compound, dataVersion, this.getDataVersion()); // Paper - possibly use dataconverter
         return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow());
     }
 
@@ -574,7 +574,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
 
         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
         int dataVersion = compound.getInt("DataVersion");
-        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
+        compound = ca.spottedleaf.moonrise.common.PlatformHooks.get().convertNBT(References.ENTITY, MinecraftServer.getServer().fixerUpper, compound, dataVersion, this.getDataVersion()); // Paper - possibly use dataconverter
         if (!preserveUUID) {
             // Generate a new UUID so we don't have to worry about deserializing the same entity twice
             compound.remove("UUID");

From 57c683647f72d55fb6b46febe5ed5ba7cbfcfd02 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Mon, 16 Dec 2024 19:22:36 +0100
Subject: [PATCH 210/285] reformat spigot package

---
 .../main/java/org/spigotmc/AsyncCatcher.java  |  13 +-
 .../main/java/org/spigotmc/LimitStream.java   |  27 +-
 .../src/main/java/org/spigotmc/Metrics.java   | 641 ------------------
 .../java/org/spigotmc/RestartCommand.java     | 159 ++---
 .../main/java/org/spigotmc/SpigotCommand.java |  16 +-
 .../main/java/org/spigotmc/SpigotConfig.java  | 391 +++++------
 .../java/org/spigotmc/SpigotWorldConfig.java  | 408 +++++------
 .../main/java/org/spigotmc/TickLimiter.java   |   4 +-
 .../org/spigotmc/TicksPerSecondCommand.java   |  64 +-
 .../main/java/org/spigotmc/TrackingRange.java |   4 +-
 .../java/org/spigotmc/WatchdogThread.java     | 208 +++---
 11 files changed, 559 insertions(+), 1376 deletions(-)
 delete mode 100644 paper-server/src/main/java/org/spigotmc/Metrics.java

diff --git a/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
index ef25987604..86fba4d18a 100644
--- a/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -2,17 +2,14 @@ package org.spigotmc;
 
 import net.minecraft.server.MinecraftServer;
 
-public class AsyncCatcher
-{
+public class AsyncCatcher {
 
     public static boolean enabled = true;
 
-    public static void catchOp(String reason)
-    {
-        if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
-        {
-            MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
-            throw new IllegalStateException( "Asynchronous " + reason + "!" );
+    public static void catchOp(String reason) {
+        if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
+            MinecraftServer.LOGGER.error("Thread {} failed main thread check: {}", Thread.currentThread().getName(), reason, new Throwable()); // Paper
+            throw new IllegalStateException("Asynchronous " + reason + "!");
         }
     }
 }
diff --git a/paper-server/src/main/java/org/spigotmc/LimitStream.java b/paper-server/src/main/java/org/spigotmc/LimitStream.java
index 8de241a921..60e9ec374e 100644
--- a/paper-server/src/main/java/org/spigotmc/LimitStream.java
+++ b/paper-server/src/main/java/org/spigotmc/LimitStream.java
@@ -5,35 +5,30 @@ import java.io.IOException;
 import java.io.InputStream;
 import net.minecraft.nbt.NbtAccounter;
 
-public class LimitStream extends FilterInputStream
-{
+public class LimitStream extends FilterInputStream {
 
     private final NbtAccounter limit;
 
-    public LimitStream(InputStream is, NbtAccounter limit)
-    {
-        super( is );
+    public LimitStream(InputStream is, NbtAccounter limit) {
+        super(is);
         this.limit = limit;
     }
 
     @Override
-    public int read() throws IOException
-    {
-        this.limit.accountBytes( 1 );
+    public int read() throws IOException {
+        this.limit.accountBytes(1);
         return super.read();
     }
 
     @Override
-    public int read(byte[] b) throws IOException
-    {
-        this.limit.accountBytes( b.length );
-        return super.read( b );
+    public int read(byte[] b) throws IOException {
+        this.limit.accountBytes(b.length);
+        return super.read(b);
     }
 
     @Override
-    public int read(byte[] b, int off, int len) throws IOException
-    {
-        this.limit.accountBytes( len );
-        return super.read( b, off, len );
+    public int read(byte[] b, int off, int len) throws IOException {
+        this.limit.accountBytes(len);
+        return super.read(b, off, len);
     }
 }
diff --git a/paper-server/src/main/java/org/spigotmc/Metrics.java b/paper-server/src/main/java/org/spigotmc/Metrics.java
deleted file mode 100644
index 0abb93bd40..0000000000
--- a/paper-server/src/main/java/org/spigotmc/Metrics.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/*
- * Copyright 2011-2013 Tyler Blair. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are
- * permitted provided that the following conditions are met:
- *
- *    1. Redistributions of source code must retain the above copyright notice, this list of
- *       conditions and the following disclaimer.
- *
- *    2. Redistributions in binary form must reproduce the above copyright notice, this list
- *       of conditions and the following disclaimer in the documentation and/or other materials
- *       provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and documentation are those of the
- * authors and contributors and should not be interpreted as representing official policies,
- * either expressed or implied, of anybody else.
- */
-package org.spigotmc;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import net.minecraft.server.MinecraftServer;
-import org.bukkit.Bukkit;
-import org.bukkit.configuration.InvalidConfigurationException;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-/**
- * <p> The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. </p> <p>
- * Public methods provided by this class: </p>
- * <code>
- * Graph createGraph(String name); <br/>
- * void addCustomData(BukkitMetrics.Plotter plotter); <br/>
- * void start(); <br/>
- * </code>
- */
-public class Metrics {
-
-    /**
-     * The current revision number
-     */
-    private static final int REVISION = 6;
-    /**
-     * The base url of the metrics domain
-     */
-    private static final String BASE_URL = "https://mcstats.spigotmc.org";
-    /**
-     * The url used to report a server's status
-     */
-    private static final String REPORT_URL = "/report/%s";
-    /**
-     * The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and
-     * want to change it.
-     */
-    private static final String CUSTOM_DATA_SEPARATOR = "~~";
-    /**
-     * Interval of time to ping (in minutes)
-     */
-    private static final int PING_INTERVAL = 10;
-    /**
-     * All of the custom graphs to submit to metrics
-     */
-    private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
-    /**
-     * The default graph, used for addCustomData when you don't want a specific graph
-     */
-    private final Graph defaultGraph = new Graph("Default");
-    /**
-     * The plugin configuration file
-     */
-    private final YamlConfiguration configuration;
-    /**
-     * The plugin configuration file
-     */
-    private final File configurationFile;
-    /**
-     * Unique server id
-     */
-    private final String guid;
-    /**
-     * Debug mode
-     */
-    private final boolean debug;
-    /**
-     * Lock for synchronization
-     */
-    private final Object optOutLock = new Object();
-    /**
-     * The scheduled task
-     */
-    private volatile Timer task = null;
-
-    public Metrics() throws IOException {
-        // load the config
-        this.configurationFile = this.getConfigFile();
-        this.configuration = YamlConfiguration.loadConfiguration(this.configurationFile);
-
-        // add some defaults
-        this.configuration.addDefault("opt-out", false);
-        this.configuration.addDefault("guid", UUID.randomUUID().toString());
-        this.configuration.addDefault("debug", false);
-
-        // Do we need to create the file?
-        if (this.configuration.get("guid", null) == null) {
-            this.configuration.options().header("http://mcstats.org").copyDefaults(true);
-            this.configuration.save(this.configurationFile);
-        }
-
-        // Load the guid then
-        this.guid = this.configuration.getString("guid");
-        this.debug = this.configuration.getBoolean("debug", false);
-    }
-
-    /**
-     * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
-     * website. Plotters can be added to the graph object returned.
-     *
-     * @param name The name of the graph
-     * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
-     */
-    public Graph createGraph(final String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("Graph name cannot be null");
-        }
-
-        // Construct the graph object
-        final Graph graph = new Graph(name);
-
-        // Now we can add our graph
-        this.graphs.add(graph);
-
-        // and return back
-        return graph;
-    }
-
-    /**
-     * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
-     *
-     * @param graph The name of the graph
-     */
-    public void addGraph(final Graph graph) {
-        if (graph == null) {
-            throw new IllegalArgumentException("Graph cannot be null");
-        }
-
-        this.graphs.add(graph);
-    }
-
-    /**
-     * Adds a custom data plotter to the default graph
-     *
-     * @param plotter The plotter to use to plot custom data
-     */
-    public void addCustomData(final Plotter plotter) {
-        if (plotter == null) {
-            throw new IllegalArgumentException("Plotter cannot be null");
-        }
-
-        // Add the plotter to the graph o/
-        this.defaultGraph.addPlotter(plotter);
-
-        // Ensure the default graph is included in the submitted graphs
-        this.graphs.add(this.defaultGraph);
-    }
-
-    /**
-     * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
-     * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
-     * ticks.
-     *
-     * @return True if statistics measuring is running, otherwise false.
-     */
-    public boolean start() {
-        synchronized (this.optOutLock) {
-            // Did we opt out?
-            if (this.isOptOut()) {
-                return false;
-            }
-
-            // Is metrics already running?
-            if (this.task != null) {
-                return true;
-            }
-
-            // Begin hitting the server with glorious data
-            this.task = new Timer("Spigot Metrics Thread", true);
-
-            this.task.scheduleAtFixedRate(new TimerTask() {
-                private boolean firstPost = true;
-
-                public void run() {
-                    try {
-                        // This has to be synchronized or it can collide with the disable method.
-                        synchronized (Metrics.this.optOutLock) {
-                            // Disable Task, if it is running and the server owner decided to opt-out
-                            if (Metrics.this.isOptOut() && Metrics.this.task != null) {
-                                Metrics.this.task.cancel();
-                                Metrics.this.task = null;
-                                // Tell all plotters to stop gathering information.
-                                for (Graph graph : Metrics.this.graphs) {
-                                    graph.onOptOut();
-                                }
-                            }
-                        }
-
-                        // We use the inverse of firstPost because if it is the first time we are posting,
-                        // it is not a interval ping, so it evaluates to FALSE
-                        // Each time thereafter it will evaluate to TRUE, i.e PING!
-                        Metrics.this.postPlugin(!this.firstPost);
-
-                        // After the first post we set firstPost to false
-                        // Each post thereafter will be a ping
-                        this.firstPost = false;
-                    } catch (IOException e) {
-                        if (Metrics.this.debug) {
-                            Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
-                        }
-                    }
-                }
-            }, 0, TimeUnit.MINUTES.toMillis(Metrics.PING_INTERVAL));
-
-            return true;
-        }
-    }
-
-    /**
-     * Has the server owner denied plugin metrics?
-     *
-     * @return true if metrics should be opted out of it
-     */
-    public boolean isOptOut() {
-        synchronized (this.optOutLock) {
-            try {
-                // Reload the metrics file
-                this.configuration.load(this.getConfigFile());
-            } catch (IOException ex) {
-                if (this.debug) {
-                    Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
-                }
-                return true;
-            } catch (InvalidConfigurationException ex) {
-                if (this.debug) {
-                    Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
-                }
-                return true;
-            }
-            return this.configuration.getBoolean("opt-out", false);
-        }
-    }
-
-    /**
-     * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
-     *
-     * @throws java.io.IOException
-     */
-    public void enable() throws IOException {
-        // This has to be synchronized or it can collide with the check in the task.
-        synchronized (this.optOutLock) {
-            // Check if the server owner has already set opt-out, if not, set it.
-            if (this.isOptOut()) {
-                this.configuration.set("opt-out", false);
-                this.configuration.save(this.configurationFile);
-            }
-
-            // Enable Task, if it is not running
-            if (this.task == null) {
-                this.start();
-            }
-        }
-    }
-
-    /**
-     * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
-     *
-     * @throws java.io.IOException
-     */
-    public void disable() throws IOException {
-        // This has to be synchronized or it can collide with the check in the task.
-        synchronized (this.optOutLock) {
-            // Check if the server owner has already set opt-out, if not, set it.
-            if (!this.isOptOut()) {
-                this.configuration.set("opt-out", true);
-                this.configuration.save(this.configurationFile);
-            }
-
-            // Disable Task, if it is running
-            if (this.task != null) {
-                this.task.cancel();
-                this.task = null;
-            }
-        }
-    }
-
-    /**
-     * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
-     *
-     * @return the File object for the config file
-     */
-    public File getConfigFile() {
-        // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
-        // is to abuse the plugin object we already have
-        // plugin.getDataFolder() => base/plugins/PluginA/
-        // pluginsFolder => base/plugins/
-        // The base is not necessarily relative to the startup directory.
-        // File pluginsFolder = plugin.getDataFolder().getParentFile();
-
-        // return => base/plugins/PluginMetrics/config.yml
-        return new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "PluginMetrics"), "config.yml");
-    }
-
-    /**
-     * Generic method that posts a plugin to the metrics website
-     */
-    private void postPlugin(final boolean isPing) throws IOException {
-        // Server software specific section
-        String pluginName = "Spigot";
-        boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
-        String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown";
-        String serverVersion = Bukkit.getVersion();
-        int playersOnline = Bukkit.getServer().getOnlinePlayers().size();
-
-        // END server software specific section -- all code below does not use any code outside of this class / Java
-
-        // Construct the post data
-        final StringBuilder data = new StringBuilder();
-
-        // The plugin's description file containg all of the plugin data such as name, version, author, etc
-        data.append(Metrics.encode("guid")).append('=').append(Metrics.encode(this.guid));
-        Metrics.encodeDataPair(data, "version", pluginVersion);
-        Metrics.encodeDataPair(data, "server", serverVersion);
-        Metrics.encodeDataPair(data, "players", Integer.toString(playersOnline));
-        Metrics.encodeDataPair(data, "revision", String.valueOf(Metrics.REVISION));
-
-        // New data as of R6
-        String osname = System.getProperty("os.name");
-        String osarch = System.getProperty("os.arch");
-        String osversion = System.getProperty("os.version");
-        String java_version = System.getProperty("java.version");
-        int coreCount = Runtime.getRuntime().availableProcessors();
-
-        // normalize os arch .. amd64 -> x86_64
-        if (osarch.equals("amd64")) {
-            osarch = "x86_64";
-        }
-
-        Metrics.encodeDataPair(data, "osname", osname);
-        Metrics.encodeDataPair(data, "osarch", osarch);
-        Metrics.encodeDataPair(data, "osversion", osversion);
-        Metrics.encodeDataPair(data, "cores", Integer.toString(coreCount));
-        Metrics.encodeDataPair(data, "online-mode", Boolean.toString(onlineMode));
-        Metrics.encodeDataPair(data, "java_version", java_version);
-
-        // If we're pinging, append it
-        if (isPing) {
-            Metrics.encodeDataPair(data, "ping", "true");
-        }
-
-        // Acquire a lock on the graphs, which lets us make the assumption we also lock everything
-        // inside of the graph (e.g plotters)
-        synchronized (this.graphs) {
-            final Iterator<Graph> iter = this.graphs.iterator();
-
-            while (iter.hasNext()) {
-                final Graph graph = iter.next();
-
-                for (Plotter plotter : graph.getPlotters()) {
-                    // The key name to send to the metrics server
-                    // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top
-                    // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME
-                    final String key = String.format("C%s%s%s%s", Metrics.CUSTOM_DATA_SEPARATOR, graph.getName(), Metrics.CUSTOM_DATA_SEPARATOR, plotter.getColumnName());
-
-                    // The value to send, which for the foreseeable future is just the string
-                    // value of plotter.getValue()
-                    final String value = Integer.toString(plotter.getValue());
-
-                    // Add it to the http post data :)
-                    Metrics.encodeDataPair(data, key, value);
-                }
-            }
-        }
-
-        // Create the url
-        URL url = new URL(Metrics.BASE_URL + String.format(Metrics.REPORT_URL, Metrics.encode(pluginName)));
-
-        // Connect to the website
-        URLConnection connection;
-
-        // Mineshafter creates a socks proxy, so we can safely bypass it
-        // It does not reroute POST requests so we need to go around it
-        if (this.isMineshafterPresent()) {
-            connection = url.openConnection(Proxy.NO_PROXY);
-        } else {
-            connection = url.openConnection();
-        }
-
-        connection.setDoOutput(true);
-
-        // Write the data
-        final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
-        writer.write(data.toString());
-        writer.flush();
-
-        // Now read the response
-        final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
-        final String response = reader.readLine();
-
-        // close resources
-        writer.close();
-        reader.close();
-
-        if (response == null || response.startsWith("ERR")) {
-            throw new IOException(response); //Throw the exception
-        } else {
-            // Is this the first update this hour?
-            if (response.contains("OK This is your first update this hour")) {
-                synchronized (this.graphs) {
-                    final Iterator<Graph> iter = this.graphs.iterator();
-
-                    while (iter.hasNext()) {
-                        final Graph graph = iter.next();
-
-                        for (Plotter plotter : graph.getPlotters()) {
-                            plotter.reset();
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Check if mineshafter is present. If it is, we need to bypass it to send POST requests
-     *
-     * @return true if mineshafter is installed on the server
-     */
-    private boolean isMineshafterPresent() {
-        try {
-            Class.forName("mineshafter.MineServer");
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
-    /**
-     * <p>Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair
-     * MUST be included manually, e.g:</p>
-     * <code>
-     * StringBuffer data = new StringBuffer();
-     * data.append(encode("guid")).append('=').append(encode(guid));
-     * encodeDataPair(data, "version", description.getVersion());
-     * </code>
-     *
-     * @param buffer the stringbuilder to append the data pair onto
-     * @param key the key value
-     * @param value the value
-     */
-    private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException {
-        buffer.append('&').append(Metrics.encode(key)).append('=').append(Metrics.encode(value));
-    }
-
-    /**
-     * Encode text as UTF-8
-     *
-     * @param text the text to encode
-     * @return the encoded text, as UTF-8
-     */
-    private static String encode(final String text) throws UnsupportedEncodingException {
-        return URLEncoder.encode(text, "UTF-8");
-    }
-
-    /**
-     * Represents a custom graph on the website
-     */
-    public static class Graph {
-
-        /**
-         * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
-         * rejected
-         */
-        private final String name;
-        /**
-         * The set of plotters that are contained within this graph
-         */
-        private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
-
-        private Graph(final String name) {
-            this.name = name;
-        }
-
-        /**
-         * Gets the graph's name
-         *
-         * @return the Graph's name
-         */
-        public String getName() {
-            return this.name;
-        }
-
-        /**
-         * Add a plotter to the graph, which will be used to plot entries
-         *
-         * @param plotter the plotter to add to the graph
-         */
-        public void addPlotter(final Plotter plotter) {
-            this.plotters.add(plotter);
-        }
-
-        /**
-         * Remove a plotter from the graph
-         *
-         * @param plotter the plotter to remove from the graph
-         */
-        public void removePlotter(final Plotter plotter) {
-            this.plotters.remove(plotter);
-        }
-
-        /**
-         * Gets an <b>unmodifiable</b> set of the plotter objects in the graph
-         *
-         * @return an unmodifiable {@link java.util.Set} of the plotter objects
-         */
-        public Set<Plotter> getPlotters() {
-            return Collections.unmodifiableSet(this.plotters);
-        }
-
-        @Override
-        public int hashCode() {
-            return this.name.hashCode();
-        }
-
-        @Override
-        public boolean equals(final Object object) {
-            if (!(object instanceof Graph)) {
-                return false;
-            }
-
-            final Graph graph = (Graph) object;
-            return graph.name.equals(this.name);
-        }
-
-        /**
-         * Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
-         */
-        protected void onOptOut() {
-        }
-    }
-
-    /**
-     * Interface used to collect custom data for a plugin
-     */
-    public abstract static class Plotter {
-
-        /**
-         * The plot's name
-         */
-        private final String name;
-
-        /**
-         * Construct a plotter with the default plot name
-         */
-        public Plotter() {
-            this("Default");
-        }
-
-        /**
-         * Construct a plotter with a specific plot name
-         *
-         * @param name the name of the plotter to use, which will show up on the website
-         */
-        public Plotter(final String name) {
-            this.name = name;
-        }
-
-        /**
-         * Get the current value for the plotted point. Since this function defers to an external function it may or may
-         * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
-         * from any thread so care should be taken when accessing resources that need to be synchronized.
-         *
-         * @return the current value for the point to be plotted.
-         */
-        public abstract int getValue();
-
-        /**
-         * Get the column name for the plotted point
-         *
-         * @return the plotted point's column name
-         */
-        public String getColumnName() {
-            return this.name;
-        }
-
-        /**
-         * Called after the website graphs have been updated
-         */
-        public void reset() {
-        }
-
-        @Override
-        public int hashCode() {
-            return this.getColumnName().hashCode();
-        }
-
-        @Override
-        public boolean equals(final Object object) {
-            if (!(object instanceof Plotter)) {
-                return false;
-            }
-
-            final Plotter plotter = (Plotter) object;
-            return plotter.name.equals(this.name) && plotter.getValue() == this.getValue();
-        }
-    }
-}
diff --git a/paper-server/src/main/java/org/spigotmc/RestartCommand.java b/paper-server/src/main/java/org/spigotmc/RestartCommand.java
index 39e56b95aa..b87f66ad04 100644
--- a/paper-server/src/main/java/org/spigotmc/RestartCommand.java
+++ b/paper-server/src/main/java/org/spigotmc/RestartCommand.java
@@ -1,115 +1,85 @@
 package org.spigotmc;
 
 import java.io.File;
-import java.util.List;
+import java.util.Locale;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.level.ServerPlayer;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 import org.bukkit.craftbukkit.util.CraftChatMessage;
 
-public class RestartCommand extends Command
-{
+public class RestartCommand extends Command {
 
-    public RestartCommand(String name)
-    {
-        super( name );
+    public RestartCommand(String name) {
+        super(name);
         this.description = "Restarts the server";
         this.usageMessage = "/restart";
-        this.setPermission( "bukkit.command.restart" );
+        this.setPermission("bukkit.command.restart");
     }
 
     @Override
-    public boolean execute(CommandSender sender, String currentAlias, String[] args)
-    {
-        if ( this.testPermission( sender ) )
-        {
-            MinecraftServer.getServer().processQueue.add( new Runnable()
-            {
-                @Override
-                public void run()
-                {
-                    RestartCommand.restart();
-                }
-            } );
+    public boolean execute(CommandSender sender, String currentAlias, String[] args) {
+        if (this.testPermission(sender)) {
+            MinecraftServer.getServer().processQueue.add(RestartCommand::restart);
         }
         return true;
     }
 
-    public static void restart()
-    {
-        RestartCommand.restart( SpigotConfig.restartScript );
+    public static void restart() {
+        RestartCommand.restart(SpigotConfig.restartScript);
     }
 
-    private static void restart(final String restartScript)
-    {
-        AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
-        try
-        {
+    private static void restart(final String restartScript) {
+        AsyncCatcher.enabled = false; // Disable async catcher in case it interferes with us
+        try {
             // Paper - extract method and cleanup
-            boolean isRestarting = addShutdownHook( restartScript );
-            if ( isRestarting )
-            {
-                System.out.println( "Attempting to restart with " + SpigotConfig.restartScript );
-            } else
-            {
-                System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." );
+            boolean isRestarting = addShutdownHook(restartScript);
+            if (isRestarting) {
+                System.out.println("Attempting to restart with " + SpigotConfig.restartScript);
+            } else {
+                System.out.println("Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server.");
             }
             // Stop the watchdog
             WatchdogThread.doStop();
 
-            shutdownServer( isRestarting );
+            shutdownServer(isRestarting);
             // Paper end
-        } catch ( Exception ex )
-        {
+        } catch (Exception ex) {
             ex.printStackTrace();
         }
     }
 
     // Paper start - sync copied from above with minor changes, async added
-    private static void shutdownServer(boolean isRestarting)
-    {
-        if ( MinecraftServer.getServer().isSameThread() )
-        {
+    private static void shutdownServer(boolean isRestarting) {
+        if (MinecraftServer.getServer().isSameThread()) {
             // Kick all players
-            for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) )
-            {
-                p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used))
+            for (ServerPlayer p : com.google.common.collect.ImmutableList.copyOf(MinecraftServer.getServer().getPlayerList().players)) {
+                p.connection.disconnect(CraftChatMessage.fromStringOrEmpty(SpigotConfig.restartMessage, true), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)
             }
             // Give the socket a chance to send the packets
-            try
-            {
-                Thread.sleep( 100 );
-            } catch ( InterruptedException ex )
-            {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
             }
 
             closeSocket();
 
             // Actually shutdown
-            try
-            {
+            try {
                 MinecraftServer.getServer().close(); // calls stop()
-            } catch ( Throwable t )
-            {
+            } catch (Throwable t) {
             }
 
             // Actually stop the JVM
-            System.exit( 0 );
-
-        } else
-        {
+            System.exit(0);
+        } else {
             // Mark the server to shutdown at the end of the tick
-            MinecraftServer.getServer().safeShutdown( false, isRestarting );
+            MinecraftServer.getServer().safeShutdown(false, isRestarting);
 
             // wait 10 seconds to see if we're actually going to try shutdown
-            try
-            {
-                Thread.sleep( 10000 );
-            }
-            catch (InterruptedException ignored)
-            {
-            }
+            try {
+                Thread.sleep(10000);
+            } catch (InterruptedException ignored) {}
 
             // Check if we've actually hit a state where the server is going to safely shutdown
             // if we have, let the server stop as usual
@@ -117,63 +87,46 @@ public class RestartCommand extends Command
 
             // If the server hasn't stopped by now, assume worse case and kill
             closeSocket();
-            System.exit( 0 );
+            System.exit(0);
         }
     }
     // Paper end
 
     // Paper - Split from moved code
-    private static void closeSocket()
-    {
+    private static void closeSocket() {
         // Close the socket so we can rebind with the new process
         MinecraftServer.getServer().getConnection().stop();
 
         // Give time for it to kick in
-        try
-        {
-            Thread.sleep( 100 );
-        } catch ( InterruptedException ex )
-        {
-        }
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException ignored) {}
     }
     // Paper end
 
     // Paper start - copied from above and modified to return if the hook registered
-    private static boolean addShutdownHook(String restartScript)
-    {
-        String[] split = restartScript.split( " " );
-        if ( split.length > 0 && new File( split[0] ).isFile() )
-        {
-            Thread shutdownHook = new Thread()
-            {
-                @Override
-                public void run()
-                {
-                    try
-                    {
-                        String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH);
-                        if ( os.contains( "win" ) )
-                        {
-                            Runtime.getRuntime().exec( "cmd /c start " + restartScript );
-                        } else
-                        {
-                            Runtime.getRuntime().exec( "sh " + restartScript );
-                        }
-                    } catch ( Exception e )
-                    {
-                        e.printStackTrace();
+    private static boolean addShutdownHook(String restartScript) {
+        String[] split = restartScript.split(" ");
+        if (split.length > 0 && new File(split[0]).isFile()) {
+            Thread shutdownHook = new Thread(() -> {
+                try {
+                    String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+                    if (os.contains("win")) {
+                        Runtime.getRuntime().exec("cmd /c start " + restartScript);
+                    } else {
+                        Runtime.getRuntime().exec("sh " + restartScript);
                     }
+                } catch (Exception e) {
+                    e.printStackTrace();
                 }
-            };
+            });
 
-            shutdownHook.setDaemon( true );
-            Runtime.getRuntime().addShutdownHook( shutdownHook );
+            shutdownHook.setDaemon(true);
+            Runtime.getRuntime().addShutdownHook(shutdownHook);
             return true;
-        } else
-        {
+        } else {
             return false;
         }
     }
     // Paper end
-
 }
diff --git a/paper-server/src/main/java/org/spigotmc/SpigotCommand.java b/paper-server/src/main/java/org/spigotmc/SpigotCommand.java
index ac0fd418fc..1b60abf5f5 100644
--- a/paper-server/src/main/java/org/spigotmc/SpigotCommand.java
+++ b/paper-server/src/main/java/org/spigotmc/SpigotCommand.java
@@ -1,12 +1,14 @@
 package org.spigotmc;
 
 import java.io.File;
+import net.kyori.adventure.text.format.NamedTextColor;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.level.ServerLevel;
-import org.bukkit.ChatColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 
+import static net.kyori.adventure.text.Component.text;
+
 public class SpigotCommand extends Command {
 
     public SpigotCommand(String name) {
@@ -21,13 +23,17 @@ public class SpigotCommand extends Command {
         if (!this.testPermission(sender)) return true;
 
         if (args.length != 1) {
-            sender.sendMessage(ChatColor.RED + "Usage: " + this.usageMessage);
+            sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED));
             return false;
         }
 
         if (args[0].equals("reload")) {
-            Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues.");
-            Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server.");
+            Command.broadcastCommandMessage(sender, text().color(NamedTextColor.RED)
+                .append(text("Please note that this command is not supported and may cause issues."))
+                .appendNewline()
+                .append(text("If you encounter any issues please use the /stop command to restart your server."))
+                .build()
+            );
 
             MinecraftServer console = MinecraftServer.getServer();
             org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings"));
@@ -36,7 +42,7 @@ public class SpigotCommand extends Command {
             }
             console.server.reloadCount++;
 
-            Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete.");
+            Command.broadcastCommandMessage(sender, text("Reload complete.", NamedTextColor.GREEN));
         }
 
         return true;
diff --git a/paper-server/src/main/java/org/spigotmc/SpigotConfig.java b/paper-server/src/main/java/org/spigotmc/SpigotConfig.java
index 4dbb109d05..e0d4222a99 100644
--- a/paper-server/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/paper-server/src/main/java/org/spigotmc/SpigotConfig.java
@@ -28,360 +28,289 @@ import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.InvalidConfigurationException;
 import org.bukkit.configuration.file.YamlConfiguration;
 
-public class SpigotConfig
-{
+public class SpigotConfig {
 
     private static File CONFIG_FILE;
-    private static final String HEADER = "This is the main configuration file for Spigot.\n"
-            + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
-            + "with caution, and make sure you know what each option does before configuring.\n"
-            + "For a reference for any variable inside this file, check out the Spigot wiki at\n"
-            + "http://www.spigotmc.org/wiki/spigot-configuration/\n"
-            + "\n"
-            + "If you need help with the configuration or have any questions related to Spigot,\n"
-            + "join us at the Discord or drop by our forums and leave a post.\n"
-            + "\n"
-            + "Discord: https://www.spigotmc.org/go/discord\n"
-            + "Forums: http://www.spigotmc.org/\n";
+    private static final String HEADER = """
+        This is the main configuration file for Spigot.
+        As you can see, there's tons to configure. Some options may impact gameplay, so use
+        with caution, and make sure you know what each option does before configuring.
+        For a reference for any variable inside this file, check out the Spigot wiki at
+        http://www.spigotmc.org/wiki/spigot-configuration/
+        
+        If you need help with the configuration or have any questions related to Spigot,
+        join us at the Discord or drop by our forums and leave a post.
+        
+        Discord: https://www.spigotmc.org/go/discord
+        Forums: http://www.spigotmc.org/
+        """;
     /*========================================================================*/
     public static YamlConfiguration config;
     static int version;
     static Map<String, Command> commands;
     /*========================================================================*/
-    private static Metrics metrics;
 
-    public static void init(File configFile)
-    {
+    public static void init(File configFile) {
         SpigotConfig.CONFIG_FILE = configFile;
         SpigotConfig.config = new YamlConfiguration();
-        try
-        {
-            SpigotConfig.config.load( SpigotConfig.CONFIG_FILE );
-        } catch ( IOException ex )
-        {
-        } catch ( InvalidConfigurationException ex )
-        {
-            Bukkit.getLogger().log( Level.SEVERE, "Could not load spigot.yml, please correct your syntax errors", ex );
-            throw Throwables.propagate( ex );
+        try {
+            SpigotConfig.config.load(SpigotConfig.CONFIG_FILE);
+        } catch (IOException ignored) {
+        } catch (InvalidConfigurationException ex) {
+            Bukkit.getLogger().log(Level.SEVERE, "Could not load spigot.yml, please correct your syntax errors", ex);
+            throw Throwables.propagate(ex);
         }
 
-        SpigotConfig.config.options().header( SpigotConfig.HEADER );
-        SpigotConfig.config.options().copyDefaults( true );
+        SpigotConfig.config.options().header(SpigotConfig.HEADER);
+        SpigotConfig.config.options().copyDefaults(true);
 
-        SpigotConfig.commands = new HashMap<String, Command>();
-        SpigotConfig.commands.put( "spigot", new SpigotCommand( "spigot" ) );
+        SpigotConfig.commands = new HashMap<>();
+        SpigotConfig.commands.put("spigot", new SpigotCommand("spigot"));
 
-        SpigotConfig.version = SpigotConfig.getInt( "config-version", 12 );
-        SpigotConfig.set( "config-version", 12 );
-        SpigotConfig.readConfig( SpigotConfig.class, null );
+        SpigotConfig.version = SpigotConfig.getInt("config-version", 12);
+        SpigotConfig.set("config-version", 12);
+        SpigotConfig.readConfig(SpigotConfig.class, null);
     }
 
-    public static void registerCommands()
-    {
-        for ( Map.Entry<String, Command> entry : SpigotConfig.commands.entrySet() )
-        {
-            MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() );
+    public static void registerCommands() {
+        for (Map.Entry<String, Command> entry : SpigotConfig.commands.entrySet()) {
+            MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Spigot", entry.getValue());
         }
-
-        /* // Paper - Replace with our own
-        if ( SpigotConfig.metrics == null )
-        {
-            try
-            {
-                SpigotConfig.metrics = new Metrics();
-                SpigotConfig.metrics.start();
-            } catch ( IOException ex )
-            {
-                Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex );
-            }
-        }
-        */ // Paper end
     }
 
-    public static void readConfig(Class<?> clazz, Object instance) // Paper - package-private -> public
-    {
-        for ( Method method : clazz.getDeclaredMethods() )
-        {
-            if ( Modifier.isPrivate( method.getModifiers() ) )
-            {
-                if ( method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE )
-                {
-                    try
-                    {
-                        method.setAccessible( true );
-                        method.invoke( instance );
-                    } catch ( InvocationTargetException ex )
-                    {
-                        throw Throwables.propagate( ex.getCause() );
-                    } catch ( Exception ex )
-                    {
-                        Bukkit.getLogger().log( Level.SEVERE, "Error invoking " + method, ex );
+    public static void readConfig(Class<?> clazz, Object instance) { // Paper - package-private -> public
+        for (Method method : clazz.getDeclaredMethods()) {
+            if (Modifier.isPrivate(method.getModifiers())) {
+                if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
+                    try {
+                        method.setAccessible(true);
+                        method.invoke(instance);
+                    } catch (InvocationTargetException ex) {
+                        throw Throwables.propagate(ex.getCause());
+                    } catch (Exception ex) {
+                        Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
                     }
                 }
             }
         }
 
-        try
-        {
-            SpigotConfig.config.save( SpigotConfig.CONFIG_FILE );
-        } catch ( IOException ex )
-        {
-            Bukkit.getLogger().log( Level.SEVERE, "Could not save " + SpigotConfig.CONFIG_FILE, ex );
+        try {
+            SpigotConfig.config.save(SpigotConfig.CONFIG_FILE);
+        } catch (IOException ex) {
+            Bukkit.getLogger().log(Level.SEVERE, "Could not save " + SpigotConfig.CONFIG_FILE, ex);
         }
     }
 
-    private static void set(String path, Object val)
-    {
-        SpigotConfig.config.set( path, val );
+    private static void set(String path, Object val) {
+        SpigotConfig.config.set(path, val);
     }
 
-    private static boolean getBoolean(String path, boolean def)
-    {
-        SpigotConfig.config.addDefault( path, def );
-        return SpigotConfig.config.getBoolean( path, SpigotConfig.config.getBoolean( path ) );
+    private static boolean getBoolean(String path, boolean def) {
+        SpigotConfig.config.addDefault(path, def);
+        return SpigotConfig.config.getBoolean(path, SpigotConfig.config.getBoolean(path));
     }
 
-    private static int getInt(String path, int def)
-    {
-        SpigotConfig.config.addDefault( path, def );
-        return SpigotConfig.config.getInt( path, SpigotConfig.config.getInt( path ) );
+    private static int getInt(String path, int def) {
+        SpigotConfig.config.addDefault(path, def);
+        return SpigotConfig.config.getInt(path, SpigotConfig.config.getInt(path));
     }
 
-    private static <T> List getList(String path, T def)
-    {
-        SpigotConfig.config.addDefault( path, def );
-        return (List<T>) SpigotConfig.config.getList( path, SpigotConfig.config.getList( path ) );
+    private static <T> List getList(String path, T def) {
+        SpigotConfig.config.addDefault(path, def);
+        return (List<T>) SpigotConfig.config.getList(path, SpigotConfig.config.getList(path));
     }
 
-    private static String getString(String path, String def)
-    {
-        SpigotConfig.config.addDefault( path, def );
-        return SpigotConfig.config.getString( path, SpigotConfig.config.getString( path ) );
+    private static String getString(String path, String def) {
+        SpigotConfig.config.addDefault(path, def);
+        return SpigotConfig.config.getString(path, SpigotConfig.config.getString(path));
     }
 
-    private static double getDouble(String path, double def)
-    {
-        SpigotConfig.config.addDefault( path, def );
-        return SpigotConfig.config.getDouble( path, SpigotConfig.config.getDouble( path ) );
+    private static double getDouble(String path, double def) {
+        SpigotConfig.config.addDefault(path, def);
+        return SpigotConfig.config.getDouble(path, SpigotConfig.config.getDouble(path));
     }
 
     public static boolean logCommands;
-    private static void logCommands()
-    {
-        SpigotConfig.logCommands = SpigotConfig.getBoolean( "commands.log", true );
+    private static void logCommands() {
+        SpigotConfig.logCommands = SpigotConfig.getBoolean("commands.log", true);
     }
 
     public static int tabComplete;
     public static boolean sendNamespaced;
-    private static void tabComplete()
-    {
-        if ( SpigotConfig.version < 6 )
-        {
-            boolean oldValue = SpigotConfig.getBoolean( "commands.tab-complete", true );
-            if ( oldValue )
-            {
-                SpigotConfig.set( "commands.tab-complete", 0 );
-            } else
-            {
-                SpigotConfig.set( "commands.tab-complete", -1 );
+    private static void tabComplete() {
+        if (SpigotConfig.version < 6) {
+            boolean oldValue = SpigotConfig.getBoolean("commands.tab-complete", true);
+            if (oldValue) {
+                SpigotConfig.set("commands.tab-complete", 0);
+            } else {
+                SpigotConfig.set("commands.tab-complete", -1);
             }
         }
-        SpigotConfig.tabComplete = SpigotConfig.getInt( "commands.tab-complete", 0 );
-        SpigotConfig.sendNamespaced = SpigotConfig.getBoolean( "commands.send-namespaced", true );
+        SpigotConfig.tabComplete = SpigotConfig.getInt("commands.tab-complete", 0);
+        SpigotConfig.sendNamespaced = SpigotConfig.getBoolean("commands.send-namespaced", true);
     }
 
     public static String whitelistMessage;
     public static String unknownCommandMessage;
     public static String serverFullMessage;
     public static String outdatedClientMessage = "Outdated client! Please use {0}";
-    public static String outdatedServerMessage = "Outdated server! I\'m still on {0}";
-    private static String transform(String s)
-    {
-        return ChatColor.translateAlternateColorCodes( '&', s ).replaceAll( "\\\\n", "\n" );
+    public static String outdatedServerMessage = "Outdated server! I'm still on {0}";
+
+    private static String transform(String s) {
+        return ChatColor.translateAlternateColorCodes('&', s).replaceAll("\\\\n", "\n");
     }
-    private static void messages()
-    {
-        if (SpigotConfig.version < 8)
-        {
-            SpigotConfig.set( "messages.outdated-client", SpigotConfig.outdatedClientMessage );
-            SpigotConfig.set( "messages.outdated-server", SpigotConfig.outdatedServerMessage );
+
+    private static void messages() {
+        if (SpigotConfig.version < 8) {
+            SpigotConfig.set("messages.outdated-client", SpigotConfig.outdatedClientMessage);
+            SpigotConfig.set("messages.outdated-server", SpigotConfig.outdatedServerMessage);
         }
 
-        SpigotConfig.whitelistMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.whitelist", "You are not whitelisted on this server!" ) );
-        SpigotConfig.unknownCommandMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.unknown-command", "Unknown command. Type \"/help\" for help." ) );
-        SpigotConfig.serverFullMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.server-full", "The server is full!" ) );
-        SpigotConfig.outdatedClientMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-client", SpigotConfig.outdatedClientMessage ) );
-        SpigotConfig.outdatedServerMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-server", SpigotConfig.outdatedServerMessage ) );
+        SpigotConfig.whitelistMessage = SpigotConfig.transform(SpigotConfig.getString("messages.whitelist", "You are not whitelisted on this server!"));
+        SpigotConfig.unknownCommandMessage = SpigotConfig.transform(SpigotConfig.getString("messages.unknown-command", "Unknown command. Type \"/help\" for help."));
+        SpigotConfig.serverFullMessage = SpigotConfig.transform(SpigotConfig.getString("messages.server-full", "The server is full!"));
+        SpigotConfig.outdatedClientMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-client", SpigotConfig.outdatedClientMessage));
+        SpigotConfig.outdatedServerMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-server", SpigotConfig.outdatedServerMessage));
     }
 
     public static int timeoutTime = 60;
     public static boolean restartOnCrash = true;
     public static String restartScript = "./start.sh";
     public static String restartMessage;
-    private static void watchdog()
-    {
-        SpigotConfig.timeoutTime = SpigotConfig.getInt( "settings.timeout-time", SpigotConfig.timeoutTime );
-        SpigotConfig.restartOnCrash = SpigotConfig.getBoolean( "settings.restart-on-crash", SpigotConfig.restartOnCrash );
-        SpigotConfig.restartScript = SpigotConfig.getString( "settings.restart-script", SpigotConfig.restartScript );
-        SpigotConfig.restartMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.restart", "Server is restarting" ) );
-        SpigotConfig.commands.put( "restart", new RestartCommand( "restart" ) );
-        // WatchdogThread.doStart( SpigotConfig.timeoutTime, SpigotConfig.restartOnCrash ); // Paper - moved to after paper config initialization
+    private static void watchdog() {
+        SpigotConfig.timeoutTime = SpigotConfig.getInt("settings.timeout-time", SpigotConfig.timeoutTime);
+        SpigotConfig.restartOnCrash = SpigotConfig.getBoolean("settings.restart-on-crash", SpigotConfig.restartOnCrash);
+        SpigotConfig.restartScript = SpigotConfig.getString("settings.restart-script", SpigotConfig.restartScript);
+        SpigotConfig.restartMessage = SpigotConfig.transform(SpigotConfig.getString("messages.restart", "Server is restarting"));
+        SpigotConfig.commands.put("restart", new RestartCommand("restart"));
     }
 
     public static boolean bungee;
     private static void bungee() {
-        if ( SpigotConfig.version < 4 )
-        {
-            SpigotConfig.set( "settings.bungeecord", false );
-            System.out.println( "Outdated config, disabling BungeeCord support!" );
+        if (SpigotConfig.version < 4) {
+            SpigotConfig.set("settings.bungeecord", false);
+            System.out.println("Outdated config, disabling BungeeCord support!");
         }
-        SpigotConfig.bungee = SpigotConfig.getBoolean( "settings.bungeecord", false );
+        SpigotConfig.bungee = SpigotConfig.getBoolean("settings.bungeecord", false);
     }
 
-    private static void nettyThreads()
-    {
-        int count = SpigotConfig.getInt( "settings.netty-threads", 4 );
-        System.setProperty( "io.netty.eventLoopThreads", Integer.toString( count ) );
-        Bukkit.getLogger().log( Level.INFO, "Using {0} threads for Netty based IO", count );
+    private static void nettyThreads() {
+        int count = SpigotConfig.getInt("settings.netty-threads", 4);
+        System.setProperty("io.netty.eventLoopThreads", Integer.toString(count));
+        Bukkit.getLogger().log(Level.INFO, "Using {0} threads for Netty based IO", count);
     }
 
     public static boolean disableStatSaving;
     public static Map<ResourceLocation, Integer> forcedStats = new HashMap<>();
-    private static void stats()
-    {
-        SpigotConfig.disableStatSaving = SpigotConfig.getBoolean( "stats.disable-saving", false );
 
-        if ( !SpigotConfig.config.contains( "stats.forced-stats" ) ) {
-            SpigotConfig.config.createSection( "stats.forced-stats" );
+    private static void stats() {
+        SpigotConfig.disableStatSaving = SpigotConfig.getBoolean("stats.disable-saving", false);
+
+        if (!SpigotConfig.config.contains("stats.forced-stats")) {
+            SpigotConfig.config.createSection("stats.forced-stats");
         }
 
-        ConfigurationSection section = SpigotConfig.config.getConfigurationSection( "stats.forced-stats" );
-        for ( String name : section.getKeys( true ) )
-        {
-            if ( section.isInt( name ) )
-            {
-                try
-                {
-                    ResourceLocation key = ResourceLocation.parse( name );
-                    if ( BuiltInRegistries.CUSTOM_STAT.get( key ) == null )
-                    {
+        ConfigurationSection section = SpigotConfig.config.getConfigurationSection("stats.forced-stats");
+        for (String name : section.getKeys(true)) {
+            if (section.isInt(name)) {
+                try {
+                    ResourceLocation key = ResourceLocation.parse(name);
+                    if (BuiltInRegistries.CUSTOM_STAT.get(key) == null) {
                         Bukkit.getLogger().log(Level.WARNING, "Ignoring non existent stats.forced-stats " + name);
                         continue;
                     }
-                    SpigotConfig.forcedStats.put( key, section.getInt( name ) );
-                } catch (Exception ex)
-                {
+                    SpigotConfig.forcedStats.put(key, section.getInt(name));
+                } catch (Exception ex) {
                     Bukkit.getLogger().log(Level.WARNING, "Ignoring invalid stats.forced-stats " + name);
                 }
             }
         }
     }
 
-    private static void tpsCommand()
-    {
-        SpigotConfig.commands.put( "tps", new TicksPerSecondCommand( "tps" ) );
+    private static void tpsCommand() {
+        SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps"));
     }
 
     public static int playerSample;
-    private static void playerSample()
-    {
-        SpigotConfig.playerSample = Math.max( SpigotConfig.getInt( "settings.sample-count", 12 ), 0 ); // Paper - Avoid negative counts
-        Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger
+    private static void playerSample() {
+        SpigotConfig.playerSample = Math.max(SpigotConfig.getInt("settings.sample-count", 12), 0); // Paper - Avoid negative counts
+        Bukkit.getLogger().log(Level.INFO, "Server Ping Player Sample Count: {0}", playerSample); // Paper - Use logger
     }
 
     public static int playerShuffle;
-    private static void playerShuffle()
-    {
-        SpigotConfig.playerShuffle = SpigotConfig.getInt( "settings.player-shuffle", 0 );
+    private static void playerShuffle() {
+        SpigotConfig.playerShuffle = SpigotConfig.getInt("settings.player-shuffle", 0);
     }
 
     public static List<String> spamExclusions;
-    private static void spamExclusions()
-    {
-        SpigotConfig.spamExclusions = SpigotConfig.getList( "commands.spam-exclusions", Arrays.asList( new String[]
-        {
-                "/skill"
-        } ) );
+    private static void spamExclusions() {
+        SpigotConfig.spamExclusions = SpigotConfig.getList("commands.spam-exclusions", List.of("/skill"));
     }
 
     public static boolean silentCommandBlocks;
-    private static void silentCommandBlocks()
-    {
-        SpigotConfig.silentCommandBlocks = SpigotConfig.getBoolean( "commands.silent-commandblock-console", false );
+    private static void silentCommandBlocks() {
+        SpigotConfig.silentCommandBlocks = SpigotConfig.getBoolean("commands.silent-commandblock-console", false);
     }
 
     public static Set<String> replaceCommands;
-    private static void replaceCommands()
-    {
-        if ( SpigotConfig.config.contains( "replace-commands" ) )
-        {
-            SpigotConfig.set( "commands.replace-commands", SpigotConfig.config.getStringList( "replace-commands" ) );
-            SpigotConfig.config.set( "replace-commands", null );
+    private static void replaceCommands() {
+        if (SpigotConfig.config.contains("replace-commands")) {
+            SpigotConfig.set("commands.replace-commands", SpigotConfig.config.getStringList("replace-commands"));
+            SpigotConfig.config.set("replace-commands", null);
         }
-        SpigotConfig.replaceCommands = new HashSet<String>( (List<String>) SpigotConfig.getList( "commands.replace-commands",
-                Arrays.asList( "setblock", "summon", "testforblock", "tellraw" ) ) );
+        SpigotConfig.replaceCommands = new HashSet<>(SpigotConfig.getList("commands.replace-commands",
+            Arrays.asList("setblock", "summon", "testforblock", "tellraw")));
     }
 
     public static int userCacheCap;
-    private static void userCacheCap()
-    {
-        SpigotConfig.userCacheCap = SpigotConfig.getInt( "settings.user-cache-size", 1000 );
+    private static void userCacheCap() {
+        SpigotConfig.userCacheCap = SpigotConfig.getInt("settings.user-cache-size", 1000);
     }
 
     public static boolean saveUserCacheOnStopOnly;
-    private static void saveUserCacheOnStopOnly()
-    {
-        SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean( "settings.save-user-cache-on-stop-only", false );
+    private static void saveUserCacheOnStopOnly() {
+        SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", false);
     }
 
     public static double movedWronglyThreshold;
-    private static void movedWronglyThreshold()
-    {
-        SpigotConfig.movedWronglyThreshold = SpigotConfig.getDouble( "settings.moved-wrongly-threshold", 0.0625D );
+    private static void movedWronglyThreshold() {
+        SpigotConfig.movedWronglyThreshold = SpigotConfig.getDouble("settings.moved-wrongly-threshold", 0.0625D);
     }
 
     public static double movedTooQuicklyMultiplier;
-    private static void movedTooQuicklyMultiplier()
-    {
-        SpigotConfig.movedTooQuicklyMultiplier = SpigotConfig.getDouble( "settings.moved-too-quickly-multiplier", 10.0D );
+    private static void movedTooQuicklyMultiplier() {
+        SpigotConfig.movedTooQuicklyMultiplier = SpigotConfig.getDouble("settings.moved-too-quickly-multiplier", 10.0D);
     }
 
     public static double maxAbsorption = 2048;
-    public static double maxHealth = 2048;
-    public static double movementSpeed = 2048;
+    public static double maxHealth = 1024;
+    public static double movementSpeed = 1024;
     public static double attackDamage = 2048;
-    private static void attributeMaxes()
-    {
-        SpigotConfig.maxAbsorption = SpigotConfig.getDouble( "settings.attribute.maxAbsorption.max", SpigotConfig.maxAbsorption );
-        ( (RangedAttribute) Attributes.MAX_ABSORPTION.value() ).maxValue = SpigotConfig.maxAbsorption;
-        SpigotConfig.maxHealth = SpigotConfig.getDouble( "settings.attribute.maxHealth.max", SpigotConfig.maxHealth );
-        ( (RangedAttribute) Attributes.MAX_HEALTH.value() ).maxValue = SpigotConfig.maxHealth;
-        SpigotConfig.movementSpeed = SpigotConfig.getDouble( "settings.attribute.movementSpeed.max", SpigotConfig.movementSpeed );
-        ( (RangedAttribute) Attributes.MOVEMENT_SPEED.value() ).maxValue = SpigotConfig.movementSpeed;
-        SpigotConfig.attackDamage = SpigotConfig.getDouble( "settings.attribute.attackDamage.max", SpigotConfig.attackDamage );
-        ( (RangedAttribute) Attributes.ATTACK_DAMAGE.value() ).maxValue = SpigotConfig.attackDamage;
+    private static void attributeMaxes() {
+        SpigotConfig.maxAbsorption = SpigotConfig.getDouble("settings.attribute.maxAbsorption.max", SpigotConfig.maxAbsorption);
+        ((RangedAttribute) Attributes.MAX_ABSORPTION.value()).maxValue = SpigotConfig.maxAbsorption;
+        SpigotConfig.maxHealth = SpigotConfig.getDouble("settings.attribute.maxHealth.max", SpigotConfig.maxHealth);
+        ((RangedAttribute) Attributes.MAX_HEALTH.value()).maxValue = SpigotConfig.maxHealth;
+        SpigotConfig.movementSpeed = SpigotConfig.getDouble("settings.attribute.movementSpeed.max", SpigotConfig.movementSpeed);
+        ((RangedAttribute) Attributes.MOVEMENT_SPEED.value()).maxValue = SpigotConfig.movementSpeed;
+        SpigotConfig.attackDamage = SpigotConfig.getDouble("settings.attribute.attackDamage.max", SpigotConfig.attackDamage);
+        ((RangedAttribute) Attributes.ATTACK_DAMAGE.value()).maxValue = SpigotConfig.attackDamage;
     }
 
     public static boolean debug;
-    private static void debug()
-    {
-        SpigotConfig.debug = SpigotConfig.getBoolean( "settings.debug", false );
+    private static void debug() {
+        SpigotConfig.debug = SpigotConfig.getBoolean("settings.debug", false);
 
-        if ( SpigotConfig.debug && !LogManager.getRootLogger().isTraceEnabled() )
-        {
+        if (SpigotConfig.debug && !LogManager.getRootLogger().isTraceEnabled()) {
             // Enable debug logging
-            LoggerContext ctx = (LoggerContext) LogManager.getContext( false );
+            LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
             Configuration conf = ctx.getConfiguration();
-            conf.getLoggerConfig( LogManager.ROOT_LOGGER_NAME ).setLevel( org.apache.logging.log4j.Level.ALL );
-            ctx.updateLoggers( conf );
+            conf.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(org.apache.logging.log4j.Level.ALL);
+            ctx.updateLoggers(conf);
         }
 
-        if ( LogManager.getRootLogger().isTraceEnabled() )
-        {
-            Bukkit.getLogger().info( "Debug logging is enabled" );
-        } else
-        {
-            // Bukkit.getLogger().info( "Debug logging is disabled" ); // Paper - Don't log if debug logging isn't enabled.
+        if (LogManager.getRootLogger().isTraceEnabled()) {
+            Bukkit.getLogger().info("Debug logging is enabled");
         }
     }
 
@@ -389,7 +318,7 @@ public class SpigotConfig
     public static List<String> disabledAdvancements;
     private static void disabledAdvancements() {
         SpigotConfig.disableAdvancementSaving = SpigotConfig.getBoolean("advancements.disable-saving", false);
-        SpigotConfig.disabledAdvancements = SpigotConfig.getList("advancements.disabled", Arrays.asList(new String[]{"minecraft:story/disabled"}));
+        SpigotConfig.disabledAdvancements = SpigotConfig.getList("advancements.disabled", List.of("minecraft:story/disabled"));
     }
 
     public static boolean logVillagerDeaths;
diff --git a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 2c408fa4ab..89e2adbc1e 100644
--- a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -4,86 +4,73 @@ import java.util.List;
 import org.bukkit.Bukkit;
 import org.bukkit.configuration.file.YamlConfiguration;
 
-public class SpigotWorldConfig
-{
+public class SpigotWorldConfig {
 
     private final String worldName;
     private final YamlConfiguration config;
     private boolean verbose;
 
-    public SpigotWorldConfig(String worldName)
-    {
+    public SpigotWorldConfig(String worldName) {
         this.worldName = worldName;
         this.config = SpigotConfig.config;
         this.init();
     }
 
-    public void init()
-    {
-        this.verbose = this.getBoolean( "verbose", false ); // Paper
+    public void init() {
+        this.verbose = this.getBoolean("verbose", false); // Paper
 
-        this.log( "-------- World Settings For [" + this.worldName + "] --------" );
-        SpigotConfig.readConfig( SpigotWorldConfig.class, this );
+        this.log("-------- World Settings For [" + this.worldName + "] --------");
+        SpigotConfig.readConfig(SpigotWorldConfig.class, this);
     }
 
-    private void log(String s)
-    {
-        if ( this.verbose )
-        {
-            Bukkit.getLogger().info( s );
+    private void log(String s) {
+        if (this.verbose) {
+            Bukkit.getLogger().info(s);
         }
     }
 
-    private void set(String path, Object val)
-    {
-        this.config.set( "world-settings.default." + path, val );
+    private void set(String path, Object val) {
+        this.config.set("world-settings.default." + path, val);
     }
 
-    public boolean getBoolean(String path, boolean def)
-    {
-        this.config.addDefault( "world-settings.default." + path, def );
-        return this.config.getBoolean( "world-settings." + this.worldName + "." + path, this.config.getBoolean( "world-settings.default." + path ) );
+    public boolean getBoolean(String path, boolean def) {
+        this.config.addDefault("world-settings.default." + path, def);
+        return this.config.getBoolean("world-settings." + this.worldName + "." + path, this.config.getBoolean("world-settings.default." + path));
     }
 
-    public double getDouble(String path, double def)
-    {
-        this.config.addDefault( "world-settings.default." + path, def );
-        return this.config.getDouble( "world-settings." + this.worldName + "." + path, this.config.getDouble( "world-settings.default." + path ) );
+    public double getDouble(String path, double def) {
+        this.config.addDefault("world-settings.default." + path, def);
+        return this.config.getDouble("world-settings." + this.worldName + "." + path, this.config.getDouble("world-settings.default." + path));
     }
 
-    public int getInt(String path)
-    {
-        return this.config.getInt( "world-settings." + this.worldName + "." + path );
+    public int getInt(String path) {
+        return this.config.getInt("world-settings." + this.worldName + "." + path);
     }
 
-    public int getInt(String path, int def)
-    {
+    public int getInt(String path, int def) {
         // Paper start - get int without setting default
         return this.getInt(path, def, true);
     }
-    public int getInt(String path, int def, boolean setDef)
-    {
-        if (setDef) this.config.addDefault( "world-settings.default." + path, def );
-        return this.config.getInt( "world-settings." + this.worldName + "." + path, this.config.getInt( "world-settings.default." + path, def ) );
+
+    public int getInt(String path, int def, boolean setDef) {
+        if (setDef) this.config.addDefault("world-settings.default." + path, def);
+        return this.config.getInt("world-settings." + this.worldName + "." + path, this.config.getInt("world-settings.default." + path, def));
         // Paper end
     }
 
-    public <T> List getList(String path, T def)
-    {
-        this.config.addDefault( "world-settings.default." + path, def );
-        return (List<T>) this.config.getList( "world-settings." + this.worldName + "." + path, this.config.getList( "world-settings.default." + path ) );
+    public <T> List getList(String path, T def) {
+        this.config.addDefault("world-settings.default." + path, def);
+        return (List<T>) this.config.getList("world-settings." + this.worldName + "." + path, this.config.getList("world-settings.default." + path));
     }
 
-    public String getString(String path, String def)
-    {
-        this.config.addDefault( "world-settings.default." + path, def );
-        return this.config.getString( "world-settings." + this.worldName + "." + path, this.config.getString( "world-settings.default." + path ) );
+    public String getString(String path, String def) {
+        this.config.addDefault("world-settings.default." + path, def);
+        return this.config.getString("world-settings." + this.worldName + "." + path, this.config.getString("world-settings.default." + path));
     }
 
-    private Object get(String path, Object def)
-    {
-        this.config.addDefault( "world-settings.default." + path, def );
-        return this.config.get( "world-settings." + this.worldName + "." + path, this.config.get( "world-settings.default." + path ) );
+    private Object get(String path, Object def) {
+        this.config.addDefault("world-settings.default." + path, def);
+        return this.config.get("world-settings." + this.worldName + "." + path, this.config.get("world-settings.default." + path));
     }
 
     // Crop growth rates
@@ -109,102 +96,92 @@ public class SpigotWorldConfig
     public int caveVinesModifier;
     public int glowBerryModifier; // Paper
     public int pitcherPlantModifier; // Paper
-    private int getAndValidateGrowth(String crop)
-    {
-        int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 );
-        if ( modifier == 0 )
-        {
-            this.log( "Cannot set " + crop + " growth to zero, defaulting to 100" );
+
+    private int getAndValidateGrowth(String crop) {
+        int modifier = this.getInt("growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100);
+        if (modifier == 0) {
+            this.log("Cannot set " + crop + " growth to zero, defaulting to 100");
             modifier = 100;
         }
-        this.log( crop + " Growth Modifier: " + modifier + "%" );
+        this.log(crop + " Growth Modifier: " + modifier + "%");
 
         return modifier;
     }
-    private void growthModifiers()
-    {
-        this.cactusModifier = this.getAndValidateGrowth( "Cactus" );
-        this.caneModifier = this.getAndValidateGrowth( "Cane" );
-        this.melonModifier = this.getAndValidateGrowth( "Melon" );
-        this.mushroomModifier = this.getAndValidateGrowth( "Mushroom" );
-        this.pumpkinModifier = this.getAndValidateGrowth( "Pumpkin" );
-        this.saplingModifier = this.getAndValidateGrowth( "Sapling" );
-        this.beetrootModifier = this.getAndValidateGrowth( "Beetroot" );
-        this.carrotModifier = this.getAndValidateGrowth( "Carrot" );
-        this.potatoModifier = this.getAndValidateGrowth( "Potato" );
+
+    private void growthModifiers() {
+        this.cactusModifier = this.getAndValidateGrowth("Cactus");
+        this.caneModifier = this.getAndValidateGrowth("Cane");
+        this.melonModifier = this.getAndValidateGrowth("Melon");
+        this.mushroomModifier = this.getAndValidateGrowth("Mushroom");
+        this.pumpkinModifier = this.getAndValidateGrowth("Pumpkin");
+        this.saplingModifier = this.getAndValidateGrowth("Sapling");
+        this.beetrootModifier = this.getAndValidateGrowth("Beetroot");
+        this.carrotModifier = this.getAndValidateGrowth("Carrot");
+        this.potatoModifier = this.getAndValidateGrowth("Potato");
         this.torchFlowerModifier = this.getAndValidateGrowth("TorchFlower"); // Paper
-        this.wheatModifier = this.getAndValidateGrowth( "Wheat" );
-        this.wartModifier = this.getAndValidateGrowth( "NetherWart" );
-        this.vineModifier = this.getAndValidateGrowth( "Vine" );
-        this.cocoaModifier = this.getAndValidateGrowth( "Cocoa" );
-        this.bambooModifier = this.getAndValidateGrowth( "Bamboo" );
-        this.sweetBerryModifier = this.getAndValidateGrowth( "SweetBerry" );
-        this.kelpModifier = this.getAndValidateGrowth( "Kelp" );
-        this.twistingVinesModifier = this.getAndValidateGrowth( "TwistingVines" );
-        this.weepingVinesModifier = this.getAndValidateGrowth( "WeepingVines" );
-        this.caveVinesModifier = this.getAndValidateGrowth( "CaveVines" );
+        this.wheatModifier = this.getAndValidateGrowth("Wheat");
+        this.wartModifier = this.getAndValidateGrowth("NetherWart");
+        this.vineModifier = this.getAndValidateGrowth("Vine");
+        this.cocoaModifier = this.getAndValidateGrowth("Cocoa");
+        this.bambooModifier = this.getAndValidateGrowth("Bamboo");
+        this.sweetBerryModifier = this.getAndValidateGrowth("SweetBerry");
+        this.kelpModifier = this.getAndValidateGrowth("Kelp");
+        this.twistingVinesModifier = this.getAndValidateGrowth("TwistingVines");
+        this.weepingVinesModifier = this.getAndValidateGrowth("WeepingVines");
+        this.caveVinesModifier = this.getAndValidateGrowth("CaveVines");
         this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); // Paper
         this.pitcherPlantModifier = this.getAndValidateGrowth("PitcherPlant"); // Paper
     }
 
     public double itemMerge;
-    private void itemMerge()
-    {
-        this.itemMerge = this.getDouble("merge-radius.item", 0.5 );
-        this.log( "Item Merge Radius: " + this.itemMerge );
+    private void itemMerge() {
+        this.itemMerge = this.getDouble("merge-radius.item", 0.5);
+        this.log("Item Merge Radius: " + this.itemMerge);
     }
 
     public double expMerge;
-    private void expMerge()
-    {
-        this.expMerge = this.getDouble("merge-radius.exp", -1 );
-        this.log( "Experience Merge Radius: " + this.expMerge );
+    private void expMerge() {
+        this.expMerge = this.getDouble("merge-radius.exp", -1);
+        this.log("Experience Merge Radius: " + this.expMerge);
     }
 
     public int viewDistance;
-    private void viewDistance()
-    {
-        if ( SpigotConfig.version < 12 )
-        {
-            this.set( "view-distance", null );
+    private void viewDistance() {
+        if (SpigotConfig.version < 12) {
+            this.set("view-distance", null);
         }
 
-        Object viewDistanceObject = this.get( "view-distance", "default" );
-        this.viewDistance = ( viewDistanceObject ) instanceof Number ? ( (Number) viewDistanceObject ).intValue() : -1;
-        if ( this.viewDistance <= 0 )
-        {
+        Object viewDistanceObject = this.get("view-distance", "default");
+        this.viewDistance = (viewDistanceObject) instanceof Number ? ((Number) viewDistanceObject).intValue() : -1;
+        if (this.viewDistance <= 0) {
             this.viewDistance = Bukkit.getViewDistance();
         }
 
-        this.viewDistance = Math.max( Math.min( this.viewDistance, 32 ), 3 );
-        this.log( "View Distance: " + this.viewDistance );
+        this.viewDistance = Math.max(Math.min(this.viewDistance, 32), 3);
+        this.log("View Distance: " + this.viewDistance);
     }
 
     public int simulationDistance;
-    private void simulationDistance()
-    {
-        Object simulationDistanceObject = this.get( "simulation-distance", "default" );
-        this.simulationDistance = ( simulationDistanceObject ) instanceof Number ? ( (Number) simulationDistanceObject ).intValue() : -1;
-        if ( this.simulationDistance <= 0 )
-        {
+    private void simulationDistance() {
+        Object simulationDistanceObject = this.get("simulation-distance", "default");
+        this.simulationDistance = (simulationDistanceObject) instanceof Number ? ((Number) simulationDistanceObject).intValue() : -1;
+        if (this.simulationDistance <= 0) {
             this.simulationDistance = Bukkit.getSimulationDistance();
         }
 
-        this.log( "Simulation Distance: " + this.simulationDistance );
+        this.log("Simulation Distance: " + this.simulationDistance);
     }
 
     public byte mobSpawnRange;
-    private void mobSpawnRange()
-    {
-        this.mobSpawnRange = (byte) getInt( "mob-spawn-range", 8 ); // Paper - Vanilla
-        this.log( "Mob Spawn Range: " + this.mobSpawnRange );
+    private void mobSpawnRange() {
+        this.mobSpawnRange = (byte) getInt("mob-spawn-range", 8); // Paper - Vanilla
+        this.log("Mob Spawn Range: " + this.mobSpawnRange);
     }
 
     public int itemDespawnRate;
-    private void itemDespawnRate()
-    {
-        this.itemDespawnRate = this.getInt( "item-despawn-rate", 6000 );
-        this.log( "Item Despawn Rate: " + this.itemDespawnRate );
+    private void itemDespawnRate() {
+        this.itemDespawnRate = this.getInt("item-despawn-rate", 6000);
+        this.log("Item Despawn Rate: " + this.itemDespawnRate);
     }
 
     public int animalActivationRange = 32;
@@ -216,34 +193,34 @@ public class SpigotWorldConfig
     public int waterActivationRange = 16;
     public int villagerActivationRange = 32;
     public int wakeUpInactiveAnimals = 4;
-    public int wakeUpInactiveAnimalsEvery = 60*20;
-    public int wakeUpInactiveAnimalsFor = 5*20;
+    public int wakeUpInactiveAnimalsEvery = 60 * 20;
+    public int wakeUpInactiveAnimalsFor = 5 * 20;
     public int wakeUpInactiveMonsters = 8;
-    public int wakeUpInactiveMonstersEvery = 20*20;
-    public int wakeUpInactiveMonstersFor = 5*20;
+    public int wakeUpInactiveMonstersEvery = 20 * 20;
+    public int wakeUpInactiveMonstersFor = 5 * 20;
     public int wakeUpInactiveVillagers = 4;
-    public int wakeUpInactiveVillagersEvery = 30*20;
-    public int wakeUpInactiveVillagersFor = 5*20;
+    public int wakeUpInactiveVillagersEvery = 30 * 20;
+    public int wakeUpInactiveVillagersFor = 5 * 20;
     public int wakeUpInactiveFlying = 8;
-    public int wakeUpInactiveFlyingEvery = 10*20;
-    public int wakeUpInactiveFlyingFor = 5*20;
-    public int villagersWorkImmunityAfter = 5*20;
+    public int wakeUpInactiveFlyingEvery = 10 * 20;
+    public int wakeUpInactiveFlyingFor = 5 * 20;
+    public int villagersWorkImmunityAfter = 5 * 20;
     public int villagersWorkImmunityFor = 20;
     public boolean villagersActiveForPanic = true;
     // Paper end
     public boolean tickInactiveVillagers = true;
     public boolean ignoreSpectatorActivation = false;
-    private void activationRange()
-    {
+
+    private void activationRange() {
         boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper
-        this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange );
-        this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange );
-        this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange );
-        this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange );
+        this.animalActivationRange = this.getInt("entity-activation-range.animals", this.animalActivationRange);
+        this.monsterActivationRange = this.getInt("entity-activation-range.monsters", this.monsterActivationRange);
+        this.raiderActivationRange = this.getInt("entity-activation-range.raiders", this.raiderActivationRange);
+        this.miscActivationRange = this.getInt("entity-activation-range.misc", this.miscActivationRange);
         // Paper start
-        this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange );
-        this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange );
-        this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange );
+        this.waterActivationRange = this.getInt("entity-activation-range.water", this.waterActivationRange);
+        this.villagerActivationRange = this.getInt("entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange);
+        this.flyingMonsterActivationRange = this.getInt("entity-activation-range.flying-monsters", this.flyingMonsterActivationRange);
 
         this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals);
         this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery);
@@ -261,13 +238,13 @@ public class SpigotWorldConfig
         this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery);
         this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor);
 
-        this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter );
-        this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor );
-        this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic );
+        this.villagersWorkImmunityAfter = this.getInt("entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter);
+        this.villagersWorkImmunityFor = this.getInt("entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor);
+        this.villagersActiveForPanic = this.getBoolean("entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic);
         // Paper end
-        this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers );
-        this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation );
-        this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation );
+        this.tickInactiveVillagers = this.getBoolean("entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers);
+        this.ignoreSpectatorActivation = this.getBoolean("entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation);
+        this.log("Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation);
     }
 
     public int playerTrackingRange = 128;
@@ -276,81 +253,71 @@ public class SpigotWorldConfig
     public int miscTrackingRange = 96;
     public int displayTrackingRange = 128;
     public int otherTrackingRange = 64;
-    private void trackingRange()
-    {
-        this.playerTrackingRange = this.getInt( "entity-tracking-range.players", this.playerTrackingRange );
-        this.animalTrackingRange = this.getInt( "entity-tracking-range.animals", this.animalTrackingRange );
-        this.monsterTrackingRange = this.getInt( "entity-tracking-range.monsters", this.monsterTrackingRange );
-        this.miscTrackingRange = this.getInt( "entity-tracking-range.misc", this.miscTrackingRange );
-        this.displayTrackingRange = this.getInt( "entity-tracking-range.display", this.displayTrackingRange );
-        this.otherTrackingRange = this.getInt( "entity-tracking-range.other", this.otherTrackingRange );
-        this.log( "Entity Tracking Range: Pl " + this.playerTrackingRange + " / An " + this.animalTrackingRange + " / Mo " + this.monsterTrackingRange + " / Mi " + this.miscTrackingRange + " / Di " + this.displayTrackingRange + " / Other " + this.otherTrackingRange );
+    private void trackingRange() {
+        this.playerTrackingRange = this.getInt("entity-tracking-range.players", this.playerTrackingRange);
+        this.animalTrackingRange = this.getInt("entity-tracking-range.animals", this.animalTrackingRange);
+        this.monsterTrackingRange = this.getInt("entity-tracking-range.monsters", this.monsterTrackingRange);
+        this.miscTrackingRange = this.getInt("entity-tracking-range.misc", this.miscTrackingRange);
+        this.displayTrackingRange = this.getInt("entity-tracking-range.display", this.displayTrackingRange);
+        this.otherTrackingRange = this.getInt("entity-tracking-range.other", this.otherTrackingRange);
+        this.log("Entity Tracking Range: Pl " + this.playerTrackingRange + " / An " + this.animalTrackingRange + " / Mo " + this.monsterTrackingRange + " / Mi " + this.miscTrackingRange + " / Di " + this.displayTrackingRange + " / Other " + this.otherTrackingRange);
     }
 
     public int hopperTransfer;
     public int hopperCheck;
     public int hopperAmount;
     public boolean hopperCanLoadChunks;
-    private void hoppers()
-    {
+    private void hoppers() {
         // Set the tick delay between hopper item movements
-        this.hopperTransfer = this.getInt( "ticks-per.hopper-transfer", 8 );
-        if ( SpigotConfig.version < 11 )
-        {
-            this.set( "ticks-per.hopper-check", 1 );
+        this.hopperTransfer = this.getInt("ticks-per.hopper-transfer", 8);
+        if (SpigotConfig.version < 11) {
+            this.set("ticks-per.hopper-check", 1);
         }
-        this.hopperCheck = this.getInt( "ticks-per.hopper-check", 1 );
-        this.hopperAmount = this.getInt( "hopper-amount", 1 );
-        this.hopperCanLoadChunks = this.getBoolean( "hopper-can-load-chunks", false );
-        this.log( "Hopper Transfer: " + this.hopperTransfer + " Hopper Check: " + this.hopperCheck + " Hopper Amount: " + this.hopperAmount + " Hopper Can Load Chunks: " + this.hopperCanLoadChunks );
+        this.hopperCheck = this.getInt("ticks-per.hopper-check", 1);
+        this.hopperAmount = this.getInt("hopper-amount", 1);
+        this.hopperCanLoadChunks = this.getBoolean("hopper-can-load-chunks", false);
+        this.log("Hopper Transfer: " + this.hopperTransfer + " Hopper Check: " + this.hopperCheck + " Hopper Amount: " + this.hopperAmount + " Hopper Can Load Chunks: " + this.hopperCanLoadChunks);
     }
 
     public int arrowDespawnRate;
     public int tridentDespawnRate;
-    private void arrowDespawnRate()
-    {
-        this.arrowDespawnRate = this.getInt( "arrow-despawn-rate", 1200 );
-        this.tridentDespawnRate = this.getInt( "trident-despawn-rate", this.arrowDespawnRate );
-        this.log( "Arrow Despawn Rate: " + this.arrowDespawnRate + " Trident Respawn Rate:" + this.tridentDespawnRate );
+    private void arrowDespawnRate() {
+        this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 1200);
+        this.tridentDespawnRate = this.getInt("trident-despawn-rate", this.arrowDespawnRate);
+        this.log("Arrow Despawn Rate: " + this.arrowDespawnRate + " Trident Respawn Rate:" + this.tridentDespawnRate);
     }
 
     public boolean zombieAggressiveTowardsVillager;
-    private void zombieAggressiveTowardsVillager()
-    {
-        this.zombieAggressiveTowardsVillager = this.getBoolean( "zombie-aggressive-towards-villager", true );
-        this.log( "Zombie Aggressive Towards Villager: " + this.zombieAggressiveTowardsVillager );
+    private void zombieAggressiveTowardsVillager() {
+        this.zombieAggressiveTowardsVillager = this.getBoolean("zombie-aggressive-towards-villager", true);
+        this.log("Zombie Aggressive Towards Villager: " + this.zombieAggressiveTowardsVillager);
     }
 
     public boolean nerfSpawnerMobs;
-    private void nerfSpawnerMobs()
-    {
-        this.nerfSpawnerMobs = this.getBoolean( "nerf-spawner-mobs", false );
-        this.log( "Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs );
+    private void nerfSpawnerMobs() {
+        this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", false);
+        this.log("Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs);
     }
 
     public boolean enableZombiePigmenPortalSpawns;
-    private void enableZombiePigmenPortalSpawns()
-    {
-        this.enableZombiePigmenPortalSpawns = this.getBoolean( "enable-zombie-pigmen-portal-spawns", true );
-        this.log( "Allow Zombie Pigmen to spawn from portal blocks: " + this.enableZombiePigmenPortalSpawns );
+    private void enableZombiePigmenPortalSpawns() {
+        this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", true);
+        this.log("Allow Zombie Pigmen to spawn from portal blocks: " + this.enableZombiePigmenPortalSpawns);
     }
 
     public int dragonDeathSoundRadius;
-    private void keepDragonDeathPerWorld()
-    {
-        this.dragonDeathSoundRadius = this.getInt( "dragon-death-sound-radius", 0 );
+    private void keepDragonDeathPerWorld() {
+        this.dragonDeathSoundRadius = this.getInt("dragon-death-sound-radius", 0);
     }
 
     public int witherSpawnSoundRadius;
-    private void witherSpawnSoundRadius()
-    {
-        this.witherSpawnSoundRadius = this.getInt( "wither-spawn-sound-radius", 0 );
+    private void witherSpawnSoundRadius() {
+        this.witherSpawnSoundRadius = this.getInt("wither-spawn-sound-radius", 0);
     }
 
     public int endPortalSoundRadius;
-    private void endPortalSoundRadius()
-    {
-        this.endPortalSoundRadius = this.getInt( "end-portal-sound-radius", 0 );
+    private void endPortalSoundRadius() {
+        this.endPortalSoundRadius = this.getInt("end-portal-sound-radius", 0);
     }
 
     public int villageSeed;
@@ -375,28 +342,29 @@ public class SpigotWorldConfig
     public int buriedTreasureSeed;
     public Integer mineshaftSeed;
     public Long strongholdSeed;
+
     private <N extends Number> N getSeed(String path, java.util.function.Function<String, N> toNumberFunc) {
         final String value = this.getString(path, "default");
         return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null;
     }
+
     // Paper end
-    private void initWorldGenSeeds()
-    {
-        this.villageSeed = this.getInt( "seed-village", 10387312 );
-        this.desertSeed = this.getInt( "seed-desert", 14357617 );
-        this.iglooSeed = this.getInt( "seed-igloo", 14357618 );
-        this.jungleSeed = this.getInt( "seed-jungle", 14357619 );
-        this.swampSeed = this.getInt( "seed-swamp", 14357620 );
-        this.monumentSeed = this.getInt( "seed-monument", 10387313 );
-        this.shipwreckSeed = this.getInt( "seed-shipwreck", 165745295 );
-        this.oceanSeed = this.getInt( "seed-ocean", 14357621 );
-        this.outpostSeed = this.getInt( "seed-outpost", 165745296 );
-        this.endCitySeed = this.getInt( "seed-endcity", 10387313 );
-        this.slimeSeed = this.getInt( "seed-slime", 987234911 );
-        this.netherSeed = this.getInt( "seed-nether", 30084232 );
-        this.mansionSeed = this.getInt( "seed-mansion", 10387319 );
-        this.fossilSeed = this.getInt( "seed-fossil", 14357921 );
-        this.portalSeed = this.getInt( "seed-portal", 34222645 );
+    private void initWorldGenSeeds() {
+        this.villageSeed = this.getInt("seed-village", 10387312);
+        this.desertSeed = this.getInt("seed-desert", 14357617);
+        this.iglooSeed = this.getInt("seed-igloo", 14357618);
+        this.jungleSeed = this.getInt("seed-jungle", 14357619);
+        this.swampSeed = this.getInt("seed-swamp", 14357620);
+        this.monumentSeed = this.getInt("seed-monument", 10387313);
+        this.shipwreckSeed = this.getInt("seed-shipwreck", 165745295);
+        this.oceanSeed = this.getInt("seed-ocean", 14357621);
+        this.outpostSeed = this.getInt("seed-outpost", 165745296);
+        this.endCitySeed = this.getInt("seed-endcity", 10387313);
+        this.slimeSeed = this.getInt("seed-slime", 987234911);
+        this.netherSeed = this.getInt("seed-nether", 30084232);
+        this.mansionSeed = this.getInt("seed-mansion", 10387319);
+        this.fossilSeed = this.getInt("seed-fossil", 14357921);
+        this.portalSeed = this.getInt("seed-portal", 34222645);
         // Paper start - add missing structure set configs
         this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232);
         this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867);
@@ -405,8 +373,8 @@ public class SpigotWorldConfig
         this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt);
         this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong);
         // Paper end
-        this.log( "Custom Map Seeds:  Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed
-                + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed );
+        this.log("Custom Map Seeds:  Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed
+            + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed);
     }
 
     public float jumpWalkExhaustion;
@@ -416,54 +384,48 @@ public class SpigotWorldConfig
     public float swimMultiplier;
     public float sprintMultiplier;
     public float otherMultiplier;
-    private void initHunger()
-    {
-        if ( SpigotConfig.version < 10 )
-        {
-            this.set( "hunger.walk-exhaustion", null );
-            this.set( "hunger.sprint-exhaustion", null );
-            this.set( "hunger.combat-exhaustion", 0.1 );
-            this.set( "hunger.regen-exhaustion", 6.0 );
+    private void initHunger() {
+        if (SpigotConfig.version < 10) {
+            this.set("hunger.walk-exhaustion", null);
+            this.set("hunger.sprint-exhaustion", null);
+            this.set("hunger.combat-exhaustion", 0.1);
+            this.set("hunger.regen-exhaustion", 6.0);
         }
 
-        this.jumpWalkExhaustion = (float) this.getDouble( "hunger.jump-walk-exhaustion", 0.05 );
-        this.jumpSprintExhaustion = (float) this.getDouble( "hunger.jump-sprint-exhaustion", 0.2 );
-        this.combatExhaustion = (float) this.getDouble( "hunger.combat-exhaustion", 0.1 );
-        this.regenExhaustion = (float) this.getDouble( "hunger.regen-exhaustion", 6.0 );
-        this.swimMultiplier = (float) this.getDouble( "hunger.swim-multiplier", 0.01 );
-        this.sprintMultiplier = (float) this.getDouble( "hunger.sprint-multiplier", 0.1 );
-        this.otherMultiplier = (float) this.getDouble( "hunger.other-multiplier", 0.0 );
+        this.jumpWalkExhaustion = (float) this.getDouble("hunger.jump-walk-exhaustion", 0.05);
+        this.jumpSprintExhaustion = (float) this.getDouble("hunger.jump-sprint-exhaustion", 0.2);
+        this.combatExhaustion = (float) this.getDouble("hunger.combat-exhaustion", 0.1);
+        this.regenExhaustion = (float) this.getDouble("hunger.regen-exhaustion", 6.0);
+        this.swimMultiplier = (float) this.getDouble("hunger.swim-multiplier", 0.01);
+        this.sprintMultiplier = (float) this.getDouble("hunger.sprint-multiplier", 0.1);
+        this.otherMultiplier = (float) this.getDouble("hunger.other-multiplier", 0.0);
     }
 
     public int currentPrimedTnt = 0;
     public int maxTntTicksPerTick;
     private void maxTntPerTick() {
-        if ( SpigotConfig.version < 7 )
-        {
-            this.set( "max-tnt-per-tick", 100 );
+        if (SpigotConfig.version < 7) {
+            this.set("max-tnt-per-tick", 100);
         }
-        this.maxTntTicksPerTick = this.getInt( "max-tnt-per-tick", 100 );
-        this.log( "Max TNT Explosions: " + this.maxTntTicksPerTick );
+        this.maxTntTicksPerTick = this.getInt("max-tnt-per-tick", 100);
+        this.log("Max TNT Explosions: " + this.maxTntTicksPerTick);
     }
 
     public int hangingTickFrequency;
-    private void hangingTickFrequency()
-    {
-        this.hangingTickFrequency = this.getInt( "hanging-tick-frequency", 100 );
+    private void hangingTickFrequency() {
+        this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 100);
     }
 
     public int tileMaxTickTime;
     public int entityMaxTickTime;
-    private void maxTickTimes()
-    {
+    private void maxTickTimes() {
         this.tileMaxTickTime = this.getInt("max-tick-time.tile", 50);
         this.entityMaxTickTime = this.getInt("max-tick-time.entity", 50);
         this.log("Tile Max Tick Time: " + this.tileMaxTickTime + "ms Entity max Tick Time: " + this.entityMaxTickTime + "ms");
     }
 
     public int thunderChance;
-    private void thunderChance()
-    {
+    private void thunderChance() {
         this.thunderChance = this.getInt("thunder-chance", 100000);
     }
 
diff --git a/paper-server/src/main/java/org/spigotmc/TickLimiter.java b/paper-server/src/main/java/org/spigotmc/TickLimiter.java
index 4074538ea6..961489499e 100644
--- a/paper-server/src/main/java/org/spigotmc/TickLimiter.java
+++ b/paper-server/src/main/java/org/spigotmc/TickLimiter.java
@@ -5,8 +5,8 @@ public class TickLimiter {
     private final int maxTime;
     private long startTime;
 
-    public TickLimiter(int maxtime) {
-        this.maxTime = maxtime;
+    public TickLimiter(int maxTime) {
+        this.maxTime = maxTime;
     }
 
     public void initTick() {
diff --git a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java
index 9eb2823cc8..6d4851aa8c 100644
--- a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+++ b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java
@@ -1,51 +1,55 @@
 package org.spigotmc;
 
-import net.minecraft.server.MinecraftServer;
-import org.bukkit.ChatColor;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.JoinConfiguration;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
 import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 
-public class TicksPerSecondCommand extends Command
-{
+import static net.kyori.adventure.text.Component.text;
 
-    public TicksPerSecondCommand(String name)
-    {
-        super( name );
+public class TicksPerSecondCommand extends Command {
+
+    private boolean hasShownMemoryWarning; // Paper
+
+    public TicksPerSecondCommand(String name) {
+        super(name);
         this.description = "Gets the current ticks per second for the server";
         this.usageMessage = "/tps";
-        this.setPermission( "bukkit.command.tps" );
+        this.setPermission("bukkit.command.tps");
     }
+
     // Paper start
-    private static final net.kyori.adventure.text.Component WARN_MSG = net.kyori.adventure.text.Component.text()
-        .append(net.kyori.adventure.text.Component.text("Warning: ", net.kyori.adventure.text.format.NamedTextColor.RED))
-        .append(net.kyori.adventure.text.Component.text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", net.kyori.adventure.text.format.NamedTextColor.GOLD))
+    private static final Component WARN_MSG = text()
+        .append(text("Warning: ", NamedTextColor.RED))
+        .append(text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", NamedTextColor.GOLD))
         .build();
     // Paper end
 
     @Override
-    public boolean execute(CommandSender sender, String currentAlias, String[] args)
-    {
-        if ( !this.testPermission( sender ) )
-        {
+    public boolean execute(CommandSender sender, String currentAlias, String[] args) {
+        if (!this.testPermission(sender)) {
             return true;
         }
 
         // Paper start - Further improve tick handling
         double[] tps = org.bukkit.Bukkit.getTPS();
-        net.kyori.adventure.text.Component[] tpsAvg = new net.kyori.adventure.text.Component[tps.length];
+        Component[] tpsAvg = new Component[tps.length];
 
-        for ( int i = 0; i < tps.length; i++) {
-            tpsAvg[i] = TicksPerSecondCommand.format( tps[i] );
+        for (int i = 0; i < tps.length; i++) {
+            tpsAvg[i] = TicksPerSecondCommand.format(tps[i]);
         }
 
-        net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
-        builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD));
-        builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg));
+        TextComponent.Builder builder = text();
+        builder.append(text("TPS from last 1m, 5m, 15m: ", NamedTextColor.GOLD));
+        builder.append(Component.join(JoinConfiguration.commas(true), tpsAvg));
         sender.sendMessage(builder.asComponent());
         if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) {
-            sender.sendMessage(net.kyori.adventure.text.Component.text()
-                .append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD))
-                .append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN))
+            sender.sendMessage(text()
+                .append(text("Current Memory Usage: ", NamedTextColor.GOLD))
+                .append(text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", NamedTextColor.GREEN))
             );
             if (!this.hasShownMemoryWarning) {
                 sender.sendMessage(WARN_MSG);
@@ -57,13 +61,11 @@ public class TicksPerSecondCommand extends Command
         return true;
     }
 
-    private boolean hasShownMemoryWarning; // Paper
-    private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static
-    {
-        // Paper
-        net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED );
-        String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0  ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
-        return net.kyori.adventure.text.Component.text(amount, color);
+    private static Component format(double tps) { // Paper - Made static
+        // Paper start
+        TextColor color = ((tps > 18.0) ? NamedTextColor.GREEN : (tps > 16.0) ? NamedTextColor.YELLOW : NamedTextColor.RED);
+        String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
+        return text(amount, color);
         // Paper end
     }
 }
diff --git a/paper-server/src/main/java/org/spigotmc/TrackingRange.java b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
index 4835431493..05db2aab74 100644
--- a/paper-server/src/main/java/org/spigotmc/TrackingRange.java
+++ b/paper-server/src/main/java/org/spigotmc/TrackingRange.java
@@ -18,9 +18,7 @@ public final class TrackingRange {
      * Gets the range an entity should be 'tracked' by players and visible in
      * the client.
      *
-     * @param entity
      * @param defaultRange Default range defined by Mojang
-     * @return
      */
     public static int getEntityTrackingRange(final Entity entity, final int defaultRange) {
         if (defaultRange == 0) {
@@ -51,7 +49,7 @@ public final class TrackingRange {
         } else {
             if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) {
                 // Exempt ender dragon
-                return ((ServerLevel) entity.getCommandSenderWorld()).getChunkSource().chunkMap.serverViewDistance;
+                return ((ServerLevel) entity.level()).getChunkSource().chunkMap.serverViewDistance;
             }
             return config.otherTrackingRange;
         }
diff --git a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
index ad282d3491..7fa7816014 100644
--- a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
@@ -5,11 +5,12 @@ import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import io.papermc.paper.configuration.GlobalConfiguration;
 import net.minecraft.server.MinecraftServer;
 import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftServer;
 
-public class WatchdogThread extends Thread
-{
+public class WatchdogThread extends Thread {
 
     private static WatchdogThread instance;
     private long timeoutTime;
@@ -21,160 +22,141 @@ public class WatchdogThread extends Thread
     private volatile long lastTick;
     private volatile boolean stopping;
 
-    private WatchdogThread(long timeoutTime, boolean restart)
-    {
-        super( "Paper Watchdog Thread" );
+    private WatchdogThread(long timeoutTime, boolean restart) {
+        super("Paper Watchdog Thread");
         this.timeoutTime = timeoutTime;
         this.restart = restart;
-        earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper
-        earlyWarningDelay = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningDelay, timeoutTime); // Paper
+        this.earlyWarningEvery = Math.min(GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper
+        this.earlyWarningDelay = Math.min(GlobalConfiguration.get().watchdog.earlyWarningDelay, timeoutTime); // Paper
     }
 
-    private static long monotonicMillis()
-    {
+    private static long monotonicMillis() {
         return System.nanoTime() / 1000000L;
     }
 
-    public static void doStart(int timeoutTime, boolean restart)
-    {
-        if ( WatchdogThread.instance == null )
-        {
-            WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
+    public static void doStart(int timeoutTime, boolean restart) {
+        if (WatchdogThread.instance == null) {
+            WatchdogThread.instance = new WatchdogThread(timeoutTime * 1000L, restart);
             WatchdogThread.instance.start();
-        } else
-        {
+        } else {
             WatchdogThread.instance.timeoutTime = timeoutTime * 1000L;
             WatchdogThread.instance.restart = restart;
         }
     }
 
-    public static void tick()
-    {
+    public static void tick() {
         WatchdogThread.instance.lastTick = WatchdogThread.monotonicMillis();
     }
 
-    public static void doStop()
-    {
-        if ( WatchdogThread.instance != null )
-        {
+    public static void doStop() {
+        if (WatchdogThread.instance != null) {
             WatchdogThread.instance.stopping = true;
         }
     }
 
     @Override
-    public void run()
-    {
-        while ( !this.stopping )
-        {
-            //
+    public void run() {
+        while (!this.stopping) {
             // Paper start
-            Logger log = Bukkit.getServer().getLogger();
+            Logger logger = Bukkit.getServer().getLogger();
             long currentTime = WatchdogThread.monotonicMillis();
-            if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable
-            {
-                boolean isLongTimeout = currentTime > lastTick + timeoutTime;
+            if (this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) { // Paper - Add property to disable
+                boolean isLongTimeout = currentTime > this.lastTick + this.timeoutTime;
                 // Don't spam early warning dumps
-                if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue;
-                if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
-                lastEarlyWarning = currentTime;
+                if (!isLongTimeout && (this.earlyWarningEvery <= 0 ||
+                    !hasStarted || currentTime < this.lastEarlyWarning + this.earlyWarningEvery ||
+                    currentTime < this.lastTick + this.earlyWarningDelay))
+                    continue;
+                if (!isLongTimeout && MinecraftServer.getServer().hasStopped())
+                    continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
+                this.lastEarlyWarning = currentTime;
                 if (isLongTimeout) {
-                // Paper end
-                log.log( Level.SEVERE, "------------------------------" );
-                log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper
-                log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" );
-                log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" );
-                log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" );
-                log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" );
-                log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" );
-                log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" );
-                log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() );
-                //
-                if ( net.minecraft.world.level.Level.lastPhysicsProblem != null )
-                {
-                    log.log( Level.SEVERE, "------------------------------" );
-                    log.log( Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed" );
-                    log.log( Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem );
-                }
-                //
-                // Paper start - Warn in watchdog if an excessive velocity was ever set
-                if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) {
-                    log.log(Level.SEVERE, "------------------------------");
-                    log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
-                    log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
-                    log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage());
-                    for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) {
-                        log.log( Level.SEVERE, "\t\t" + stack );
+                    // Paper end
+                    logger.log(Level.SEVERE, "------------------------------");
+                    logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug."); // Paper
+                    logger.log(Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author");
+                    logger.log(Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring");
+                    logger.log(Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once");
+                    logger.log(Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes");
+                    logger.log(Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues");
+                    logger.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
+                    logger.log(Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion());
+
+                    if (net.minecraft.world.level.Level.lastPhysicsProblem != null) {
+                        logger.log(Level.SEVERE, "------------------------------");
+                        logger.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
+                        logger.log(Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem);
                     }
-                }
-                // Paper end
-                } else
-                {
-                    log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH  - " + Bukkit.getServer().getVersion() + " ---");
-                    log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
+
+                    // Paper start - Warn in watchdog if an excessive velocity was ever set
+                    if (CraftServer.excessiveVelEx != null) {
+                        logger.log(Level.SEVERE, "------------------------------");
+                        logger.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
+                        logger.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
+                        logger.log(Level.SEVERE, CraftServer.excessiveVelEx.getMessage());
+                        for (StackTraceElement stack : CraftServer.excessiveVelEx.getStackTrace()) {
+                            logger.log(Level.SEVERE, "\t\t" + stack);
+                        }
+                    }
+                    // Paper end
+                } else {
+                    logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH  - " + Bukkit.getServer().getVersion() + " ---");
+                    logger.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
                 }
                 // Paper end - Different message for short timeout
-                log.log( Level.SEVERE, "------------------------------" );
-                log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
-                WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
-                log.log( Level.SEVERE, "------------------------------" );
-                //
+                logger.log(Level.SEVERE, "------------------------------");
+                logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):"); // Paper
+                WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger);
+                logger.log(Level.SEVERE, "------------------------------");
+
                 // Paper start - Only print full dump on long timeouts
-                if ( isLongTimeout )
-                {
-                log.log( Level.SEVERE, "Entire Thread Dump:" );
-                ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true );
-                for ( ThreadInfo thread : threads )
-                {
-                    WatchdogThread.dumpThread( thread, log );
-                }
+                if (isLongTimeout) {
+                    logger.log(Level.SEVERE, "Entire Thread Dump:");
+                    ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
+                    for (ThreadInfo thread : threads) {
+                        WatchdogThread.dumpThread(thread, logger);
+                    }
                 } else {
-                    log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---");
+                    logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---");
                 }
 
-                log.log( Level.SEVERE, "------------------------------" );
+                logger.log(Level.SEVERE, "------------------------------");
 
-                if ( isLongTimeout )
-                {
-                if ( this.restart && !MinecraftServer.getServer().hasStopped() )
-                {
-                    RestartCommand.restart();
+                if (isLongTimeout) {
+                    if (this.restart && !MinecraftServer.getServer().hasStopped()) {
+                        RestartCommand.restart();
+                    }
+                    break;
                 }
-                break;
-                } // Paper end
+                // Paper end
             }
 
-            try
-            {
-                sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout
-            } catch ( InterruptedException ex )
-            {
+            try {
+                sleep(1000); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout
+            } catch (InterruptedException ex) {
                 this.interrupt();
             }
         }
     }
 
-    private static void dumpThread(ThreadInfo thread, Logger log)
-    {
-        log.log( Level.SEVERE, "------------------------------" );
-        //
-        log.log( Level.SEVERE, "Current Thread: " + thread.getThreadName() );
-        log.log( Level.SEVERE, "\tPID: " + thread.getThreadId()
-                + " | Suspended: " + thread.isSuspended()
-                + " | Native: " + thread.isInNative()
-                + " | State: " + thread.getThreadState() );
-        if ( thread.getLockedMonitors().length != 0 )
-        {
-            log.log( Level.SEVERE, "\tThread is waiting on monitor(s):" );
-            for ( MonitorInfo monitor : thread.getLockedMonitors() )
-            {
-                log.log( Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame() );
+    private static void dumpThread(ThreadInfo thread, Logger log) {
+        log.log(Level.SEVERE, "------------------------------");
+
+        log.log(Level.SEVERE, "Current Thread: " + thread.getThreadName());
+        log.log(Level.SEVERE, "\tPID: " + thread.getThreadId()
+            + " | Suspended: " + thread.isSuspended()
+            + " | Native: " + thread.isInNative()
+            + " | State: " + thread.getThreadState());
+        if (thread.getLockedMonitors().length != 0) {
+            log.log(Level.SEVERE, "\tThread is waiting on monitor(s):");
+            for (MonitorInfo monitor : thread.getLockedMonitors()) {
+                log.log(Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame());
             }
         }
-        log.log( Level.SEVERE, "\tStack:" );
-        //
-        for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper
-        {
-            log.log( Level.SEVERE, "\t\t" + stack );
+        log.log(Level.SEVERE, "\tStack:");
+
+        for (StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace())) { // Paper
+            log.log(Level.SEVERE, "\t\t" + stack);
         }
     }
 }

From e7995023f12b5eef09624c79032f26659d498c77 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Mon, 16 Dec 2024 23:46:48 +0100
Subject: [PATCH 211/285] Update more feature patches

---
 ...-Optimize-Bit-Operations-by-inlining.patch | 140 +++++++++---------
 ...der-Remove-Streams-Optimized-collect.patch | 100 +++++++------
 2 files changed, 122 insertions(+), 118 deletions(-)

diff --git a/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch b/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch
index 7d7f2d776f..a453424d45 100644
--- a/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch
+++ b/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch
@@ -6,11 +6,12 @@ Subject: [PATCH] Optimize Bit Operations by inlining
 Inline bit operations and reduce instruction count to make these hot
 operations faster
 
-diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/core/BlockPos.java
-+++ b/src/main/java/net/minecraft/core/BlockPos.java
-@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
+
+diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java
+index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..01ba7bc16e30440e35ce062bc21dae7e59bb6f8d 100644
+--- a/net/minecraft/core/BlockPos.java
++++ b/net/minecraft/core/BlockPos.java
+@@ -51,15 +51,17 @@ public class BlockPos extends Vec3i {
      };
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final BlockPos ZERO = new BlockPos(0, 0, 0);
@@ -36,18 +37,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public BlockPos(int x, int y, int z) {
          super(x, y, z);
-@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
-         this(pos.getX(), pos.getY(), pos.getZ());
+@@ -69,28 +71,29 @@ public class BlockPos extends Vec3i {
+         this(vector.getX(), vector.getY(), vector.getZ());
      }
  
-+    public static long getAdjacent(int baseX, int baseY, int baseZ, Direction enumdirection) { return asLong(baseX + enumdirection.getStepX(), baseY + enumdirection.getStepY(), baseZ + enumdirection.getStepZ()); } // Paper
-     public static long offset(long value, Direction direction) {
-         return offset(value, direction.getStepX(), direction.getStepY(), direction.getStepZ());
++    public static long getAdjacent(int baseX, int baseY, int baseZ, Direction direction) { return asLong(baseX + direction.getStepX(), baseY + direction.getStepY(), baseZ + direction.getStepZ()); } // Paper
+     public static long offset(long pos, Direction direction) {
+         return offset(pos, direction.getStepX(), direction.getStepY(), direction.getStepZ());
      }
  
-     public static long offset(long value, int x, int y, int z) {
--        return asLong(getX(value) + x, getY(value) + y, getZ(value) + z);
-+        return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z); // Paper - simplify/inline
+     public static long offset(long pos, int dx, int dy, int dz) {
+-        return asLong(getX(pos) + dx, getY(pos) + dy, getZ(pos) + dz);
++        return asLong((int) (pos >> 38) + dx, (int) ((pos << 52) >> 52) + dy, (int) ((pos << 26) >> 38) + dz); // Paper - simplify/inline
      }
  
      public static int getX(long packedPos) {
@@ -71,23 +72,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static BlockPos containing(double x, double y, double z) {
-@@ -0,0 +0,0 @@ public class BlockPos extends Vec3i {
+@@ -114,10 +117,7 @@ public class BlockPos extends Vec3i {
      }
  
      public static long asLong(int x, int y, int z) {
 -        long l = 0L;
--        l |= ((long)x & PACKED_X_MASK) << X_OFFSET;
--        l |= ((long)y & PACKED_Y_MASK) << 0;
--        return l | ((long)z & PACKED_Z_MASK) << Z_OFFSET;
-+        return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); // Paper - inline constants and simplify
+-        l |= (x & PACKED_X_MASK) << X_OFFSET;
+-        l |= (y & PACKED_Y_MASK) << 0;
+-        return l | (z & PACKED_Z_MASK) << Z_OFFSET;
++        return ((x & 67108863L) << 38) | ((y &  4095L)) | ((z & 67108863L) << 12); // Paper - inline constants and simplify
      }
  
-     public static long getFlatIndex(long y) {
-diff --git a/src/main/java/net/minecraft/core/SectionPos.java b/src/main/java/net/minecraft/core/SectionPos.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/core/SectionPos.java
-+++ b/src/main/java/net/minecraft/core/SectionPos.java
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+     public static long getFlatIndex(long packedPos) {
+diff --git a/net/minecraft/core/SectionPos.java b/net/minecraft/core/SectionPos.java
+index 1780d8e14cea62971da75e4dcc80d1805434037b..737f853a264d720ce15f6bbffc206e3003fff380 100644
+--- a/net/minecraft/core/SectionPos.java
++++ b/net/minecraft/core/SectionPos.java
+@@ -38,7 +38,7 @@ public class SectionPos extends Vec3i {
      }
  
      public static SectionPos of(BlockPos pos) {
@@ -96,7 +97,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static SectionPos of(ChunkPos chunkPos, int y) {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+@@ -54,7 +54,7 @@ public class SectionPos extends Vec3i {
      }
  
      public static SectionPos of(long packed) {
@@ -105,109 +106,108 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static SectionPos bottomOf(ChunkAccess chunk) {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+@@ -65,8 +65,16 @@ public class SectionPos extends Vec3i {
          return offset(packed, direction.getStepX(), direction.getStepY(), direction.getStepZ());
      }
  
 +    // Paper start
-+    public static long getAdjacentFromBlockPos(int x, int y, int z, Direction enumdirection) {
-+        return (((long) ((x >> 4) + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getStepY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getStepZ()) & 4194303L) << 20);
++    public static long getAdjacentFromBlockPos(int x, int y, int z, Direction direction) {
++        return (((long) ((x >> 4) + direction.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + direction.getStepY()) & 1048575L)) | (((long) ((z >> 4) + direction.getStepZ()) & 4194303L) << 20);
 +    }
-+    public static long getAdjacentFromSectionPos(int x, int y, int z, Direction enumdirection) {
-+        return (((long) (x + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getStepY()) & 1048575L)) | (((long) (z + enumdirection.getStepZ()) & 4194303L) << 20);
++    public static long getAdjacentFromSectionPos(int x, int y, int z, Direction direction) {
++        return (((long) (x + direction.getStepX()) & 4194303L) << 42) | (((long) ((y) + direction.getStepY()) & 1048575L)) | (((long) (z + direction.getStepZ()) & 4194303L) << 20);
 +    }
 +    // Paper end
-     public static long offset(long packed, int x, int y, int z) {
--        return asLong(x(packed) + x, y(packed) + y, z(packed) + z);
-+        return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20); // Simplify to reduce instruction count
+     public static long offset(long packed, int dx, int dy, int dz) {
+-        return asLong(x(packed) + dx, y(packed) + dy, z(packed) + dz);
++        return (((long) ((int) (packed >> 42) + dx) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + dy) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + dz) & 4194303L) << 20); // Simplify to reduce instruction count
      }
  
-     public static int posToSectionCoord(double coord) {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+     public static int posToSectionCoord(double pos) {
+@@ -86,10 +94,7 @@ public class SectionPos extends Vec3i {
      }
  
      public static short sectionRelativePos(BlockPos pos) {
--        int i = sectionRelative(pos.getX());
--        int j = sectionRelative(pos.getY());
--        int k = sectionRelative(pos.getZ());
--        return (short)(i << 8 | k << 4 | j << 0);
+-        int relativeBlockPosX = sectionRelative(pos.getX());
+-        int relativeBlockPosY = sectionRelative(pos.getY());
+-        int relativeBlockPosZ = sectionRelative(pos.getZ());
+-        return (short)(relativeBlockPosX << 8 | relativeBlockPosZ << 4 | relativeBlockPosY << 0);
 +        return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15); // Paper - simplify/inline
      }
  
-     public static int sectionRelativeX(short packedLocalPos) {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+     public static int sectionRelativeX(short x) {
+@@ -152,16 +157,16 @@ public class SectionPos extends Vec3i {
          return this.getZ();
      }
  
 -    public int minBlockX() {
 -        return sectionToBlockCoord(this.x());
-+    public final int minBlockX() { // Paper - make final
++    public final int minBlockX() { // Paper - final
 +        return this.getX() << 4; // Paper - inline
      }
  
 -    public int minBlockY() {
 -        return sectionToBlockCoord(this.y());
-+    public final int minBlockY() { // Paper - make final
++    public final int minBlockY() { // Paper - final
 +        return this.getY() << 4; // Paper - inline
      }
  
 -    public int minBlockZ() {
 -        return sectionToBlockCoord(this.z());
-+    public int minBlockZ() { // Paper - make final
++    public final int minBlockZ() { // Paper - final
 +        return this.getZ() << 4; // Paper - inline
      }
  
      public int maxBlockX() {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+@@ -177,7 +182,7 @@ public class SectionPos extends Vec3i {
      }
  
-     public static long blockToSection(long blockPos) {
--        return asLong(blockToSectionCoord(BlockPos.getX(blockPos)), blockToSectionCoord(BlockPos.getY(blockPos)), blockToSectionCoord(BlockPos.getZ(blockPos)));
-+        // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i)));
-+        return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count
+     public static long blockToSection(long levelPos) {
+-        return asLong(blockToSectionCoord(BlockPos.getX(levelPos)), blockToSectionCoord(BlockPos.getY(levelPos)), blockToSectionCoord(BlockPos.getZ(levelPos)));
++        return (((long) (int) (levelPos >> 42) & 4194303L) << 42) | (((long) (int) ((levelPos << 52) >> 56) & 1048575L)) | (((long) (int) ((levelPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count
      }
  
      public static long getZeroNode(int x, int z) {
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
-         return asLong(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ()));
+@@ -205,15 +210,17 @@ public class SectionPos extends Vec3i {
+         return asLong(blockToSectionCoord(blockPos.getX()), blockToSectionCoord(blockPos.getY()), blockToSectionCoord(blockPos.getZ()));
      }
  
 +    // Paper start
-+    public static long blockPosAsSectionLong(int i, int j, int k) {
-+        return (((long) (i >> 4) & 4194303L) << 42) | (((long) (j >> 4) & 1048575L)) | (((long) (k >> 4) & 4194303L) << 20);
++    public static long blockPosAsSectionLong(int x, int y, int z) {
++        return (((long) (x >> 4) & 4194303L) << 42) | (((long) (y >> 4) & 1048575L)) | (((long) (z >> 4) & 4194303L) << 20);
 +    }
 +    // Paper end
-+
      public static long asLong(int x, int y, int z) {
 -        long l = 0L;
--        l |= ((long)x & 4194303L) << 42;
--        l |= ((long)y & 1048575L) << 0;
--        return l | ((long)z & 4194303L) << 20;
-+        return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20); // Paper - Simplify to reduce instruction count
+-        l |= (x & 4194303L) << 42;
+-        l |= (y & 1048575L) << 0;
+-        return l | (z & 4194303L) << 20;
++        return ((x & 4194303L) << 42) | ((y & 1048575L)) | ((z & 4194303L) << 20); // Paper - Simplify to reduce instruction count
      }
  
      public long asLong() {
 -        return asLong(this.x(), this.y(), this.z());
-+        return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count
++        return ((this.getX() & 4194303L) << 42) | ((this.getY() & 1048575L)) | ((this.getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class SectionPos extends Vec3i {
+@@ -226,16 +233,13 @@ public class SectionPos extends Vec3i {
      }
  
      public static Stream<SectionPos> cube(SectionPos center, int radius) {
--        int i = center.x();
--        int j = center.y();
--        int k = center.z();
--        return betweenClosedStream(i - radius, j - radius, k - radius, i + radius, j + radius, k + radius);
+-        int sectionX = center.x();
+-        int sectionY = center.y();
+-        int sectionZ = center.z();
+-        return betweenClosedStream(sectionX - radius, sectionY - radius, sectionZ - radius, sectionX + radius, sectionY + radius, sectionZ + radius);
 +        return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius); // Paper - simplify/inline
      }
  
-     public static Stream<SectionPos> aroundChunk(ChunkPos center, int radius, int minY, int maxY) {
--        int i = center.x;
--        int j = center.z;
--        return betweenClosedStream(i - radius, minY, j - radius, i + radius, maxY, j + radius);
-+        return betweenClosedStream(center.x - radius, minY, center.z - radius, center.x + radius, maxY, center.z + radius); // Paper - simplify/inline
+-    public static Stream<SectionPos> aroundChunk(ChunkPos chunkPos, int x, int y, int z) {
++    public static Stream<SectionPos> aroundChunk(ChunkPos chunkPos, int radius, int minY, int maxY) { // Paper - fix params
+         int i = chunkPos.x;
+         int i1 = chunkPos.z;
+-        return betweenClosedStream(i - x, y, i1 - x, i + x, z, i1 + x);
++        return betweenClosedStream(i - radius, minY, i1 - radius, i + radius, maxY, i1 + radius); // Paper - simplify/inline
      }
  
-     public static Stream<SectionPos> betweenClosedStream(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
+     public static Stream<SectionPos> betweenClosedStream(final int x1, final int y1, final int z1, final int x2, final int y2, final int z2) {
diff --git a/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
index fabf3cd051..5fd0a6de02 100644
--- a/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
+++ b/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
@@ -15,100 +15,104 @@ Optimize collection by creating a list instead of a set of the key and value.
 This lets us get faster foreach iteration, as well as avoids map lookups on
 the values when needed.
 
-diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
-+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
-@@ -0,0 +0,0 @@ public class PathFinder {
-         if (node == null) {
+
+diff --git a/net/minecraft/world/level/pathfinder/PathFinder.java b/net/minecraft/world/level/pathfinder/PathFinder.java
+index a6ef296f7af1f784e1f0772947a6cb3519a3bc2a..81de6c1bbef1cafd3036e736dd305fbedc8368c6 100644
+--- a/net/minecraft/world/level/pathfinder/PathFinder.java
++++ b/net/minecraft/world/level/pathfinder/PathFinder.java
+@@ -43,8 +43,12 @@ public class PathFinder {
+         if (start == null) {
              return null;
          } else {
--            Map<Target, BlockPos> map = positions.stream()
--                .collect(Collectors.toMap(pos -> this.nodeEvaluator.getTarget((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()), Function.identity()));
+-            Map<Target, BlockPos> map = targetPositions.stream()
+-                .collect(Collectors.toMap(pos -> this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), Function.identity()));
 +            // Paper start - Perf: remove streams and optimize collection
 +            List<Map.Entry<Target, BlockPos>> map = Lists.newArrayList();
-+            for (final BlockPos pos : positions) {
++            for (BlockPos pos : targetPositions) {
 +                map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getTarget(pos.getX(), pos.getY(), pos.getZ()), pos));
 +            }
 +            // Paper end - Perf: remove streams and optimize collection
-             Path path = this.findPath(node, map, followRange, distance, rangeMultiplier);
+             Path path = this.findPath(start, map, maxRange, accuracy, searchDepthMultiplier);
              this.nodeEvaluator.done();
              return path;
-@@ -0,0 +0,0 @@ public class PathFinder {
+@@ -52,19 +56,19 @@ public class PathFinder {
      }
  
      @Nullable
--    private Path findPath(Node startNode, Map<Target, BlockPos> positions, float followRange, int distance, float rangeMultiplier) {
-+    // Paper start - Perf: remove streams and optimize collection
-+    private Path findPath(Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
+-    private Path findPath(Node node, Map<Target, BlockPos> targetPositions, float maxRange, int accuracy, float searchDepthMultiplier) {
++    private Path findPath(Node node, List<Map.Entry<Target, BlockPos>> positions, float maxRange, int accuracy, float searchDepthMultiplier) { // Paper - optimize collection
          ProfilerFiller profilerFiller = Profiler.get();
          profilerFiller.push("find_path");
          profilerFiller.markForCharting(MetricCategory.PATH_FINDING);
--        Set<Target> set = positions.keySet();
-+        // Set<Target> set = positions.keySet();
-         startNode.g = 0.0F;
--        startNode.h = this.getBestH(startNode, set);
-+        startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
-         startNode.f = startNode.h;
+-        Set<Target> set = targetPositions.keySet();
++        // Set<Target> set = targetPositions.keySet(); // Paper
+         node.g = 0.0F;
+-        node.h = this.getBestH(node, set);
++        node.h = this.getBestH(node, positions); // Paper - optimize collection
+         node.f = node.h;
          this.openSet.clear();
-         this.openSet.insert(startNode);
--        Set<Node> set2 = ImmutableSet.of();
-+        // Set<Node> set2 = ImmutableSet.of(); // Paper - unused - diff on change
+         this.openSet.insert(node);
+-        Set<Node> set1 = ImmutableSet.of();
++        // Set<Node> set1 = ImmutableSet.of(); // Paper - unused - diff on change
          int i = 0;
--        Set<Target> set3 = Sets.newHashSetWithExpectedSize(set.size());
+-        Set<Target> set2 = Sets.newHashSetWithExpectedSize(set.size());
 +        List<Map.Entry<Target, BlockPos>> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection
-         int j = (int)((float)this.maxVisitedNodes * rangeMultiplier);
+         int i1 = (int)(this.maxVisitedNodes * searchDepthMultiplier);
  
          while (!this.openSet.isEmpty()) {
-@@ -0,0 +0,0 @@ public class PathFinder {
-             Node node = this.openSet.pop();
-             node.closed = true;
+@@ -75,14 +79,18 @@ public class PathFinder {
+             Node node1 = this.openSet.pop();
+             node1.closed = true;
  
 -            for (Target target : set) {
 +            // Paper start - optimize collection
-+            for (int i1 = 0; i1 < positions.size(); i1++) {
-+                final Map.Entry<Target, BlockPos> entry = positions.get(i1);
++            for (int positionIndex = 0, size = positions.size(); positionIndex < size; positionIndex++) {
++                final Map.Entry<Target, BlockPos> entry = positions.get(positionIndex);
 +                Target target = entry.getKey();
-                 if (node.distanceManhattan(target) <= (float)distance) {
+                 if (node1.distanceManhattan(target) <= accuracy) {
                      target.setReached();
--                    set3.add(target);
+-                    set2.add(target);
 +                    entryList.add(entry);
 +                    // Paper end - Perf: remove streams and optimize collection
                  }
              }
  
--            if (!set3.isEmpty()) {
+-            if (!set2.isEmpty()) {
 +            if (!entryList.isEmpty()) { // Paper - Perf: remove streams and optimize collection; rename
                  break;
              }
  
-@@ -0,0 +0,0 @@ public class PathFinder {
-                     if (node2.walkedDistance < followRange && (!node2.inOpenSet() || g < node2.g)) {
-                         node2.cameFrom = node;
-                         node2.g = g;
+@@ -97,7 +105,7 @@ public class PathFinder {
+                     if (node2.walkedDistance < maxRange && (!node2.inOpenSet() || f1 < node2.g)) {
+                         node2.cameFrom = node1;
+                         node2.g = f1;
 -                        node2.h = this.getBestH(node2, set) * 1.5F;
 +                        node2.h = this.getBestH(node2, positions) * 1.5F; // Paper - Perf: remove streams and optimize collection
                          if (node2.inOpenSet()) {
                              this.openSet.changeCost(node2, node2.g + node2.h);
                          } else {
-@@ -0,0 +0,0 @@ public class PathFinder {
+@@ -109,25 +117,34 @@ public class PathFinder {
              }
          }
  
--        Optional<Path> optional = !set3.isEmpty()
--            ? set3.stream().map(node -> this.reconstructPath(node.getBestNode(), positions.get(node), true)).min(Comparator.comparingInt(Path::getNodeCount))
+-        Optional<Path> optional = !set2.isEmpty()
+-            ? set2.stream()
+-                .map(pathfinder -> this.reconstructPath(pathfinder.getBestNode(), targetPositions.get(pathfinder), true))
+-                .min(Comparator.comparingInt(Path::getNodeCount))
 -            : set.stream()
--                .map(targetx -> this.reconstructPath(targetx.getBestNode(), positions.get(targetx), false))
+-                .map(pathfinder -> this.reconstructPath(pathfinder.getBestNode(), targetPositions.get(pathfinder), false))
 -                .min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount));
 +        // Paper start - Perf: remove streams and optimize collection
 +        Path best = null;
 +        boolean entryListIsEmpty = entryList.isEmpty();
-+        Comparator<Path> comparator = entryListIsEmpty ? Comparator.comparingInt(Path::getNodeCount)
++        Comparator<Path> comparator = entryListIsEmpty
++            ? Comparator.comparingInt(Path::getNodeCount)
 +            : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount);
 +        for (Map.Entry<Target, BlockPos> entry : entryListIsEmpty ? positions : entryList) {
 +            Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty);
-+            if (best == null || comparator.compare(path, best) < 0)
++            if (best == null || comparator.compare(path, best) < 0) {
 +                best = path;
++            }
 +        }
          profilerFiller.pop();
 -        return optional.isEmpty() ? null : optional.get();
@@ -116,8 +120,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - Perf: remove streams and optimize collection
      }
  
-     protected float distance(Node a, Node b) {
-         return a.distanceTo(b);
+     protected float distance(Node first, Node second) {
+         return first.distanceTo(second);
      }
  
 -    private float getBestH(Node node, Set<Target> targets) {
@@ -129,6 +133,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        for (int i = 0, targetsSize = targets.size(); i < targetsSize; i++) {
 +            final Target target = targets.get(i).getKey();
 +            // Paper end - Perf: remove streams and optimize collection
-             float g = node.distanceTo(target);
-             target.updateBest(g, node);
-             f = Math.min(g, f);
+             float f1 = node.distanceTo(target);
+             target.updateBest(f1, node);
+             f = Math.min(f1, f);

From 6f26d535826c51d5d3da2ffbeaa7344f581938f6 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 11:00:56 +0100
Subject: [PATCH 212/285] Update hopper optimization patch

---
 feature-patches/1048-Optimize-Hoppers.patch   | 580 +++++++++---------
 .../1051-Remove-streams-from-hot-code.patch   | 168 ++---
 2 files changed, 388 insertions(+), 360 deletions(-)

diff --git a/feature-patches/1048-Optimize-Hoppers.patch b/feature-patches/1048-Optimize-Hoppers.patch
index 3b088a1787..a7a16b45a9 100644
--- a/feature-patches/1048-Optimize-Hoppers.patch
+++ b/feature-patches/1048-Optimize-Hoppers.patch
@@ -12,28 +12,27 @@ Subject: [PATCH] Optimize Hoppers
 * Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried)
 * Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins)
 
-diff --git a/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java
+
+diff --git a/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdfe19136a2
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java
-@@ -0,0 +0,0 @@
++++ b/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java
+@@ -0,0 +1,29 @@
 +package io.papermc.paper.event.inventory;
 +
 +import org.bukkit.event.inventory.InventoryMoveItemEvent;
 +import org.bukkit.inventory.Inventory;
 +import org.bukkit.inventory.ItemStack;
-+import org.checkerframework.checker.nullness.qual.NonNull;
-+import org.checkerframework.framework.qual.DefaultQualifier;
-+import org.jetbrains.annotations.NotNull;
++import org.jspecify.annotations.NullMarked;
 +
-+@DefaultQualifier(NonNull.class)
++@NullMarked
 +public class PaperInventoryMoveItemEvent extends InventoryMoveItemEvent {
 +
 +    public boolean calledSetItem;
 +    public boolean calledGetItem;
 +
-+    public PaperInventoryMoveItemEvent(final @NotNull Inventory sourceInventory, final @NotNull ItemStack itemStack, final @NotNull Inventory destinationInventory, final boolean didSourceInitiate) {
++    public PaperInventoryMoveItemEvent(final Inventory sourceInventory, final ItemStack itemStack, final Inventory destinationInventory, final boolean didSourceInitiate) {
 +        super(sourceInventory, itemStack, destinationInventory, didSourceInitiate);
 +    }
 +
@@ -49,23 +48,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.calledSetItem = true;
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             ServerLevel worldserver = (ServerLevel) iterator.next();
-             worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
-             worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
-+            net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
- 
-             gameprofilerfiller.push(() -> {
-                 String s = String.valueOf(worldserver);
-diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/item/ItemStack.java
-+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
-@@ -0,0 +0,0 @@ public final class ItemStack implements DataComponentHolder {
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..415ab7faf371507e278d0da5bf0ffa9972585876 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -1564,6 +1564,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         for (ServerLevel serverLevel : this.getAllLevels()) {
+             serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
+             serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
++            net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+             profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
+             /* Drop global time updates
+             if (this.tickCount % 20 == 0) {
+diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
+index 50cd12def88c9449cad8875c553f5ed9ef1cd791..3d93bb1aac5ad4830fc1dceddb6bebacee28f72a 100644
+--- a/net/minecraft/world/item/ItemStack.java
++++ b/net/minecraft/world/item/ItemStack.java
+@@ -815,10 +815,16 @@ public final class ItemStack implements DataComponentHolder {
      }
  
      public ItemStack copy() {
@@ -74,42 +73,42 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return this.copy(false);
 +    }
 +
-+    public ItemStack copy(boolean originalItem) {
++    public ItemStack copy(final boolean originalItem) {
 +        if (!originalItem && this.isEmpty()) {
 +            // Paper end - Perf: Optimize Hoppers
-             return ItemStack.EMPTY;
+             return EMPTY;
          } else {
--            ItemStack itemstack = new ItemStack(this.getItem(), this.count, this.components.copy());
-+            ItemStack itemstack = new ItemStack(originalItem ? this.item : this.getItem(), this.count, this.components.copy()); // Paper - Perf: Optimize Hoppers
- 
-             itemstack.setPopTime(this.getPopTime());
-             return itemstack;
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
-@@ -0,0 +0,0 @@ import org.bukkit.inventory.InventoryHolder;
- // CraftBukkit end
+-            ItemStack itemStack = new ItemStack(this.getItem(), this.count, this.components.copy());
++            ItemStack itemStack = new ItemStack(originalItem ? this.item : this.getItem(), this.count, this.components.copy()); // Paper - Perf: Optimize Hoppers
+             itemStack.setPopTime(this.getPopTime());
+             return itemStack;
+         }
+diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java
+index 2ebdf1ad323bb53dfe9eed319e25856b35a1443c..77618757c0e678532dbab814aceed83f7f1cd892 100644
+--- a/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -26,6 +26,7 @@ import net.minecraft.world.level.block.state.BlockState;
+ import org.slf4j.Logger;
  
  public abstract class BlockEntity {
-+    static boolean ignoreTileUpdates; // Paper - Perf: Optimize Hoppers
- 
++    static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers
      // CraftBukkit start - data containers
-     private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
-@@ -0,0 +0,0 @@ public abstract class BlockEntity {
+     private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+     public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer;
+@@ -196,6 +197,7 @@ public abstract class BlockEntity {
  
      public void setChanged() {
          if (this.level != null) {
-+            if (ignoreTileUpdates) return; // Paper - Perf: Optimize Hoppers
-             BlockEntity.setChanged(this.level, this.worldPosition, this.blockState);
++            if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers
+             setChanged(this.level, this.worldPosition, this.blockState);
+         }
+     }
+diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+index 60e1e44f328e66d52ebf08476b533fef83bc5eba..8139868201c2eaca29588b840a2bd85778f1b3d5 100644
+--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -139,18 +139,56 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
          }
- 
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
- 
      }
  
 +    // Paper start - Perf: Optimize Hoppers
@@ -117,10 +116,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static final int HOPPER_HAS_ITEMS = 1;
 +    private static final int HOPPER_IS_FULL = 2;
 +
-+    private static int getFullState(final HopperBlockEntity tileEntity) {
-+        tileEntity.unpackLootTable(null);
++    private static int getFullState(final HopperBlockEntity hoper) {
++        hoper.unpackLootTable(null);
 +
-+        final List<ItemStack> hopperItems = tileEntity.getItems();
++        final List<ItemStack> hopperItems = hoper.items;
 +
 +        boolean empty = true;
 +        boolean full = true;
@@ -149,32 +148,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Perf: Optimize Hoppers
 +
-     private static boolean tryMoveItems(Level world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier booleansupplier) {
-         if (world.isClientSide) {
+     private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) {
+         if (level.isClientSide) {
              return false;
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-             if (!blockEntity.isOnCooldown() && (Boolean) state.getValue(HopperBlock.ENABLED)) {
+         } else {
+             if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) {
                  boolean flag = false;
- 
 -                if (!blockEntity.isEmpty()) {
 +                final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers
 +                if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers
-                     flag = HopperBlockEntity.ejectItems(world, pos, blockEntity);
+                     flag = ejectItems(level, pos, blockEntity);
                  }
  
 -                if (!blockEntity.inventoryFull()) {
+-                    flag |= validator.getAsBoolean();
 +                if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers
-                     flag |= booleansupplier.getAsBoolean();
++                    flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items
                  }
  
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-         return false;
+                 if (flag) {
+@@ -174,6 +212,206 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+         return true;
      }
  
 +    // Paper start - Perf: Optimize Hoppers
++    public static boolean skipHopperEvents;
 +    private static boolean skipPullModeEventFire;
 +    private static boolean skipPushModeEventFire;
-+    public static boolean skipHopperEvents;
 +
 +    private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) {
 +        skipPushModeEventFire = skipHopperEvents;
@@ -233,7 +233,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            if (movedItem == null) { // cancelled
 +                origItemStack.setCount(originalItemCount);
 +                // Drastically improve performance by returning true.
-+                // No plugin could of relied on the behavior of false as the other call
++                // No plugin could have relied on the behavior of false as the other call
 +                // site for IMIE did not exhibit the same behavior
 +                return true;
 +            }
@@ -248,68 +248,72 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
 +            }
 +
-+            ignoreTileUpdates = true;
++            ignoreBlockEntityUpdates = true;
 +            container.setItem(i, origItemStack);
-+            ignoreTileUpdates = false;
++            ignoreBlockEntityUpdates = false;
 +            container.setChanged();
 +            return true;
 +        }
 +        origItemStack.setCount(originalItemCount);
 +
 +        if (level.paperConfig().hopper.cooldownWhenFull) {
-+            cooldownHopper(hopper);
++            applyCooldown(hopper);
 +        }
 +
 +        return false;
 +    }
 +
 +    @Nullable
-+    private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) {
-+        final Inventory destinationInventory = getInventory(iinventory);
-+        final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(hopper.getOwner(false).getInventory(),
-+            CraftItemStack.asCraftMirror(itemstack), destinationInventory, true);
++    private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) {
++        final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination);
++        final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(
++            hopper.getOwner(false).getInventory(),
++            org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack),
++            destinationInventory,
++            true
++        );
 +        final boolean result = event.callEvent();
 +        if (!event.calledGetItem && !event.calledSetItem) {
 +            skipPushModeEventFire = true;
 +        }
 +        if (!result) {
-+            cooldownHopper(hopper);
++            applyCooldown(hopper);
 +            return null;
 +        }
 +
 +        if (event.calledSetItem) {
-+            return CraftItemStack.asNMSCopy(event.getItem());
++            return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
 +        } else {
-+            return itemstack;
++            return itemStack;
 +        }
 +    }
 +
 +    @Nullable
 +    private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) {
-+        final Inventory sourceInventory = getInventory(container);
-+        final Inventory destination = getInventory(hopper);
++        final org.bukkit.inventory.Inventory sourceInventory = getInventory(container);
++        final org.bukkit.inventory.Inventory destination = getInventory(hopper);
 +
 +        // Mirror is safe as no plugins ever use this item
-+        final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, CraftItemStack.asCraftMirror(itemstack), destination, false);
++        final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false);
 +        final boolean result = event.callEvent();
 +        if (!event.calledGetItem && !event.calledSetItem) {
 +            skipPullModeEventFire = true;
 +        }
 +        if (!result) {
-+            cooldownHopper(hopper);
++            applyCooldown(hopper);
 +            return null;
 +        }
 +
 +        if (event.calledSetItem) {
-+            return CraftItemStack.asNMSCopy(event.getItem());
++            return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
 +        } else {
 +            return itemstack;
 +        }
 +    }
 +
-+    private static Inventory getInventory(final Container container) {
-+        final Inventory sourceInventory;
-+        if (container instanceof CompoundContainer compoundContainer) {
++    private static org.bukkit.inventory.Inventory getInventory(final Container container) {
++        final org.bukkit.inventory.Inventory sourceInventory;
++        if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) {
 +            // Have to special-case large chests as they work oddly
 +            sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
 +        } else if (container instanceof BlockEntity blockEntity) {
@@ -317,28 +321,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        } else if (container.getOwner() != null) {
 +            sourceInventory = container.getOwner().getInventory();
 +        } else {
-+            sourceInventory = new CraftInventory(container);
++            sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container);
 +        }
 +        return sourceInventory;
 +    }
 +
-+    private static void cooldownHopper(final Hopper hopper) {
++    private static void applyCooldown(final Hopper hopper) {
 +        if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) {
 +            blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer);
 +        }
 +    }
 +
-+    private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
-+        if (iinventory instanceof WorldlyContainer) {
-+            for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
-+                if (!test.test(iinventory.getItem(i), i)) {
++    private static boolean allMatch(Container container, Direction direction, java.util.function.BiPredicate<ItemStack, Integer> test) {
++        if (container instanceof WorldlyContainer) {
++            for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) {
++                if (!test.test(container.getItem(slot), slot)) {
 +                    return false;
 +                }
 +            }
 +        } else {
-+            int size = iinventory.getContainerSize();
-+            for (int i = 0; i < size; i++) {
-+                if (!test.test(iinventory.getItem(i), i)) {
++            int size = container.getContainerSize();
++            for (int slot = 0; slot < size; slot++) {
++                if (!test.test(container.getItem(slot), slot)) {
 +                    return false;
 +                }
 +            }
@@ -346,319 +350,335 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return true;
 +    }
 +
-+    private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) {
-+        if (iinventory instanceof WorldlyContainer) {
-+            for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) {
-+                if (test.test(iinventory.getItem(i), i)) {
++    private static boolean anyMatch(Container container, Direction direction, java.util.function.BiPredicate<ItemStack, Integer> test) {
++        if (container instanceof WorldlyContainer) {
++            for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) {
++                if (test.test(container.getItem(slot), slot)) {
 +                    return true;
 +                }
 +            }
 +        } else {
-+            int size = iinventory.getContainerSize();
-+            for (int i = 0; i < size; i++) {
-+                if (test.test(iinventory.getItem(i), i)) {
++            int size = container.getContainerSize();
++            for (int slot = 0; slot < size; slot++) {
++                if (test.test(container.getItem(slot), slot)) {
 +                    return true;
 +                }
 +            }
 +        }
 +        return true;
 +    }
-+    private static final java.util.function.BiPredicate<ItemStack, Integer> STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize();
-+    private static final java.util.function.BiPredicate<ItemStack, Integer> IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty();
++    private static final java.util.function.BiPredicate<ItemStack, Integer> STACK_SIZE_TEST = (itemStack, i) -> itemStack.getCount() >= itemStack.getMaxStackSize();
++    private static final java.util.function.BiPredicate<ItemStack, Integer> IS_EMPTY_TEST = (itemStack, i) -> itemStack.isEmpty();
 +    // Paper end - Perf: Optimize Hoppers
 +
-     private static boolean ejectItems(Level world, BlockPos pos, HopperBlockEntity blockEntity) {
-         Container iinventory = HopperBlockEntity.getAttachedContainer(world, pos, blockEntity);
- 
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-             if (HopperBlockEntity.isFullContainer(iinventory, enumdirection)) {
+     private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) {
+         Container attachedContainer = getAttachedContainer(level, pos, blockEntity);
+         if (attachedContainer == null) {
+@@ -183,57 +421,60 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+             if (isFullContainer(attachedContainer, opposite)) {
                  return false;
              } else {
--                for (int i = 0; i < blockEntity.getContainerSize(); ++i) {
--                    ItemStack itemstack = blockEntity.getItem(i);
--
--                    if (!itemstack.isEmpty()) {
--                        int j = itemstack.getCount();
+-                for (int i = 0; i < blockEntity.getContainerSize(); i++) {
+-                    ItemStack item = blockEntity.getItem(i);
+-                    if (!item.isEmpty()) {
+-                        int count = item.getCount();
 -                        // 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
+-                        ItemStack original = item.copy();
+-                        org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(
+-                            blockEntity.removeItem(i, level.spigotConfig.hopperAmount)
+-                        ); // Spigot
 -
--                        Inventory destinationInventory;
+-                        org.bukkit.inventory.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();
+-                        if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) {
+-                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
+-                        } else if (attachedContainer.getOwner() != null) {
+-                            destinationInventory = attachedContainer.getOwner().getInventory();
 -                        } else {
--                            destinationInventory = new CraftInventory(iinventory);
+-                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer);
 -                        }
 -
--                        InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true);
--                        world.getCraftServer().getPluginManager().callEvent(event);
--                        if (event.isCancelled()) {
+-                        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(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot
+-                            blockEntity.setCooldown(level.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);
+-                        ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite);
 -                        // CraftBukkit end
 -
--                        if (itemstack1.isEmpty()) {
--                            iinventory.setChanged();
+-                        if (itemStack.isEmpty()) {
+-                            attachedContainer.setChanged();
 -                            return true;
 -                        }
 -
--                        itemstack.setCount(j);
+-                        item.setCount(count);
 -                        // Spigot start
--                        itemstack.shrink(origCount - itemstack1.getCount());
--                        if (j <= world.spigotConfig.hopperAmount) {
+-                        item.shrink(origCount - itemStack.getCount());
+-                        if (count <= level.spigotConfig.hopperAmount) {
 -                            // Spigot end
--                            blockEntity.setItem(i, itemstack);
+-                            blockEntity.setItem(i, item);
 -                        }
 -                    }
 -                }
 -
 -                return false;
 +                // Paper start - Perf: Optimize Hoppers
-+                return hopperPush(world, iinventory, enumdirection, blockEntity);
-+                //for (int i = 0; i < blockEntity.getContainerSize(); ++i) {
-+                //    ItemStack itemstack = blockEntity.getItem(i);
-+
-+                //    if (!itemstack.isEmpty()) {
-+                //        int j = itemstack.getCount();
++                return hopperPush(level, attachedContainer, opposite, blockEntity);
++                //for (int i = 0; i < blockEntity.getContainerSize(); i++) {
++                //    ItemStack item = blockEntity.getItem(i);
++                //    if (!item.isEmpty()) {
++                //        int count = item.getCount();
 +                //        // 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
++                //        ItemStack original = item.copy();
++                //        org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(
++                //            blockEntity.removeItem(i, level.spigotConfig.hopperAmount)
++                //        ); // Spigot
 +
-+                //        Inventory destinationInventory;
++                //        org.bukkit.inventory.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();
++                //        if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) {
++                //            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
++                //        } else if (attachedContainer.getOwner() != null) {
++                //            destinationInventory = attachedContainer.getOwner().getInventory();
 +                //        } else {
-+                //            destinationInventory = new CraftInventory(iinventory);
++                //            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer);
 +                //        }
 +
-+                //        InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentityhopper.getOwner().getInventory(), oitemstack, destinationInventory, true);
-+                //        world.getCraftServer().getPluginManager().callEvent(event);
-+                //        if (event.isCancelled()) {
++                //        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(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot
++                //            blockEntity.setCooldown(level.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);
++                //        ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite);
 +                //        // CraftBukkit end
 +
-+                //        if (itemstack1.isEmpty()) {
-+                //            iinventory.setChanged();
++                //        if (itemStack.isEmpty()) {
++                //            attachedContainer.setChanged();
 +                //            return true;
 +                //        }
 +
-+                //        itemstack.setCount(j);
++                //        item.setCount(count);
 +                //        // Spigot start
-+                //        itemstack.shrink(origCount - itemstack1.getCount());
-+                //        if (j <= world.spigotConfig.hopperAmount) {
-+                //          // Spigot end
-+                //            blockEntity.setItem(i, itemstack);
++                //        item.shrink(origCount - itemStack.getCount());
++                //        if (count <= level.spigotConfig.hopperAmount) {
++                //            // Spigot end
++                //            blockEntity.setItem(i, item);
 +                //        }
 +                //    }
 +                //}
 +
-+                // return false;
++                //return false;
 +                // Paper end - Perf: Optimize Hoppers
              }
          }
      }
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-                 return false;
-             }
-         }
--
-         return true;
-     }
- 
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
- 
-         if (iinventory != null) {
-             Direction enumdirection = Direction.DOWN;
+@@ -288,6 +529,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+         Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState);
+         if (sourceContainer != null) {
+             Direction direction = Direction.DOWN;
 +            skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers
-             int[] aint = HopperBlockEntity.getSlots(iinventory, enumdirection);
-             int i = aint.length;
  
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-         ItemStack itemstack = iinventory.getItem(i);
- 
-         if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) {
--            int j = itemstack.getCount();
+             for (int i : getSlots(sourceContainer, direction)) {
+                 if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot
+@@ -313,55 +555,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+     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();
 -            // 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
+-            ItemStack original = item.copy();
+-            org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(
+-                container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot
+-            );
 -
--            Inventory sourceInventory;
+-            org.bukkit.inventory.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();
+-            if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) {
+-                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
+-            } else if (container.getOwner() != null) {
+-                sourceInventory = container.getOwner().getInventory();
 -            } else {
--                sourceInventory = new CraftInventory(iinventory);
+-                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container);
 -            }
 -
--            InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false);
+-            org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(
+-                sourceInventory,
+-                oitemstack,
+-                hopper.getOwner().getInventory(),
+-                false
+-            );
 -
--            Bukkit.getServer().getPluginManager().callEvent(event);
--            if (event.isCancelled()) {
--                iinventory.setItem(i, original);
+-            if (!event.callEvent()) {
+-                container.setItem(slot, original);
 -
--                if (ihopper instanceof HopperBlockEntity) {
--                    ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot
+-                if (hopper instanceof final HopperBlockEntity hopperBlockEntity) {
+-                    hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
 -                }
 -
 -                return false;
 -            }
 -            int origCount = event.getItem().getAmount(); // Spigot
--            ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
+-            ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null);
 -            // CraftBukkit end
 -
--            if (itemstack1.isEmpty()) {
--                iinventory.setChanged();
+-            if (itemStack.isEmpty()) {
+-                container.setChanged();
 -                return true;
 -            }
 -
--            itemstack.setCount(j);
+-            item.setCount(count);
 -            // Spigot start
--            itemstack.shrink(origCount - itemstack1.getCount());
--            if (j <= world.spigotConfig.hopperAmount) {
+-            item.shrink(origCount - itemStack.getCount());
+-            if (count <= level.spigotConfig.hopperAmount) {
 -                // Spigot end
--                iinventory.setItem(i, itemstack);
+-                container.setItem(slot, item);
 -            }
 +            // Paper start - Perf: Optimize Hoppers
-+            return hopperPull(world, ihopper, iinventory, itemstack, i);
-+        //    int j = itemstack.getCount();
-+        //    // 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
++            return hopperPull(level, hopper, container, item, slot);
++            //int count = item.getCount();
++            //// 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
++            //);
 +
-+        //    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);
-+        //    }
++            //org.bukkit.inventory.Inventory sourceInventory;
++            //// Have to special case large chests as they work oddly
++            //if (container instanceof final net.minecraft.world.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);
++            //}
 +
-+        //    InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false);
++            //org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(
++            //    sourceInventory,
++            //    oitemstack,
++            //    hopper.getOwner().getInventory(),
++            //    false
++            //);
 +
-+        //    Bukkit.getServer().getPluginManager().callEvent(event);
-+        //    if (event.isCancelled()) {
-+        //        iinventory.setItem(i, original);
++            //if (!event.callEvent()) {
++            //    container.setItem(slot, original);
 +
-+        //        if (ihopper instanceof HopperBlockEntity) {
-+        //            ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot
-+        //        }
++            //    if (hopper instanceof final HopperBlockEntity hopperBlockEntity) {
++            //        hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
++            //    }
 +
-+        //        return false;
-+        //    }
-+        //    int origCount = event.getItem().getAmount(); // Spigot
-+        //    ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
-+        //    // CraftBukkit end
++            //    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 (itemstack1.isEmpty()) {
-+        //        iinventory.setChanged();
-+        //        return true;
-+        //    }
++            //if (itemStack.isEmpty()) {
++            //    container.setChanged();
++            //    return true;
++            //}
 +
-+        //    itemstack.setCount(j);
-+        //    // Spigot start
-+        //    itemstack.shrink(origCount - itemstack1.getCount());
-+        //    if (j <= world.spigotConfig.hopperAmount) {
-+        //        // Spigot end
-+        //        iinventory.setItem(i, itemstack);
-+        //    }
++            //item.setCount(count);
++            //// Spigot start
++            //item.shrink(origCount - itemStack.getCount());
++            //if (count <= level.spigotConfig.hopperAmount) {
++            //    // Spigot end
++            //    container.setItem(slot, item);
++            //}
 +            // Paper end - Perf: Optimize Hoppers
          }
  
          return false;
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-     public static boolean addItem(Container inventory, ItemEntity itemEntity) {
+@@ -370,13 +615,15 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+     public static boolean addItem(Container container, ItemEntity item) {
          boolean flag = false;
          // CraftBukkit start
--        InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
-+        if (InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers
-+        InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation
-         itemEntity.level().getCraftServer().getPluginManager().callEvent(event);
-         if (event.isCancelled()) {
++        if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers
+         org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent(
+-            container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity()
++            getInventory(container), (org.bukkit.entity.Item) item.getBukkitEntity() // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation
+         );
+         if (!event.callEvent()) {
              return false;
          }
          // CraftBukkit end
 +        } // Paper - Perf: Optimize Hoppers
-         ItemStack itemstack = itemEntity.getItem().copy();
-         ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null);
- 
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
-                     stack = stack.split(to.getMaxStackSize());
+         ItemStack itemStack = item.getItem().copy();
+         ItemStack itemStack1 = addItem(null, container, itemStack, null);
+         if (itemStack1.isEmpty()) {
+@@ -431,7 +678,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+                     stack = stack.split(destination.getMaxStackSize());
                  }
                  // Spigot end
-+                ignoreTileUpdates = true; // Paper - Perf: Optimize Hoppers
-                 to.setItem(slot, stack);
-+                ignoreTileUpdates = false; // Paper - Perf: Optimize Hoppers
++                ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers
+                 destination.setItem(slot, stack);
++                ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers
                  stack = leftover; // Paper - Make hoppers respect inventory max stack size
                  flag = true;
-             } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) {
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+             } else if (canMergeItems(item, stack)) {
+@@ -519,13 +768,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
  
      @Nullable
-     public static Container getContainerAt(Level world, BlockPos pos) {
--        return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D);
-+        return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true);
+     public static Container getContainerAt(Level level, BlockPos pos) {
+-        return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
++        return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, true); // Paper - Optimize hoppers
      }
  
      @Nullable
-     private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z) {
+     private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) {
 +        // Paper start - Perf: Optimize Hoppers
-+        return HopperBlockEntity.getContainerAt(world, pos, state, x, y, z, false);
++        return HopperBlockEntity.getContainerAt(level, pos, state, x, y, z, false);
 +    }
 +    @Nullable
-+    private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z, boolean optimizeEntities) {
++    private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, final boolean optimizeEntities) {
 +        // Paper end - Perf: Optimize Hoppers
-         Container iinventory = HopperBlockEntity.getBlockContainer(world, pos, state);
- 
--        if (iinventory == null) {
-+        if (iinventory == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers
-             iinventory = HopperBlockEntity.getEntityContainer(world, x, y, z);
+         Container blockContainer = getBlockContainer(level, pos, state);
+-        if (blockContainer == null) {
++        if (blockContainer == null && (!optimizeEntities || !level.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers
+             blockContainer = getEntityContainer(level, x, y, z);
          }
  
-@@ -0,0 +0,0 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+@@ -551,14 +806,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
  
      @Nullable
-     private static Container getEntityContainer(Level world, double x, double y, double z) {
--        List<Entity> list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR);
-+        List<Entity> list = world.getEntitiesOfClass((Class) Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize hoppers
- 
-         return !list.isEmpty() ? (Container) list.get(world.random.nextInt(list.size())) : null;
+     private static Container getEntityContainer(Level level, double x, double y, double z) {
+-        List<Entity> entities = level.getEntities(
+-            (Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR
++        List<Entity> entities = level.getEntitiesOfClass(
++            (Class) Container.class, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR // Paper - Perf: Optimize hoppers
+         );
+         return !entities.isEmpty() ? (Container)entities.get(level.random.nextInt(entities.size())) : null;
      }
  
-     private static boolean canMergeItems(ItemStack first, ItemStack second) {
--        return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second);
-+        return first.getCount() < first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?!
+     private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) {
+-        return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2);
++        return stack1.getCount() < stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?!
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-@@ -0,0 +0,0 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
+diff --git a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+index 73b3ddb120d6b6f89e478960e78bed415baea205..f9c31da81d84033abfc1179fc643bceffe35da17 100644
+--- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+@@ -53,7 +53,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
  
      @Override
-     public ItemStack getItem(int slot) {
+     public ItemStack getItem(int index) {
 -        this.unpackLootTable(null);
-+        if (slot == 0) this.unpackLootTable(null); // Paper - Perf: Optimize Hoppers
-         return super.getItem(slot);
++        if (index == 0) this.unpackLootTable(null); // Paper - Perf: Optimize Hoppers
+         return super.getItem(index);
      }
  
diff --git a/feature-patches/1051-Remove-streams-from-hot-code.patch b/feature-patches/1051-Remove-streams-from-hot-code.patch
index 815e75e1bd..afbe9f1e4c 100644
--- a/feature-patches/1051-Remove-streams-from-hot-code.patch
+++ b/feature-patches/1051-Remove-streams-from-hot-code.patch
@@ -6,44 +6,50 @@ Subject: [PATCH] Remove streams from hot code
 Co-authored-by: Bjarne Koll <git@lynxplay.dev>
 Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
 
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
-@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+diff --git a/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/net/minecraft/world/entity/ai/behavior/GateBehavior.java
+index c215d97c24e6501e1a48a76fc08bf48ff4dfe462..bd31d1cac0d022a72bd536c41d1ef811886e7068 100644
+--- a/net/minecraft/world/entity/ai/behavior/GateBehavior.java
++++ b/net/minecraft/world/entity/ai/behavior/GateBehavior.java
+@@ -57,7 +57,7 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
          if (this.hasRequiredMemories(entity)) {
              this.status = Behavior.Status.RUNNING;
              this.orderPolicy.apply(this.behaviors);
--            this.runningPolicy.apply(this.behaviors.stream(), world, entity, time);
-+            this.runningPolicy.apply(this.behaviors, world, entity, time); // Paper - Perf: Remove streams from hot code
+-            this.runningPolicy.apply(this.behaviors.stream(), level, entity, gameTime);
++            this.runningPolicy.apply(this.behaviors, level, entity, gameTime); // Paper - Perf: Remove streams from hot code
              return true;
          } else {
              return false;
-@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+@@ -66,10 +66,13 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
  
      @Override
-     public final void tickOrStop(ServerLevel world, E entity, long time) {
--        this.behaviors.stream().filter(task -> task.getStatus() == Behavior.Status.RUNNING).forEach(task -> task.tickOrStop(world, entity, time));
+     public final void tickOrStop(ServerLevel level, E entity, long gameTime) {
+-        this.behaviors
+-            .stream()
+-            .filter(behavior -> behavior.getStatus() == Behavior.Status.RUNNING)
+-            .forEach(behavior -> behavior.tickOrStop(level, entity, gameTime));
 +        // Paper start - Perf: Remove streams from hot code
-+        for (final BehaviorControl<? super E> task : this.behaviors) {
-+            if (task.getStatus() == Behavior.Status.RUNNING) {
-+                task.tickOrStop(world, entity, time);
++        for (final BehaviorControl<? super E> behavior : this.behaviors) {
++            if (behavior.getStatus() == Behavior.Status.RUNNING) {
++                behavior.tickOrStop(level, entity, gameTime);
 +            }
 +        }
 +        // Paper end - Perf: Remove streams from hot code
-         if (this.behaviors.stream().noneMatch(task -> task.getStatus() == Behavior.Status.RUNNING)) {
-             this.doStop(world, entity, time);
+         if (this.behaviors.stream().noneMatch(behavior -> behavior.getStatus() == Behavior.Status.RUNNING)) {
+             this.doStop(level, entity, gameTime);
          }
-@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+@@ -78,11 +81,16 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
      @Override
-     public final void doStop(ServerLevel world, E entity, long time) {
+     public final void doStop(ServerLevel level, E entity, long gameTime) {
          this.status = Behavior.Status.STOPPED;
--        this.behaviors.stream().filter(task -> task.getStatus() == Behavior.Status.RUNNING).forEach(task -> task.doStop(world, entity, time));
+-        this.behaviors
+-            .stream()
+-            .filter(behavior -> behavior.getStatus() == Behavior.Status.RUNNING)
+-            .forEach(behavior -> behavior.doStop(level, entity, gameTime));
 -        this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory);
 +        // Paper start - Perf: Remove streams from hot code
-+        for (final BehaviorControl<? super E> task : this.behaviors) {
-+            if (task.getStatus() == Behavior.Status.RUNNING) {
-+                task.doStop(world, entity, time);
++        for (final BehaviorControl<? super E> behavior : this.behaviors) {
++            if (behavior.getStatus() == Behavior.Status.RUNNING) {
++                behavior.doStop(level, entity, gameTime);
 +            }
 +        }
 +        for (final MemoryModuleType<?> exitErasedMemory : this.exitErasedMemories) {
@@ -53,17 +59,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
+@@ -116,20 +124,30 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
  
      public static enum RunningPolicy {
          RUN_ONE {
 +            // Paper start - Perf: Remove streams from hot code
              @Override
--            public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
--                tasks.filter(task -> task.getStatus() == Behavior.Status.STOPPED).filter(task -> task.tryStart(world, entity, time)).findFirst();
-+            public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
-+                for (final BehaviorControl<? super E> task : tasks) {
-+                    if (task.getStatus() == Behavior.Status.STOPPED && task.tryStart(world, entity, time)) {
+-            public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime) {
+-                behaviors.filter(behavior -> behavior.getStatus() == Behavior.Status.STOPPED)
+-                    .filter(behavior -> behavior.tryStart(level, owner, gameTime))
+-                    .findFirst();
++            public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime) {
++                for (final BehaviorControl<? super E> behavior : behaviors) {
++                    if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.tryStart(level, owner, gameTime)) {
 +                        break;
 +                    }
 +                }
@@ -73,28 +81,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          TRY_ALL {
 +            // Paper start - Perf: Remove streams from hot code
              @Override
--            public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
--                tasks.filter(task -> task.getStatus() == Behavior.Status.STOPPED).forEach(task -> task.tryStart(world, entity, time));
-+            public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
-+                for (final BehaviorControl<? super E> task : tasks) {
-+                    if (task.getStatus() == Behavior.Status.STOPPED) {
-+                        task.tryStart(world, entity, time);
+-            public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime) {
+-                behaviors.filter(behavior -> behavior.getStatus() == Behavior.Status.STOPPED).forEach(behavior -> behavior.tryStart(level, owner, gameTime));
++            public <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime) {
++                for (final BehaviorControl<? super E> behavior : behaviors) {
++                    if (behavior.getStatus() == Behavior.Status.STOPPED) {
++                        behavior.tryStart(level, owner, gameTime);
 +                    }
 +                }
 +                // Paper end - Perf: Remove streams from hot code
              }
          };
  
--        public abstract <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time);
-+        public abstract <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time); // Paper - Perf: Remove streams from hot code
+-        public abstract <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime);
++        public abstract <E extends LivingEntity> void apply(ShufflingList<BehaviorControl<? super E>> behaviors, ServerLevel level, E owner, long gameTime); // Paper - Perf: Remove streams from hot code
      }
  }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java
-@@ -0,0 +0,0 @@ public class GossipContainer {
-         return this.gossips.entrySet().stream().flatMap(entry -> entry.getValue().unpack(entry.getKey()));
+diff --git a/net/minecraft/world/entity/ai/gossip/GossipContainer.java b/net/minecraft/world/entity/ai/gossip/GossipContainer.java
+index 2c839dc80f451c83135828a97aced1a531004bab..b74a4ce1b629d440681a1f5c026997ccaf1d0373 100644
+--- a/net/minecraft/world/entity/ai/gossip/GossipContainer.java
++++ b/net/minecraft/world/entity/ai/gossip/GossipContainer.java
+@@ -59,8 +59,22 @@ public class GossipContainer {
+         return this.gossips.entrySet().stream().flatMap(gossip -> gossip.getValue().unpack(gossip.getKey()));
      }
  
 +    // Paper start - Perf: Remove streams from hot code
@@ -111,35 +119,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Perf: Remove streams from hot code
 +
-     private Collection<GossipContainer.GossipEntry> selectGossipsForTransfer(RandomSource random, int count) {
+     private Collection<GossipContainer.GossipEntry> selectGossipsForTransfer(RandomSource random, int amount) {
 -        List<GossipContainer.GossipEntry> list = this.unpack().toList();
 +        List<GossipContainer.GossipEntry> list = this.decompress(); // Paper - Perf: Remove streams from hot code
          if (list.isEmpty()) {
              return Collections.emptyList();
          } else {
-@@ -0,0 +0,0 @@ public class GossipContainer {
+@@ -145,7 +159,7 @@ public class GossipContainer {
  
      public <T> T store(DynamicOps<T> ops) {
          return GossipContainer.GossipEntry.LIST_CODEC
 -            .encodeStart(ops, this.unpack().toList())
 +            .encodeStart(ops, this.decompress()) // Paper - Perf: Remove streams from hot code
-             .resultOrPartial(error -> LOGGER.warn("Failed to serialize gossips: {}", error))
+             .resultOrPartial(errorMessage -> LOGGER.warn("Failed to serialize gossips: {}", errorMessage))
              .orElseGet(ops::emptyList);
      }
-@@ -0,0 +0,0 @@ public class GossipContainer {
+@@ -172,12 +186,23 @@ public class GossipContainer {
          final Object2IntMap<GossipType> entries = new Object2IntOpenHashMap<>();
  
-         public int weightedValue(Predicate<GossipType> gossipTypeFilter) {
+         public int weightedValue(Predicate<GossipType> gossipType) {
 -            return this.entries
 -                .object2IntEntrySet()
 -                .stream()
--                .filter(entry -> gossipTypeFilter.test(entry.getKey()))
--                .mapToInt(entry -> entry.getIntValue() * entry.getKey().weight)
+-                .filter(gossip -> gossipType.test(gossip.getKey()))
+-                .mapToInt(gossip -> gossip.getIntValue() * gossip.getKey().weight)
 -                .sum();
 +            // Paper start - Perf: Remove streams from hot code
 +            int weight = 0;
 +            for (Object2IntMap.Entry<GossipType> entry : entries.object2IntEntrySet()) {
-+                if (gossipTypeFilter.test(entry.getKey())) {
++                if (gossipType.test(entry.getKey())) {
 +                    weight += entry.getIntValue() * entry.getKey().weight;
 +                }
 +            }
@@ -155,29 +163,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // Paper end - Perf: Remove streams from hot code
          }
  
-         public Stream<GossipContainer.GossipEntry> unpack(UUID target) {
-diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
-@@ -0,0 +0,0 @@ public class NearestItemSensor extends Sensor<Mob> {
+         public Stream<GossipContainer.GossipEntry> unpack(UUID identifier) {
+diff --git a/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
+index 38873e56e95dc772b184e4271f7af1fb411ac9f8..09fd13e2d958da8326276c4dadf25bf488aff5ac 100644
+--- a/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
++++ b/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
+@@ -24,13 +24,17 @@ public class NearestItemSensor extends Sensor<Mob> {
      @Override
-     protected void doTick(ServerLevel world, Mob entity) {
+     protected void doTick(ServerLevel level, Mob entity) {
          Brain<?> brain = entity.getBrain();
--        List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> true);
-+        List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(world, itemEntity.getItem())); // Paper - Perf: Move predicate into getEntities
-         list.sort(Comparator.comparingDouble(entity::distanceToSqr));
--        Optional<ItemEntity> optional = list.stream()
--            .filter(itemEntity -> entity.wantsToPickUp(world, itemEntity.getItem()))
--            .filter(itemEntityx -> itemEntityx.closerThan(entity, 32.0))
+-        List<ItemEntity> entitiesOfClass = level.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> true);
++        List<ItemEntity> entitiesOfClass = level.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(level, itemEntity.getItem())); // Paper - Perf: Move predicate into getEntities
+         entitiesOfClass.sort(Comparator.comparingDouble(entity::distanceToSqr));
+-        Optional<ItemEntity> optional = entitiesOfClass.stream()
+-            .filter(itemEntity -> entity.wantsToPickUp(level, itemEntity.getItem()))
+-            .filter(itemEntity -> itemEntity.closerThan(entity, 32.0))
 -            .filter(entity::hasLineOfSight)
 -            .findFirst();
 -        brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional);
 +        // Paper start - Perf: remove streams from hot code
 +        ItemEntity nearest = null;
-+        for (ItemEntity entityItem : list) {
-+            if (entity.hasLineOfSight(entityItem)) { // Paper - Perf: Move predicate into getEntities
-+                nearest = entityItem;
++        for (final ItemEntity itemEntity : entitiesOfClass) {
++            if (entity.hasLineOfSight(itemEntity)) { // Paper - Perf: Move predicate into getEntities
++                nearest = itemEntity;
 +                break;
 +            }
 +        }
@@ -185,31 +193,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - Perf: remove streams from hot code
      }
  }
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java
-@@ -0,0 +0,0 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
-         int j = pos.getMinBlockZ();
-         ObjectList<Beardifier.Rigid> objectList = new ObjectArrayList<>(10);
-         ObjectList<JigsawJunction> objectList2 = new ObjectArrayList<>(32);
--        world.startsForStructure(pos, structure -> structure.terrainAdaptation() != TerrainAdjustment.NONE)
+diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java
+index 1a09da5aa1ae047a002d6779326c2a29e47d32b5..131923282c9ecbcb1d7f45a826da907c02bd2716 100644
+--- a/net/minecraft/world/level/levelgen/Beardifier.java
++++ b/net/minecraft/world/level/levelgen/Beardifier.java
+@@ -35,9 +35,10 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
+         int minBlockZ = chunkPos.getMinBlockZ();
+         ObjectList<Beardifier.Rigid> list = new ObjectArrayList<>(10);
+         ObjectList<JigsawJunction> list1 = new ObjectArrayList<>(32);
+-        structureManager.startsForStructure(chunkPos, structure -> structure.terrainAdaptation() != TerrainAdjustment.NONE)
 -            .forEach(
--                start -> {
+-                structureStart -> {
 +        // Paper start - Perf: Remove streams from hot code
-+        for (net.minecraft.world.level.levelgen.structure.StructureStart start : world.startsForStructure(pos, (structure) -> {
++        for (net.minecraft.world.level.levelgen.structure.StructureStart structureStart : structureManager.startsForStructure(chunkPos, structure -> {
 +            return structure.terrainAdaptation() != TerrainAdjustment.NONE;
 +        })) { // Paper end - Perf: Remove streams from hot code
-                     TerrainAdjustment terrainAdjustment = start.getStructure().terrainAdaptation();
+                     TerrainAdjustment terrainAdjustment = structureStart.getStructure().terrainAdaptation();
  
-                     for (StructurePiece structurePiece : start.getPieces()) {
-@@ -0,0 +0,0 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
+                     for (StructurePiece structurePiece : structureStart.getPieces()) {
+@@ -65,8 +66,7 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker {
                              }
                          }
                      }
 -                }
 -            );
 +        } // Paper - Perf: Remove streams from hot code
-         return new Beardifier(objectList.iterator(), objectList2.iterator());
+         return new Beardifier(list.iterator(), list1.iterator());
      }
  

From 4e2639900269de28877e9dc4327cfe91f2a029d3 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 11:05:41 +0100
Subject: [PATCH 213/285] Apply the updated patches

---
 .../patches/features/0017-Optimize-Hoppers.patch    | 13 ++++++-------
 .../0018-Optimize-Bit-Operations-by-inlining.patch  |  5 ++---
 .../0019-Remove-streams-from-hot-code.patch         |  0
 ...athfinder-Remove-Streams-Optimized-collect.patch |  1 -
 ...atch => 0021-Rewrite-dataconverter-system.patch} |  2 +-
 5 files changed, 9 insertions(+), 12 deletions(-)
 rename feature-patches/1048-Optimize-Hoppers.patch => paper-server/patches/features/0017-Optimize-Hoppers.patch (98%)
 rename feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch => paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch (98%)
 rename feature-patches/1051-Remove-streams-from-hot-code.patch => paper-server/patches/features/0019-Remove-streams-from-hot-code.patch (100%)
 rename feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch => paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch (99%)
 rename paper-server/patches/features/{0017-Rewrite-dataconverter-system.patch => 0021-Rewrite-dataconverter-system.patch} (99%)

diff --git a/feature-patches/1048-Optimize-Hoppers.patch b/paper-server/patches/features/0017-Optimize-Hoppers.patch
similarity index 98%
rename from feature-patches/1048-Optimize-Hoppers.patch
rename to paper-server/patches/features/0017-Optimize-Hoppers.patch
index a7a16b45a9..c501740535 100644
--- a/feature-patches/1048-Optimize-Hoppers.patch
+++ b/paper-server/patches/features/0017-Optimize-Hoppers.patch
@@ -12,7 +12,6 @@ Subject: [PATCH] Optimize Hoppers
 * Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried)
 * Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins)
 
-
 diff --git a/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdfe19136a2
@@ -49,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
 +    }
 +}
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..415ab7faf371507e278d0da5bf0ffa9972585876 100644
+index d450d4af96716caff4b29a84d1d83ec4010854f0..8657e4fd7c5e0e23b69d9a982408a7d038f0a787 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -1564,6 +1564,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1563,6 +1563,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          for (ServerLevel serverLevel : this.getAllLevels()) {
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
@@ -104,7 +103,7 @@ index 2ebdf1ad323bb53dfe9eed319e25856b35a1443c..77618757c0e678532dbab814aceed83f
          }
      }
 diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-index 60e1e44f328e66d52ebf08476b533fef83bc5eba..8139868201c2eaca29588b840a2bd85778f1b3d5 100644
+index 60e1e44f328e66d52ebf08476b533fef83bc5eba..eb02249b518c2d262315c4cd5965bec5d81166a1 100644
 --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
 @@ -139,18 +139,56 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
@@ -116,10 +115,10 @@ index 60e1e44f328e66d52ebf08476b533fef83bc5eba..8139868201c2eaca29588b840a2bd857
 +    private static final int HOPPER_HAS_ITEMS = 1;
 +    private static final int HOPPER_IS_FULL = 2;
 +
-+    private static int getFullState(final HopperBlockEntity hoper) {
-+        hoper.unpackLootTable(null);
++    private static int getFullState(final HopperBlockEntity hopper) {
++        hopper.unpackLootTable(null);
 +
-+        final List<ItemStack> hopperItems = hoper.items;
++        final List<ItemStack> hopperItems = hopper.items;
 +
 +        boolean empty = true;
 +        boolean full = true;
diff --git a/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch b/paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch
similarity index 98%
rename from feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch
rename to paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch
index a453424d45..2958bae139 100644
--- a/feature-patches/1050-Optimize-Bit-Operations-by-inlining.patch
+++ b/paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch
@@ -6,9 +6,8 @@ Subject: [PATCH] Optimize Bit Operations by inlining
 Inline bit operations and reduce instruction count to make these hot
 operations faster
 
-
 diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java
-index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..01ba7bc16e30440e35ce062bc21dae7e59bb6f8d 100644
+index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..0822a0faf7af9e746e7936ac17597b1f51e40a92 100644
 --- a/net/minecraft/core/BlockPos.java
 +++ b/net/minecraft/core/BlockPos.java
 @@ -51,15 +51,17 @@ public class BlockPos extends Vec3i {
@@ -85,7 +84,7 @@ index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..01ba7bc16e30440e35ce062bc21dae7e
  
      public static long getFlatIndex(long packedPos) {
 diff --git a/net/minecraft/core/SectionPos.java b/net/minecraft/core/SectionPos.java
-index 1780d8e14cea62971da75e4dcc80d1805434037b..737f853a264d720ce15f6bbffc206e3003fff380 100644
+index 1780d8e14cea62971da75e4dcc80d1805434037b..ce8c394ea1a7bc5bf5c568c82e6158b19df517d8 100644
 --- a/net/minecraft/core/SectionPos.java
 +++ b/net/minecraft/core/SectionPos.java
 @@ -38,7 +38,7 @@ public class SectionPos extends Vec3i {
diff --git a/feature-patches/1051-Remove-streams-from-hot-code.patch b/paper-server/patches/features/0019-Remove-streams-from-hot-code.patch
similarity index 100%
rename from feature-patches/1051-Remove-streams-from-hot-code.patch
rename to paper-server/patches/features/0019-Remove-streams-from-hot-code.patch
diff --git a/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
similarity index 99%
rename from feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
rename to paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
index 5fd0a6de02..84bcbe2f2e 100644
--- a/feature-patches/1052-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
+++ b/paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
@@ -15,7 +15,6 @@ Optimize collection by creating a list instead of a set of the key and value.
 This lets us get faster foreach iteration, as well as avoids map lookups on
 the values when needed.
 
-
 diff --git a/net/minecraft/world/level/pathfinder/PathFinder.java b/net/minecraft/world/level/pathfinder/PathFinder.java
 index a6ef296f7af1f784e1f0772947a6cb3519a3bc2a..81de6c1bbef1cafd3036e736dd305fbedc8368c6 100644
 --- a/net/minecraft/world/level/pathfinder/PathFinder.java
diff --git a/paper-server/patches/features/0017-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0021-Rewrite-dataconverter-system.patch
similarity index 99%
rename from paper-server/patches/features/0017-Rewrite-dataconverter-system.patch
rename to paper-server/patches/features/0021-Rewrite-dataconverter-system.patch
index 844c0f9320..63941b2edb 100644
--- a/paper-server/patches/features/0017-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0021-Rewrite-dataconverter-system.patch
@@ -30621,7 +30621,7 @@ index 1110ca4075a1bbaa46b66686435dab91b275c945..c2218630c3074c8b3f82364e37503b12
          return structureTemplate.save(new CompoundTag());
      }
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index d450d4af96716caff4b29a84d1d83ec4010854f0..646c2f2b617ed706021c83c9fc4492860dfdd4e9 100644
+index 8657e4fd7c5e0e23b69d9a982408a7d038f0a787..415ab7faf371507e278d0da5bf0ffa9972585876 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

From 05c0d4a6e2acfc1558f6af6384625c4938fd8554 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 11:11:41 +0100
Subject: [PATCH 214/285] Unapply hopper patch for now Apparently depends on
 moonrise change

---
 .../2017-Optimize-Hoppers.patch                                 | 0
 ...ing.patch => 0017-Optimize-Bit-Operations-by-inlining.patch} | 0
 ...m-hot-code.patch => 0018-Remove-streams-from-hot-code.patch} | 0
 ...-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch} | 0
 ...ter-system.patch => 0020-Rewrite-dataconverter-system.patch} | 2 +-
 5 files changed, 1 insertion(+), 1 deletion(-)
 rename paper-server/patches/features/0017-Optimize-Hoppers.patch => feature-patches/2017-Optimize-Hoppers.patch (100%)
 rename paper-server/patches/features/{0018-Optimize-Bit-Operations-by-inlining.patch => 0017-Optimize-Bit-Operations-by-inlining.patch} (100%)
 rename paper-server/patches/features/{0019-Remove-streams-from-hot-code.patch => 0018-Remove-streams-from-hot-code.patch} (100%)
 rename paper-server/patches/features/{0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch => 0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch} (100%)
 rename paper-server/patches/features/{0021-Rewrite-dataconverter-system.patch => 0020-Rewrite-dataconverter-system.patch} (99%)

diff --git a/paper-server/patches/features/0017-Optimize-Hoppers.patch b/feature-patches/2017-Optimize-Hoppers.patch
similarity index 100%
rename from paper-server/patches/features/0017-Optimize-Hoppers.patch
rename to feature-patches/2017-Optimize-Hoppers.patch
diff --git a/paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch b/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
similarity index 100%
rename from paper-server/patches/features/0018-Optimize-Bit-Operations-by-inlining.patch
rename to paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
diff --git a/paper-server/patches/features/0019-Remove-streams-from-hot-code.patch b/paper-server/patches/features/0018-Remove-streams-from-hot-code.patch
similarity index 100%
rename from paper-server/patches/features/0019-Remove-streams-from-hot-code.patch
rename to paper-server/patches/features/0018-Remove-streams-from-hot-code.patch
diff --git a/paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/paper-server/patches/features/0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
similarity index 100%
rename from paper-server/patches/features/0020-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
rename to paper-server/patches/features/0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
diff --git a/paper-server/patches/features/0021-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
similarity index 99%
rename from paper-server/patches/features/0021-Rewrite-dataconverter-system.patch
rename to paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
index 63941b2edb..844c0f9320 100644
--- a/paper-server/patches/features/0021-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
@@ -30621,7 +30621,7 @@ index 1110ca4075a1bbaa46b66686435dab91b275c945..c2218630c3074c8b3f82364e37503b12
          return structureTemplate.save(new CompoundTag());
      }
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 8657e4fd7c5e0e23b69d9a982408a7d038f0a787..415ab7faf371507e278d0da5bf0ffa9972585876 100644
+index d450d4af96716caff4b29a84d1d83ec4010854f0..646c2f2b617ed706021c83c9fc4492860dfdd4e9 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa

From ac69f75d23618d914c0d705b578ac47dbac2bff0 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 17 Dec 2024 11:45:39 +0100
Subject: [PATCH 215/285] Apply Improve-performance-of-mass-crafts directly

---
 ...5-Improve-performance-of-mass-crafts.patch | 92 -------------------
 .../inventory/CraftingContainer.java.patch    |  4 +-
 .../world/inventory/CraftingMenu.java.patch   | 27 +++++-
 .../world/inventory/ResultSlot.java.patch     | 11 +++
 .../TransientCraftingContainer.java.patch     |  6 +-
 5 files changed, 42 insertions(+), 98 deletions(-)
 delete mode 100644 feature-patches/1065-Improve-performance-of-mass-crafts.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch

diff --git a/feature-patches/1065-Improve-performance-of-mass-crafts.patch b/feature-patches/1065-Improve-performance-of-mass-crafts.patch
deleted file mode 100644
index c09f2cf750..0000000000
--- a/feature-patches/1065-Improve-performance-of-mass-crafts.patch
+++ /dev/null
@@ -1,92 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <jake.m.potrebic@gmail.com>
-Date: Sun, 13 Aug 2023 15:41:52 -0700
-Subject: [PATCH] Improve performance of mass crafts
-
-When the server crafts all available items in CraftingMenu or InventoryMenu the game
-checks either 4 or 9 times for each individual craft for a matching recipe for that container.
-This check can be expensive if 64 total crafts are being performed with the recipe matching logic
-being run 64 * 9 + 64 times. A breakdown of those times is below. This patch caches the last matching
-recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run.
-
-Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft
-where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the
-'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done
-for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed
-from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So
-for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items.
-
-After this patch, the full iteration over all recipes checking for a match should run once for a full craft to find the
-initial recipe match. Then that recipe will be checked first for all future recipe match checks.
-
-diff --git a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-+++ b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-@@ -0,0 +0,0 @@ public interface CraftingContainer extends Container, StackedContentsCompatible
-     List<ItemStack> getItems();
- 
-     // CraftBukkit start
--    default RecipeHolder<?> getCurrentRecipe() {
-+    default RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
-         return null;
-     }
- 
--    default void setCurrentRecipe(RecipeHolder<?> recipe) {
-+    default void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> recipe) { // Paper - use correct generic
-     }
-     // CraftBukkit end
- 
-diff --git a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-+++ b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-@@ -0,0 +0,0 @@ public class CraftingMenu extends AbstractCraftingMenu {
-         CraftingInput craftinginput = craftingInventory.asCraftInput();
-         ServerPlayer entityplayer = (ServerPlayer) player;
-         ItemStack itemstack = ItemStack.EMPTY;
-+        if (recipe == null) recipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first
-         Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe);
-         craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit
- 
-diff --git a/src/main/java/net/minecraft/world/inventory/ResultSlot.java b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-+++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-@@ -0,0 +0,0 @@ public class ResultSlot extends Slot {
-     private NonNullList<ItemStack> getRemainingItems(CraftingInput input, Level world) {
-         return world instanceof ServerLevel serverLevel
-             ? serverLevel.recipeAccess()
--                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel)
-+                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel, this.craftSlots.getCurrentRecipe()) // Paper - Perf: Improve mass crafting; check last recipe used first
-                 .map(recipe -> recipe.value().getRemainingItems(input))
-                 .orElseGet(() -> copyAllInputItems(input))
-             : CraftingRecipe.defaultCraftingReminder(input);
-diff --git a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-+++ b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-@@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer {
- 
-     // CraftBukkit start - add fields
-     public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
--    private RecipeHolder<?> currentRecipe;
-+    private RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe; // Paper - use correct generic
-     public Container resultInventory;
-     private Player owner;
-     private int maxStack = MAX_STACK;
-@@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer {
-     }
- 
-     @Override
--    public RecipeHolder<?> getCurrentRecipe() {
-+    public RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
-         return this.currentRecipe;
-     }
- 
-     @Override
--    public void setCurrentRecipe(RecipeHolder<?> currentRecipe) {
-+    public void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe) { // Paper - use correct generic
-         this.currentRecipe = currentRecipe;
-     }
- 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
index 2225d1d404..2aef7bff97 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
@@ -5,11 +5,11 @@
      List<ItemStack> getItems();
  
 +    // CraftBukkit start
-+    default net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++    default net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() {
 +        return null;
 +    }
 +
-+    default void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> recipe) {
++    default void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> recipe) {
 +    }
 +    // CraftBukkit end
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
index f490a6a0d3..d04a6f281e 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -18,9 +18,34 @@
          this.access = access;
          this.player = playerInventory.player;
          this.addResultSlot(this.player, 124, 35);
-@@ -56,6 +_,7 @@
+@@ -55,7 +_,32 @@
+         CraftingInput craftInput = craftSlots.asCraftInput();
          ServerPlayer serverPlayer = (ServerPlayer)player;
          ItemStack itemStack = ItemStack.EMPTY;
++        // Paper start - Perf: Improve mass crafting; check last recipe used first
++        /*
++        When the server crafts all available items in CraftingMenu or InventoryMenu the game
++        checks either 4 or 9 times for each individual craft for a matching recipe for that container.
++        This check can be expensive if 64 total crafts are being performed with the recipe matching logic
++        being run 64 * 9 + 64 times. A breakdown of those times is below. This caches the last matching
++        recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run.
++
++        Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft
++        where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the
++        'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done
++        for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed
++        from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So
++        for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items.
++
++        After this change, the full iteration over all recipes checking for a match should run once for a full craft to find the
++        initial recipe match. Then that recipe will be checked first for all future recipe match checks.
++
++        See also: ResultSlot class
++         */
++        if (recipe == null) {
++            recipe = craftSlots.getCurrentRecipe();
++        }
++        // Paper end - Perf: Improve mass crafting; check last recipe used first
          Optional<RecipeHolder<CraftingRecipe>> recipeFor = level.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftInput, level, recipe);
 +        craftSlots.setCurrentRecipe(recipeFor.orElse(null)); // CraftBukkit
          if (recipeFor.isPresent()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch
new file mode 100644
index 0000000000..c8c1e2c156
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/inventory/ResultSlot.java
++++ b/net/minecraft/world/inventory/ResultSlot.java
+@@ -72,7 +_,7 @@
+     private NonNullList<ItemStack> getRemainingItems(CraftingInput input, Level level) {
+         return level instanceof ServerLevel serverLevel
+             ? serverLevel.recipeAccess()
+-                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel)
++                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel, this.craftSlots.getCurrentRecipe()) // Paper - Perf: Improve mass crafting; check last recipe used first
+                 .map(recipe -> recipe.value().getRemainingItems(input))
+                 .orElseGet(() -> copyAllInputItems(input))
+             : CraftingRecipe.defaultCraftingReminder(input);
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
index 898f5850c4..85f7ca8636 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -6,7 +6,7 @@
  
 +    // CraftBukkit start - add fields
 +    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
-+    private net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe;
++    private net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe;
 +    public net.minecraft.world.Container resultInventory;
 +    private Player owner;
 +    private int maxStack = MAX_STACK;
@@ -51,12 +51,12 @@
 +    }
 +
 +    @Override
-+    public net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++    public net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() {
 +        return this.currentRecipe;
 +    }
 +
 +    @Override
-+    public void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe) {
++    public void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe) {
 +        this.currentRecipe = currentRecipe;
 +    }
 +

From d37b5601d212fbff17342760813ee78e9ee761a9 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 12:12:59 +0100
Subject: [PATCH 216/285] Update redstone optimization and lag compensation
 patches

---
 ...er-desync-when-new-players-are-added.patch |  74 +++----
 .../1069-Lag-compensation-ticks.patch         | 115 ++++++-----
 ...3-Eigencraft-redstone-implementation.patch | 109 +++++-----
 ...nate-Current-redstone-implementation.patch | 189 +++++++++---------
 4 files changed, 237 insertions(+), 250 deletions(-)

diff --git a/feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch b/feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch
index e80eb74101..d7fc36933f 100644
--- a/feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch
+++ b/feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch
@@ -28,30 +28,30 @@ which is most likely in an unloaded chunk - which means that the
 client will not tick the entity and thus not lerp the entity
 from its old position to its new position.
 
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
-@@ -0,0 +0,0 @@ public class ClientboundAddEntityPacket implements Packet<ClientGamePacketListen
+diff --git a/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java b/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
+index db31989ebe3d7021cfd2311439e9a00f819b0841..1373977b339405ef59bb3ea03d195285c96dd3fe 100644
+--- a/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.java
+@@ -42,9 +42,11 @@ public class ClientboundAddEntityPacket implements Packet<ClientGamePacketListen
          this(
              entity.getId(),
              entity.getUUID(),
--            entityTrackerEntry.getPositionBase().x(),
--            entityTrackerEntry.getPositionBase().y(),
--            entityTrackerEntry.getPositionBase().z(),
+-            serverEntity.getPositionBase().x(),
+-            serverEntity.getPositionBase().y(),
+-            serverEntity.getPositionBase().z(),
 +            // Paper start - fix entity tracker desync
 +            entity.trackingPosition().x(),
 +            entity.trackingPosition().y(),
 +            entity.trackingPosition().z(),
 +            // Paper end - fix entity tracker desync
-             entityTrackerEntry.getLastSentXRot(),
-             entityTrackerEntry.getLastSentYRot(),
+             serverEntity.getLastSentXRot(),
+             serverEntity.getLastSentYRot(),
              entity.getType(),
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
+index f8f145cd9614f7e38d6aa72501fe31837340a8bb..fa48e63fea42f387dcd154c08af54cfb0d3b08af 100644
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -1419,6 +1419,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                          this.serverEntity.addPairing(player);
                          }
                          // Paper end - entity tracking events
@@ -59,11 +59,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      }
                  } else if (this.seenBy.remove(player.connection)) {
                      this.serverEntity.removePairing(player);
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -0,0 +0,0 @@ public class ServerEntity {
+diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
+index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..66f7926a2639ade41cb89419e38e12053314c982 100644
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
+@@ -90,6 +90,13 @@ public class ServerEntity {
          this.trackedDataValues = entity.getEntityData().getNonDefaultValues();
      }
  
@@ -75,31 +75,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end - fix desync when a player is added to the tracker
 +
      public void sendChanges() {
-         // Paper start - optimise collisions
-         if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
-@@ -0,0 +0,0 @@ public class ServerEntity {
-             }
+         List<Entity> passengers = this.entity.getPassengers();
+         if (!passengers.equals(this.lastPassengers)) {
+@@ -125,7 +132,7 @@ public class ServerEntity {
+             this.sendDirtyEntityData();
          }
  
 -        if (this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) {
 +        if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker
-             byte b0 = Mth.packDegrees(this.entity.getYRot());
+             byte b = Mth.packDegrees(this.entity.getYRot());
              byte b1 = Mth.packDegrees(this.entity.getXRot());
-             boolean flag = Math.abs(b0 - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
-@@ -0,0 +0,0 @@ public class ServerEntity {
-                     long k = this.positionCodec.encodeZ(vec3d);
-                     boolean flag5 = i < -32768L || i > 32767L || j < -32768L || j > 32767L || k < -32768L || k > 32767L;
- 
--                    if (!flag5 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) {
-+                    if (!this.forceStateResync && !flag5 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) { // Paper - fix desync when a player is added to the tracker
-                         if ((!flag2 || !flag) && !(this.entity instanceof AbstractArrow)) {
-                             if (flag2) {
-                                 packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) i), (short) ((int) j), (short) ((int) k), this.entity.onGround());
-@@ -0,0 +0,0 @@ public class ServerEntity {
+             boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
+@@ -160,7 +167,7 @@ public class ServerEntity {
+                 long l1 = this.positionCodec.encodeY(vec3);
+                 long l2 = this.positionCodec.encodeZ(vec3);
+                 boolean flag5 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
+-                if (flag5 || this.teleportDelay > 400 || this.wasRiding || this.wasOnGround != this.entity.onGround()) {
++                if (this.forceStateResync || flag5 || this.teleportDelay > 400 || this.wasRiding || this.wasOnGround != this.entity.onGround()) { // Paper - fix desync when a player is added to the tracker
+                     this.wasOnGround = this.entity.onGround();
+                     this.teleportDelay = 0;
+                     packet = ClientboundEntityPositionSyncPacket.of(this.entity);
+@@ -225,6 +232,7 @@ public class ServerEntity {
              }
  
              this.entity.hasImpulse = false;
 +            this.forceStateResync = false; // Paper - fix desync when a player is added to the tracker
          }
  
-         ++this.tickCount;
+         this.tickCount++;
diff --git a/feature-patches/1069-Lag-compensation-ticks.patch b/feature-patches/1069-Lag-compensation-ticks.patch
index a2f30eb99d..1d692f7144 100644
--- a/feature-patches/1069-Lag-compensation-ticks.patch
+++ b/feature-patches/1069-Lag-compensation-ticks.patch
@@ -7,66 +7,65 @@ Areas affected by lag comepnsation:
  - Block breaking and destroying
  - Eating food items
 
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
-     public volatile Thread shutdownThread; // Paper
-     public volatile boolean abnormalExit = false; // Paper
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..8aa9ae2925ad44d57a27be3e520fcf20e30237d6 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -299,6 +299,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+     public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
+     public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
+     private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
 +    public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
  
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
+     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
-             worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
-             net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
-+            worldserver.updateLagCompensationTick(); // Paper - lag compensation
- 
-             gameprofilerfiller.push(() -> {
-                 String s = String.valueOf(worldserver);
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         );
+@@ -1564,6 +1565,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         for (ServerLevel serverLevel : this.getAllLevels()) {
+             serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
+             serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
++            serverLevel.updateLagCompensationTick(); // Paper - lag compensation
+             profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
+             /* Drop global time updates
+             if (this.tickCount % 20 == 0) {
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index ca9427a7eae9a66f4f1ccedda7b1def7ac2a88da..efc18884358907661d1226409f11d19a394073b3 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -2362,4 +2362,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         return this.server.getPlayerList().getPlayer(uuid);
      }
-     // Paper end - chunk tick iteration
+     // Paper end - check global player list where appropriate
++
 +    // Paper start - lag compensation
-+    private long lagCompensationTick = net.minecraft.server.MinecraftServer.SERVER_INIT;
++    private long lagCompensationTick = MinecraftServer.SERVER_INIT;
 +
 +    public long getLagCompensationTick() {
 +        return this.lagCompensationTick;
 +    }
 +
 +    public void updateLagCompensationTick() {
-+        this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
++        this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
 +    }
 +    // Paper end - lag compensation
- 
-     // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
-     public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -0,0 +0,0 @@ public class ServerPlayerGameMode {
+ }
+diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
+index 6176f0738aa1a18df5d7d4d49fd6961e3f2eb736..d6da40d7188a55a9b2eeedb540c8e275359342e4 100644
+--- a/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -111,7 +111,7 @@ public class ServerPlayerGameMode {
      }
  
      public void tick() {
--        this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
-+        this.gameTicks = (int)this.level.getLagCompensationTick(); // CraftBukkit; // Paper - lag compensation
-         BlockState iblockdata;
- 
+-        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit;
++        this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensation
          if (this.hasDelayedDestroy) {
-diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
-+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
+             BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
+             if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
+diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
+index c83aeaf4e50dd7290c608dfe260a3bd2404b6004..8439e1593c9973243383dc7091c587f7e72eb9e0 100644
+--- a/net/minecraft/world/entity/LivingEntity.java
++++ b/net/minecraft/world/entity/LivingEntity.java
+@@ -3831,6 +3831,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
          this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
      }
      // Paper end - Properly cancel usable items
@@ -77,33 +76,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private void updatingUsingItem() {
          if (this.isUsingItem()) {
              if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3844,7 +3848,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
  
-     protected void updateUsingItem(ItemStack stack) {
-         stack.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
--        if (--this.useItemRemaining == 0 && !this.level().isClientSide && !stack.useOnRelease()) {
+     protected void updateUsingItem(ItemStack usingItem) {
+         usingItem.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
+-        if (--this.useItemRemaining == 0 && !this.level().isClientSide && !usingItem.useOnRelease()) {
 +        // Paper start - lag compensate eating
 +        // we add 1 to the expected time to avoid lag compensating when we should not
 +        final boolean shouldLagCompensate = this.useItem.has(DataComponents.FOOD) && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1L + this.totalEatTimeTicks) * 50L * (1000L * 1000L));
-+        if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !stack.useOnRelease()) {
++        if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !usingItem.useOnRelease()) {
 +            this.useItemRemaining = 0;
 +            // Paper end - lag compensate eating
              this.completeUsingItem();
          }
- 
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
- 
-         if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
-             this.useItem = itemstack;
--            this.useItemRemaining = itemstack.getUseDuration(this);
+     }
+@@ -3878,7 +3887,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
+         ItemStack itemInHand = this.getItemInHand(hand);
+         if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
+             this.useItem = itemInHand;
+-            this.useItemRemaining = itemInHand.getUseDuration(this);
 +            // Paper start - lag compensate eating
-+            this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(this);
++            this.useItemRemaining = this.totalEatTimeTicks = itemInHand.getUseDuration(this);
 +            this.eatStartTime = System.nanoTime();
 +            // Paper end - lag compensate eating
              if (!this.level().isClientSide) {
                  this.setLivingEntityFlag(1, true);
                  this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3902,7 +3914,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  }
              } else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
                  this.useItem = ItemStack.EMPTY;
@@ -114,8 +113,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                // Paper end - lag compensate eating
              }
          }
- 
-@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity implements Attackable {
+     }
+@@ -4026,7 +4041,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
          }
  
          this.useItem = ItemStack.EMPTY;
diff --git a/feature-patches/1073-Eigencraft-redstone-implementation.patch b/feature-patches/1073-Eigencraft-redstone-implementation.patch
index 87c9d93cc3..9b2677a63c 100644
--- a/feature-patches/1073-Eigencraft-redstone-implementation.patch
+++ b/feature-patches/1073-Eigencraft-redstone-implementation.patch
@@ -3,8 +3,6 @@ From: theosib <millerti@172.16.221.1>
 Date: Thu, 27 Sep 2018 01:43:35 -0600
 Subject: [PATCH] Eigencraft redstone implementation
 
-Author: theosib <millerti@172.16.221.1>
-
 Original license: MIT
 
 This patch implements theosib's redstone algorithms to completely overhaul the way redstone works.
@@ -17,19 +15,15 @@ A lot of this code is self-contained in a helper class.
 Aside from making the obvious class/function renames and obfhelpers I didn't need to modify much.
 Just added Bukkit's event system and took a few liberties with dead code and comment misspellings.
 
-== AT ==
-public net.minecraft.world.level.block.RedStoneWireBlock shouldSignal
-public net.minecraft.world.level.block.RedStoneWireBlock canSurvive(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z
-
 Co-authored-by: egg82 <eggys82@gmail.com>
 
-diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
+diff --git a/io/papermc/paper/redstone/RedstoneWireTurbo.java b/io/papermc/paper/redstone/RedstoneWireTurbo.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ff747a1ecdf3c888bca0d69de4f85dcd810b6139
 --- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.util;
++++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java
+@@ -0,0 +1,954 @@
++package io.papermc.paper.redstone;
 +
 +import java.util.List;
 +import java.util.Map;
@@ -48,14 +42,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import com.google.common.collect.Lists;
 +import com.google.common.collect.Maps;
 +
-+/**
-+ * Used for the faster redstone algorithm.
-+ * Original author: theosib
-+ * Original license: MIT
-+ *
-+ * Ported to Paper and updated to 1.13 by egg82
-+ */
-+public class RedstoneWireTurbo {
++public final class RedstoneWireTurbo {
 +    /*
 +     * This is Helper class for BlockRedstoneWire.  It implements a minimally-invasive
 +     * bolt-on accelerator that performs a breadth-first search through redstone wire blocks
@@ -862,7 +849,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     */
 +    private BlockState calculateCurrentChanges(final Level worldIn, final UpdateNode upd) {
 +        BlockState state = upd.currentState;
-+        final int i = state.getValue(RedStoneWireBlock.POWER).intValue();
++        final int i = state.getValue(RedStoneWireBlock.POWER);
 +        int j = 0;
 +        j = getMaxCurrentStrength(upd, j);
 +        int l = 0;
@@ -986,21 +973,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     */
 +    private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) {
 +        if (upd.type != UpdateNode.Type.REDSTONE) return strength;
-+        final int i = upd.currentState.getValue(RedStoneWireBlock.POWER).intValue();
++        final int i = upd.currentState.getValue(RedStoneWireBlock.POWER);
 +        return i > strength ? i : strength;
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
-         return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER);
+diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
+index 84e6c986917128d4488afa23d29c689cadb4f55d..f02232ce97779db0d12a5d5da1d767326d78ea4c 100644
+--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
+@@ -290,6 +290,60 @@ public class RedStoneWireBlock extends Block {
+         return state.isFaceSturdy(level, pos, Direction.UP) || state.is(Blocks.HOPPER);
      }
  
 +    // Paper start - Optimize redstone
 +    // The bulk of the new functionality is found in RedstoneWireTurbo.java
-+    com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this);
++    io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this);
 +
 +    /*
 +     * Modified version of pre-existing updateSurroundingRedstone, which is called from
@@ -1052,46 +1039,46 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end
 +
-     private void updatePowerStrength(Level world, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean blockAdded) {
-         if (useExperimentalEvaluator(world)) {
-             new ExperimentalRedstoneWireEvaluator(this).updatePowerStrength(world, pos, state, orientation, blockAdded);
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
+     private void updatePowerStrength(Level level, BlockPos pos, BlockState state, @Nullable Orientation orientation, boolean updateShape) {
+         if (useExperimentalEvaluator(level)) {
+             new ExperimentalRedstoneWireEvaluator(this).updatePowerStrength(level, pos, state, orientation, updateShape);
+@@ -318,7 +372,7 @@ public class RedStoneWireBlock extends Block {
      @Override
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
-         if (!oldState.is(state.getBlock()) && !world.isClientSide) {
--            this.updatePowerStrength(world, pos, state, null, true);
-+            this.updateSurroundingRedstone(world, pos, state, null, true); // Paper - Optimize redstone
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+         if (!oldState.is(state.getBlock()) && !level.isClientSide) {
+-            this.updatePowerStrength(level, pos, state, null, true);
++            this.updateSurroundingRedstone(level, pos, state, null, true); // Paper - Optimize redstone
  
              for (Direction direction : Direction.Plane.VERTICAL) {
-                 world.updateNeighborsAt(pos.relative(direction), this);
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
-                     world.updateNeighborsAt(pos.relative(direction), this);
+                 level.updateNeighborsAt(pos.relative(direction), this);
+@@ -337,7 +391,7 @@ public class RedStoneWireBlock extends Block {
+                     level.updateNeighborsAt(pos.relative(direction), this);
                  }
  
--                this.updatePowerStrength(world, pos, state, null, false);
-+                this.updateSurroundingRedstone(world, pos, state, null, false); // Paper - Optimize redstone
-                 this.updateNeighborsOfNeighboringWires(world, pos);
+-                this.updatePowerStrength(level, pos, state, null, false);
++                this.updateSurroundingRedstone(level, pos, state, null, false); // Paper - Optimize redstone
+                 this.updateNeighborsOfNeighboringWires(level, pos);
              }
          }
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
-         if (!world.isClientSide) {
-             if (sourceBlock != this || !useExperimentalEvaluator(world)) {
-                 if (state.canSurvive(world, pos)) {
--                    this.updatePowerStrength(world, pos, state, wireOrientation, false);
-+                    this.updateSurroundingRedstone(world, pos, state, wireOrientation, false); // Paper - Optimize redstone
+@@ -363,7 +417,7 @@ public class RedStoneWireBlock extends Block {
+         if (!level.isClientSide) {
+             if (neighborBlock != this || !useExperimentalEvaluator(level)) {
+                 if (state.canSurvive(level, pos)) {
+-                    this.updatePowerStrength(level, pos, state, orientation, false);
++                    this.updateSurroundingRedstone(level, pos, state, orientation, false); // Paper - Optimize redstone
                  } else {
-                     dropResources(state, world, pos);
-                     world.removeBlock(pos, false);
-diff --git a/src/main/java/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java b/src/main/java/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
-+++ b/src/main/java/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
-@@ -0,0 +0,0 @@ public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator {
- 
+                     dropResources(state, level, pos);
+                     level.removeBlock(pos, false);
+diff --git a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
+index 380fc51a252022195e178daccd8aa53dd1d71a2e..2d77780b6727f82ffc3cb216ca5f2d6483496cfd 100644
+--- a/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
++++ b/net/minecraft/world/level/redstone/DefaultRedstoneWireEvaluator.java
+@@ -44,7 +44,7 @@ public class DefaultRedstoneWireEvaluator extends RedstoneWireEvaluator {
+         }
      }
  
--    private int calculateTargetStrength(Level world, BlockPos pos) {
-+    public int calculateTargetStrength(Level world, BlockPos pos) { // Paper - Optimize redstone
-         int i = this.getBlockSignal(world, pos);
- 
-         return i == 15 ? i : Math.max(i, this.getIncomingWireSignal(world, pos));
+-    private int calculateTargetStrength(Level level, BlockPos pos) {
++    public int calculateTargetStrength(Level level, BlockPos pos) { // Paper - Optimize redstone
+         int blockSignal = this.getBlockSignal(level, pos);
+         return blockSignal == 15 ? blockSignal : Math.max(blockSignal, this.getIncomingWireSignal(level, pos));
+     }
diff --git a/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch b/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch
index 50c8d18711..30c303e0c2 100644
--- a/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch
+++ b/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch
@@ -20,12 +20,12 @@ Alternate Current needs the following modifications:
 * RedStoneWireBlock: Replace calls to vanilla's or Eigencraft's methods for handling power changes with calls to
 Alternate Current's wire handler.
 
-diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java
+diff --git a/alternate/current/wire/LevelHelper.java b/alternate/current/wire/LevelHelper.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..eda108e2df9bf7d1ddd89287b8d2c2d7f1637c96
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/LevelHelper.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/LevelHelper.java
+@@ -0,0 +1,66 @@
 +package alternate.current.wire;
 +
 +import org.bukkit.craftbukkit.block.CraftBlock;
@@ -92,12 +92,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return true;
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/Node.java b/src/main/java/alternate/current/wire/Node.java
+diff --git a/alternate/current/wire/Node.java b/alternate/current/wire/Node.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8af6c69098e64945361d116b5fd6ac21e97fcd8d
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/Node.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/Node.java
+@@ -0,0 +1,113 @@
 +package alternate.current.wire;
 +
 +import java.util.Arrays;
@@ -211,12 +211,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        throw new UnsupportedOperationException("Not a WireNode!");
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/PriorityQueue.java b/src/main/java/alternate/current/wire/PriorityQueue.java
+diff --git a/alternate/current/wire/PriorityQueue.java b/alternate/current/wire/PriorityQueue.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..d71b4d0e4c44a2620b41b89475412db53bea20ed
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/PriorityQueue.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/PriorityQueue.java
+@@ -0,0 +1,211 @@
 +package alternate.current.wire;
 +
 +import java.util.AbstractQueue;
@@ -428,12 +428,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return prev;
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/SimpleQueue.java b/src/main/java/alternate/current/wire/SimpleQueue.java
+diff --git a/alternate/current/wire/SimpleQueue.java b/alternate/current/wire/SimpleQueue.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2b30074252551e1dc55d5be17d26fb4a2d8eb2e4
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/SimpleQueue.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/SimpleQueue.java
+@@ -0,0 +1,112 @@
 +package alternate.current.wire;
 +
 +import java.util.AbstractQueue;
@@ -546,12 +546,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/UpdateOrder.java b/src/main/java/alternate/current/wire/UpdateOrder.java
+diff --git a/alternate/current/wire/UpdateOrder.java b/alternate/current/wire/UpdateOrder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..29338efd16cf62bb49e81cce09fbafd9b4319e7c
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/UpdateOrder.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/UpdateOrder.java
+@@ -0,0 +1,390 @@
 +package alternate.current.wire;
 +
 +import java.util.Locale;
@@ -942,12 +942,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public abstract void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer<Node> action);
 +
 +}
-diff --git a/src/main/java/alternate/current/wire/WireConnection.java b/src/main/java/alternate/current/wire/WireConnection.java
+diff --git a/alternate/current/wire/WireConnection.java b/alternate/current/wire/WireConnection.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4fd8cb29024330397cfe4cbc1f237d285bfb7b3e
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/WireConnection.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/WireConnection.java
+@@ -0,0 +1,30 @@
 +package alternate.current.wire;
 +
 +/**
@@ -978,12 +978,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.accept = accept;
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/WireConnectionManager.java b/src/main/java/alternate/current/wire/WireConnectionManager.java
+diff --git a/alternate/current/wire/WireConnectionManager.java b/alternate/current/wire/WireConnectionManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f03b313e58385d626490a9e64c9616fd08aa951e
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/WireConnectionManager.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/WireConnectionManager.java
+@@ -0,0 +1,134 @@
 +package alternate.current.wire;
 +
 +import java.util.Arrays;
@@ -1118,12 +1118,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java
+diff --git a/alternate/current/wire/WireHandler.java b/alternate/current/wire/WireHandler.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..259b301b2c8b64cb7974a235afb260e0e991af54
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/WireHandler.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/WireHandler.java
+@@ -0,0 +1,1073 @@
 +package alternate.current.wire;
 +
 +import java.util.Iterator;
@@ -2197,12 +2197,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    }
 +}
-diff --git a/src/main/java/alternate/current/wire/WireNode.java b/src/main/java/alternate/current/wire/WireNode.java
+diff --git a/alternate/current/wire/WireNode.java b/alternate/current/wire/WireNode.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9f5689bdb
 --- /dev/null
-+++ b/src/main/java/alternate/current/wire/WireNode.java
-@@ -0,0 +0,0 @@
++++ b/alternate/current/wire/WireNode.java
+@@ -0,0 +1,122 @@
 +package alternate.current.wire;
 +
 +import net.minecraft.core.BlockPos;
@@ -2325,11 +2325,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return LevelHelper.setWireState(level, pos, state, added);
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index efc18884358907661d1226409f11d19a394073b3..9cc47bda7197ca3f63b0ede9905c9a13931f84ed 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -214,6 +214,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      public final UUID uuid;
      public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
      public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
@@ -2337,7 +2337,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public LevelChunk getChunkIfLoaded(int x, int z) {
          return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2213,6 +2214,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return this.chunkSource.getGenerator().getSeaLevel();
      }
  
@@ -2348,14 +2348,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - optimize redstone (Alternate Current)
 +
-     private final class EntityCallbacks implements LevelCallback<Entity> {
- 
-         EntityCallbacks() {}
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+     final class EntityCallbacks implements LevelCallback<Entity> {
+         @Override
+         public void onCreated(Entity entity) {
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index 872c3b8826f436b15f6ab0a3619692c5202eadc3..cb6ca60af3d3f90501e4693a78466b9f7462362d 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -1401,6 +1401,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
  
      public abstract FuelValues fuelValues();
  
@@ -2371,74 +2371,75 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end - optimize redstone (Alternate Current)
 +
      public static enum ExplosionInteraction implements StringRepresentable {
- 
-         NONE("none"), BLOCK("block"), MOB("mob"), TNT("tnt"), TRIGGER("trigger"), STANDARD("standard"); // CraftBukkit - Add STANDARD which will always use Explosion.Effect.DESTROY
-diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
-         return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER);
+         NONE("none"),
+         BLOCK("block"),
+diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
+index f02232ce97779db0d12a5d5da1d767326d78ea4c..12c9d60314c99fb65e640d255a2d0c6b7790ad4d 100644
+--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
+@@ -290,7 +290,7 @@ public class RedStoneWireBlock extends Block {
+         return state.isFaceSturdy(level, pos, Direction.UP) || state.is(Blocks.HOPPER);
      }
  
 -    // Paper start - Optimize redstone
 +    // Paper start - Optimize redstone (Eigencraft)
      // The bulk of the new functionality is found in RedstoneWireTurbo.java
-     com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this);
+     io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this);
  
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
+@@ -372,7 +372,13 @@ public class RedStoneWireBlock extends Block {
      @Override
-     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
-         if (!oldState.is(state.getBlock()) && !world.isClientSide) {
--            this.updateSurroundingRedstone(world, pos, state, null, true); // Paper - Optimize redstone
+     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+         if (!oldState.is(state.getBlock()) && !level.isClientSide) {
+-            this.updateSurroundingRedstone(level, pos, state, null, true); // Paper - Optimize redstone
 +            // Paper start - optimize redstone - replace call to updatePowerStrength
-+            if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+                world.getWireHandler().onWireAdded(pos, state); // Alternate Current
++            if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
++                level.getWireHandler().onWireAdded(pos, state); // Alternate Current
 +            } else {
-+                this.updateSurroundingRedstone(world, pos, state, null, true); // Vanilla/Eigencraft
++                this.updateSurroundingRedstone(level, pos, state, null, true); // Vanilla/Eigencraft
 +            }
-+            // Paper end
++            // Paper end - optimize redstone
  
              for (Direction direction : Direction.Plane.VERTICAL) {
-                 world.updateNeighborsAt(pos.relative(direction), this);
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
-                     world.updateNeighborsAt(pos.relative(direction), this);
+                 level.updateNeighborsAt(pos.relative(direction), this);
+@@ -391,7 +397,13 @@ public class RedStoneWireBlock extends Block {
+                     level.updateNeighborsAt(pos.relative(direction), this);
                  }
  
--                this.updateSurroundingRedstone(world, pos, state, null, false); // Paper - Optimize redstone
+-                this.updateSurroundingRedstone(level, pos, state, null, false); // Paper - Optimize redstone
 +                // Paper start - optimize redstone - replace call to updatePowerStrength
-+                if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+                    world.getWireHandler().onWireRemoved(pos, state); // Alternate Current
++                if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
++                    level.getWireHandler().onWireRemoved(pos, state); // Alternate Current
 +                } else {
-+                    this.updateSurroundingRedstone(world, pos, state, null, false); // Vanilla/Eigencraft
++                    this.updateSurroundingRedstone(level, pos, state, null, false); // Vanilla/Eigencraft
 +                }
-                 this.updateNeighborsOfNeighboringWires(world, pos);
++                // Paper end - optimize redstone
+                 this.updateNeighborsOfNeighboringWires(level, pos);
              }
          }
-@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block {
+@@ -415,9 +427,15 @@ public class RedStoneWireBlock extends Block {
      @Override
-     protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
-         if (!world.isClientSide) {
+     protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) {
+         if (!level.isClientSide) {
 +            // Paper start - optimize redstone (Alternate Current)
 +            // Alternate Current handles breaking of redstone wires in the WireHandler.
-+            if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
-+                world.getWireHandler().onWireUpdated(pos, state, wireOrientation);
++            if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
++                level.getWireHandler().onWireUpdated(pos, state, orientation);
 +            } else
-+            // Paper end - optimize redstone (Alternate Current)
-             if (sourceBlock != this || !useExperimentalEvaluator(world)) {
-                 if (state.canSurvive(world, pos)) {
--                    this.updateSurroundingRedstone(world, pos, state, wireOrientation, false); // Paper - Optimize redstone
-+                    this.updateSurroundingRedstone(world, pos, state, wireOrientation, false); // Paper - Optimize redstone (Eigencraft)
++                // Paper end - optimize redstone (Alternate Current)
+             if (neighborBlock != this || !useExperimentalEvaluator(level)) {
+                 if (state.canSurvive(level, pos)) {
+-                    this.updateSurroundingRedstone(level, pos, state, orientation, false); // Paper - Optimize redstone
++                    this.updateSurroundingRedstone(level, pos, state, orientation, false); // Paper - Optimize redstone (Eigencraft)
                  } else {
-                     dropResources(state, world, pos);
-                     world.removeBlock(pos, false);
-diff --git a/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java b/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
-+++ b/src/main/java/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
-@@ -0,0 +0,0 @@ public class ExperimentalRedstoneUtils {
-             if (up != null) {
-                 orientation = orientation.withFront(up);
+                     dropResources(state, level, pos);
+                     level.removeBlock(pos, false);
+diff --git a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
+index 83f5da3e24834882193b9d7e3a788517e4b12b55..db7a7b091d2eb0f5b7efbe0db2b91709dee688e8 100644
+--- a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
++++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
+@@ -17,6 +17,11 @@ public class ExperimentalRedstoneUtils {
+             if (front != null) {
+                 orientation = orientation.withFront(front);
              }
 +            // Paper start - Optimize redstone (Alternate Current) - use default front instead of random
 +            else if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {

From 4b519277838a21d3a3dee6e621a6416380b210c5 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 12:32:56 +0100
Subject: [PATCH 217/285] Update Improve-exact-choice-recipe-ingredients patch

---
 ...rove-exact-choice-recipe-ingredients.patch | 291 +++++++++---------
 1 file changed, 146 insertions(+), 145 deletions(-)

diff --git a/feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch b/feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch
index be741b29e9..91119626f1 100644
--- a/feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch
+++ b/feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch
@@ -6,15 +6,12 @@ Subject: [PATCH] Improve exact choice recipe ingredients
 Fixes exact choices not working with recipe book clicks
 and shapeless recipes.
 
-== AT ==
-public net.minecraft.world.item.ItemStackLinkedSet TYPE_AND_TAG
-
-diff --git a/src/main/java/io/papermc/paper/inventory/recipe/ItemOrExact.java b/src/main/java/io/papermc/paper/inventory/recipe/ItemOrExact.java
+diff --git a/io/papermc/paper/inventory/recipe/ItemOrExact.java b/io/papermc/paper/inventory/recipe/ItemOrExact.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ce745e49cd54fe3ae187785563a1bd311d14b5b2
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/inventory/recipe/ItemOrExact.java
-@@ -0,0 +0,0 @@
++++ b/io/papermc/paper/inventory/recipe/ItemOrExact.java
+@@ -0,0 +1,63 @@
 +package io.papermc.paper.inventory.recipe;
 +
 +import net.minecraft.core.Holder;
@@ -78,12 +75,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +}
-diff --git a/src/main/java/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java b/src/main/java/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
+diff --git a/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f47c12e9dd6cfa857ca07a764edc22de372e25b6
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
-@@ -0,0 +0,0 @@
++++ b/io/papermc/paper/inventory/recipe/StackedContentsExtrasMap.java
+@@ -0,0 +1,68 @@
 +package io.papermc.paper.inventory.recipe;
 +
 +import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -152,92 +149,94 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return false;
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-+++ b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-@@ -0,0 +0,0 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
+diff --git a/net/minecraft/recipebook/ServerPlaceRecipe.java b/net/minecraft/recipebook/ServerPlaceRecipe.java
+index 6475509689439636275b06b9eac33f0922d8fcfd..6c398c91909f42e352e80d0781549df9d834a060 100644
+--- a/net/minecraft/recipebook/ServerPlaceRecipe.java
++++ b/net/minecraft/recipebook/ServerPlaceRecipe.java
+@@ -40,6 +40,7 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
              return RecipeBookMenu.PostPlaceAction.NOTHING;
          } else {
              StackedItemContents stackedItemContents = new StackedItemContents();
 +            stackedItemContents.initializeExtras(recipe.value(), null); // Paper - Improve exact choice recipe ingredients
              inventory.fillStackedContents(stackedItemContents);
-             handler.fillCraftSlotsStackedContents(stackedItemContents);
+             menu.fillCraftSlotsStackedContents(stackedItemContents);
              return serverPlaceRecipe.tryPlaceRecipe(recipe, stackedItemContents);
-@@ -0,0 +0,0 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
+@@ -99,7 +100,7 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
          }
  
-         int j = this.calculateAmountToCraft(i, bl);
+         int i = this.calculateAmountToCraft(biggestCraftableStack, flag);
 -        List<Holder<Item>> list = new ArrayList<>();
 +        List<io.papermc.paper.inventory.recipe.ItemOrExact> list = new ArrayList<>(); // Paper - Improve exact choice recipe ingredients
-         if (finder.canCraft(recipe.value(), j, list::add)) {
-             int k = clampToMaxStackSize(j, list);
-             if (k != j) {
-@@ -0,0 +0,0 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
-                 this.gridWidth, this.gridHeight, recipe.value(), recipe.value().placementInfo().slotsToIngredientIndex(), (slotx, index, x, y) -> {
-                     if (slotx != -1) {
-                         Slot slot2 = this.inputGridSlots.get(index);
--                        Holder<Item> holder = list.get(slotx);
-+                        io.papermc.paper.inventory.recipe.ItemOrExact holder = list.get(slotx); // Paper - Improve exact choice recipe ingredients
-                         int jx = k;
+         if (stackedItemContents.canCraft(recipe.value(), i, list::add)) {
+             int i1 = clampToMaxStackSize(i, list);
+             if (i1 != i) {
+@@ -114,7 +115,7 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
+                 this.gridWidth, this.gridHeight, recipe.value(), recipe.value().placementInfo().slotsToIngredientIndex(), (item1, slot1, x, y) -> {
+                     if (item1 != -1) {
+                         Slot slot2 = this.inputGridSlots.get(slot1);
+-                        Holder<Item> holder = list.get(item1);
++                        io.papermc.paper.inventory.recipe.ItemOrExact holder = list.get(item1); // Paper - Improve exact choice recipe ingredients
+                         int i2 = i1;
  
-                         while (jx > 0) {
-@@ -0,0 +0,0 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
+                         while (i2 > 0) {
+@@ -129,9 +130,11 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
          }
      }
  
--    private static int clampToMaxStackSize(int count, List<Holder<Item>> entries) {
--        for (Holder<Item> holder : entries) {
--            count = Math.min(count, holder.value().getDefaultMaxStackSize());
+-    private static int clampToMaxStackSize(int amount, List<Holder<Item>> items) {
+-        for (Holder<Item> holder : items) {
+-            amount = Math.min(amount, holder.value().getDefaultMaxStackSize());
 +    // Paper start - Improve exact choice recipe ingredients
-+    private static int clampToMaxStackSize(int count, List<io.papermc.paper.inventory.recipe.ItemOrExact> entries) {
-+        for (io.papermc.paper.inventory.recipe.ItemOrExact holder : entries) {
-+            count = Math.min(count, holder.getMaxStackSize());
++    private static int clampToMaxStackSize(int amount, List<io.papermc.paper.inventory.recipe.ItemOrExact> items) {
++        for (io.papermc.paper.inventory.recipe.ItemOrExact holder : items) {
++            amount = Math.min(amount, holder.getMaxStackSize());
 +            // Paper end - Improve exact choice recipe ingredients
          }
  
-         return count;
-@@ -0,0 +0,0 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
+         return amount;
+@@ -160,7 +163,7 @@ public class ServerPlaceRecipe<R extends Recipe<?>> {
          }
      }
  
 -    private int moveItemToGrid(Slot slot, Holder<Item> item, int count) {
 +    private int moveItemToGrid(Slot slot, io.papermc.paper.inventory.recipe.ItemOrExact item, int count) { // Paper - Improve exact choice recipe ingredients
-         ItemStack itemStack = slot.getItem();
-         int i = this.inventory.findSlotMatchingCraftingIngredient(item, itemStack);
+         ItemStack item1 = slot.getItem();
+         int i = this.inventory.findSlotMatchingCraftingIngredient(item, item1);
          if (i == -1) {
-diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/player/Inventory.java
-+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java
-@@ -0,0 +0,0 @@ public class Inventory implements Container, Nameable {
+diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
+index e8522f5ccd69ff5513782a31a3b53219bc17b0e5..9a72651c5efefc6290ae14aa50ca79572d274562 100644
+--- a/net/minecraft/world/entity/player/Inventory.java
++++ b/net/minecraft/world/entity/player/Inventory.java
+@@ -178,12 +178,12 @@ public class Inventory implements Container, Nameable {
          return !stack.isDamaged() && !stack.isEnchanted() && !stack.has(DataComponents.CUSTOM_NAME);
      }
  
 -    public int findSlotMatchingCraftingIngredient(Holder<Item> item, ItemStack stack) {
 +    public int findSlotMatchingCraftingIngredient(io.papermc.paper.inventory.recipe.ItemOrExact item, ItemStack stack) { // Paper - Improve exact choice recipe ingredients
-         for (int i = 0; i < this.items.size(); ++i) {
-             ItemStack itemstack1 = (ItemStack) this.items.get(i);
- 
--            if (!itemstack1.isEmpty() && itemstack1.is(item) && Inventory.isUsableForCrafting(itemstack1) && (stack.isEmpty() || ItemStack.isSameItemSameComponents(stack, itemstack1))) {
-+            if (!itemstack1.isEmpty() && item.matches(itemstack1) && (!(item instanceof io.papermc.paper.inventory.recipe.ItemOrExact.Item) || Inventory.isUsableForCrafting(itemstack1)) && (stack.isEmpty() || ItemStack.isSameItemSameComponents(stack, itemstack1))) { // Paper - Improve exact choice recipe ingredients
+         for (int i = 0; i < this.items.size(); i++) {
+             ItemStack itemStack = this.items.get(i);
+             if (!itemStack.isEmpty()
+-                && itemStack.is(item)
+-                && isUsableForCrafting(itemStack)
++                && item.matches(itemStack) // Paper - Improve exact choice recipe ingredients
++                && (!(item instanceof io.papermc.paper.inventory.recipe.ItemOrExact.Item) || Inventory.isUsableForCrafting(itemStack))  // Paper - Improve exact choice recipe ingredients
+                 && (stack.isEmpty() || ItemStack.isSameItemSameComponents(stack, itemStack))) {
                  return i;
              }
-         }
-diff --git a/src/main/java/net/minecraft/world/entity/player/StackedContents.java b/src/main/java/net/minecraft/world/entity/player/StackedContents.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/player/StackedContents.java
-+++ b/src/main/java/net/minecraft/world/entity/player/StackedContents.java
-@@ -0,0 +0,0 @@ import java.util.List;
+diff --git a/net/minecraft/world/entity/player/StackedContents.java b/net/minecraft/world/entity/player/StackedContents.java
+index a4b528574ab371af94b0e07819e471cec94da244..a3fea6c8397046596afe3c8b5589f2ed37fcdfc3 100644
+--- a/net/minecraft/world/entity/player/StackedContents.java
++++ b/net/minecraft/world/entity/player/StackedContents.java
+@@ -13,7 +13,7 @@ import java.util.List;
  import javax.annotation.Nullable;
  
  public class StackedContents<T> {
 -    public final Reference2IntOpenHashMap<T> amounts = new Reference2IntOpenHashMap<>();
 +    public final it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap<T> amounts = new it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap<>(); // Paper - Improve exact choice recipe ingredients (don't use "reference" map)
  
-     boolean hasAtLeast(T input, int minimum) {
-         return this.amounts.getInt(input) >= minimum;
-@@ -0,0 +0,0 @@ public class StackedContents<T> {
+     boolean hasAtLeast(T item, int amount) {
+         return this.amounts.getInt(item) >= amount;
+@@ -49,7 +49,7 @@ public class StackedContents<T> {
      List<T> getUniqueAvailableIngredientItems(Iterable<? extends StackedContents.IngredientInfo<T>> ingredients) {
          List<T> list = new ArrayList<>();
  
@@ -246,7 +245,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              if (entry.getIntValue() > 0 && anyIngredientMatches(ingredients, entry.getKey())) {
                  list.add(entry.getKey());
              }
-@@ -0,0 +0,0 @@ public class StackedContents<T> {
+@@ -71,13 +71,13 @@ public class StackedContents<T> {
      @VisibleForTesting
      public int getResultUpperBound(List<? extends StackedContents.IngredientInfo<T>> ingredients) {
          int i = Integer.MAX_VALUE;
@@ -255,18 +254,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          label31:
          for (StackedContents.IngredientInfo<T> ingredientInfo : ingredients) {
-             int j = 0;
+             int i1 = 0;
  
 -            for (Entry<T> entry : objectIterable) {
 +            for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<T> entry : objectIterable) { // Paper - Improve exact choice recipe ingredients (don't use "reference" map)
-                 int k = entry.getIntValue();
-                 if (k > j) {
+                 int intValue = entry.getIntValue();
+                 if (intValue > i1) {
                      if (ingredientInfo.acceptsItem(entry.getKey())) {
-diff --git a/src/main/java/net/minecraft/world/entity/player/StackedItemContents.java b/src/main/java/net/minecraft/world/entity/player/StackedItemContents.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/player/StackedItemContents.java
-+++ b/src/main/java/net/minecraft/world/entity/player/StackedItemContents.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.item.crafting.PlacementInfo;
+diff --git a/net/minecraft/world/entity/player/StackedItemContents.java b/net/minecraft/world/entity/player/StackedItemContents.java
+index 6bbe2e51ef71d193e0a5d3cace2b0ad1760ce759..83ccde54c625d40dc595e000c533f60aa929bd5a 100644
+--- a/net/minecraft/world/entity/player/StackedItemContents.java
++++ b/net/minecraft/world/entity/player/StackedItemContents.java
+@@ -9,9 +9,14 @@ import net.minecraft.world.item.crafting.PlacementInfo;
  import net.minecraft.world.item.crafting.Recipe;
  
  public class StackedItemContents {
@@ -274,25 +273,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper start - Improve exact choice recipe ingredients
 +    private final StackedContents<io.papermc.paper.inventory.recipe.ItemOrExact> raw = new StackedContents<>();
 +    @Nullable
-+    private io.papermc.paper.inventory.recipe.StackedContentsExtrasMap extrasMap = null;
++    private io.papermc.paper.inventory.recipe.StackedContentsExtrasMap extrasMap;
 +    // Paper start - Improve exact choice recipe ingredients
  
-     public void accountSimpleStack(ItemStack item) {
-+        if (this.extrasMap != null && this.extrasMap.accountStack(item, Math.min(64, item.getCount()))) return; // Paper - Improve exact choice recipe ingredients; max of 64 due to accountStack method below
-         if (Inventory.isUsableForCrafting(item)) {
-             this.accountStack(item);
+     public void accountSimpleStack(ItemStack stack) {
++        if (this.extrasMap != null && this.extrasMap.accountStack(stack, Math.min(64, stack.getCount()))) return; // Paper - Improve exact choice recipe ingredients; max of 64 due to accountStack method below
+         if (Inventory.isUsableForCrafting(stack)) {
+             this.accountStack(stack);
          }
-@@ -0,0 +0,0 @@ public class StackedItemContents {
-     public void accountStack(ItemStack item, int maxCount) {
-         if (!item.isEmpty()) {
-             int i = Math.min(maxCount, item.getCount());
--            this.raw.account(item.getItemHolder(), i);
-+            if (this.extrasMap != null && !item.getComponentsPatch().isEmpty() && this.extrasMap.accountStack(item, i)) return; // Paper - Improve exact choice recipe ingredients; if an exact ingredient, don't include it
-+            this.raw.account(new io.papermc.paper.inventory.recipe.ItemOrExact.Item(item.getItemHolder()), i);
+@@ -24,34 +29,51 @@ public class StackedItemContents {
+     public void accountStack(ItemStack stack, int maxStackSize) {
+         if (!stack.isEmpty()) {
+             int min = Math.min(maxStackSize, stack.getCount());
+-            this.raw.account(stack.getItemHolder(), min);
++            if (this.extrasMap != null && !stack.getComponentsPatch().isEmpty() && this.extrasMap.accountStack(stack, min)) return; // Paper - Improve exact choice recipe ingredients; if an exact ingredient, don't include it
++            this.raw.account(new io.papermc.paper.inventory.recipe.ItemOrExact.Item(stack.getItemHolder()), min);
          }
      }
  
--    public boolean canCraft(Recipe<?> recipe, @Nullable StackedContents.Output<Holder<Item>> itemCallback) {
+-    public boolean canCraft(Recipe<?> recipe, @Nullable StackedContents.Output<Holder<Item>> output) {
++    public boolean canCraft(Recipe<?> recipe, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output) { // Paper - Improve exact choice recipe ingredients
+         return this.canCraft(recipe, 1, output);
+     }
+ 
+-    public boolean canCraft(Recipe<?> recipe, int maxCount, @Nullable StackedContents.Output<Holder<Item>> output) {
 +    // Paper start - Improve exact choice recipe ingredients
 +    public void initializeExtras(final Recipe<?> recipe, @Nullable final net.minecraft.world.item.crafting.CraftingInput input) {
 +        if (this.extrasMap == null) {
@@ -309,70 +313,64 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Improve exact choice recipe ingredients
 +
-+    public boolean canCraft(Recipe<?> recipe, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback) { // Paper - Improve exact choice recipe ingredients
-         return this.canCraft(recipe, 1, itemCallback);
-     }
- 
--    public boolean canCraft(Recipe<?> recipe, int quantity, @Nullable StackedContents.Output<Holder<Item>> itemCallback) {
-+    public boolean canCraft(Recipe<?> recipe, int quantity, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback) { // Paper - Improve exact choice recipe ingredients
++    public boolean canCraft(Recipe<?> recipe, int maxCount, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output) { // Paper - Improve exact choice recipe ingredients
          PlacementInfo placementInfo = recipe.placementInfo();
-         return !placementInfo.isImpossibleToPlace() && this.canCraft(placementInfo.ingredients(), quantity, itemCallback);
+         return !placementInfo.isImpossibleToPlace() && this.canCraft(placementInfo.ingredients(), maxCount, output);
      }
  
-     public boolean canCraft(
--        List<? extends StackedContents.IngredientInfo<Holder<Item>>> rawIngredients, @Nullable StackedContents.Output<Holder<Item>> itemCallback
-+        List<? extends StackedContents.IngredientInfo<io.papermc.paper.inventory.recipe.ItemOrExact>> rawIngredients, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback // Paper - Improve exact choice recipe ingredients
-     ) {
-         return this.canCraft(rawIngredients, 1, itemCallback);
+-    public boolean canCraft(List<? extends StackedContents.IngredientInfo<Holder<Item>>> ingredients, @Nullable StackedContents.Output<Holder<Item>> output) {
++    public boolean canCraft(List<? extends StackedContents.IngredientInfo<io.papermc.paper.inventory.recipe.ItemOrExact>> ingredients, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output) { // Paper - Improve exact choice recipe ingredients
+         return this.canCraft(ingredients, 1, output);
      }
  
      private boolean canCraft(
--        List<? extends StackedContents.IngredientInfo<Holder<Item>>> rawIngredients, int quantity, @Nullable StackedContents.Output<Holder<Item>> itemCallback
-+        List<? extends StackedContents.IngredientInfo<io.papermc.paper.inventory.recipe.ItemOrExact>> rawIngredients, int quantity, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback // Paper - Improve exact choice recipe ingredients
+-        List<? extends StackedContents.IngredientInfo<Holder<Item>>> ingredients, int maxCount, @Nullable StackedContents.Output<Holder<Item>> output
++        List<? extends StackedContents.IngredientInfo<io.papermc.paper.inventory.recipe.ItemOrExact>> ingredients, int maxCount, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output // Paper - Improve exact choice recipe ingredients
      ) {
-         return this.raw.tryPick(rawIngredients, quantity, itemCallback);
+         return this.raw.tryPick(ingredients, maxCount, output);
      }
  
--    public int getBiggestCraftableStack(Recipe<?> recipe, @Nullable StackedContents.Output<Holder<Item>> itemCallback) {
-+    public int getBiggestCraftableStack(Recipe<?> recipe, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback) { // Paper - Improve exact choice recipe ingredients
-         return this.getBiggestCraftableStack(recipe, Integer.MAX_VALUE, itemCallback);
+-    public int getBiggestCraftableStack(Recipe<?> recipe, @Nullable StackedContents.Output<Holder<Item>> output) {
++    public int getBiggestCraftableStack(Recipe<?> recipe, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output) { // Paper - Improve exact choice recipe ingredients
+         return this.getBiggestCraftableStack(recipe, Integer.MAX_VALUE, output);
      }
  
--    public int getBiggestCraftableStack(Recipe<?> recipe, int max, @Nullable StackedContents.Output<Holder<Item>> itemCallback) {
-+    public int getBiggestCraftableStack(Recipe<?> recipe, int max, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> itemCallback) { // Paper - Improve exact choice recipe ingredients
-         return this.raw.tryPickAll(recipe.placementInfo().ingredients(), max, itemCallback);
+-    public int getBiggestCraftableStack(Recipe<?> recipe, int maxCount, @Nullable StackedContents.Output<Holder<Item>> output) {
++    public int getBiggestCraftableStack(Recipe<?> recipe, int maxCount, @Nullable StackedContents.Output<io.papermc.paper.inventory.recipe.ItemOrExact> output) { // Paper - Improve exact choice recipe ingredients
+         return this.raw.tryPickAll(recipe.placementInfo().ingredients(), maxCount, output);
      }
  
-diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
-+++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
-@@ -0,0 +0,0 @@ import java.util.List;
- import javax.annotation.Nullable;
- // CraftBukkit end
+diff --git a/net/minecraft/world/item/crafting/Ingredient.java b/net/minecraft/world/item/crafting/Ingredient.java
+index e43641650d66a62b5b7b58c43833ce504970ab1e..879c8fe1f20decc793cfa39e686b61d521bd76ba 100644
+--- a/net/minecraft/world/item/crafting/Ingredient.java
++++ b/net/minecraft/world/item/crafting/Ingredient.java
+@@ -21,7 +21,7 @@ import net.minecraft.world.item.Items;
+ import net.minecraft.world.item.crafting.display.SlotDisplay;
+ import net.minecraft.world.level.ItemLike;
  
 -public final class Ingredient implements StackedContents.IngredientInfo<Holder<Item>>, Predicate<ItemStack> {
 +public final class Ingredient implements StackedContents.IngredientInfo<io.papermc.paper.inventory.recipe.ItemOrExact>, Predicate<ItemStack> { // Paper - Improve exact choice recipe ingredients
- 
-     public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM).map(Ingredient::new, (recipeitemstack) -> {
-         return recipeitemstack.values;
-@@ -0,0 +0,0 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
+     public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
+         .map(Ingredient::new, ingredient -> ingredient.values);
+     public static final StreamCodec<RegistryFriendlyByteBuf, Optional<Ingredient>> OPTIONAL_CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM)
+@@ -35,20 +35,24 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
      private final HolderSet<Item> values;
      // CraftBukkit start
-     @Nullable
--    private List<ItemStack> itemStacks;
+     @javax.annotation.Nullable
+-    private java.util.List<ItemStack> itemStacks;
 +    private java.util.Set<ItemStack> itemStacks; // Paper - Improve exact choice recipe ingredients
  
      public boolean isExact() {
          return this.itemStacks != null;
      }
  
--    public List<ItemStack> itemStacks() {
+     @javax.annotation.Nullable
+-    public java.util.List<ItemStack> itemStacks() {
 +    public java.util.Set<ItemStack> itemStacks() { // Paper - Improve exact choice recipe ingredients
          return this.itemStacks;
      }
  
-     public static Ingredient ofStacks(List<ItemStack> stacks) {
+     public static Ingredient ofStacks(java.util.List<ItemStack> stacks) {
          Ingredient recipe = Ingredient.of(stacks.stream().map(ItemStack::getItem));
 -        recipe.itemStacks = stacks;
 +        // Paper start - Improve exact choice recipe ingredients
@@ -383,29 +381,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return recipe;
      }
      // CraftBukkit end
-@@ -0,0 +0,0 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
-     public boolean test(ItemStack itemstack) {
+@@ -81,21 +85,22 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
+     public boolean test(ItemStack stack) {
          // CraftBukkit start
          if (this.isExact()) {
 -            for (ItemStack itemstack1 : this.itemStacks()) {
--                if (itemstack1.getItem() == itemstack.getItem() && ItemStack.isSameItemSameComponents(itemstack, itemstack1)) {
+-                if (itemstack1.getItem() == stack.getItem() && ItemStack.isSameItemSameComponents(stack, itemstack1)) {
 -                    return true;
 -                }
 -            }
 -
 -            return false;
-+            return this.itemStacks.contains(itemstack); // Paper - Improve exact choice recipe ingredients (hashing FTW!)
++            return this.itemStacks.contains(stack); // Paper - Improve exact choice recipe ingredients (hashing FTW!)
          }
          // CraftBukkit end
-         return itemstack.is(this.values);
+         return stack.is(this.values);
      }
  
--    public boolean acceptsItem(Holder<Item> holder) {
--        return this.values.contains(holder);
 +    // Paper start - Improve exact choice recipe ingredients
-+    @Override
-+    public boolean acceptsItem(final io.papermc.paper.inventory.recipe.ItemOrExact holder) {
-+        return switch (holder) {
+     @Override
+-    public boolean acceptsItem(Holder<Item> item) {
+-        return this.values.contains(item);
++    public boolean acceptsItem(final io.papermc.paper.inventory.recipe.ItemOrExact itemOrExact) {
++        return switch (itemOrExact) {
 +            case io.papermc.paper.inventory.recipe.ItemOrExact.Item(final Holder<Item> item) ->
 +                !this.isExact() && this.values.contains(item);
 +            case io.papermc.paper.inventory.recipe.ItemOrExact.Exact(final ItemStack exact) ->
@@ -414,8 +412,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - Improve exact choice recipe ingredients
      }
  
-     public boolean equals(Object object) {
-@@ -0,0 +0,0 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
+     @Override
+@@ -120,6 +125,11 @@ public final class Ingredient implements StackedContents.IngredientInfo<Holder<I
      }
  
      public SlotDisplay display() {
@@ -424,22 +422,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return new SlotDisplay.Composite(this.itemStacks().stream().<SlotDisplay>map(SlotDisplay.ItemStackSlotDisplay::new).toList());
 +        }
 +        // Paper end - show exact ingredients in recipe book
-         return (SlotDisplay) this.values.unwrap().map(SlotDisplay.TagSlotDisplay::new, (list) -> {
-             return new SlotDisplay.Composite(list.stream().map(Ingredient::displayForSingleItem).toList());
-         });
-diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
-+++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java
-@@ -0,0 +0,0 @@ public class ShapelessRecipe implements CraftingRecipe {
-     }
+         return (SlotDisplay)this.values
+             .unwrap()
+             .map(SlotDisplay.TagSlotDisplay::new, list -> new SlotDisplay.Composite(list.stream().map(Ingredient::displayForSingleItem).toList()));
+diff --git a/net/minecraft/world/item/crafting/ShapelessRecipe.java b/net/minecraft/world/item/crafting/ShapelessRecipe.java
+index fb317eafeed39adff793bffa8f6b21c37a32086c..d601b54b1de2f2ae44fe2b20c8116c71a6340e45 100644
+--- a/net/minecraft/world/item/crafting/ShapelessRecipe.java
++++ b/net/minecraft/world/item/crafting/ShapelessRecipe.java
+@@ -72,13 +72,18 @@ public class ShapelessRecipe implements CraftingRecipe {
  
-     public boolean matches(CraftingInput input, Level world) {
--        return input.ingredientCount() != this.ingredients.size() ? false : (input.size() == 1 && this.ingredients.size() == 1 ? ((Ingredient) this.ingredients.getFirst()).test(input.getItem(0)) : input.stackedContents().canCraft((Recipe) this, (StackedContents.Output) null));
+     @Override
+     public boolean matches(CraftingInput input, Level level) {
 +        // Paper start - Improve exact choice recipe ingredients & unwrap ternary
-+        if (input.ingredientCount() != this.ingredients.size()) {
-+            return false;
-+        }
+         if (input.ingredientCount() != this.ingredients.size()) {
+             return false;
+-        } else {
+-            return input.size() == 1 && this.ingredients.size() == 1
+-                ? this.ingredients.getFirst().test(input.getItem(0))
+-                : input.stackedContents().canCraft(this, null);
+         }
 +        if (input.size() == 1 && this.ingredients.size() == 1) {
 +            return this.ingredients.getFirst().test(input.getItem(0));
 +        }
@@ -450,4 +451,4 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - Improve exact choice recipe ingredients & unwrap ternary
      }
  
-     public ItemStack assemble(CraftingInput input, HolderLookup.Provider registries) {
+     @Override

From 7daedfcbc3e40875375a6b2b16e37134390c332e Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 12:46:53 +0100
Subject: [PATCH 218/285] Update Optimise-collision-checking, apply more
 updated patches

---
 ...on-checking-in-player-move-packet-ha.patch | 119 +++++++++---------
 ...er-desync-when-new-players-are-added.patch |   0
 .../0022-Lag-compensation-ticks.patch         |   0
 ...3-Eigencraft-redstone-implementation.patch |   0
 ...nate-Current-redstone-implementation.patch |   4 +-
 ...rove-exact-choice-recipe-ingredients.patch |   0
 6 files changed, 61 insertions(+), 62 deletions(-)
 rename feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch => paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch (100%)
 rename feature-patches/1069-Lag-compensation-ticks.patch => paper-server/patches/features/0022-Lag-compensation-ticks.patch (100%)
 rename feature-patches/1073-Eigencraft-redstone-implementation.patch => paper-server/patches/features/0023-Eigencraft-redstone-implementation.patch (100%)
 rename feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch => paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch (99%)
 rename feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch => paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch (100%)

diff --git a/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch b/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch
index 3f4cd40208..9f8d28a29d 100644
--- a/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch
+++ b/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch
@@ -1,65 +1,64 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
 Date: Thu, 2 Jul 2020 12:02:43 -0700
 Subject: [PATCH] Optimise collision checking in player move packet handling
 
 Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
 
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f82dbbefb 100644
+--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -562,7 +562,7 @@ public class ServerGamePacketListenerImpl
                      return;
                  }
  
--                boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
-+                AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet
- 
-                 d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
-                 d7 = d4 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+-                boolean flag = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
++                AABB oldBox = rootVehicle.getBoundingBox(); // Paper - copy from player movement packet
+                 d3 = d - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
+                 d4 = d1 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
+                 d5 = d2 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
+@@ -572,6 +572,7 @@ public class ServerGamePacketListenerImpl
                  }
  
-                 entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8));
-+                boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
-                 double d11 = d7;
- 
-                 d6 = d3 - entity.getX();
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+                 rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
++                boolean didCollide = toX != rootVehicle.getX() || toY != rootVehicle.getY() || toZ != rootVehicle.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
+                 d3 = d - rootVehicle.getX();
+                 d4 = d1 - rootVehicle.getY();
+                 if (d4 > -0.5 || d4 < 0.5) {
+@@ -582,14 +583,22 @@ public class ServerGamePacketListenerImpl
+                 d7 = d3 * d3 + d4 * d4 + d5 * d5;
                  boolean flag2 = false;
- 
-                 if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
+                 if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
 -                    flag2 = true;
 +                    flag2 = true; // Paper - diff on change, this should be moved wrongly
-                     ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)});
+                     LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
                  }
  
-                 entity.absMoveTo(d3, d4, d5, f, f1);
-                 this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
--                boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
- 
+                 rootVehicle.absMoveTo(d, d1, d2, f, f1);
+                 this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+-                boolean flag3 = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
 -                if (flag && (flag2 || !flag3)) {
 +                // Paper start - optimise out extra getCubes
 +                boolean teleportBack = flag2; // violating this is always a fail
 +                if (!teleportBack) {
 +                    // note: only call after setLocation, or else getBoundingBox is wrong
-+                    AABB newBox = entity.getBoundingBox();
++                    AABB newBox = rootVehicle.getBoundingBox();
 +                    if (didCollide || !oldBox.equals(newBox)) {
-+                        teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox);
++                        teleportBack = this.hasNewCollision(serverLevel, rootVehicle, oldBox, newBox);
 +                    } // else: no collision at all detected, why do we care?
 +                }
 +                if (teleportBack) { // Paper end - optimise out extra getCubes
-                     entity.absMoveTo(d0, d1, d2, f, f1);
-                     this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
-                     this.send(ClientboundMoveVehiclePacket.fromEntity(entity));
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+                     rootVehicle.absMoveTo(x, y, z, f, f1);
+                     this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+                     this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
+@@ -667,9 +676,32 @@ public class ServerGamePacketListenerImpl
      }
  
      private boolean noBlocksAround(Entity entity) {
--        return entity.level().getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir);
+-        return entity.level()
+-            .getBlockStates(entity.getBoundingBox().inflate(0.0625).expandTowards(0.0, -0.55, 0.0))
+-            .allMatch(BlockBehaviour.BlockStateBase::isAir);
 +        // Paper start - stop using streams, this is already a known fixed problem in Entity#move
-+        AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D);
++        AABB box = entity.getBoundingBox().inflate(0.0625).expandTowards(0.0, -0.55, 0.0);
 +        int minX = Mth.floor(box.minX);
 +        int minY = Mth.floor(box.minY);
 +        int minZ = Mth.floor(box.minZ);
@@ -67,15 +66,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        int maxY = Mth.floor(box.maxY);
 +        int maxZ = Mth.floor(box.maxZ);
 +
-+        Level world = entity.level();
++        Level level = entity.level();
 +        BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
 +
 +        for (int y = minY; y <= maxY; ++y) {
 +            for (int z = minZ; z <= maxZ; ++z) {
 +                for (int x = minX; x <= maxX; ++x) {
 +                    pos.set(x, y, z);
-+                    BlockState type = world.getBlockStateIfLoaded(pos);
-+                    if (type != null && !type.isAir()) {
++                    BlockState blockState = level.getBlockStateIfLoaded(pos);
++                    if (blockState != null && !blockState.isAir()) {
 +                        return false;
 +                    }
 +                }
@@ -87,61 +86,61 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+@@ -1361,7 +1393,7 @@ public class ServerGamePacketListenerImpl
                                  }
                              }
  
--                            AABB axisalignedbb = this.player.getBoundingBox();
-+                            AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB
- 
-                             d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
-                             d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- 
-                             this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8));
+-                            AABB boundingBox = this.player.getBoundingBox();
++                            AABB boundingBox = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB
+                             d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
+                             d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
+                             d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
+@@ -1400,6 +1432,7 @@ public class ServerGamePacketListenerImpl
+                             boolean flag1 = this.player.verticalCollisionBelow;
+                             this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
                              this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
 +                            boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
                              // Paper start - prevent position desync
                              if (this.awaitingPositionFromClient != null) {
                                  return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+@@ -1431,7 +1464,17 @@ public class ServerGamePacketListenerImpl
                              }
  
                              // Paper start - Add fail move event
--                            boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2));
+-                            boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && serverLevel.noCollision(this.player, boundingBox) || this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2));
 +                            // Paper start - optimise out extra getCubes
 +                            boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && movedWrongly;
-+                            this.player.absMoveTo(d0, d1, d2, f, f1); // prevent desync by tping to the set position, dropped for unknown reasons by mojang
++                            this.player.absMoveTo(d, d1, d2, f, f1); // prevent desync by tping to the set position, dropped for unknown reasons by mojang
 +                            if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) {
 +                                AABB newBox = this.player.getBoundingBox();
-+                                if (didCollide || !axisalignedbb.equals(newBox)) {
++                                if (didCollide || !boundingBox.equals(newBox)) {
 +                                    // note: only call after setLocation, or else getBoundingBox is wrong
-+                                    teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox);
++                                    teleportBack = this.hasNewCollision(serverLevel, this.player, boundingBox, newBox);
 +                                } // else: no collision at all detected, why do we care?
 +                            }
 +                            // Paper end - optimise out extra getCubes
                              if (teleportBack) {
                                  io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
                                      toX, toY, toZ, toYaw, toPitch, false);
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+@@ -1567,7 +1610,7 @@ public class ServerGamePacketListenerImpl
  
      private boolean updateAwaitingTeleport() {
          if (this.awaitingPositionFromClient != null) {
 -            if (this.tickCount - this.awaitingTeleportTime > 20) {
 +            if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT
                  this.awaitingTeleportTime = this.tickCount;
-                 this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot());
-             }
-@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+                 this.teleport(
+                     this.awaitingPositionFromClient.x,
+@@ -1586,6 +1629,33 @@ public class ServerGamePacketListenerImpl
          }
      }
  
 +    // Paper start - optimise out extra getCubes
-+    private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) {
++    private boolean hasNewCollision(final ServerLevel level, final Entity entity, final AABB oldBox, final AABB newBox) {
 +        final List<AABB> collisionsBB = new java.util.ArrayList<>();
 +        final List<VoxelShape> collisionsVoxel = new java.util.ArrayList<>();
 +        ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions(
-+            world, entity, newBox, collisionsVoxel, collisionsBB,
++            level, entity, newBox, collisionsVoxel, collisionsBB,
 +            ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS | ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER,
 +            null, null
 +        );
@@ -163,6 +162,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return false;
 +    }
 +    // Paper end - optimise out extra getCubes
-     private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) {
-         AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ());
-         Iterable<VoxelShape> iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D));
+     private boolean isPlayerCollidingWithAnythingNew(LevelReader level, AABB box, double x, double y, double z) {
+         AABB aabb = this.player.getBoundingBox().move(x - this.player.getX(), y - this.player.getY(), z - this.player.getZ());
+         Iterable<VoxelShape> collisions = level.getCollisions(this.player, aabb.deflate(1.0E-5F));
diff --git a/feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch b/paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch
similarity index 100%
rename from feature-patches/1068-Fix-entity-tracker-desync-when-new-players-are-added.patch
rename to paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch
diff --git a/feature-patches/1069-Lag-compensation-ticks.patch b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
similarity index 100%
rename from feature-patches/1069-Lag-compensation-ticks.patch
rename to paper-server/patches/features/0022-Lag-compensation-ticks.patch
diff --git a/feature-patches/1073-Eigencraft-redstone-implementation.patch b/paper-server/patches/features/0023-Eigencraft-redstone-implementation.patch
similarity index 100%
rename from feature-patches/1073-Eigencraft-redstone-implementation.patch
rename to paper-server/patches/features/0023-Eigencraft-redstone-implementation.patch
diff --git a/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch b/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
similarity index 99%
rename from feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch
rename to paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
index 30c303e0c2..789e1ac2c4 100644
--- a/feature-patches/1074-Add-Alternate-Current-redstone-implementation.patch
+++ b/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
@@ -2434,7 +2434,7 @@ index f02232ce97779db0d12a5d5da1d767326d78ea4c..12c9d60314c99fb65e640d255a2d0c6b
                      dropResources(state, level, pos);
                      level.removeBlock(pos, false);
 diff --git a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
-index 83f5da3e24834882193b9d7e3a788517e4b12b55..db7a7b091d2eb0f5b7efbe0db2b91709dee688e8 100644
+index 83f5da3e24834882193b9d7e3a788517e4b12b55..0c50a0bbbef1229230712b7c04364fea06859674 100644
 --- a/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
 +++ b/net/minecraft/world/level/redstone/ExperimentalRedstoneUtils.java
 @@ -17,6 +17,11 @@ public class ExperimentalRedstoneUtils {
@@ -2442,7 +2442,7 @@ index 83f5da3e24834882193b9d7e3a788517e4b12b55..db7a7b091d2eb0f5b7efbe0db2b91709
                  orientation = orientation.withFront(front);
              }
 +            // Paper start - Optimize redstone (Alternate Current) - use default front instead of random
-+            else if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
++            else if (level.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
 +                orientation = orientation.withFront(Direction.WEST);
 +            }
 +            // Paper end - Optimize redstone (Alternate Current)
diff --git a/feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch b/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
similarity index 100%
rename from feature-patches/1075-Improve-exact-choice-recipe-ingredients.patch
rename to paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch

From 2d83f05a6c5d6323d9b7962654ec21a644343086 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 13:32:46 +0100
Subject: [PATCH 219/285] Update watchdog patches

---
 .../1061-Improved-Watchdog-Support.patch      | 357 ++++++------------
 ...l-more-information-in-watchdog-dumps.patch | 229 ++++-------
 .../patches/features/0007-Anti-Xray.patch     |  11 +-
 .../0020-Rewrite-dataconverter-system.patch   |   4 +-
 .../0022-Lag-compensation-ticks.patch         |   6 +-
 .../io/papermc/paper/FeatureHooks.java.patch  |   9 +-
 .../server/MinecraftServer.java.patch         |   6 +-
 .../dedicated/DedicatedServer.java.patch      |   3 +-
 .../util/ServerShutdownThread.java            |  18 +-
 .../java/org/spigotmc/RestartCommand.java     |   2 +-
 .../java/org/spigotmc/WatchdogThread.java     |  32 +-
 paper-server/src/main/resources/log4j2.xml    |   2 +-
 12 files changed, 253 insertions(+), 426 deletions(-)

diff --git a/feature-patches/1061-Improved-Watchdog-Support.patch b/feature-patches/1061-Improved-Watchdog-Support.patch
index 3ed20e43df..90799d6e60 100644
--- a/feature-patches/1061-Improved-Watchdog-Support.patch
+++ b/feature-patches/1061-Improved-Watchdog-Support.patch
@@ -40,25 +40,28 @@ This is to ensure that if main isn't truely stuck, it's not manipulating state w
 This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they
 are properly accounted for and wont trip watchdog on init.
 
-diff --git a/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java b/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java
+diff --git a/io/papermc/paper/util/LogManagerShutdownThread.java b/io/papermc/paper/util/LogManagerShutdownThread.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3d7df554b89cff23f64da7ad48b5e4d26ac2baf7
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java
-@@ -0,0 +0,0 @@
++++ b/io/papermc/paper/util/LogManagerShutdownThread.java
+@@ -0,0 +1,29 @@
 +package io.papermc.paper.util;
 +
-+public class LogManagerShutdownThread extends Thread {
++import org.apache.logging.log4j.LogManager;
++
++public final class LogManagerShutdownThread extends Thread {
 +
 +    static LogManagerShutdownThread INSTANCE = new LogManagerShutdownThread();
-+    public static final void hook() {
++
++    public static void hook() {
 +        if (INSTANCE == null) {
 +            throw new IllegalStateException("Cannot re-hook after being unhooked");
 +        }
 +        Runtime.getRuntime().addShutdownHook(INSTANCE);
 +    }
 +
-+    public static final void unhook() {
++    public static void unhook() {
 +        Runtime.getRuntime().removeShutdownHook(INSTANCE);
 +        INSTANCE = null;
 +    }
@@ -69,38 +72,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    @Override
 +    public void run() {
-+        org.apache.logging.log4j.LogManager.shutdown();
++        LogManager.shutdown();
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/CrashReport.java
-+++ b/src/main/java/net/minecraft/CrashReport.java
-@@ -0,0 +0,0 @@ public class CrashReport {
+diff --git a/net/minecraft/CrashReport.java b/net/minecraft/CrashReport.java
+index 3e0e88afcf010d9a3d46e48bca5cbdf98fe97544..8bd7999c17c8772451f873966f8c90969aee1482 100644
+--- a/net/minecraft/CrashReport.java
++++ b/net/minecraft/CrashReport.java
+@@ -205,6 +205,7 @@ public class CrashReport {
      }
  
-     public static CrashReport forThrowable(Throwable cause, String title) {
+     public static CrashReport forThrowable(Throwable cause, String description) {
 +        if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper
          while (cause instanceof CompletionException && cause.getCause() != null) {
              cause = cause.getCause();
          }
-diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/Main.java
-+++ b/src/main/java/net/minecraft/server/Main.java
-@@ -0,0 +0,0 @@ public class Main {
-     @SuppressForbidden(reason = "System.out needed before bootstrap") // CraftBukkit - decompile error
+diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
+index 4437283a5d157eede121b98be0112c1067eded5e..fc9ec242743f755a1f0c9ec6bccd11c82375d655 100644
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
+@@ -68,6 +68,7 @@ public class Main {
+     )
      @DontObfuscate
-     public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring)
+     public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args)
 +        io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper
          SharedConstants.tryDetectVersion();
          /* CraftBukkit start - Replace everything
-         OptionParser optionparser = new OptionParser();
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         OptionParser optionParser = new OptionParser();
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a685c02aeb1 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -287,7 +287,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
      public int autosavePeriod;
      // Paper - don't store the vanilla dispatcher
@@ -109,17 +112,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // CraftBukkit end
      // Spigot start
      public static final int TPS = 20;
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -300,6 +300,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
- 
+     public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
 +    public volatile Thread shutdownThread; // Paper
-+    public volatile boolean abnormalExit = false; // Paper
-+
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
++    public volatile boolean abnormalExit; // Paper
+ 
+     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-         AtomicReference<S> atomicreference = new AtomicReference();
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -393,6 +395,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
          */
          // Paper end
@@ -127,7 +129,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
          // CraftBukkit end
          this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -877,6 +880,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      // CraftBukkit start
      private boolean hasStopped = false;
      private boolean hasLoggedStop = false; // Paper - Debugging
@@ -135,7 +137,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Object stopLock = new Object();
      public final boolean hasStopped() {
          synchronized (this.stopLock) {
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -892,6 +896,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.hasStopped = true;
          }
          if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -146,10 +148,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          // CraftBukkit end
          if (this.metricsRecorder.isRecording()) {
              this.cancelRecordingMetrics();
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
+@@ -964,6 +972,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+             this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
-         // Paper end - rewrite chunk system
+         // Spigot end
 +        // Paper start - Improved watchdog support - move final shutdown items here
 +        Util.shutdownExecutors();
 +        try {
@@ -162,7 +164,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public String getLocalIp() {
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1056,6 +1073,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      protected void runServer() {
          try {
@@ -170,7 +172,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              if (!this.initServer()) {
                  throw new IllegalStateException("Failed to initialize server");
              }
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1066,6 +1084,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
              this.server.spark.enableBeforePlugins(); // Paper - spark
              // Spigot start
@@ -188,20 +190,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              org.spigotmc.WatchdogThread.hasStarted = true; // Paper
              Arrays.fill( this.recentTps, 20 );
              // Paper start - further improve server tick loop
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1155,6 +1184,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
              }
-         } catch (Throwable throwable2) {
+         } catch (Throwable var69) {
 +            // Paper start
-+            if (throwable2 instanceof ThreadDeath) {
-+                MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", throwable2);
++            if (var69 instanceof ThreadDeath) {
++                MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", var69);
 +                return;
 +            }
 +            // Paper end
-             MinecraftServer.LOGGER.error("Encountered an unexpected exception", throwable2);
-             CrashReport crashreport = MinecraftServer.constructOrExtractCrashReport(throwable2);
- 
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+             LOGGER.error("Encountered an unexpected exception", var69);
+             CrashReport crashReport = constructOrExtractCrashReport(var69);
+             this.fillSystemReport(crashReport.getSystemReport());
+@@ -1177,15 +1212,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.services.profileCache().clearExecutor();
                  }
  
@@ -219,9 +221,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                //io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
 +                //this.onServerExit(); // Paper - moved into stop
              }
- 
          }
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+     }
+@@ -1289,6 +1324,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      @Override
      public TickTask wrapRunnable(Runnable runnable) {
@@ -234,37 +236,37 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return new TickTask(this.tickCount, runnable);
      }
  
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             this.resources.managers.updateStaticRegistryTags();
-             this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
-             this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
--            this.getPlayerList().saveAll();
-+            // Paper start
-+            if (Thread.currentThread() != this.serverThread) {
-+                return;
-+            }
-+            // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
-+            for (ServerPlayer player : this.getPlayerList().getPlayers()) {
-+                player.getAdvancements().save();
-+            }
-+            // Paper end
-             this.getPlayerList().reloadResources();
-             this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
-             this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
-diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-             long j = Util.getNanos() - i;
-             String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
- 
--            DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
-+            DedicatedServer.LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), s); // Paper - clarify startup log messages & add total time
-             if (dedicatedserverproperties.announcePlayerAchievements != null) {
-                 ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
+@@ -2085,7 +2126,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+                     this.resources.managers.updateStaticRegistryTags();
+                     this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
+                     this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
+-                    this.getPlayerList().saveAll();
++                    // Paper start
++                    if (Thread.currentThread() != this.serverThread) {
++                        return;
++                    }
++                    // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
++                    for (ServerPlayer player : this.getPlayerList().getPlayers()) {
++                        player.getAdvancements().save();
++                    }
++                    // Paper end
+                     this.getPlayerList().reloadResources();
+                     this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
+                     this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
+diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
+index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..d51d0c56e0cb68556ad366d52312bdb81ed17e9e 100644
+--- a/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -322,7 +322,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+             this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
+             long l = Util.getNanos() - nanos;
+             String string = String.format(Locale.ROOT, "%.3fs", l / 1.0E9);
+-            LOGGER.info("Done ({})! For help, type \"help\"", string);
++            LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), string); // Paper - clarify startup log messages & add total time
+             if (properties.announcePlayerAchievements != null) {
+                 this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
              }
-@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -418,7 +418,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
              // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
          }
  
@@ -274,7 +276,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -726,7 +727,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
      @Override
      public void stopServer() {
          super.stopServer();
@@ -283,173 +285,52 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          SkullBlockEntity.clear();
      }
  
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
+index d227714de0fe13544779fae6cf0e9ff6af5469c7..393bd2ec0962d3870f5b4cb74200e5467b50cdb8 100644
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -513,7 +513,7 @@ public abstract class PlayerList {
          this.cserver.getPluginManager().callEvent(playerQuitEvent);
-         entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
+         player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
  
--        entityplayer.doTick(); // SPIGOT-924
-+        if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
+-        player.doTick(); // SPIGOT-924
++        if (this.server.isSameThread()) player.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
          // CraftBukkit end
  
          // Paper start - Configurable player collision; Remove from collideRule team if needed
-diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-@@ -0,0 +0,0 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
-     public static boolean isNonRecoverable(Throwable exception) {
-         return exception instanceof ReportedException reportedException
+diff --git a/net/minecraft/util/thread/BlockableEventLoop.java b/net/minecraft/util/thread/BlockableEventLoop.java
+index 186c1b2e3599770385150eb7acdcd890aa5835eb..bfea9a2ae5e0bd5dae2873f715d192dfcbe97ee5 100644
+--- a/net/minecraft/util/thread/BlockableEventLoop.java
++++ b/net/minecraft/util/thread/BlockableEventLoop.java
+@@ -169,6 +169,6 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
+     public static boolean isNonRecoverable(Throwable error) {
+         return error instanceof ReportedException reportedException
              ? isNonRecoverable(reportedException.getCause())
--            : exception instanceof OutOfMemoryError || exception instanceof StackOverflowError;
-+            : exception instanceof OutOfMemoryError || exception instanceof StackOverflowError || exception instanceof ThreadDeath; // Paper
+-            : error instanceof OutOfMemoryError || error instanceof StackOverflowError;
++            : error instanceof OutOfMemoryError || error instanceof StackOverflowError || error instanceof ThreadDeath; // Paper
      }
  }
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index cb6ca60af3d3f90501e4693a78466b9f7462362d..127e25dab3a5e4df9cdf8eefd0485ea07b7696d9 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -863,6 +863,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          try {
-             tickConsumer.accept(entity);
-         } catch (Throwable throwable) {
-+            if (throwable instanceof ThreadDeath) throw throwable; // Paper
+             consumerEntity.accept(entity);
+         } catch (Throwable var6) {
++            if (var6 instanceof ThreadDeath) throw var6; // Paper
              // Paper start - Prevent block entity and entity crashes
              final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
-             MinecraftServer.LOGGER.error(msg, throwable);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
+             MinecraftServer.LOGGER.error(msg, var6);
+diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
+index d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba..205f5a687eb685284a2e403f3eb6bdc694fc5423 100644
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -855,6 +855,7 @@ public class LevelChunk extends ChunkAccess {
  
-                         gameprofilerfiller.pop();
-                     } catch (Throwable throwable) {
-+                        if (throwable instanceof ThreadDeath) throw throwable; // Paper
+                         profilerFiller.pop();
+                     } catch (Throwable var5) {
++                        if (var5 instanceof ThreadDeath) throw var5; // Paper
                          // Paper start - Prevent block entity and entity crashes
                          final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
-                         net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-@@ -0,0 +0,0 @@ public class ServerShutdownThread extends Thread {
-     @Override
-     public void run() {
-         try {
-+            // Paper start - try to shutdown on main
-+            server.safeShutdown(false, false);
-+            for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) {
-+                Thread.sleep(100);
-+            }
-+            if (server.hasStopped()) {
-+                while (!server.hasFullyShutdown) Thread.sleep(1000);
-+                return;
-+            }
-+            // Looks stalled, close async
-             org.spigotmc.AsyncCatcher.enabled = false; // Spigot
-+            server.forceTicks = true;
-             this.server.close();
-+            while (!server.hasFullyShutdown) Thread.sleep(1000);
-+        } catch (InterruptedException e) {
-+            e.printStackTrace();
-+            // Paper end
-         } finally {
-+            org.apache.logging.log4j.LogManager.shutdown(); // Paper
-             try {
--                net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+                //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
-             } catch (Exception e) {
-             }
-         }
-diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/spigotmc/RestartCommand.java
-+++ b/src/main/java/org/spigotmc/RestartCommand.java
-@@ -0,0 +0,0 @@ public class RestartCommand extends Command
-     // Paper end
- 
-     // Paper start - copied from above and modified to return if the hook registered
--    private static boolean addShutdownHook(String restartScript)
-+    public static boolean addShutdownHook(String restartScript) // Paper
-     {
-         String[] split = restartScript.split( " " );
-         if ( split.length > 0 && new File( split[0] ).isFile() )
-diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/spigotmc/WatchdogThread.java
-+++ b/src/main/java/org/spigotmc/WatchdogThread.java
-@@ -0,0 +0,0 @@ import org.bukkit.Bukkit;
- public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThread // Paper - rewrite chunk system
- {
- 
-+    public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper - Improved watchdog support
-     private static WatchdogThread instance;
-     private long timeoutTime;
-     private boolean restart;
-@@ -0,0 +0,0 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
-     {
-         if ( WatchdogThread.instance == null )
-         {
-+            if (timeoutTime <= 0) timeoutTime = 300; // Paper
-             WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
-             WatchdogThread.instance.start();
-         } else
-@@ -0,0 +0,0 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
-             // Paper start
-             Logger log = Bukkit.getServer().getLogger();
-             long currentTime = WatchdogThread.monotonicMillis();
--            if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable
-+            MinecraftServer server = MinecraftServer.getServer();
-+            if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable
-             {
--                boolean isLongTimeout = currentTime > lastTick + timeoutTime;
-+                boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000);
-                 // Don't spam early warning dumps
-                 if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue;
--                if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
-+                if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
-                 lastEarlyWarning = currentTime;
-                 if (isLongTimeout) {
-                 // Paper end
-@@ -0,0 +0,0 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
- 
-                 if ( isLongTimeout )
-                 {
--                if ( this.restart && !MinecraftServer.getServer().hasStopped() )
-+                if ( !server.hasStopped() )
-                 {
--                    RestartCommand.restart();
-+                    AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
-+                    server.forceTicks = true;
-+                    if (restart) {
-+                        RestartCommand.addShutdownHook( SpigotConfig.restartScript );
-+                    }
-+                    // try one last chance to safe shutdown on main incase it 'comes back'
-+                    server.abnormalExit = true;
-+                    server.safeShutdown(false, restart);
-+                    try {
-+                        Thread.sleep(1000);
-+                    } catch (InterruptedException e) {
-+                        e.printStackTrace();
-+                    }
-+                    if (!server.hasStopped()) {
-+                        server.close();
-+                    }
-                 }
-                 break;
-                 } // Paper end
-diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/log4j2.xml
-+++ b/src/main/resources/log4j2.xml
-@@ -0,0 +0,0 @@
- <?xml version="1.0" encoding="UTF-8"?>
--<Configuration status="WARN">
-+<Configuration status="WARN" shutdownHook="disable">
-     <Appenders>
-         <Queue name="ServerGuiConsole">
-             <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
+                         net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
diff --git a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch b/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
index 9d5a4deee3..20f825b57b 100644
--- a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
+++ b/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
@@ -6,32 +6,55 @@ Subject: [PATCH] Detail more information in watchdog dumps
 - Dump position, world, velocity, and uuid for currently ticking entities
 - Dump player name, player uuid, position, and world for packet handling
 
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
+index 8fe485c5bf79804bb4d1f774f95a92b14a576e80..0bcae6256d3b3fb6b2e0c2f23907d4659b236ef3 100644
+--- a/net/minecraft/network/Connection.java
++++ b/net/minecraft/network/Connection.java
+@@ -603,7 +603,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
              if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
                  || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
                  || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
 +            // Paper start - detailed watchdog information
 +            net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
 +            try {
-             tickablepacketlistener.tick();
+             tickablePacketListener.tick();
 +            } finally {
 +                net.minecraft.network.protocol.PacketUtils.packetProcessing.pop();
 +            } // Paper end - detailed watchdog information
              } // Paper end - Buffer joins to world
          }
  
-diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-@@ -0,0 +0,0 @@ public class PacketUtils {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
+diff --git a/net/minecraft/network/protocol/PacketUtils.java b/net/minecraft/network/protocol/PacketUtils.java
+index e65c62dbe4c1560ae153e4c4344e9194c783a2f4..4535858701b2bb232b9d2feb2af6551526232ddc 100644
+--- a/net/minecraft/network/protocol/PacketUtils.java
++++ b/net/minecraft/network/protocol/PacketUtils.java
+@@ -21,6 +21,8 @@ public class PacketUtils {
+     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
+         if (!executor.isSameThread()) {
+             executor.executeIfPossible(() -> {
++                packetProcessing.push(processor); // Paper - detailed watchdog information
++                try { // Paper - detailed watchdog information
+                 if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
+                 if (processor.shouldHandleMessage(packet)) {
+                     try {
+@@ -35,6 +37,12 @@ public class PacketUtils {
+                 } else {
+                     LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
+                 }
++                // Paper start - detailed watchdog information
++                } finally {
++                    totalMainThreadPacketsProcessed.getAndIncrement();
++                    packetProcessing.pop();
++                }
++                // Paper end - detailed watchdog information
+             });
+             throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
+         }
+@@ -61,4 +69,22 @@ public class PacketUtils {
  
+         packetListener.fillCrashReport(crashReport);
+     }
++
 +    // Paper start - detailed watchdog information
 +    public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
 +    static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
@@ -41,46 +64,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    public static java.util.List<PacketListener> getCurrentPacketProcessors() {
-+        java.util.List<PacketListener> ret = new java.util.ArrayList<>(4);
++        java.util.List<PacketListener> listeners = new java.util.ArrayList<>(4);
 +        for (PacketListener listener : packetProcessing) {
-+            ret.add(listener);
++            listeners.add(listener);
 +        }
 +
-+        return ret;
++        return listeners;
 +    }
 +    // Paper end - detailed watchdog information
-+
-     public PacketUtils() {}
- 
-     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException {
-@@ -0,0 +0,0 @@ public class PacketUtils {
-     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, BlockableEventLoop<?> engine) throws RunningOnDifferentThreadException {
-         if (!engine.isSameThread()) {
-             engine.executeIfPossible(() -> {
-+                packetProcessing.push(listener); // Paper - detailed watchdog information
-+                try { // Paper - detailed watchdog information
-                 if (listener instanceof ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // CraftBukkit - Don't handle sync packets for kicked players
-                 if (listener.shouldHandleMessage(packet)) {
-                     try {
-@@ -0,0 +0,0 @@ public class PacketUtils {
-                 } else {
-                     PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
-                 }
-+                // Paper start - detailed watchdog information
-+                } finally {
-+                    totalMainThreadPacketsProcessed.getAndIncrement();
-+                    packetProcessing.pop();
-+                }
-+                // Paper end - detailed watchdog information
- 
-             });
-             throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
- 
+ }
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index 9cc47bda7197ca3f63b0ede9905c9a13931f84ed..05f45b490e823a455b23b23b26a7da3b447059ea 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -956,7 +956,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         }
      }
  
 +    // Paper start - log detailed entity tick information
@@ -103,13 +101,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                currentlyTickingEntity.lazySet(entity);
 +            }
 +            // Paper end - log detailed entity tick information
-         // Spigot start
-         /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out EAR 2
-             entity.tickCount++;
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         entity.setOldPosAndRot();
+         ProfilerFiller profilerFiller = Profiler.get();
+         entity.tickCount++;
+@@ -972,6 +991,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         for (Entity entity1 : entity.getPassengers()) {
              this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
          }
- 
 +        // Paper start - log detailed entity tick information
 +        } finally {
 +            if (currentlyTickingEntity.get() == entity) {
@@ -119,12 +117,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - log detailed entity tick information
      }
  
-     private void tickPassenger(Entity vehicle, Entity passenger, boolean isActive) { // Paper - EAR 2
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
+index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..1ff09959aa95d9822ccb6724bbb3f441c768511a 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -956,8 +956,43 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return this.onGround;
      }
  
@@ -168,8 +166,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (this.noPhysics) {
              this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
          } else {
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-                 gameprofilerfiller.pop();
+@@ -1075,6 +1110,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+                 profilerFiller.pop();
              }
          }
 +        // Paper start - detailed watchdog information
@@ -181,115 +179,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Paper end - detailed watchdog information
      }
  
-     private void applyMovementEmissionAndPlaySound(Entity.MovementEmission moveEffect, Vec3 movement, BlockPos landingPos, BlockState landingState) {
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
+@@ -4348,7 +4390,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
-     public void setDeltaMovement(Vec3 velocity) {
+     public void setDeltaMovement(Vec3 deltaMovement) {
 +        synchronized (this.posLock) { // Paper
-         this.deltaMovement = velocity;
+         this.deltaMovement = deltaMovement;
 +        } // Paper
      }
  
-     public void addDeltaMovement(Vec3 velocity) {
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     public void addDeltaMovement(Vec3 addend) {
+@@ -4445,7 +4489,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          }
          // Paper end - Fix MC-4
          if (this.position.x != x || this.position.y != y || this.position.z != z) {
 +            synchronized (this.posLock) { // Paper
              this.position = new Vec3(x, y, z);
 +            } // Paper
-             int i = Mth.floor(x);
-             int j = Mth.floor(y);
-             int k = Mth.floor(z);
-diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/spigotmc/WatchdogThread.java
-+++ b/src/main/java/org/spigotmc/WatchdogThread.java
-@@ -0,0 +0,0 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
-     private volatile long lastTick;
-     private volatile boolean stopping;
- 
-+    // Paper start - log detailed tick information
-+    private void dumpEntity(net.minecraft.world.entity.Entity entity) {
-+        Logger log = Bukkit.getServer().getLogger();
-+        double posX, posY, posZ;
-+        net.minecraft.world.phys.Vec3 mot;
-+        double moveStartX, moveStartY, moveStartZ;
-+        net.minecraft.world.phys.Vec3 moveVec;
-+        synchronized (entity.posLock) {
-+            posX = entity.getX();
-+            posY = entity.getY();
-+            posZ = entity.getZ();
-+            mot = entity.getDeltaMovement();
-+            moveStartX = entity.getMoveStartX();
-+            moveStartY = entity.getMoveStartY();
-+            moveStartZ = entity.getMoveStartZ();
-+            moveVec = entity.getMoveVector();
-+        }
-+
-+        String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString();
-+        java.util.UUID entityUUID = entity.getUUID();
-+        net.minecraft.world.level.Level world = entity.level();
-+
-+        log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName());
-+        log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger());
-+        log.log(Level.SEVERE, "Entity UUID: " + entityUUID);
-+        log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
-+        log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
-+        log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox());
-+        if (moveVec != null) {
-+            log.log(Level.SEVERE, "Move call information: ");
-+            log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
-+            log.log(Level.SEVERE, "Move vector: " + moveVec.toString());
-+        }
-+    }
-+
-+    private void dumpTickingInfo() {
-+        Logger log = Bukkit.getServer().getLogger();
-+
-+        // ticking entities
-+        for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) {
-+            this.dumpEntity(entity);
-+            net.minecraft.world.entity.Entity vehicle = entity.getVehicle();
-+            if (vehicle != null) {
-+                log.log(Level.SEVERE, "Detailing vehicle for above entity:");
-+                this.dumpEntity(vehicle);
-+            }
-+        }
-+
-+        // packet processors
-+        for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) {
-+            if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) {
-+                net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player;
-+                long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets();
-+                if (player == null) {
-+                    log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener);
-+                    log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
-+                } else {
-+                    this.dumpEntity(player);
-+                    net.minecraft.world.entity.Entity vehicle = player.getVehicle();
-+                    if (vehicle != null) {
-+                        log.log(Level.SEVERE, "Detailing vehicle for above entity:");
-+                        this.dumpEntity(vehicle);
-+                    }
-+                    log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
-+                }
-+            } else {
-+                log.log(Level.SEVERE, "Handling packet for connection: " + packetListener);
-+            }
-+        }
-+    }
-+    // Paper end - log detailed tick information
-+
-     private WatchdogThread(long timeoutTime, boolean restart)
-     {
-         super( "Paper Watchdog Thread" );
-@@ -0,0 +0,0 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
-                 log.log( Level.SEVERE, "------------------------------" );
-                 log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
-                 ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - rewrite chunk system
-+                this.dumpTickingInfo(); // Paper - log detailed tick information
-                 WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
-                 log.log( Level.SEVERE, "------------------------------" );
-                 //
+             int floor = Mth.floor(x);
+             int floor1 = Mth.floor(y);
+             int floor2 = Mth.floor(z);
diff --git a/paper-server/patches/features/0007-Anti-Xray.patch b/paper-server/patches/features/0007-Anti-Xray.patch
index b322872f20..8e51f5c6ae 100644
--- a/paper-server/patches/features/0007-Anti-Xray.patch
+++ b/paper-server/patches/features/0007-Anti-Xray.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Anti-Xray
 
 
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index aad9d9687dffb872a12ba0ba39d674895b7474e7..764daee7cd619c56314bcea9a4c35702afcb262d 100644
+index 0eba4fce940b90e67f3746480c040178ba9f5525..3bdbd3d566dee767204d898e0bb4f82f0060d9ca 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.longs.LongSets;
@@ -16,7 +16,7 @@ index aad9d9687dffb872a12ba0ba39d674895b7474e7..764daee7cd619c56314bcea9a4c35702
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
-@@ -35,20 +36,25 @@ public final class FeatureHooks {
+@@ -36,20 +37,25 @@ public final class FeatureHooks {
      }
  
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
@@ -46,13 +46,6 @@ index aad9d9687dffb872a12ba0ba39d674895b7474e7..764daee7cd619c56314bcea9a4c35702
      }
  
      public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
-@@ -74,4 +80,4 @@ public final class FeatureHooks {
-     public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
-         return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
-     }
--}
-\ No newline at end of file
-+}
 diff --git a/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java b/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
 index d4872b7f4e9591b3b1c67406312905851303f521..cb41460e94161675e2ab43f4b1b5286ee38e2e13 100644
 --- a/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java
diff --git a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
index 844c0f9320..4e1356ce74 100644
--- a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
@@ -30621,10 +30621,10 @@ index 1110ca4075a1bbaa46b66686435dab91b275c945..c2218630c3074c8b3f82364e37503b12
          return structureTemplate.save(new CompoundTag());
      }
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index d450d4af96716caff4b29a84d1d83ec4010854f0..646c2f2b617ed706021c83c9fc4492860dfdd4e9 100644
+index 7b233bd4c5c373fe38585e0f8d3e6367dd357741..fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -303,6 +303,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
diff --git a/paper-server/patches/features/0022-Lag-compensation-ticks.patch b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
index 1d692f7144..50c0323115 100644
--- a/paper-server/patches/features/0022-Lag-compensation-ticks.patch
+++ b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
@@ -8,10 +8,10 @@ Areas affected by lag comepnsation:
  - Eating food items
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..8aa9ae2925ad44d57a27be3e520fcf20e30237d6 100644
+index fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06..22dc6bec58702762e4a31415f9aed2df2b3ad0d6 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -299,6 +299,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
      public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
@@ -19,7 +19,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..8aa9ae2925ad44d57a27be3e520fcf20
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -1564,6 +1565,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1566,6 +1567,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          for (ServerLevel serverLevel : this.getAllLevels()) {
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
index 69bf0f769f..5c3b4d6856 100644
--- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
+++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
@@ -1,6 +1,6 @@
 --- /dev/null
 +++ b/io/papermc/paper/FeatureHooks.java
-@@ -1,0 +_,77 @@
+@@ -1,0 +_,84 @@
 +package io.papermc.paper;
 +
 +import io.papermc.paper.command.PaperSubcommand;
@@ -16,6 +16,7 @@
 +import net.minecraft.core.Registry;
 +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
 +import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
 +import net.minecraft.world.entity.monster.Spider;
 +import net.minecraft.world.level.ChunkPos;
 +import net.minecraft.world.level.Level;
@@ -77,4 +78,10 @@
 +    public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
 +        return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
 +    }
++
++    public static void dumpTickingInfo() {
++    }
++
++    private static void dumpEntity(final Entity entity) {
++    }
 +}
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index eb9c14d4a5..3aa38008fc 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -41,7 +41,7 @@
      @Nullable
      private KeyPair keyPair;
      @Nullable
-@@ -271,10 +_,33 @@
+@@ -271,10 +_,35 @@
      private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
      private final DiscontinuousFrame tickFrame;
  
@@ -54,7 +54,7 @@
 +    public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
 +    public int autosavePeriod;
 +    // Paper - don't store the vanilla dispatcher
-+    private boolean forceTicks;
++    public boolean forceTicks;
 +    // CraftBukkit end
 +    // Spigot start
 +    public static final int TPS = 20;
@@ -63,6 +63,8 @@
 +    @Deprecated(forRemoval = true) // Paper
 +    public final double[] recentTps = new double[ 3 ];
 +    // Spigot end
++    public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
++    public volatile boolean abnormalExit; // Paper - Improved watchdog support
 +    public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
 +    public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
 +    private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index def9b71a6f..243d71c069 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -301,7 +301,7 @@
      }
  
      @Override
-@@ -271,12 +_,14 @@
+@@ -271,12 +_,15 @@
          }
  
          if (this.rconThread != null) {
@@ -314,6 +314,7 @@
 +            // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
          }
 +
++        this.hasFullyShutdown = true; // Paper - Improved watchdog support
 +        System.exit(0); // CraftBukkit
      }
  
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
index c6e8441e29..e8e93538df 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -12,11 +12,27 @@ public class ServerShutdownThread extends Thread {
     @Override
     public void run() {
         try {
+            // Paper start - try to shutdown on main
+            server.safeShutdown(false, false);
+            for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) {
+                Thread.sleep(100);
+            }
+            if (server.hasStopped()) {
+                while (!server.hasFullyShutdown) Thread.sleep(1000);
+                return;
+            }
+            // Looks stalled, close async
             org.spigotmc.AsyncCatcher.enabled = false; // Spigot
+            server.forceTicks = true;
             this.server.close();
+            while (!server.hasFullyShutdown) Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            // Paper end
         } finally {
+            org.apache.logging.log4j.LogManager.shutdown(); // Paper
             try {
-                net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
+                //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
             } catch (Exception e) {
             }
         }
diff --git a/paper-server/src/main/java/org/spigotmc/RestartCommand.java b/paper-server/src/main/java/org/spigotmc/RestartCommand.java
index b87f66ad04..3287d39951 100644
--- a/paper-server/src/main/java/org/spigotmc/RestartCommand.java
+++ b/paper-server/src/main/java/org/spigotmc/RestartCommand.java
@@ -105,7 +105,7 @@ public class RestartCommand extends Command {
     // Paper end
 
     // Paper start - copied from above and modified to return if the hook registered
-    private static boolean addShutdownHook(String restartScript) {
+    public static boolean addShutdownHook(String restartScript) {
         String[] split = restartScript.split(" ");
         if (split.length > 0 && new File(split[0]).isFile()) {
             Thread shutdownHook = new Thread(() -> {
diff --git a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
index 7fa7816014..0c67e3297e 100644
--- a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
@@ -1,17 +1,19 @@
 package org.spigotmc;
 
+import io.papermc.paper.FeatureHooks;
+import io.papermc.paper.configuration.GlobalConfiguration;
 import java.lang.management.ManagementFactory;
 import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import io.papermc.paper.configuration.GlobalConfiguration;
 import net.minecraft.server.MinecraftServer;
 import org.bukkit.Bukkit;
 import org.bukkit.craftbukkit.CraftServer;
 
 public class WatchdogThread extends Thread {
 
+    public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper - Improved watchdog support
     private static WatchdogThread instance;
     private long timeoutTime;
     private boolean restart;
@@ -36,6 +38,7 @@ public class WatchdogThread extends Thread {
 
     public static void doStart(int timeoutTime, boolean restart) {
         if (WatchdogThread.instance == null) {
+            if (timeoutTime <= 0) timeoutTime = 300; // Paper
             WatchdogThread.instance = new WatchdogThread(timeoutTime * 1000L, restart);
             WatchdogThread.instance.start();
         } else {
@@ -60,14 +63,15 @@ public class WatchdogThread extends Thread {
             // Paper start
             Logger logger = Bukkit.getServer().getLogger();
             long currentTime = WatchdogThread.monotonicMillis();
-            if (this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) { // Paper - Add property to disable
-                boolean isLongTimeout = currentTime > this.lastTick + this.timeoutTime;
+            MinecraftServer server = MinecraftServer.getServer();
+            if (this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG))) { // Paper - add property to disable
+                boolean isLongTimeout = currentTime > this.lastTick + this.timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > this.lastTick + 1000);
                 // Don't spam early warning dumps
                 if (!isLongTimeout && (this.earlyWarningEvery <= 0 ||
                     !hasStarted || currentTime < this.lastEarlyWarning + this.earlyWarningEvery ||
                     currentTime < this.lastTick + this.earlyWarningDelay))
                     continue;
-                if (!isLongTimeout && MinecraftServer.getServer().hasStopped())
+                if (!isLongTimeout && server.hasStopped())
                     continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
                 this.lastEarlyWarning = currentTime;
                 if (isLongTimeout) {
@@ -106,6 +110,7 @@ public class WatchdogThread extends Thread {
                 // Paper end - Different message for short timeout
                 logger.log(Level.SEVERE, "------------------------------");
                 logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):"); // Paper
+                FeatureHooks.dumpTickingInfo(); // Paper - log detailed tick information
                 WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger);
                 logger.log(Level.SEVERE, "------------------------------");
 
@@ -123,8 +128,23 @@ public class WatchdogThread extends Thread {
                 logger.log(Level.SEVERE, "------------------------------");
 
                 if (isLongTimeout) {
-                    if (this.restart && !MinecraftServer.getServer().hasStopped()) {
-                        RestartCommand.restart();
+                    if (!server.hasStopped()) {
+                        AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
+                        server.forceTicks = true;
+                        if (this.restart) {
+                            RestartCommand.addShutdownHook(SpigotConfig.restartScript);
+                        }
+                        // try one last chance to safe shutdown on main incase it 'comes back'
+                        server.abnormalExit = true;
+                        server.safeShutdown(false, this.restart);
+                        try {
+                            Thread.sleep(1000);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                        if (!server.hasStopped()) {
+                            server.close();
+                        }
                     }
                     break;
                 }
diff --git a/paper-server/src/main/resources/log4j2.xml b/paper-server/src/main/resources/log4j2.xml
index 637d64da99..d2a75850af 100644
--- a/paper-server/src/main/resources/log4j2.xml
+++ b/paper-server/src/main/resources/log4j2.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="WARN">
+<Configuration status="WARN" shutdownHook="disable">
     <Appenders>
         <Queue name="ServerGuiConsole">
             <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />

From 183782ad2ead5f0f1a54a44eab1c8a26cd9f7bda Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 13:39:42 +0100
Subject: [PATCH 220/285] Fixup

---
 .../1061-Improved-Watchdog-Support.patch      | 42 ++++------
 ...l-more-information-in-watchdog-dumps.patch | 79 +++++++++++++++++++
 2 files changed, 95 insertions(+), 26 deletions(-)

diff --git a/feature-patches/1061-Improved-Watchdog-Support.patch b/feature-patches/1061-Improved-Watchdog-Support.patch
index 90799d6e60..c19faff35a 100644
--- a/feature-patches/1061-Improved-Watchdog-Support.patch
+++ b/feature-patches/1061-Improved-Watchdog-Support.patch
@@ -100,19 +100,10 @@ index 4437283a5d157eede121b98be0112c1067eded5e..fc9ec242743f755a1f0c9ec6bccd11c8
          /* CraftBukkit start - Replace everything
          OptionParser optionParser = new OptionParser();
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a685c02aeb1 100644
+index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae741beb75 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -287,7 +287,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
-     public int autosavePeriod;
-     // Paper - don't store the vanilla dispatcher
--    private boolean forceTicks;
-+    public boolean forceTicks; // Paper - Improved watchdog support
-     // CraftBukkit end
-     // Spigot start
-     public static final int TPS = 20;
-@@ -300,6 +300,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -302,6 +302,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
      public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
@@ -121,7 +112,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -393,6 +395,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -395,6 +397,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
          */
          // Paper end
@@ -129,7 +120,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
          Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
          // CraftBukkit end
          this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
-@@ -877,6 +880,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -879,6 +882,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      // CraftBukkit start
      private boolean hasStopped = false;
      private boolean hasLoggedStop = false; // Paper - Debugging
@@ -137,7 +128,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
      private final Object stopLock = new Object();
      public final boolean hasStopped() {
          synchronized (this.stopLock) {
-@@ -892,6 +896,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -894,6 +898,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.hasStopped = true;
          }
          if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -148,7 +139,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
          // CraftBukkit end
          if (this.metricsRecorder.isRecording()) {
              this.cancelRecordingMetrics();
-@@ -964,6 +972,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -966,6 +974,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
          // Spigot end
@@ -164,7 +155,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
      }
  
      public String getLocalIp() {
-@@ -1056,6 +1073,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1058,6 +1075,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      protected void runServer() {
          try {
@@ -172,7 +163,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
              if (!this.initServer()) {
                  throw new IllegalStateException("Failed to initialize server");
              }
-@@ -1066,6 +1084,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1068,6 +1086,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
              this.server.spark.enableBeforePlugins(); // Paper - spark
              // Spigot start
@@ -190,7 +181,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
              org.spigotmc.WatchdogThread.hasStarted = true; // Paper
              Arrays.fill( this.recentTps, 20 );
              // Paper start - further improve server tick loop
-@@ -1155,6 +1184,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1157,6 +1186,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
              }
          } catch (Throwable var69) {
@@ -203,7 +194,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
              LOGGER.error("Encountered an unexpected exception", var69);
              CrashReport crashReport = constructOrExtractCrashReport(var69);
              this.fillSystemReport(crashReport.getSystemReport());
-@@ -1177,15 +1212,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1179,15 +1214,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.services.profileCache().clearExecutor();
                  }
  
@@ -223,7 +214,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
              }
          }
      }
-@@ -1289,6 +1324,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1291,6 +1326,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      @Override
      public TickTask wrapRunnable(Runnable runnable) {
@@ -236,7 +227,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
          return new TickTask(this.tickCount, runnable);
      }
  
-@@ -2085,7 +2126,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2087,7 +2128,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.resources.managers.updateStaticRegistryTags();
                      this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
                      this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
@@ -254,7 +245,7 @@ index 8aa9ae2925ad44d57a27be3e520fcf20e30237d6..30b2bce976e3bf4c8e0b165cac527a68
                      this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
                      this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..d51d0c56e0cb68556ad366d52312bdb81ed17e9e 100644
+index 118f4ebb617d304e9a1cac2f9a853dc219a42456..ef39268caa59836506928582e88bc81e9fb22e88 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
 @@ -322,7 +322,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -266,17 +257,16 @@ index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..d51d0c56e0cb68556ad366d52312bdb8
              if (properties.announcePlayerAchievements != null) {
                  this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
              }
-@@ -418,7 +418,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-             // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
+@@ -419,7 +419,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
          }
  
+         this.hasFullyShutdown = true; // Paper - Improved watchdog support
 -        System.exit(0); // CraftBukkit
-+        hasFullyShutdown = true; // Paper
 +        System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
      }
  
      @Override
-@@ -726,7 +727,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -727,7 +727,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
      @Override
      public void stopServer() {
          super.stopServer();
diff --git a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch b/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
index 20f825b57b..aea5521520 100644
--- a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
+++ b/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
@@ -6,6 +6,85 @@ Subject: [PATCH] Detail more information in watchdog dumps
 - Dump position, world, velocity, and uuid for currently ticking entities
 - Dump player name, player uuid, position, and world for packet handling
 
+diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
+index d3aebc7f833764351c8e5fe1fad1aa2f8718ca37..5a561c8f48f4b5f7a3077c21f5ddd19cbcaa6aac 100644
+--- a/io/papermc/paper/FeatureHooks.java
++++ b/io/papermc/paper/FeatureHooks.java
+@@ -83,8 +83,72 @@ public final class FeatureHooks {
+     }
+ 
+     public static void dumpTickingInfo() {
++        java.util.logging.Logger log = org.bukkit.Bukkit.getServer().getLogger();
++
++        // ticking entities
++        for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) {
++            dumpEntity(entity);
++            net.minecraft.world.entity.Entity vehicle = entity.getVehicle();
++            if (vehicle != null) {
++                log.log(java.util.logging.Level.SEVERE, "Detailing vehicle for above entity:");
++                dumpEntity(vehicle);
++            }
++        }
++
++        // packet processors
++        for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) {
++            if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) {
++                net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player;
++                long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets();
++                if (player == null) {
++                    log.log(java.util.logging.Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener);
++                    log.log(java.util.logging.Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
++                } else {
++                    dumpEntity(player);
++                    net.minecraft.world.entity.Entity vehicle = player.getVehicle();
++                    if (vehicle != null) {
++                        log.log(java.util.logging.Level.SEVERE, "Detailing vehicle for above entity:");
++                        dumpEntity(vehicle);
++                    }
++                    log.log(java.util.logging.Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
++                }
++            } else {
++                log.log(java.util.logging.Level.SEVERE, "Handling packet for connection: " + packetListener);
++            }
++        }
+     }
+ 
+     private static void dumpEntity(final Entity entity) {
++        java.util.logging.Logger log = org.bukkit.Bukkit.getServer().getLogger();
++        double posX, posY, posZ;
++        net.minecraft.world.phys.Vec3 mot;
++        double moveStartX, moveStartY, moveStartZ;
++        net.minecraft.world.phys.Vec3 moveVec;
++        synchronized (entity.posLock) {
++            posX = entity.getX();
++            posY = entity.getY();
++            posZ = entity.getZ();
++            mot = entity.getDeltaMovement();
++            moveStartX = entity.getMoveStartX();
++            moveStartY = entity.getMoveStartY();
++            moveStartZ = entity.getMoveStartZ();
++            moveVec = entity.getMoveVector();
++        }
++
++        String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString();
++        java.util.UUID entityUUID = entity.getUUID();
++        net.minecraft.world.level.Level world = entity.level();
++
++        log.log(java.util.logging.Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName());
++        log.log(java.util.logging.Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger());
++        log.log(java.util.logging.Level.SEVERE, "Entity UUID: " + entityUUID);
++        log.log(java.util.logging.Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
++        log.log(java.util.logging.Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
++        log.log(java.util.logging.Level.SEVERE, "Entity AABB: " + entity.getBoundingBox());
++        if (moveVec != null) {
++            log.log(java.util.logging.Level.SEVERE, "Move call information: ");
++            log.log(java.util.logging.Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
++            log.log(java.util.logging.Level.SEVERE, "Move vector: " + moveVec.toString());
++        }
+     }
+-}
+\ No newline at end of file
++}
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
 index 8fe485c5bf79804bb4d1f774f95a92b14a576e80..0bcae6256d3b3fb6b2e0c2f23907d4659b236ef3 100644
 --- a/net/minecraft/network/Connection.java

From 7caf863b526572460e7642dad62d2b46b5f0bfba Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 16:50:59 +0100
Subject: [PATCH 221/285] Small diff cleanups from patch updating And remove a
 dumb log change in PacketEncoder

---
 .../0005-Allow-Saving-of-Oversized-Chunks.patch      |  6 +++---
 .../mojang/datafixers/DataFixerBuilder.java.patch    |  2 +-
 .../core/cauldron/CauldronInteraction.java.patch     |  4 ++--
 .../dispenser/BoatDispenseItemBehavior.java.patch    |  6 +++---
 .../dispenser/DefaultDispenseItemBehavior.java.patch |  8 +++-----
 .../net/minecraft/nbt/ByteArrayTag.java.patch        |  5 -----
 .../sources/net/minecraft/nbt/IntArrayTag.java.patch |  5 -----
 .../sources/net/minecraft/nbt/NbtIo.java.patch       |  5 -----
 .../net/minecraft/network/PacketEncoder.java.patch   | 10 +---------
 .../minecraft/network/chat/ChatDecorator.java.patch  |  4 ++--
 .../net/minecraft/network/chat/Component.java.patch  |  2 +-
 .../ClientboundPlayerInfoUpdatePacket.java.patch     |  4 ++--
 .../game/ClientboundSystemChatPacket.java.patch      |  5 -----
 .../game/ServerboundUseItemOnPacket.java.patch       |  5 -----
 .../game/ServerboundUseItemPacket.java.patch         |  5 -----
 .../handshake/ClientIntentionPacket.java.patch       |  5 -----
 .../server/commands/DifficultyCommand.java.patch     |  7 +++----
 .../server/commands/ReloadCommand.java.patch         | 12 ++++++------
 .../server/players/UserBanListEntry.java.patch       |  5 -----
 .../minecraft/stats/ServerStatsCounter.java.patch    |  5 -----
 .../world/entity/ai/attributes/Attributes.java.patch |  5 -----
 .../minecraft/world/item/DebugStickItem.java.patch   |  5 -----
 .../chunk/ChunkGeneratorStructureState.java.patch    |  5 -----
 .../minecraft/world/level/chunk/DataLayer.java.patch |  7 -------
 .../world/level/chunk/storage/RegionFile.java.patch  |  5 -----
 25 files changed, 27 insertions(+), 110 deletions(-)
 delete mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch

diff --git a/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch b/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
index 9c998777de..950f460736 100644
--- a/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
+++ b/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
@@ -31,10 +31,10 @@ this fix, as the data will remain in the oversized file. Once the server returns
 to a jar with this fix, the data will be restored.
 
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 2b4fa89f15a42ddd627983fbd1377fb7c9864896..7491644233d52dc56d83de5ad3373f6a9a455378 100644
+index d0854fa02be52f560fc91adeb8495a6bd22f6f74..783a2d80f6197dd0af0dc81909f0353a8ea2ecf4 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -54,6 +54,7 @@ public class RegionFile implements AutoCloseable {
+@@ -53,6 +53,7 @@ public class RegionFile implements AutoCloseable {
          this.info = info;
          this.path = path;
          this.version = version;
@@ -42,7 +42,7 @@ index 2b4fa89f15a42ddd627983fbd1377fb7c9864896..7491644233d52dc56d83de5ad3373f6a
          if (!Files.isDirectory(externalFileDir)) {
              throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath());
          } else {
-@@ -424,4 +425,75 @@ public class RegionFile implements AutoCloseable {
+@@ -423,4 +424,75 @@ public class RegionFile implements AutoCloseable {
      interface CommitOp {
          void run() throws IOException;
      }
diff --git a/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
index 70f040476a..020d6ed859 100644
--- a/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
+++ b/paper-server/patches/sources/com/mojang/datafixers/DataFixerBuilder.java.patch
@@ -7,7 +7,7 @@
 +    private final int minDataFixPrecacheVersion; // Paper - Perf: Cache DataFixerUpper Rewrite Rules on demand
  
      public DataFixerBuilder(final int dataVersion) {
-+        minDataFixPrecacheVersion = Integer.getInteger("Paper.minPrecachedDatafixVersion", dataVersion+1) * 10; // Paper - Perf: default to precache nothing - mojang stores versions * 10 to allow for 'sub versions'
++        this.minDataFixPrecacheVersion = Integer.getInteger("Paper.minPrecachedDatafixVersion", dataVersion + 1) * 10; // Paper - Perf: default to precache nothing - Mojang stores versions * 10 to allow for 'sub versions'
          this.dataVersion = dataVersion;
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
index a6f2415f52..d551548c54 100644
--- a/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -32,7 +32,7 @@
                      player.awardStat(Stats.USE_CAULDRON);
                      player.awardStat(Stats.ITEM_USED.get(item));
 -                    level.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState());
-+                    // world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit
++                    // level.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit
                      level.playSound(null, pos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
                      level.gameEvent(null, GameEvent.FLUID_PLACE, pos);
                  }
@@ -66,7 +66,7 @@
                  player.awardStat(Stats.USE_CAULDRON);
                  player.awardStat(Stats.ITEM_USED.get(item));
 -                LayeredCauldronBlock.lowerFillLevel(state, level, pos);
-+                // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
++                // LayeredCauldronBlock.lowerFillLevel(state, level, pos); // CraftBukkit
                  level.playSound(null, pos, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
                  level.gameEvent(null, GameEvent.FLUID_PICKUP, pos);
              }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
index 13ffca4845..bceaccc7b8 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
@@ -24,9 +24,9 @@
 +            shrink = false; // Paper - shrink below
 +            // Chain to handler for new item
 +            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
-+            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                idispensebehavior.dispense(blockSource, eventStack);
++            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                dispenseBehavior.dispense(blockSource, eventStack);
 +                return item;
 +            }
 +        }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
index ea53919bd2..3533ecd0e6 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
@@ -1,13 +1,11 @@
 --- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
 +++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
-@@ -8,25 +_,48 @@
- import net.minecraft.world.level.block.DispenserBlock;
- 
+@@ -10,23 +_,46 @@
  public class DefaultDispenseItemBehavior implements DispenseItemBehavior {
-+    private Direction direction; // Paper - cache facing direction
      private static final int DEFAULT_ACCURACY = 6;
  
 +    // CraftBukkit start
++    private Direction direction; // Paper - cache facing direction
 +    private boolean dropper;
 +
 +    public DefaultDispenseItemBehavior(boolean dropper) {
@@ -29,7 +27,7 @@
  
      protected ItemStack execute(BlockSource blockSource, ItemStack item) {
 -        Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
-+        // Paper - cached enum direction
++        // Paper - cache facing direction
          Position dispensePosition = DispenserBlock.getDispensePosition(blockSource);
          ItemStack itemStack = item.split(1);
 -        spawnItem(blockSource.level(), itemStack, 6, direction, dispensePosition);
diff --git a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
index 6fd3bac562..3268ad447a 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/nbt/ByteArrayTag.java
 +++ b/net/minecraft/nbt/ByteArrayTag.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.nbt;
- 
- import java.io.DataInput;
 @@ -23,6 +_,7 @@
          private static byte[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
              accounter.accountBytes(24L);
diff --git a/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
index 16917267c6..4f18628a8b 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/nbt/IntArrayTag.java
 +++ b/net/minecraft/nbt/IntArrayTag.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.nbt;
- 
- import java.io.DataInput;
 @@ -23,6 +_,7 @@
          private static int[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
              accounter.accountBytes(24L);
diff --git a/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
index 752f9b8153..3fc90b348f 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/nbt/NbtIo.java
 +++ b/net/minecraft/nbt/NbtIo.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.nbt;
- 
- import java.io.BufferedOutputStream;
 @@ -118,6 +_,12 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
index 814472ca6a..7318c99f0c 100644
--- a/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/PacketEncoder.java.patch
@@ -14,15 +14,7 @@
              this.protocolInfo.codec().encode(byteBuf, packet);
              int i = byteBuf.readableBytes();
              if (LOGGER.isDebugEnabled()) {
-@@ -32,14 +_,40 @@
- 
-             JvmProfiler.INSTANCE.onPacketSent(this.protocolInfo.id(), packetType, channelHandlerContext.channel().remoteAddress(), i);
-         } catch (Throwable var9) {
--            LOGGER.error("Error sending packet {}", packetType, var9);
-+            LOGGER.error("Error sending packet {} (skippable? {})", packetType, packet.isSkippable(), var9);
-             if (packet.isSkippable()) {
-                 throw new SkipPacketException(var9);
-             }
+@@ -39,7 +_,33 @@
  
              throw var9;
          } finally {
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
index 26ba981ff1..dbc8aeb60e 100644
--- a/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/ChatDecorator.java.patch
@@ -7,10 +7,10 @@
 -    ChatDecorator PLAIN = (player, message) -> message;
 -
 -    Component decorate(@Nullable ServerPlayer player, Component message);
-+    ChatDecorator PLAIN = (sender, message) -> java.util.concurrent.CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events
++    ChatDecorator PLAIN = (player, message) -> java.util.concurrent.CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events
 +
 +    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events (callers should use the overload with CommandSourceStack)
-+    java.util.concurrent.CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, Component message); // Paper - adventure; support async chat decoration events
++    java.util.concurrent.CompletableFuture<Component> decorate(@Nullable ServerPlayer player, Component message); // Paper - adventure; support async chat decoration events
 +
 +    // Paper start - adventure; support async chat decoration events
 +    default java.util.concurrent.CompletableFuture<Component> decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) {
diff --git a/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
index 96b7d8debc..5df40c7c2d 100644
--- a/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/chat/Component.java.patch
@@ -9,7 +9,7 @@
 +
 +    // CraftBukkit start
 +    default java.util.stream.Stream<Component> stream() {
-+        return com.google.common.collect.Streams.concat(new java.util.stream.Stream[]{java.util.stream.Stream.of(this), this.getSiblings().stream().flatMap(Component::stream)});
++        return com.google.common.collect.Streams.concat(java.util.stream.Stream.of(this), this.getSiblings().stream().flatMap(Component::stream));
 +    }
 +
 +    @Override
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
index 131ab3783d..de1815cf3d 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch
@@ -71,12 +71,12 @@
              (entryBuilder, buffer) -> entryBuilder.chatSession = buffer.readNullable(RemoteChatSession.Data::read),
 -            (buffer, entry) -> buffer.writeNullable(entry.chatSession, RemoteChatSession.Data::write)
 +            // Paper start - Prevent causing expired keys from impacting new joins
-+            (buf, entry) -> {
++            (buffer, entry) -> {
 +                RemoteChatSession.Data chatSession = entry.chatSession;
 +                if (chatSession != null && chatSession.profilePublicKey().hasExpired()) {
 +                    chatSession = null;
 +                }
-+                buf.writeNullable(chatSession, RemoteChatSession.Data::write);
++                buffer.writeNullable(chatSession, RemoteChatSession.Data::write);
 +            }
 +            // Paper end - Prevent causing expired keys from impacting new joins
          ),
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
index 5d012097a1..c741092d39 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
 +++ b/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.network.protocol.game;
- 
- import net.minecraft.network.RegistryFriendlyByteBuf;
 @@ -16,6 +_,16 @@
          ClientboundSystemChatPacket::overlay,
          ClientboundSystemChatPacket::new
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
index f650a0dbfb..73bc58e48b 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.network.protocol.game;
- 
- import net.minecraft.network.FriendlyByteBuf;
 @@ -14,6 +_,7 @@
      private final BlockHitResult blockHit;
      private final InteractionHand hand;
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
index 460014a7e7..b4e853fe82 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java
 +++ b/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.network.protocol.game;
- 
- import net.minecraft.network.FriendlyByteBuf;
 @@ -14,6 +_,7 @@
      private final int sequence;
      private final float yRot;
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
index 65ea146b9f..752d456ac2 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
 +++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.network.protocol.handshake;
- 
- import net.minecraft.network.FriendlyByteBuf;
 @@ -20,7 +_,7 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
index e17945d462..5206cb78a7 100644
--- a/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/DifficultyCommand.java.patch
@@ -1,16 +1,15 @@
 --- a/net/minecraft/server/commands/DifficultyCommand.java
 +++ b/net/minecraft/server/commands/DifficultyCommand.java
-@@ -31,10 +_,11 @@
+@@ -31,10 +_,10 @@
  
      public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
          MinecraftServer server = source.getServer();
 -        if (server.getWorldData().getDifficulty() == difficulty) {
-+        net.minecraft.server.level.ServerLevel serverLevel = source.getLevel(); // CraftBukkit
-+        if (serverLevel.getDifficulty() == difficulty) { // CraftBukkit
++        if (source.getLevel().getDifficulty() == difficulty) { // CraftBukkit
              throw ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
          } else {
 -            server.setDifficulty(difficulty, true);
-+            server.setDifficulty(serverLevel, difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix)
++            server.setDifficulty(source.getLevel(), difficulty, true); // Paper - per level difficulty; don't skip other difficulty-changing logic (fix upstream's fix)
              source.sendSuccess(() -> Component.translatable("commands.difficulty.success", difficulty.getDisplayName()), true);
              return 0;
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
index f1221352b7..47fc313fa8 100644
--- a/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/commands/ReloadCommand.java.patch
@@ -15,12 +15,12 @@
      }
 +
 +    // CraftBukkit start
-+    public static void reload(MinecraftServer minecraftserver) {
-+        PackRepository resourcepackrepository = minecraftserver.getPackRepository();
-+        WorldData savedata = minecraftserver.getWorldData();
-+        Collection<String> collection = resourcepackrepository.getSelectedIds();
-+        Collection<String> collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection);
-+        minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent
++    public static void reload(MinecraftServer server) {
++        PackRepository packRepository = server.getPackRepository();
++        WorldData worldData = server.getWorldData();
++        Collection<String> selectedIds = packRepository.getSelectedIds();
++        Collection<String> collection = discoverNewPacks(packRepository, worldData, selectedIds);
++        server.reloadResources(collection, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent
 +    }
 +    // CraftBukkit end
  
diff --git a/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
index facd6c1cb4..6e3ece9070 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/server/players/UserBanListEntry.java
 +++ b/net/minecraft/server/players/UserBanListEntry.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.server.players;
- 
- import com.google.gson.JsonObject;
 @@ -37,19 +_,29 @@
  
      @Nullable
diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
index 978eabf546..5e1b12c3cb 100644
--- a/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/stats/ServerStatsCounter.java
 +++ b/net/minecraft/stats/ServerStatsCounter.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.stats;
- 
- import com.google.common.collect.Maps;
 @@ -51,9 +_,22 @@
                  LOGGER.error("Couldn't parse statistics file {}", file, var5);
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
index b6fa57bafe..85d0966158 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/world/entity/ai/attributes/Attributes.java
 +++ b/net/minecraft/world/entity/ai/attributes/Attributes.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.world.entity.ai.attributes;
- 
- import net.minecraft.core.Holder;
 @@ -10,7 +_,7 @@
      public static final Holder<Attribute> ARMOR_TOUGHNESS = register(
          "armor_toughness", new RangedAttribute("attribute.name.armor_toughness", 0.0, 0.0, 20.0).setSyncable(true)
diff --git a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
index 993126f590..8b7cbe2726 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/DebugStickItem.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/world/item/DebugStickItem.java
 +++ b/net/minecraft/world/item/DebugStickItem.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.world.item;
- 
- import java.util.Collection;
 @@ -51,7 +_,7 @@
      public boolean handleInteraction(
          Player player, BlockState stateClicked, LevelAccessor accessor, BlockPos pos, boolean shouldCycleState, ItemStack debugStack
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
index 0b9890be26..a10f3c36e7 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
 +++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.world.level.chunk;
- 
- import com.google.common.base.Stopwatch;
 @@ -41,22 +_,109 @@
      private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap<>();
      private boolean hasGeneratedPositions;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch
deleted file mode 100644
index f3ce378056..0000000000
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/DataLayer.java.patch
+++ /dev/null
@@ -1,7 +0,0 @@
---- a/net/minecraft/world/level/chunk/DataLayer.java
-+++ b/net/minecraft/world/level/chunk/DataLayer.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.world.level.chunk;
- 
- import java.util.Arrays;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
index 0a25ddead5..93e36df68d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
@@ -1,10 +1,5 @@
 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -1,3 +_,4 @@
-+// mc-dev import
- package net.minecraft.world.level.chunk.storage;
- 
- import com.google.common.annotations.VisibleForTesting;
 @@ -46,7 +_,7 @@
      protected final RegionBitmap usedSectors = new RegionBitmap();
  

From 88b2981e09768efa48fb5e8e3bcb05c562883520 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Tue, 17 Dec 2024 20:32:08 +0100
Subject: [PATCH 222/285] Readd final mod to ChunkHolder#getTickingChunk

---
 .../net/minecraft/server/level/ChunkHolder.java.patch    | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
index 20c5bbee04..8d1373ef67 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
@@ -32,6 +32,15 @@
      public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
          return this.tickingChunkFuture;
      }
+@@ -84,7 +_,7 @@
+     }
+ 
+     @Nullable
+-    public LevelChunk getTickingChunk() {
++    public final LevelChunk getTickingChunk() { // Paper - final for inline
+         return this.getTickingChunkFuture().getNow(UNLOADED_LEVEL_CHUNK).orElse(null);
+     }
+ 
 @@ -129,6 +_,7 @@
          } else {
              boolean flag = this.hasChangedSections;

From d94e258d01f3b9eb9fedab0ef492021756e296a2 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 21:40:20 +0100
Subject: [PATCH 223/285] Clean up add/removePluginChunkTicket No need to be
 that invasive to Vanilla code for simple, non-hot and small collection checks

---
 .../server/level/DistanceManager.java.patch   | 81 +++++++------------
 .../org/bukkit/craftbukkit/CraftWorld.java    | 11 ++-
 2 files changed, 35 insertions(+), 57 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
index f2b1b6a10a..eadab866cb 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
@@ -13,71 +13,50 @@
              for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
                  chunkHolder.updateHighestAllowedStatus(chunkMap);
              }
-@@ -143,7 +_,7 @@
-         }
-     }
- 
--    void addTicket(long chunkPos, Ticket<?> ticket) {
-+    boolean addTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-         SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
-         int ticketLevelAt = getTicketLevelAt(tickets);
-         Ticket<?> ticket1 = tickets.addOrGet(ticket);
-@@ -151,11 +_,14 @@
-         if (ticket.getTicketLevel() < ticketLevelAt) {
-             this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
-         }
-+        return ticket == ticket1; // CraftBukkit
-     }
- 
--    void removeTicket(long chunkPos, Ticket<?> ticket) {
-+    boolean removeTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-         SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
-+        boolean removed = false; // CraftBukkit
-         if (tickets.remove(ticket)) {
-+            removed = true; // CraftBukkit
-         }
- 
-         if (tickets.isEmpty()) {
-@@ -163,6 +_,7 @@
-         }
- 
-         this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
-+        return removed; // CraftBukkit
-     }
- 
-     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
-@@ -175,17 +_,29 @@
-     }
- 
+@@ -177,16 +_,42 @@
      public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
-+        // CraftBukkit start
-+        this.addRegionTicketAtDistance(type, pos, distance, value);
-+    }
-+    public <T> boolean addRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
-+        // CraftBukkit end
          Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
          long packedChunkPos = pos.toLong();
 -        this.addTicket(packedChunkPos, ticket);
-+        final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit
++        this.addTicket(packedChunkPos, ticket); // Paper - diff on change above
          this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
-+        return addded; // CraftBukkit
      }
  
      public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
-+        // CraftBukkit start
-+        removeRegionTicketAtDistance(type, pos, distance, value);
-+    }
-+    public <T> boolean removeRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
-+        // CraftBukkit end
          Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
          long packedChunkPos = pos.toLong();
--        this.removeTicket(packedChunkPos, ticket);
-+        final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit
++        this.removeTicket(packedChunkPos, ticket); // Paper - diff on change above
++        this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
++    }
++
++    // Paper start
++    public boolean addPluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
++        Ticket<org.bukkit.plugin.Plugin> ticket = new Ticket<>(TicketType.PLUGIN_TICKET, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Copied from below and keep in-line with force loading, add at level 31
++        final long packedChunkPos = pos.toLong();
++        final Set<Ticket<?>> tickets = this.getTickets(packedChunkPos);
++        if (tickets.contains(ticket)) {
++            return false;
++        }
++        this.addTicket(packedChunkPos, ticket);
++        this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
++        return true;
++    }
++
++    public boolean removePluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
++        Ticket<org.bukkit.plugin.Plugin> ticket = new Ticket<>(TicketType.PLUGIN_TICKET, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Copied from below and keep in-line with force loading, add at level 31
++        final long packedChunkPos = pos.toLong();
++        final Set<Ticket<?>> tickets = this.tickets.get(packedChunkPos); // Don't use getTickets, we don't want to create a new set
++        if (tickets == null || !tickets.contains(ticket)) {
++            return false;
++        }
+         this.removeTicket(packedChunkPos, ticket);
          this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
-+        return removed; // CraftBukkit
++        return true;
      }
++    // Paper end
  
      private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
+         return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
 @@ -217,8 +_,12 @@
          ChunkPos chunkPos = sectionPos.chunk();
          long packedChunkPos = chunkPos.toLong();
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 619b71eac6..61eac5fbbe 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -584,10 +584,9 @@ public class CraftWorld extends CraftRegionAccessor implements World {
         Preconditions.checkArgument(plugin != null, "null plugin");
         Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled");
 
-        DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
-
-        if (chunkDistanceManager.addRegionTicketAtDistance(TicketType.PLUGIN_TICKET, new ChunkPos(x, z), 2, plugin)) { // keep in-line with force loading, add at level 31
-            this.getChunkAt(x, z); // ensure loaded
+        final DistanceManager distanceManager = this.world.getChunkSource().chunkMap.distanceManager;
+        if (distanceManager.addPluginRegionTicket(new ChunkPos(x, z), plugin)) {
+            this.getChunkAt(x, z); // ensure it's loaded
             return true;
         }
 
@@ -598,8 +597,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
     public boolean removePluginChunkTicket(int x, int z, Plugin plugin) {
         Preconditions.checkNotNull(plugin, "null plugin");
 
-        DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
-        return chunkDistanceManager.removeRegionTicketAtDistance(TicketType.PLUGIN_TICKET, new ChunkPos(x, z), 2, plugin); // keep in-line with force loading, remove at level 31
+        final DistanceManager distanceManager = this.world.getChunkSource().chunkMap.distanceManager;
+        return distanceManager.removePluginRegionTicket(new ChunkPos(x, z), plugin);
     }
 
     @Override

From 2612fb57dc8f49fc8912ee966118eff1ce736ace Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 17 Dec 2024 13:03:31 -0800
Subject: [PATCH 224/285] Update paperweight to 2.0.0-beta.5

---
 build-data/paper.at | 1 +
 build.gradle.kts    | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index 3c12c9553d..0c0e4084b9 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -242,6 +242,7 @@ public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeIn
 public net.minecraft.world.entity.ai.control.MoveControl$Operation
 public net.minecraft.world.entity.ai.gossip.GossipContainer gossips
 public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips
+public net.minecraft.world.entity.ai.gossip.GossipContainer$EntityGossips <init>()V
 public net.minecraft.world.entity.ai.navigation.PathNavigation pathFinder
 public net.minecraft.world.entity.ambient.Bat targetPosition
 public net.minecraft.world.entity.animal.AbstractSchoolingFish leader
diff --git a/build.gradle.kts b/build.gradle.kts
index 91c3fd17b8..f2fae287ad 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -11,7 +11,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.4" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.5" apply false
 }
 
 subprojects {

From 972266605eeedbda16a8f37fa8744fee538a833a Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 21:59:59 +0100
Subject: [PATCH 225/285] Remove dead code, param renames in added methods

---
 ...item-frames-performance-and-bug-fixe.patch |  4 +-
 .../server/level/ServerEntity.java.patch      |  4 +-
 .../server/level/ServerPlayer.java.patch      | 60 ++-----------------
 .../minecraft/world/entity/Entity.java.patch  |  3 +-
 4 files changed, 11 insertions(+), 60 deletions(-)

diff --git a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index a7533a2d46..77da1105f4 100644
--- a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -28,10 +28,10 @@ index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..cce78d73e8adafd66d0f3ffb3fabb5e6
                  }
              }
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index cfd30aa774d3bb3049ff9331a623624c6a13f774..940509d1f31aedf20b8f5b9192c34ad004875728 100644
+index 68e3282a0aa23bd41ab7c77be287d2b49461e33c..def43c515030412b147afd6049b100a3153733d2 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -2655,6 +2655,14 @@ public class ServerPlayer extends Player {
+@@ -2607,6 +2607,14 @@ public class ServerPlayer extends Player {
                  this.awardStat(Stats.DROP);
              }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
index 5f30bfd43d..4413d91d0f 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
@@ -125,8 +125,8 @@
              Set<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
              if (!attributesToSync.isEmpty()) {
 +                // CraftBukkit start - Send scaled max health
-+                if (this.entity instanceof ServerPlayer) {
-+                    ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
++                if (this.entity instanceof ServerPlayer serverPlayer) {
++                    serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false);
 +                }
 +                // CraftBukkit end
                  this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync));
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index 0a308ad258..d506c224e1 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -24,14 +24,15 @@
          @Override
          public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) {
              ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(container.containerId, container.incrementStateId(), slot, itemStack));
-@@ -288,6 +_,31 @@
+@@ -288,6 +_,32 @@
              }
          }
  
 +        // Paper start - Add PlayerInventorySlotChangeEvent
 +        @Override
-+        public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) {
-+            Slot slot = handler.getSlot(slotId);
++        public void slotChanged(AbstractContainerMenu containerToSend, int dataSlotIndex, ItemStack oldStack, ItemStack stack) {
++            // See slotChanged above
++            Slot slot = containerToSend.getSlot(dataSlotIndex);
 +            if (!(slot instanceof ResultSlot)) {
 +                if (slot.container == ServerPlayer.this.getInventory()) {
 +                    if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) {
@@ -40,7 +41,7 @@
 +                    }
 +                    io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(
 +                        ServerPlayer.this.getBukkitEntity(),
-+                        slotId,
++                        dataSlotIndex,
 +                        org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(oldStack),
 +                        org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)
 +                    );
@@ -100,7 +101,7 @@
  
      public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
          super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
-@@ -328,16 +_,71 @@
+@@ -328,16 +_,22 @@
          this.server = server;
          this.stats = server.getPlayerList().getPlayerStats(this);
          this.advancements = server.getPlayerList().getPlayerAdvancements(this);
@@ -115,55 +116,6 @@
 +        this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper
 +        this.bukkitPickUpLoot = true;
 +        this.maxHealthCache = this.getMaxHealth();
-+    }
-+
-+    // Use method to resend items in hands in case of client desync, because the item use got cancelled.
-+    // For example, when cancelling the leash event
-+    @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all
-+    public void resendItemInHands() {
-+        this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> {
-+            this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem());
-+        });
-+        this.containerSynchronizer.sendSlotChange(this.inventoryMenu, net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, this.getOffhandItem());
-+    }
-+
-+    // Yes, this doesn't match Vanilla, but it's the best we can do for now.
-+    // If this is an issue, PRs are welcome
-+    public final BlockPos getSpawnPoint(ServerLevel worldserver) {
-+        BlockPos blockposition = worldserver.getSharedSpawnPos();
-+
-+        if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) {
-+            int i = Math.max(0, this.server.getSpawnRadius(worldserver));
-+            int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
-+
-+            if (j < i) {
-+                i = j;
-+            }
-+
-+            if (j <= 1) {
-+                i = 1;
-+            }
-+
-+            long k = (long) (i * 2 + 1);
-+            long l = k * k;
-+            int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
-+            int j1 = this.getCoprime(i1);
-+            int k1 = RandomSource.create().nextInt(i1);
-+
-+            for (int l1 = 0; l1 < i1; ++l1) {
-+                int i2 = (k1 + j1 * l1) % i1;
-+                int j2 = i2 % (i * 2 + 1);
-+                int k2 = i2 / (i * 2 + 1);
-+                BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
-+
-+                if (blockposition1 != null) {
-+                    return blockposition1;
-+                }
-+            }
-+        }
-+
-+        return blockposition;
-+    // CraftBukkit end
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index b77ca19e9f..da4d2fc24d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -974,13 +974,12 @@
                          leashable.removeLeash();
                      } else {
                          leashable.dropLeash();
-@@ -2043,6 +_,14 @@
+@@ -2043,6 +_,13 @@
              ItemStack itemInHand = player.getItemInHand(hand);
              if (itemInHand.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
                  if (!this.level().isClientSide()) {
 +                    // CraftBukkit start - fire PlayerLeashEntityEvent
 +                    if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
-+                        // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync
 +                        ((ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
 +                        player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
 +                        return InteractionResult.PASS;

From c55ad7092c017818459fc60b7b07d247cef05353 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Tue, 17 Dec 2024 22:19:33 +0100
Subject: [PATCH 226/285] Fix stats event call

---
 .../net/minecraft/stats/StatsCounter.java.patch        | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
index 5c300c2297..a1a5baa229 100644
--- a/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/StatsCounter.java.patch
@@ -1,15 +1,15 @@
 --- a/net/minecraft/stats/StatsCounter.java
 +++ b/net/minecraft/stats/StatsCounter.java
-@@ -18,6 +_,12 @@
-     }
+@@ -14,6 +_,12 @@
  
-     public void setValue(Player player, Stat<?> stat, int value) {
+     public void increment(Player player, Stat<?> stat, int amount) {
+         int i = (int)Math.min((long)this.getValue(stat) + amount, 2147483647L);
 +        // CraftBukkit start - fire Statistic events
-+        org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), value);
++        org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), i);
 +        if (cancellable != null && cancellable.isCancelled()) {
 +            return;
 +        }
 +        // CraftBukkit end
-         this.stats.put(stat, value);
+         this.setValue(player, stat, i);
      }
  

From e97bf6933c23580d51ec355264b76f873bde2be0 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 17 Dec 2024 19:41:47 -0800
Subject: [PATCH 227/285] Only include build time in manifest for numbered
 builds

---
 paper-server/build.gradle.kts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 4844881912..8a25dc0885 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -181,12 +181,12 @@ dependencies {
     // Paper end - spark
 }
 
-
 tasks.jar {
     manifest {
         val git = Git(rootProject.layout.projectDirectory.path)
         val mcVersion = rootProject.providers.gradleProperty("mcVersion").get()
         val build = System.getenv("BUILD_NUMBER") ?: null
+        val buildTime = if (build != null) Instant.now() else Instant.EPOCH
         val gitHash = git.exec(providers, "rev-parse", "--short=7", "HEAD").get().trim()
         val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash"
         val date = git.exec(providers, "show", "-s", "--format=%ci", gitHash).get().trim() // Paper
@@ -202,7 +202,7 @@ tasks.jar {
             "Brand-Id" to "papermc:paper",
             "Brand-Name" to "Paper",
             "Build-Number" to (build ?: ""),
-            "Build-Time" to Instant.now().toString(),
+            "Build-Time" to buildTime.toString(),
             "Git-Branch" to gitBranch, // Paper
             "Git-Commit" to gitHash, // Paper
         )

From 9d837fe4b66ebf71171465055d57c3fc7aaabc36 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Wed, 18 Dec 2024 10:27:19 +0100
Subject: [PATCH 228/285] Cat has luckily since stopped singing

---
 .../entity/item/FallingBlockEntity.java.patch    | 16 ++++++++--------
 .../entity/monster/piglin/Piglin.java.patch      |  9 +--------
 2 files changed, 9 insertions(+), 16 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
index 5b8f2129c4..397795f3c0 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -1,10 +1,7 @@
 --- a/net/minecraft/world/entity/item/FallingBlockEntity.java
 +++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
-@@ -47,8 +_,14 @@
- import net.minecraft.world.phys.BlockHitResult;
- import net.minecraft.world.phys.HitResult;
+@@ -49,6 +_,11 @@
  import net.minecraft.world.phys.Vec3;
-+import org.bukkit.event.entity.CreatureSpawnEvent;
  import org.slf4j.Logger;
  
 +// CraftBukkit start;
@@ -28,20 +25,23 @@
  
      public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState) {
 +        // CraftBukkit start
-+        return FallingBlockEntity.fall(level, pos, blockState, CreatureSpawnEvent.SpawnReason.DEFAULT);
++        return FallingBlockEntity.fall(level, pos, blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
 +    }
-+    public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++    public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
          FallingBlockEntity fallingBlockEntity = new FallingBlockEntity(
              level,
              pos.getX() + 0.5,
-@@ -89,6 +_,7 @@
+@@ -89,8 +_,9 @@
                  ? blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false))
                  : blockState
          );
 +        if (!CraftEventFactory.callEntityChangeBlockEvent(fallingBlockEntity, pos, blockState.getFluidState().createLegacyBlock())) return fallingBlockEntity; // CraftBukkit
          level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
-         level.addFreshEntity(fallingBlockEntity);
+-        level.addFreshEntity(fallingBlockEntity);
++        level.addFreshEntity(fallingBlockEntity, creatureSpawnReason); // CraftBukkit
          return fallingBlockEntity;
+     }
+ 
 @@ -139,13 +_,22 @@
      @Override
      public void tick() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
index 504d509667..1178c733ba 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
@@ -63,7 +63,7 @@
      }
  
      @VisibleForDebug
-@@ -325,11 +_,16 @@
+@@ -325,7 +_,9 @@
      @Override
      protected void finishConversion(ServerLevel serverLevel) {
          PiglinAi.cancelAdmiring(serverLevel, this);
@@ -73,13 +73,6 @@
          super.finishConversion(serverLevel);
      }
  
-+
-     private ItemStack createSpawnWeapon() {
-+        // Food time for 10 minutes WOOP WOOP
-+        // Please send help cat started singing
-         return this.random.nextFloat() < 0.5 ? new ItemStack(Items.CROSSBOW) : new ItemStack(Items.GOLDEN_SWORD);
-     }
- 
 @@ -400,7 +_,7 @@
      }
  

From c4fd69c80717bf34d4568df8bb4575ef38e555f9 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Wed, 18 Dec 2024 11:56:22 +0100
Subject: [PATCH 229/285] Fix more derps from file patch updates

---
 .../world/entity/monster/Skeleton.java.patch  |  9 ++++----
 .../world/entity/monster/Zombie.java.patch    |  7 +++---
 .../entity/monster/ZombieVillager.java.patch  | 14 +----------
 .../npc/WanderingTraderSpawner.java.patch     |  3 ++-
 .../AbstractHurtingProjectile.java.patch      |  2 +-
 .../world/inventory/BeaconMenu.java.patch     |  7 +-----
 .../inventory/BrewingStandMenu.java.patch     | 23 +++++--------------
 7 files changed, 19 insertions(+), 46 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
index e847a3c8fd..0f01620a40 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/monster/Skeleton.java
 +++ b/net/minecraft/world/entity/monster/Skeleton.java
-@@ -89,11 +_,18 @@
+@@ -89,11 +_,17 @@
      }
  
      protected void doFreezeConversion() {
@@ -10,14 +10,13 @@
                  this.level().levelEvent(null, 1048, this.blockPosition(), 0);
              }
 -        });
-+        // CraftBukkit start - add spawn and transform reasons
++        // Paper start - add spawn and transform reasons
 +        }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);
-+        // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam
 +        if (stray == null) {
++            // Reset conversion time to prevent event spam
 +            this.conversionTime = 300;
 +        }
-+        // Paper end - Fix issues with mob conversion
-+        // CraftBukkit end - add spawn and transform reasons
++        // Paper end - add spawn and transform reasons
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
index b70be66183..c3460fb7a5 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Zombie.java.patch
@@ -116,7 +116,7 @@
 +        return Zombie.convertVillagerToZombieVillager(level, villager, this.blockPosition(), this.isSilent(), org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.INFECTION) != null;
 +    }
 +
-+    public static ZombieVillager convertVillagerToZombieVillager(ServerLevel level, Villager villager, net.minecraft.core.BlockPos blockPosition, boolean silent, org.bukkit.event.entity.EntityTransformEvent.TransformReason transformReason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++    public static ZombieVillager convertVillagerToZombieVillager(ServerLevel level, Villager villager, net.minecraft.core.BlockPos blockPosition, boolean silent, org.bukkit.event.entity.EntityTransformEvent.TransformReason transformReason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
 +        // CraftBukkit end
          ZombieVillager zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), mob -> {
              mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
@@ -130,10 +130,11 @@
 +            if (!silent) {
 +                level.levelEvent(null, 1026, blockPosition, 0);
              }
-+            // CraftBukkit end
-         });
+-        });
 -        return zombieVillager != null;
++        }, transformReason, creatureSpawnReason);
 +        return zombieVillager;
++        // CraftBukkit end
      }
  
      public boolean isSunSensitive() {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
index 53b5b1fca0..d86b3f0005 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch
@@ -16,18 +16,6 @@
  
      public ZombieVillager(EntityType<? extends ZombieVillager> entityType, Level level) {
          super(entityType, level);
-@@ -140,6 +_,11 @@
-     public void tick() {
-         if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
-             int conversionProgress = this.getConversionProgress();
-+            // CraftBukkit start - Use wall time instead of ticks for villager conversion
-+            // TODO: WE WANT TO REMOVE THIS? I THOUGHT WE REMOVED IT.
-+            int elapsedTicks = net.minecraft.server.MinecraftServer.currentTick - this.lastTick;
-+            conversionProgress *= elapsedTicks;
-+            // CraftBukkit end
-             this.villagerConversionTime -= conversionProgress;
-             if (this.villagerConversionTime <= 0) {
-                 this.finishConversion((ServerLevel)this.level());
 @@ -147,6 +_,7 @@
          }
  
@@ -95,9 +83,9 @@
                      serverLevel.levelEvent(null, 1027, this.blockPosition(), 0);
                  }
 -            }
++                // CraftBukkit start
 +            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.CURED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CURED // CraftBukkit
          );
-+        // CraftBukkit start
 +        if (converted == null) {
 +            ((org.bukkit.entity.ZombieVillager) this.getBukkitEntity()).setConversionTime(-1); // SPIGOT-5208: End conversion to stop event spam
 +        }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
index 61198c578a..032bfcb48c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
 +++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
-@@ -38,41 +_,50 @@
+@@ -38,41 +_,51 @@
  
      public WanderingTraderSpawner(ServerLevelData serverLevelData) {
          this.serverLevelData = serverLevelData;
@@ -65,6 +65,7 @@
                      } else if (this.spawn(level)) {
 -                        this.spawnChance = 25;
 +                        this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin;
++                        // Paper end - Add Wandering Trader spawn rate config options
                          return 1;
                      } else {
                          return 0;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
index 75c739c6ba..0f392f4b0a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
@@ -14,7 +14,7 @@
  
              if (hitResultOnMoveVector.getType() != HitResult.Type.MISS && this.isAlive()) {
 -                this.hitTargetOrDeflectSelf(hitResultOnMoveVector);
-+                this.preHitTargetOrDeflectSelf(hitResultOnMoveVector);
++                this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event
              }
  
              this.createParticleTrail();
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
index 48cbfd3a8e..e0549ad657 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BeaconMenu.java.patch
@@ -27,7 +27,7 @@
  
      public BeaconMenu(int containerId, Container container) {
          this(containerId, container, new SimpleContainerData(3), ContainerLevelAccess.NULL);
-@@ -43,10 +_,31 @@
+@@ -43,6 +_,27 @@
  
      public BeaconMenu(int containerId, Container container, ContainerData beaconData, ContainerLevelAccess access) {
          super(MenuType.BEACON, containerId);
@@ -55,11 +55,6 @@
          checkContainerDataCount(beaconData, 3);
          this.beaconData = beaconData;
          this.access = access;
--        this.paymentSlot = new BeaconMenu.PaymentSlot(this.beacon, 0, 136, 110);
-+        this.paymentSlot = new PaymentSlot(this.beacon, 0, 136, 110);
-         this.addSlot(this.paymentSlot);
-         this.addDataSlots(beaconData);
-         this.addStandardInventorySlots(container, 36, 137);
 @@ -65,6 +_,7 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
index c60f509644..feccf70be9 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -25,16 +25,14 @@
 -        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 0, 56, 51));
 -        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 1, 79, 58));
 -        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 2, 102, 51));
--        this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionBrewing, brewingStandContainer, 3, 79, 17));
--        this.addSlot(new BrewingStandMenu.FuelSlot(brewingStandContainer, 4, 17, 17));
--        this.addDataSlots(brewingStandData);
 +        // Paper start - custom potion mixes
-+        this.addSlot(new PotionSlot(brewingStandContainer, 0, 56, 51, potionBrewing));
-+        this.addSlot(new PotionSlot(brewingStandContainer, 1, 79, 58, potionBrewing));
-+        this.addSlot(new PotionSlot(brewingStandContainer, 2, 102, 51, potionBrewing));
++        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 0, 56, 51, potionBrewing));
++        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 1, 79, 58, potionBrewing));
++        this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 2, 102, 51, potionBrewing));
 +        // Paper end - custom potion mixes
-+        this.ingredientSlot = this.addSlot(new IngredientsSlot(potionBrewing, brewingStandContainer, 3, 79, 17));
-+        this.addSlot(new FuelSlot(brewingStandContainer, 4, 17, 17));
+         this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionBrewing, brewingStandContainer, 3, 79, 17));
+         this.addSlot(new BrewingStandMenu.FuelSlot(brewingStandContainer, 4, 17, 17));
+-        this.addDataSlots(brewingStandData);
 +        // Paper start - Add recipeBrewTime
 +        this.addDataSlots(new SimpleContainerData(2) {
 +            @Override
@@ -58,15 +56,6 @@
          return this.brewingStand.stillValid(player);
      }
  
-@@ -67,7 +_,7 @@
-             ItemStack item = slot.getItem();
-             itemStack = item.copy();
-             if ((index < 0 || index > 2) && index != 3 && index != 4) {
--                if (BrewingStandMenu.FuelSlot.mayPlaceItem(itemStack)) {
-+                if (FuelSlot.mayPlaceItem(itemStack)) {
-                     if (this.moveItemStackTo(item, 4, 5, false) || this.ingredientSlot.mayPlace(item) && !this.moveItemStackTo(item, 3, 4, false)) {
-                         return ItemStack.EMPTY;
-                     }
 @@ -75,7 +_,7 @@
                      if (!this.moveItemStackTo(item, 3, 4, false)) {
                          return ItemStack.EMPTY;

From a5e786a5783f113ca2c54b34cda72ba35e1426b5 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Wed, 18 Dec 2024 12:36:30 +0100
Subject: [PATCH 230/285] Add back missing hunk in AbstractFurnaceBlockEntity

---
 .../block/entity/AbstractFurnaceBlockEntity.java.patch | 10 +++++++++-
 .../block/entity/BrewingStandBlockEntity.java.patch    |  3 +--
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
index 092b7d392d..f57bc6379f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -229,7 +229,7 @@
          player.awardRecipes(recipesToAwardAndPopExperience);
  
          for (RecipeHolder<?> recipeHolder : recipesToAwardAndPopExperience) {
-@@ -356,26 +_,52 @@
+@@ -356,30 +_,60 @@
      }
  
      public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec) {
@@ -285,3 +285,11 @@
      }
  
      @Override
+     public void fillStackedContents(StackedItemContents stackedContents) {
++        // Paper start - don't account fuel stack (fixes MC-243057)
++        stackedContents.accountStack(this.items.get(SLOT_INPUT));
++        stackedContents.accountStack(this.items.get(SLOT_RESULT));
++        // Paper end - don't account fuel stack (fixes MC-243057)
+         for (ItemStack itemStack : this.items) {
+             stackedContents.accountStack(itemStack);
+         }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
index 93f629fdbf..15e9dfa1d3 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -166,9 +166,8 @@
              return potionBrewing.isIngredient(stack);
          } else {
              return index == 4
--                ? stack.is(ItemTags.BREWING_FUEL)
+                 ? stack.is(ItemTags.BREWING_FUEL)
 -                : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE))
-+                ? stack.is(net.minecraft.tags.ItemTags.BREWING_FUEL)
 +                : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack)) // Paper - Custom Potion Mixes
                      && this.getItem(index).isEmpty();
          }

From 71bcc8c8450e25c7a716e63cdf353fcfeac6b3d5 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Wed, 18 Dec 2024 13:00:21 +0100
Subject: [PATCH 231/285] Fix diffs in block package

---
 .../world/level/block/BedBlock.java.patch     |  4 ++--
 .../level/block/BigDripleafBlock.java.patch   | 11 ++++++----
 .../level/block/CaveVinesBlock.java.patch     | 21 ++++++++-----------
 .../level/block/CraftingTableBlock.java.patch |  5 +++--
 .../level/block/DispenserBlock.java.patch     |  3 ++-
 .../block/piston/PistonBaseBlock.java.patch   |  6 +++---
 6 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
index c400669e8f..ad2f90861a 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BedBlock.java.patch
@@ -9,7 +9,7 @@
                  level.removeBlock(pos, false);
                  BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
                  if (level.getBlockState(blockPos).is(this)) {
-@@ -103,22 +_,63 @@
+@@ -103,22 +_,62 @@
                  level.explode(null, level.damageSources().badRespawnPointExplosion(center), null, center, 5.0F, true, Level.ExplosionInteraction.BLOCK);
                  return InteractionResult.SUCCESS_SERVER;
              } else if (state.getValue(OCCUPIED)) {
@@ -38,7 +38,7 @@
 +                    } else
 +                    // CraftBukkit end
                      if (bedSleepingProblem.getMessage() != null) {
-                         player.displayClientMessage(bedSleepingProblem.getMessage(), true);
+-                        player.displayClientMessage(bedSleepingProblem.getMessage(), true);
 +                        final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent
 +                        if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent
                      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
index 8f4e70e47f..11a7f55411 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BigDripleafBlock.java.patch
@@ -46,19 +46,21 @@
              } else if (tilt == Tilt.FULL) {
                  resetTilt(state, level, pos);
              }
-@@ -238,8 +_,9 @@
+@@ -238,8 +_,11 @@
          return entity.onGround() && entity.position().y > pos.getY() + 0.6875F;
      }
  
 -    private void setTiltAndScheduleTick(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound) {
 -        setTilt(state, level, pos, tilt);
 +    private void setTiltAndScheduleTick(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound, @Nullable Entity entity) {
-+        if (!BigDripleafBlock.setTilt(state, level, pos, tilt, entity)) return;
++        if (!setTilt(state, level, pos, tilt, entity)) {
++            return;
++        }
 +        // CraftBukkit end
          if (sound != null) {
              playTiltSound(level, pos, sound);
          }
-@@ -251,18 +_,25 @@
+@@ -251,18 +_,26 @@
      }
  
      private static void resetTilt(BlockState state, Level level, BlockPos pos) {
@@ -70,7 +72,8 @@
      }
  
 -    private static void setTilt(BlockState state, Level level, BlockPos pos, Tilt tilt) {
-+    private static boolean setTilt(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable Entity entity) {  // CraftBukkit
++    // CraftBukkit start
++    private static boolean setTilt(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable Entity entity) {
 +        if (entity != null) {
 +            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.setValue(BigDripleafBlock.TILT, tilt))) {
 +                return false;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
index 7f52226493..c0eb21e510 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CaveVinesBlock.java.patch
@@ -1,23 +1,20 @@
 --- a/net/minecraft/world/level/block/CaveVinesBlock.java
 +++ b/net/minecraft/world/level/block/CaveVinesBlock.java
-@@ -52,7 +_,7 @@
+@@ -52,8 +_,15 @@
  
      @Override
      protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
 -        return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F));
-+        return this.getGrowIntoState(state, random, null); // Paper - Fix Spigot growth modifiers
-     }
- 
-     @Override
-@@ -85,4 +_,11 @@
-     public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
-         level.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2);
-     }
-+    // Paper start - Fix Spigot growth modifiers
+-    }
++        // Paper start - Fix Spigot growth modifiers
++        return this.getGrowIntoState(state, random, null);
++    }
 +    @Override
 +    protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
 +        final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F);
-+        return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value);
++        return super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value);
 +    }
 +    // Paper end - Fix Spigot growth modifiers
- }
+ 
+     @Override
+     protected ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
index 3285cee056..336a3679c6 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CraftingTableBlock.java.patch
@@ -1,9 +1,10 @@
 --- a/net/minecraft/world/level/block/CraftingTableBlock.java
 +++ b/net/minecraft/world/level/block/CraftingTableBlock.java
-@@ -32,7 +_,9 @@
+@@ -31,8 +_,9 @@
+     @Override
      protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
          if (!level.isClientSide) {
-             player.openMenu(state.getMenuProvider(level, pos));
+-            player.openMenu(state.getMenuProvider(level, pos));
 +            if (player.openMenu(state.getMenuProvider(level, pos)).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.INTERACT_WITH_CRAFTING_TABLE);
 +            } // Paper - Fix InventoryOpenEvent cancellation
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
index 1660450641..2bce500d42 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch
@@ -18,7 +18,7 @@
              player.awardStat(dispenserBlockEntity instanceof DropperBlockEntity ? Stats.INSPECT_DROPPER : Stats.INSPECT_DISPENSER);
          }
  
-@@ -87,18 +_,26 @@
+@@ -87,18 +_,27 @@
              BlockSource blockSource = new BlockSource(level, pos, state, dispenserBlockEntity);
              int randomSlot = dispenserBlockEntity.getRandomSlot(level.random);
              if (randomSlot < 0) {
@@ -31,6 +31,7 @@
                  DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item);
                  if (dispenseMethod != DispenseItemBehavior.NOOP) {
 +                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent
++                    DispenserBlock.eventFired = false; // CraftBukkit - reset event status
                      dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item));
                  }
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
index bd32b0d966..239a88e07e 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -136,7 +136,7 @@
                  level.setBlock(blockPos2, Blocks.AIR.defaultBlockState(), 18);
                  level.gameEvent(GameEvent.BLOCK_DESTROY, blockPos2, GameEvent.Context.of(blockState1));
                  if (!blockState1.is(BlockTags.FIRE)) {
-@@ -321,13 +_,27 @@
+@@ -321,13 +_,26 @@
              }
  
              for (int i1 = toPush.size() - 1; i1 >= 0; i1--) {
@@ -152,13 +152,13 @@
                  map.remove(blockPos2);
                  BlockState blockState2 = Blocks.MOVING_PISTON.defaultBlockState().setValue(FACING, facing);
                  level.setBlock(blockPos2, blockState2, 68);
-                 level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, list.get(i1), facing, extending, false));
+-                level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, list.get(i1), facing, extending, false));
 +                // Paper start - fix a variety of piston desync dupes
 +                if (!allowDesync) {
 +                    blockState1 = level.getBlockState(oldPos);
 +                    map.replace(oldPos, blockState1);
 +                }
-+                level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, allowDesync ? (BlockState) list.get(i1) : blockState1, facing, extending, false));
++                level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, allowDesync ? list.get(i1) : blockState1, facing, extending, false));
 +                if (!allowDesync) {
 +                    level.setBlock(oldPos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_MOVE_BY_PISTON | 1024); // set air to prevent later physics updates from seeing this block
 +                }

From dedc6b339453647f1e8e22bb61f378cfffb6510d Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Wed, 18 Dec 2024 13:24:47 +0100
Subject: [PATCH 232/285] Fix more diffs in block package

---
 .../net/minecraft/world/level/block/LiquidBlock.java.patch | 7 ++++---
 .../minecraft/world/level/block/SculkVeinBlock.java.patch  | 5 +++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
index 3589e09e7d..35dc60692f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/LiquidBlock.java.patch
@@ -41,10 +41,11 @@
          }
      }
  
-@@ -173,13 +_,21 @@
+@@ -172,14 +_,20 @@
+                 BlockPos blockPos = pos.relative(direction.getOpposite());
                  if (level.getFluidState(blockPos).is(FluidTags.WATER)) {
                      Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
-                     level.setBlockAndUpdate(pos, block.defaultBlockState());
+-                    level.setBlockAndUpdate(pos, block.defaultBlockState());
 -                    this.fizz(level, pos);
 +                    // CraftBukkit start
 +                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) {
@@ -55,7 +56,7 @@
                  }
  
                  if (isSoulSoil && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) {
-                     level.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState());
+-                    level.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState());
 -                    this.fizz(level, pos);
 +                    // CraftBukkit start
 +                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, Blocks.BASALT.defaultBlockState())) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
index ae401d6908..bc1a1f3a48 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkVeinBlock.java.patch
@@ -17,10 +17,11 @@
          BlockState blockState = level.getBlockState(pos);
          TagKey<Block> tagKey = spreader.replaceableBlocks();
  
-@@ -108,6 +_,11 @@
+@@ -107,7 +_,11 @@
+                 BlockState blockState1 = level.getBlockState(blockPos);
                  if (blockState1.is(tagKey)) {
                      BlockState blockState2 = Blocks.SCULK.defaultBlockState();
-                     level.setBlock(blockPos, blockState2, 3);
+-                    level.setBlock(blockPos, blockState2, 3);
 +                    // CraftBukkit start - Call BlockSpreadEvent
 +                    if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, sourceBlock, blockPos, blockState2, 3)) {
 +                        return false;

From 6126012369a8a1d59a2bd091f7a270e90585b181 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Wed, 18 Dec 2024 19:09:46 +0100
Subject: [PATCH 233/285] readd beacon effect cause

---
 ...-Manager-and-add-advanced-packet-sup.patch | 28 +++++-----
 .../0006-Entity-Activation-Range-2.0.patch    | 16 +++---
 .../patches/features/0007-Anti-Xray.patch     | 16 +++---
 ...ocity-compression-and-cipher-natives.patch | 22 ++++----
 .../0022-Lag-compensation-ticks.patch         | 12 ++---
 ...nate-Current-redstone-implementation.patch |  6 +--
 ...rove-exact-choice-recipe-ingredients.patch |  4 +-
 .../brigadier/CommandDispatcher.java.patch    |  8 ---
 .../minecraft/commands/Commands.java.patch    |  4 +-
 .../net/minecraft/nbt/ByteArrayTag.java.patch |  2 +-
 .../net/minecraft/nbt/IntArrayTag.java.patch  |  2 +-
 .../net/minecraft/nbt/NbtIo.java.patch        |  7 ++-
 .../minecraft/network/Connection.java.patch   | 16 +++---
 .../ClientboundSetEquipmentPacket.java.patch  |  2 +-
 .../ClientboundSetPlayerTeamPacket.java.patch |  2 +-
 .../net/minecraft/server/Main.java.patch      | 31 ++++++-----
 .../server/MinecraftServer.java.patch         | 21 +++-----
 .../server/ServerTickRateManager.java.patch   |  4 +-
 .../server/commands/LootCommand.java.patch    | 10 ----
 .../dedicated/DedicatedServer.java.patch      | 24 ++++-----
 .../server/dedicated/Settings.java.patch      |  2 +-
 .../server/level/ChunkMap.java.patch          | 16 +++---
 .../server/level/ServerChunkCache.java.patch  |  4 +-
 .../server/level/ServerLevel.java.patch       | 52 +++++++++----------
 .../server/level/ServerPlayer.java.patch      | 10 ++--
 .../level/ServerPlayerGameMode.java.patch     | 20 +++----
 .../ServerGamePacketListenerImpl.java.patch   | 15 +++---
 ...rverHandshakePacketListenerImpl.java.patch | 22 ++++----
 .../players/GameProfileCache.java.patch       |  8 +--
 .../server/players/PlayerList.java.patch      |  4 +-
 .../server/players/StoredUserList.java.patch  |  6 +--
 .../players/UserBanListEntry.java.patch       | 12 ++---
 .../stats/ServerRecipeBook.java.patch         |  2 +-
 .../stats/ServerStatsCounter.java.patch       | 11 ++--
 .../world/CompoundContainer.java.patch        |  2 +-
 .../minecraft/world/entity/Entity.java.patch  | 21 +++++---
 .../world/entity/LightningBolt.java.patch     |  2 +-
 .../world/entity/LivingEntity.java.patch      |  2 +-
 .../world/entity/ai/goal/FloatGoal.java.patch |  2 +-
 .../ai/navigation/PathNavigation.java.patch   |  2 +-
 .../animal/AbstractSchoolingFish.java.patch   |  2 +-
 .../world/entity/animal/Animal.java.patch     |  4 +-
 .../world/entity/animal/Bee.java.patch        | 18 +++----
 .../world/entity/animal/Dolphin.java.patch    | 13 ++---
 .../entity/animal/axolotl/Axolotl.java.patch  | 10 ++--
 .../world/entity/animal/goat/Goat.java.patch  |  3 +-
 .../horse/AbstractChestedHorse.java.patch     |  3 +-
 .../animal/horse/AbstractHorse.java.patch     |  8 +--
 .../entity/animal/sniffer/Sniffer.java.patch  |  6 +--
 .../boss/enderdragon/EnderDragon.java.patch   | 34 ++++++------
 .../phases/DragonStrafePlayerPhase.java.patch |  2 +-
 .../phases/EnderDragonPhaseManager.java.patch |  5 +-
 .../entity/boss/wither/WitherBoss.java.patch  | 32 ++++++------
 .../entity/decoration/ArmorStand.java.patch   | 13 ++---
 .../entity/item/FallingBlockEntity.java.patch | 24 ++-------
 .../world/entity/item/ItemEntity.java.patch   | 16 +++---
 .../world/entity/item/PrimedTnt.java.patch    |  6 +--
 .../world/entity/monster/Bogged.java.patch    | 12 ++---
 .../world/entity/monster/Creeper.java.patch   |  9 ++--
 .../world/entity/monster/EnderMan.java.patch  | 11 ++--
 .../world/entity/monster/Ghast.java.patch     |  2 +-
 .../world/entity/monster/Phantom.java.patch   | 37 ++++++-------
 .../world/entity/monster/Slime.java.patch     |  3 +-
 .../entity/npc/WanderingTrader.java.patch     |  2 +-
 .../AbstractContainerMenu.java.patch          | 13 +++--
 .../inventory/BrewingStandMenu.java.patch     |  5 +-
 .../inventory/CartographyTableMenu.java.patch |  6 +--
 .../inventory/EnchantmentMenu.java.patch      | 11 ++--
 .../world/inventory/LecternMenu.java.patch    |  4 +-
 .../inventory/MerchantContainer.java.patch    |  8 +--
 .../inventory/StonecutterMenu.java.patch      |  2 +-
 .../world/item/FireworkRocketItem.java.patch  |  2 +-
 .../minecraft/world/item/ItemStack.java.patch | 17 ++----
 .../world/level/BaseSpawner.java.patch        |  4 +-
 .../world/level/GameRules.java.patch          |  6 +--
 .../minecraft/world/level/Level.java.patch    | 11 ++--
 .../world/level/NaturalSpawner.java.patch     |  2 +-
 .../world/level/ServerExplosion.java.patch    | 19 ++++---
 .../world/level/block/Block.java.patch        |  2 +-
 .../block/CeilingHangingSignBlock.java.patch  |  2 +-
 .../world/level/block/CropBlock.java.patch    |  4 +-
 .../level/block/EnderChestBlock.java.patch    |  2 +-
 .../block/GrowingPlantHeadBlock.java.patch    | 14 ++---
 .../level/block/NetherPortalBlock.java.patch  |  2 +-
 .../level/block/ShulkerBoxBlock.java.patch    |  2 +-
 .../world/level/block/SignBlock.java.patch    |  2 +-
 .../block/WallHangingSignBlock.java.patch     |  2 +-
 .../block/entity/BeaconBlockEntity.java.patch |  7 ++-
 .../block/entity/HopperBlockEntity.java.patch |  2 +-
 .../world/level/chunk/ChunkAccess.java.patch  |  2 +-
 .../world/level/chunk/LevelChunk.java.patch   |  2 +-
 .../level/levelgen/PatrolSpawner.java.patch   |  2 +-
 .../level/levelgen/PhantomSpawner.java.patch  |  2 +-
 .../level/material/FlowingFluid.java.patch    |  2 +-
 .../level/redstone/NeighborUpdater.java.patch |  4 +-
 .../storage/PlayerDataStorage.java.patch      | 10 ++--
 .../org/bukkit/craftbukkit/CraftServer.java   | 13 +++--
 .../java/org/bukkit/craftbukkit/Main.java     |  4 +-
 .../craftbukkit/entity/CraftEnderDragon.java  |  2 +-
 .../craftbukkit/entity/CraftPhantom.java      |  6 +--
 .../craftbukkit/entity/CraftPlayer.java       |  2 +-
 .../craftbukkit/inventory/CraftMetaItem.java  |  2 +-
 102 files changed, 443 insertions(+), 488 deletions(-)
 delete mode 100644 paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch

diff --git a/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
index b3be838455..df7d1df9fc 100644
--- a/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
+++ b/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
@@ -28,7 +28,7 @@ and then catch exceptions and close if they fire.
 Part of this commit was authored by: Spottedleaf, sandtechnology
 
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc9924666634488044c666fd1 100644
+index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7f45fdb54 100644
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
 @@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -39,7 +39,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
 +    private final Queue<WrappedConsumer> pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network
      public Channel channel;
      public SocketAddress address;
-     // Spigot Start
+     // Spigot start
 @@ -145,6 +145,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      }
      // Paper end - packet limiter
@@ -51,7 +51,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
  
      public Connection(PacketFlow receiving) {
          this.receiving = receiving;
-@@ -425,11 +429,38 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -423,11 +427,38 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      }
  
      public void send(Packet<?> packet, @Nullable PacketSendListener listener, boolean flush) {
@@ -93,7 +93,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
          }
      }
  
-@@ -438,7 +469,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -436,7 +467,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
              this.flushQueue();
              action.accept(this);
          } else {
@@ -102,7 +102,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
          }
      }
  
-@@ -452,6 +483,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -450,6 +481,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      }
  
      private void doSendPacket(Packet<?> packet, @Nullable PacketSendListener sendListener, boolean flush) {
@@ -117,7 +117,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
          ChannelFuture channelFuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet);
          if (sendListener != null) {
              channelFuture.addListener(future -> {
-@@ -467,14 +506,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -465,14 +504,24 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
              });
          }
  
@@ -143,7 +143,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
          }
      }
  
-@@ -486,16 +535,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -484,16 +533,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
          }
      }
  
@@ -206,15 +206,15 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
  
      private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
      private static int joinAttemptsThisTick; // Paper - Buffer joins to world
-@@ -561,6 +651,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-         // Spigot Start
-         this.preparing = false;
-         // Spigot End
+@@ -557,6 +647,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ 
+     public void disconnect(DisconnectionDetails disconnectionDetails) {
+         this.preparing = false; // Spigot
 +        this.clearPacketQueue(); // Paper - Optimize network
          if (this.channel == null) {
              this.delayedDisconnect = disconnectionDetails;
          }
-@@ -749,7 +840,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -745,7 +836,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public void handleDisconnection() {
          if (this.channel != null && !this.channel.isOpen()) {
              if (this.disconnectionHandled) {
@@ -223,7 +223,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
              } else {
                  this.disconnectionHandled = true;
                  PacketListener packetListener = this.getPacketListener();
-@@ -760,7 +851,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -756,7 +847,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
                      );
                      packetListener1.onDisconnect(disconnectionDetails);
                  }
@@ -232,7 +232,7 @@ index b624f001ba9d98c4dc68fcd66c0bc2de0a12308c..c4bb28857ee11dccc992466663448804
                  // Paper start - Add PlayerConnectionCloseEvent
                  if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
                      /* Player was logged in, either game listener or configuration listener */
-@@ -795,4 +886,93 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -791,4 +882,93 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public void setBandwidthLogger(LocalSampleLogger bandwithLogger) {
          this.bandwidthDebugMonitor = new BandwidthDebugMonitor(bandwithLogger);
      }
diff --git a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
index 322c566db9..811fc15914 100644
--- a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
@@ -338,7 +338,7 @@ index 0000000000000000000000000000000000000000..bd888ef719b9bfc93bace0b1d0fb771a
 +    }
 +}
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 9bbcafa8e70f95d5ab6318211a0265acbfc76b1c..f8f145cd9614f7e38d6aa72501fe31837340a8bb 100644
+index 8569f0670c13e3f635ef529f00e636bf7bca38b5..76e59a8b07ac58a38f368386e03aaa3d8586e21d 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList;
@@ -366,7 +366,7 @@ index 9bbcafa8e70f95d5ab6318211a0265acbfc76b1c..f8f145cd9614f7e38d6aa72501fe3183
  import org.slf4j.Logger;
  
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index cce78d73e8adafd66d0f3ffb3fabb5e6c025c7df..a4b523ac1926895ccc87464892fa81753ae8f73c 100644
+index 2e479c02794de1eb9753b6f1e59afc2d9d6981ba..7702004b68b7735043914f93b54b4413cd21ba41 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -551,6 +551,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -472,10 +472,10 @@ index b4a1202a9f43525caf215d2f5c86ad92ea4f6de7..47db6ac3ef23fd0da127cfb5a4d3ba9e
      public void tick() {
          super.tick();
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 020d05bb60abec10fa37e651c17f600c883af61d..2e5f1dc15ea6a20f8cbdef97ecbf00e5d56603cf 100644
+index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855844f571b 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -386,6 +386,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -380,6 +380,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      public boolean fixedPose = false; // Paper - Expand Pose API
      private final int despawnTime; // Paper - entity despawn time limit
      public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges
@@ -491,7 +491,7 @@ index 020d05bb60abec10fa37e651c17f600c883af61d..2e5f1dc15ea6a20f8cbdef97ecbf00e5
  
      public void setOrigin(@javax.annotation.Nonnull org.bukkit.Location location) {
          this.origin = location.toVector();
-@@ -423,6 +432,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -417,6 +426,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          this.position = Vec3.ZERO;
          this.blockPosition = BlockPos.ZERO;
          this.chunkPosition = ChunkPos.ZERO;
@@ -529,7 +529,7 @@ index 020d05bb60abec10fa37e651c17f600c883af61d..2e5f1dc15ea6a20f8cbdef97ecbf00e5
              movement = this.maybeBackOffFromEdge(movement, type);
              Vec3 vec3 = this.collide(movement);
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 41ef8c24903e5efceead43796e647824a54193df..9de400977ec33e485e87cdf1cf145588527e1e10 100644
+index fa67721bc0aa2d5b920d691f34d81d0a1feec202..c853c5ff5a9422b9a17be34102f80348ca89cad6 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
 @@ -3089,6 +3089,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -647,7 +647,7 @@ index 789fea258d70e60d38271ebb31270562dc7eb3ab..d0ab3db7bbd2942db19f473474371b20
                          }
                      }
 diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
-index 1c82a41acb8717b2c56498602fd1ecbe6aa58fe5..dcbd35d6bf81d7a0621020710114887b68a7dcc6 100644
+index a9c7cf74174a2c998c182da149bdfeb87cca7b8d..e346edacf7705faf867430c9c44b8322a2bd9d72 100644
 --- a/net/minecraft/world/entity/item/ItemEntity.java
 +++ b/net/minecraft/world/entity/item/ItemEntity.java
 @@ -124,6 +124,29 @@ public class ItemEntity extends Entity implements TraceableEntity {
@@ -824,7 +824,7 @@ index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c11
 +
  }
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 691fee2e2097244126f4fac0f5d00bf6916b9766..25fb8a91bd3012da383711d58cc25cbada510f56 100644
+index b6342c7161c2539b316be53e72640d44ce576b79..13dbd0da80b2199519370ac6e993ace53f42c1ea 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -153,6 +153,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
diff --git a/paper-server/patches/features/0007-Anti-Xray.patch b/paper-server/patches/features/0007-Anti-Xray.patch
index 8e51f5c6ae..801b9fcda9 100644
--- a/paper-server/patches/features/0007-Anti-Xray.patch
+++ b/paper-server/patches/features/0007-Anti-Xray.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Anti-Xray
 
 
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index 0eba4fce940b90e67f3746480c040178ba9f5525..3bdbd3d566dee767204d898e0bb4f82f0060d9ca 100644
+index 1e5d94cdcdffb4d2940f17bacdf0e6a488e84318..d3aebc7f833764351c8e5fe1fad1aa2f8718ca37 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.longs.LongSets;
@@ -153,7 +153,7 @@ index 3a384175f8e7f204234bbaf3081bdc20c47a0d4b..5699bc15eba92e22433a20cb8326b59f
  
      private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a4b523ac1926895ccc87464892fa81753ae8f73c..ca9427a7eae9a66f4f1ccedda7b1def7ac2a88da 100644
+index 7702004b68b7735043914f93b54b4413cd21ba41..4d20bda4cba578c47216d450c99389b744a59008 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -348,7 +348,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -166,7 +166,7 @@ index a4b523ac1926895ccc87464892fa81753ae8f73c..ca9427a7eae9a66f4f1ccedda7b1def7
          this.levelStorageAccess = levelStorageAccess;
          this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index 732a4f20bade67c57a4f85142849752b72e349ee..6176f0738aa1a18df5d7d4d49fd6961e3f2eb736 100644
+index d7028ed8ebdecd647467b67f62f7431a6df59f76..9fdb825be2b04a5806bbb3d39948bad0863e7ae5 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
 @@ -312,6 +312,7 @@ public class ServerPlayerGameMode {
@@ -196,7 +196,7 @@ index 342bc843c384761e883de861044f4f8930ae8763..14878690a88fd4de3e2c127086607e6c
          if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
              new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 2580b249d9a024400e295ca5beab551b3571ceb3..d227714de0fe13544779fae6cf0e9ff6af5469c7 100644
+index 8a93d1034eb0dab4b4010d52ddbb0caa5697416d..bafeeab3edbc73f6f86474e18ab4a3d96ce17157 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -404,7 +404,7 @@ public abstract class PlayerList {
@@ -209,7 +209,7 @@ index 2580b249d9a024400e295ca5beab551b3571ceb3..d227714de0fe13544779fae6cf0e9ff6
          }
          // Paper end - Send empty chunk
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 25fb8a91bd3012da383711d58cc25cbada510f56..872c3b8826f436b15f6ab0a3619692c5202eadc3 100644
+index 13dbd0da80b2199519370ac6e993ace53f42c1ea..0e4ab448755632696c4326f1df9f3855cd38a64d 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -168,6 +168,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@@ -247,7 +247,7 @@ index 25fb8a91bd3012da383711d58cc25cbada510f56..872c3b8826f436b15f6ab0a3619692c5
              if (blockState == null) {
                  // CraftBukkit start - remove blockstate if failed (or the same)
 diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
-index 12d9b532e466ec4e46920d409b5f1b3ae60b80f8..bc688ad1097ef4159dfc5f96d963a9fa63262e20 100644
+index 809b3c37d3749c76c3c243cd91c593d03693e9b3..860d1c9729c4ee97ec6f40f7aa969829070b27c0 100644
 --- a/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/net/minecraft/world/level/chunk/ChunkAccess.java
 @@ -114,14 +114,14 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
@@ -256,7 +256,7 @@ index 12d9b532e466ec4e46920d409b5f1b3ae60b80f8..bc688ad1097ef4159dfc5f96d963a9fa
  
 -        replaceMissingSections(biomeRegistry, this.sections);
 +        this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method
-         this.biomeRegistry = biomeRegistry; // Craftbukkit
+         this.biomeRegistry = biomeRegistry; // CraftBukkit
      }
  
 -    private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
@@ -269,7 +269,7 @@ index 12d9b532e466ec4e46920d409b5f1b3ae60b80f8..bc688ad1097ef4159dfc5f96d963a9fa
          }
      }
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index b3cc671f33b2c8c5f3131afffc6ee9d7b83dd3bc..d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba 100644
+index 2b5d99eb6860578cc1af8fbe2571c314846984dd..bc4260623dfb1d804f41593293a5c176c86df5a1 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
 @@ -109,7 +109,7 @@ public class LevelChunk extends ChunkAccess {
diff --git a/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch b/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
index 8ec9093e28..cf7d0a06be 100644
--- a/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
+++ b/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Use Velocity compression and cipher natives
 
 
 diff --git a/net/minecraft/network/CipherDecoder.java b/net/minecraft/network/CipherDecoder.java
-index 429325ffb7db2b85ed271ddf3da64c6fdc593673..4a445cb0ab19c6eca94fdc2172e1b286d1947c63 100644
+index 429325ffb7db2b85ed271ddf3da64c6fdc593673..d764552aea825af7bbf0f47c9d88af527d9dbe62 100644
 --- a/net/minecraft/network/CipherDecoder.java
 +++ b/net/minecraft/network/CipherDecoder.java
 @@ -7,14 +7,30 @@ import java.util.List;
@@ -17,8 +17,8 @@ index 429325ffb7db2b85ed271ddf3da64c6fdc593673..4a445cb0ab19c6eca94fdc2172e1b286
  
 -    public CipherDecoder(Cipher cipher) {
 -        this.cipher = new CipherBase(cipher);
-+    public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) {  // Paper - Use Velocity cipher
-+        this.cipher = cipher;  // Paper - Use Velocity cipher
++    public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher
++        this.cipher = cipher; // Paper - Use Velocity cipher
      }
  
      @Override
@@ -38,13 +38,13 @@ index 429325ffb7db2b85ed271ddf3da64c6fdc593673..4a445cb0ab19c6eca94fdc2172e1b286
 +
 +    // Paper start - Use Velocity cipher
 +    @Override
-+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
++    public void handlerRemoved(ChannelHandlerContext ctx) {
 +        this.cipher.close();
 +    }
 +    // Paper end - Use Velocity cipher
  }
 diff --git a/net/minecraft/network/CipherEncoder.java b/net/minecraft/network/CipherEncoder.java
-index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..e087a35bcaf326c37a3e58f4d06165a61747c5a9 100644
+index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..b927c85923147d2b346e892a3e4deee48b3d073e 100644
 --- a/net/minecraft/network/CipherEncoder.java
 +++ b/net/minecraft/network/CipherEncoder.java
 @@ -5,15 +5,31 @@ import io.netty.channel.ChannelHandlerContext;
@@ -58,8 +58,8 @@ index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..e087a35bcaf326c37a3e58f4d06165a6
  
 -    public CipherEncoder(Cipher cipher) {
 -        this.cipher = new CipherBase(cipher);
-+    public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) {  // Paper - Use Velocity cipher
-+        this.cipher = cipher;  // Paper - Use Velocity cipher
++    public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher
++        this.cipher = cipher; // Paper - Use Velocity cipher
      }
  
 +    // Paper start - Use Velocity cipher
@@ -80,7 +80,7 @@ index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..e087a35bcaf326c37a3e58f4d06165a6
 +
 +    // Paper start - Use Velocity cipher
 +    @Override
-+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
++    public void handlerRemoved(ChannelHandlerContext ctx) {
 +        this.cipher.close();
 +    }
 +    // Paper end - Use Velocity cipher
@@ -269,10 +269,10 @@ index bc674b08a41d5529fe06c6d3f077051cf4138f73..ea8a894158c44c2e7943dea43ecd8e1f
 +    // Paper end - Use Velocity cipher
  }
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index c4bb28857ee11dccc9924666634488044c666fd1..8fe485c5bf79804bb4d1f774f95a92b14a576e80 100644
+index 1672bd0eeea8db44016bbbccf9a332c7f45fdb54..bfdc637a750602c00919422ca0e3943ba34db832 100644
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
-@@ -770,11 +770,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -766,11 +766,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
          return connection;
      }
  
@@ -299,7 +299,7 @@ index c4bb28857ee11dccc9924666634488044c666fd1..8fe485c5bf79804bb4d1f774f95a92b1
  
      public boolean isEncrypted() {
          return this.encrypted;
-@@ -813,16 +824,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -809,16 +820,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      // Paper end - add proper async disconnect
      public void setupCompression(int threshold, boolean validateDecompressed) {
          if (threshold >= 0) {
diff --git a/paper-server/patches/features/0022-Lag-compensation-ticks.patch b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
index 50c0323115..1d23e4ff9d 100644
--- a/paper-server/patches/features/0022-Lag-compensation-ticks.patch
+++ b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
@@ -8,7 +8,7 @@ Areas affected by lag comepnsation:
  - Eating food items
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06..22dc6bec58702762e4a31415f9aed2df2b3ad0d6 100644
+index 5649482a8b85056bc009b868e19ca11f21d59fbf..f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -19,7 +19,7 @@ index fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06..22dc6bec58702762e4a31415f9aed2df
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -1566,6 +1567,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1561,6 +1562,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          for (ServerLevel serverLevel : this.getAllLevels()) {
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
@@ -28,7 +28,7 @@ index fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06..22dc6bec58702762e4a31415f9aed2df
              /* Drop global time updates
              if (this.tickCount % 20 == 0) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index ca9427a7eae9a66f4f1ccedda7b1def7ac2a88da..efc18884358907661d1226409f11d19a394073b3 100644
+index 4d20bda4cba578c47216d450c99389b744a59008..47b7d487467225505e3f20cea92e114331d660fd 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -2362,4 +2362,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -49,20 +49,20 @@ index ca9427a7eae9a66f4f1ccedda7b1def7ac2a88da..efc18884358907661d1226409f11d19a
 +    // Paper end - lag compensation
  }
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index 6176f0738aa1a18df5d7d4d49fd6961e3f2eb736..d6da40d7188a55a9b2eeedb540c8e275359342e4 100644
+index 9fdb825be2b04a5806bbb3d39948bad0863e7ae5..261445152f741b68f91cd8fc1eac17275ee52427 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
 @@ -111,7 +111,7 @@ public class ServerPlayerGameMode {
      }
  
      public void tick() {
--        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit;
+-        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
 +        this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensation
          if (this.hasDelayedDestroy) {
              BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
              if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index c83aeaf4e50dd7290c608dfe260a3bd2404b6004..8439e1593c9973243383dc7091c587f7e72eb9e0 100644
+index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be3879918a5d3 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
 @@ -3831,6 +3831,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
diff --git a/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch b/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
index 789e1ac2c4..1e6a539a98 100644
--- a/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
+++ b/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
 +    }
 +}
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index efc18884358907661d1226409f11d19a394073b3..9cc47bda7197ca3f63b0ede9905c9a13931f84ed 100644
+index 47b7d487467225505e3f20cea92e114331d660fd..c38eda42b33cfa4792625f40ebde6f30e591119b 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -214,6 +214,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -2352,10 +2352,10 @@ index efc18884358907661d1226409f11d19a394073b3..9cc47bda7197ca3f63b0ede9905c9a13
          @Override
          public void onCreated(Entity entity) {
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 872c3b8826f436b15f6ab0a3619692c5202eadc3..cb6ca60af3d3f90501e4693a78466b9f7462362d 100644
+index 0e4ab448755632696c4326f1df9f3855cd38a64d..8abb17d30373fab80b466891abd16b31ab536f64 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -1401,6 +1401,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1396,6 +1396,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
  
      public abstract FuelValues fuelValues();
  
diff --git a/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch b/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
index 91119626f1..8ff6dc6c20 100644
--- a/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
+++ b/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
@@ -204,7 +204,7 @@ index 6475509689439636275b06b9eac33f0922d8fcfd..6c398c91909f42e352e80d0781549df9
          int i = this.inventory.findSlotMatchingCraftingIngredient(item, item1);
          if (i == -1) {
 diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
-index e8522f5ccd69ff5513782a31a3b53219bc17b0e5..9a72651c5efefc6290ae14aa50ca79572d274562 100644
+index e8522f5ccd69ff5513782a31a3b53219bc17b0e5..80fa9153bdce9b0728cc1ee8908fe5481e84a1f8 100644
 --- a/net/minecraft/world/entity/player/Inventory.java
 +++ b/net/minecraft/world/entity/player/Inventory.java
 @@ -178,12 +178,12 @@ public class Inventory implements Container, Nameable {
@@ -219,7 +219,7 @@ index e8522f5ccd69ff5513782a31a3b53219bc17b0e5..9a72651c5efefc6290ae14aa50ca7957
 -                && itemStack.is(item)
 -                && isUsableForCrafting(itemStack)
 +                && item.matches(itemStack) // Paper - Improve exact choice recipe ingredients
-+                && (!(item instanceof io.papermc.paper.inventory.recipe.ItemOrExact.Item) || Inventory.isUsableForCrafting(itemStack))  // Paper - Improve exact choice recipe ingredients
++                && (!(item instanceof io.papermc.paper.inventory.recipe.ItemOrExact.Item) || Inventory.isUsableForCrafting(itemStack)) // Paper - Improve exact choice recipe ingredients
                  && (stack.isEmpty() || ItemStack.isSameItemSameComponents(stack, itemStack))) {
                  return i;
              }
diff --git a/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
index d5b323d68e..ffd3df5ae9 100644
--- a/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
+++ b/paper-server/patches/sources/com/mojang/brigadier/CommandDispatcher.java.patch
@@ -1,13 +1,5 @@
 --- a/com/mojang/brigadier/CommandDispatcher.java
 +++ b/com/mojang/brigadier/CommandDispatcher.java
-@@ -3,6 +_,7 @@
- 
- package com.mojang.brigadier;
- 
-+// CHECKSTYLE:OFF
- import com.mojang.brigadier.builder.LiteralArgumentBuilder;
- import com.mojang.brigadier.context.CommandContext;
- import com.mojang.brigadier.context.CommandContextBuilder;
 @@ -297,15 +_,21 @@
          List<ParseResults<S>> potentials = null;
          final int cursor = originalReader.getCursor();
diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
index 954cc79823..f16c785fbd 100644
--- a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
@@ -100,8 +100,8 @@
              MutableComponent mutableComponent = Component.literal(var12.getMessage() == null ? var12.getClass().getName() : var12.getMessage());
 -            if (LOGGER.isDebugEnabled()) {
 -                LOGGER.error("Command exception: /{}", command, var12);
-+            Commands.LOGGER.error("Command exception: /{}", command, var12); // Paper - always show execution exception in console log
-+            if (commandSourceStack.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
++            LOGGER.error("Command exception: /{}", command, var12); // Paper - always show execution exception in console log
++            if (commandSourceStack.getServer().isDebugging() || LOGGER.isDebugEnabled()) { // Paper - Debugging
                  StackTraceElement[] stackTrace = var12.getStackTrace();
  
                  for (int i = 0; i < Math.min(stackTrace.length, 3); i++) {
diff --git a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
index 3268ad447a..2d195ba0b8 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/ByteArrayTag.java.patch
@@ -4,7 +4,7 @@
          private static byte[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
              accounter.accountBytes(24L);
              int _int = input.readInt();
-+            com.google.common.base.Preconditions.checkArgument( _int < 1 << 24); // Spigot
++            com.google.common.base.Preconditions.checkArgument(_int < 1 << 24); // Spigot
              accounter.accountBytes(1L, _int);
              byte[] bytes = new byte[_int];
              input.readFully(bytes);
diff --git a/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
index 4f18628a8b..de1fb28f75 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/IntArrayTag.java.patch
@@ -4,7 +4,7 @@
          private static int[] readAccounted(DataInput input, NbtAccounter accounter) throws IOException {
              accounter.accountBytes(24L);
              int _int = input.readInt();
-+            com.google.common.base.Preconditions.checkArgument( _int < 1 << 24); // Spigot
++            com.google.common.base.Preconditions.checkArgument(_int < 1 << 24); // Spigot
              accounter.accountBytes(4L, _int);
              int[] ints = new int[_int];
  
diff --git a/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
index 3fc90b348f..5cd314e969 100644
--- a/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
+++ b/paper-server/patches/sources/net/minecraft/nbt/NbtIo.java.patch
@@ -1,13 +1,12 @@
 --- a/net/minecraft/nbt/NbtIo.java
 +++ b/net/minecraft/nbt/NbtIo.java
-@@ -118,6 +_,12 @@
+@@ -118,6 +_,11 @@
      }
  
      public static CompoundTag read(DataInput input, NbtAccounter accounter) throws IOException {
 +        // Spigot start
-+        if ( input instanceof io.netty.buffer.ByteBufInputStream )
-+        {
-+            input = new DataInputStream(new org.spigotmc.LimitStream((InputStream) input, accounter));
++        if (input instanceof io.netty.buffer.ByteBufInputStream byteBufInputStream) {
++            input = new DataInputStream(new org.spigotmc.LimitStream(byteBufInputStream, accounter));
 +        }
 +        // Spigot end
          Tag unnamedTag = readUnnamedTag(input, accounter);
diff --git a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
index f36ea815c5..e0c2fff5dd 100644
--- a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
@@ -21,11 +21,11 @@
      private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
      public Channel channel;
      public SocketAddress address;
-+    // Spigot Start
++    // Spigot start
 +    public java.util.UUID spoofedUUID;
 +    public com.mojang.authlib.properties.Property[] spoofedProfile;
 +    public boolean preparing = true;
-+    // Spigot End
++    // Spigot end
      @Nullable
      private volatile PacketListener disconnectListener;
      @Nullable
@@ -70,13 +70,11 @@
  
      public Connection(PacketFlow receiving) {
          this.receiving = receiving;
-@@ -116,6 +_,9 @@
+@@ -116,6 +_,7 @@
          super.channelActive(context);
          this.channel = context.channel();
          this.address = this.channel.remoteAddress();
-+        // Spigot Start
-+        this.preparing = false;
-+        // Spigot End
++        this.preparing = false; // Spigot
          if (this.delayedDisconnect != null) {
              this.disconnect(this.delayedDisconnect);
          }
@@ -228,13 +226,11 @@
          }
  
          if (this.tickCount++ % 20 == 0) {
-@@ -432,12 +_,15 @@
+@@ -432,12 +_,13 @@
      }
  
      public void disconnect(DisconnectionDetails disconnectionDetails) {
-+        // Spigot Start
-+        this.preparing = false;
-+        // Spigot End
++        this.preparing = false; // Spigot
          if (this.channel == null) {
              this.delayedDisconnect = disconnectionDetails;
          }
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
index f02259d4dc..d5959b2866 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java.patch
@@ -18,7 +18,7 @@
          buffer.writeVarInt(this.entity);
          int size = this.slots.size();
  
-+        try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) {  // Paper - data sanitization
++        try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) { // Paper - data sanitization
          for (int i = 0; i < size; i++) {
              Pair<EquipmentSlot, ItemStack> pair = this.slots.get(i);
              EquipmentSlot equipmentSlot = pair.getFirst();
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
index 8e6ebe5e67..3f4c2e05a8 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java.patch
@@ -17,7 +17,7 @@
              buffer.writeByte(this.options);
              buffer.writeUtf(this.nametagVisibility);
 -            buffer.writeUtf(this.collisionRule);
-+            buffer.writeUtf(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions ? "never" : this.collisionRule); // Paper - Configurable player collision
++            buffer.writeUtf(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions ? PlayerTeam.CollisionRule.NEVER.name : this.collisionRule); // Paper - Configurable player collision
              buffer.writeEnum(this.color);
              ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buffer, this.playerPrefix);
              ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buffer, this.playerSuffix);
diff --git a/paper-server/patches/sources/net/minecraft/server/Main.java.patch b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
index f166107df6..886690bbbd 100644
--- a/paper-server/patches/sources/net/minecraft/server/Main.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
@@ -65,15 +65,15 @@
              }
  
 -            if (!eula.hasAgreedToEULA()) {
-+            // Spigot Start
++            // Spigot start
 +            boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
 +            if (eulaAgreed) {
-+                System.err.println("You have used the Spigot command line EULA agreement flag.");
-+                System.err.println("By using this setting you are indicating your agreement to Mojang's EULA (https://account.mojang.com/documents/minecraft_eula).");
-+                System.err.println("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
++                LOGGER.error("You have used the Spigot command line EULA agreement flag.");
++                LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
++                LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
 +            }
-+            // Spigot End
-+            if (!eula.hasAgreedToEULA() && !eulaAgreed) { // Spigot
++            if (!eula.hasAgreedToEULA() && !eulaAgreed) {
++                // Spigot end
                  LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
                  return;
              }
@@ -84,11 +84,11 @@
 +            // Paper start - Detect headless JRE
 +            String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck();
 +            if (awtException != null) {
-+                Main.LOGGER.error("You are using a headless JRE distribution.");
-+                Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function.");
-+                Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install");
-+                Main.LOGGER.error("");
-+                Main.LOGGER.error(awtException);
++                LOGGER.error("You are using a headless JRE distribution.");
++                LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function.");
++                LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install");
++                LOGGER.error("");
++                LOGGER.error(awtException);
 +                return;
 +            }
 +            // Paper end - Detect headless JRE
@@ -195,7 +195,7 @@
                          thread1,
                          levelStorageAccess,
                          packRepository,
-@@ -257,17 +_,29 @@
+@@ -257,17 +_,34 @@
                          services,
                          LoggerChunkProgressListener::createFromGameruleRadius
                      );
@@ -203,7 +203,12 @@
                      dedicatedServer1.setPort(optionSet.valueOf(optionSpec11));
 -                    dedicatedServer1.setDemo(optionSet.has(optionSpec2));
 +                     */
-+                    dedicatedServer1.setDemo(optionSet.has("demo")); // Paper
++                    // Paper start
++                    if (optionSet.has("serverId")) {
++                        dedicatedServer1.setId((String) optionSet.valueOf("serverId"));
++                    }
++                    dedicatedServer1.setDemo(optionSet.has("demo"));
++                    // Paper end
 +                    /*
                      dedicatedServer1.setId(optionSet.valueOf(optionSpec12));
 -                    boolean flag = !optionSet.has(optionSpec) && !optionSet.valuesOf(optionSpec15).contains("nogui");
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index 3aa38008fc..ca5ee37782 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -61,7 +61,7 @@
 +    public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
 +    private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop
 +    @Deprecated(forRemoval = true) // Paper
-+    public final double[] recentTps = new double[ 3 ];
++    public final double[] recentTps = new double[3];
 +    // Spigot end
 +    public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
 +    public volatile boolean abnormalExit; // Paper - Improved watchdog support
@@ -623,16 +623,17 @@
          }
  
          LOGGER.info("Saving worlds");
-@@ -646,6 +_,15 @@
+@@ -646,6 +_,16 @@
          } catch (IOException var4) {
              LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), var4);
          }
 +        // Spigot start
 +        io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.shutdown(); // Paper
-+        try { io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
++        try {
++            io.papermc.paper.util.MCUtil.ASYNC_EXECUTOR.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
 +        } catch (java.lang.InterruptedException ignored) {} // Paper
 +        if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
-+            MinecraftServer.LOGGER.info("Saving usercache.json");
++            LOGGER.info("Saving usercache.json");
 +            this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
 +        }
 +        // Spigot end
@@ -654,15 +655,10 @@
          this.running = false;
          if (waitForServer) {
              try {
-@@ -671,6 +_,63 @@
+@@ -671,6 +_,57 @@
          }
      }
  
-+    // Spigot Start
-+    private static double calcTps(double avg, double exp, double tps) {
-+        return (avg * exp) + (tps * (1 - exp));
-+    }
-+
 +    // Paper start - Further improve server tick loop
 +    private static final long SEC_IN_NANO = 1000000000;
 +    private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
@@ -713,7 +709,6 @@
 +    }
 +    private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL));
 +    // Paper end
-+    // Spigot End
 +
      protected void runServer() {
          try {
@@ -725,7 +720,7 @@
 +            this.server.spark.enableBeforePlugins(); // Paper - spark
 +            // Spigot start
 +            org.spigotmc.WatchdogThread.hasStarted = true; // Paper
-+            Arrays.fill( this.recentTps, 20 );
++            Arrays.fill(this.recentTps, 20);
 +            // Paper start - further improve server tick loop
 +            long tickSection = Util.getNanos();
 +            long currentTime;
@@ -1181,7 +1176,7 @@
          if (this.isEnforceWhitelist()) {
              PlayerList playerList = commandSource.getServer().getPlayerList();
              UserWhiteList whiteList = playerList.getWhiteList();
-+            if (!((net.minecraft.server.dedicated.DedicatedServer) getServer()).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
++            if (!((net.minecraft.server.dedicated.DedicatedServer) this).getProperties().whiteList.get()) return; // Paper - whitelist not enabled
  
              for (ServerPlayer serverPlayer : Lists.newArrayList(playerList.getPlayers())) {
 -                if (!whiteList.isWhiteListed(serverPlayer.getGameProfile())) {
diff --git a/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
index d5767c2dab..2c1e737818 100644
--- a/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
@@ -4,12 +4,12 @@
      }
  
      public boolean stopSprinting() {
-+        // CraftBukkit start, add sendLog parameter
++        // CraftBukkit start - add sendLog parameter
 +        return this.stopSprinting(true);
 +    }
 +
 +    public boolean stopSprinting(boolean sendLog) {
-+        // CraftBukkit end
++        // CraftBukkit end - add sendLog parameter
          if (this.remainingSprintTicks > 0L) {
 -            this.finishTickSprint();
 +            this.finishTickSprint(sendLog); // CraftBukkit - add sendLog parameter
diff --git a/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch b/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch
deleted file mode 100644
index f83d4e298c..0000000000
--- a/paper-server/patches/sources/net/minecraft/server/commands/LootCommand.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/server/commands/LootCommand.java
-+++ b/net/minecraft/server/commands/LootCommand.java
-@@ -395,6 +_,7 @@
- 
-     private static int dropInWorld(CommandSourceStack source, Vec3 pos, List<ItemStack> items, LootCommand.Callback callback) throws CommandSyntaxException {
-         ServerLevel level = source.getLevel();
-+        items.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem
-         items.forEach(itemStack -> {
-             ItemEntity itemEntity = new ItemEntity(level, pos.x, pos.y, pos.z, itemStack.copy());
-             itemEntity.setDefaultPickUpDelay();
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 243d71c069..2f25d642a3 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -123,11 +123,11 @@
  
 +        // Paper start - detect running as root
 +        if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) {
-+            DedicatedServer.LOGGER.warn("****************************");
-+            DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED.");
-+            DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS.");
-+            DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/");
-+            DedicatedServer.LOGGER.warn("****************************");
++            LOGGER.warn("****************************");
++            LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED.");
++            LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS.");
++            LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/");
++            LOGGER.warn("****************************");
 +        }
 +        // Paper end - detect running as root
 +
@@ -174,12 +174,12 @@
 +        java.net.SocketAddress bindAddress;
 +        if (this.getLocalIp().startsWith("unix:")) {
 +            if (!io.netty.channel.epoll.Epoll.isAvailable()) {
-+                DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
-+                DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS.");
++                LOGGER.error("**** INVALID CONFIGURATION!");
++                LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS.");
 +                return false;
 +            } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) {
-+                DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
-+                DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy.");
++                LOGGER.error("**** INVALID CONFIGURATION!");
++                LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy.");
 +                return false;
 +            }
 +            bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length()));
@@ -229,11 +229,11 @@
 +            // Spigot start
 +            // Paper start - Add Velocity IP Forwarding Support
 +            if (usingProxy) {
-+                DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose.");
-+                DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information.");
++                LOGGER.warn("Whilst this makes it possible to use {}, unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose.", proxyFlavor);
++                LOGGER.warn("Please see {} for further information.", proxyLink);
 +                // Paper end - Add Velocity IP Forwarding Support
 +            } else {
-+                DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
++                LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
 +            }
 +            // Spigot end
              LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file.");
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
index 33fd250afb..b690197c4c 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/Settings.java.patch
@@ -24,7 +24,7 @@
      }
  
      public static Properties loadFromFile(Path path) {
-+        if (!path.toFile().exists()) return new Properties(); // CraftBukkit - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
++        if (!Files.exists(path)) return new Properties(); // CraftBukkit - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
          try {
              try {
                  Properties var13;
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
index 6ce77e18f7..26c38d11c8 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
@@ -148,7 +148,7 @@
      }
  
 -    protected void setServerViewDistance(int viewDistance) {
-+    public void setServerViewDistance(int viewDistance) { // Paper - publi
++    public void setServerViewDistance(int viewDistance) { // Paper - public
          int i = Mth.clamp(viewDistance, 2, 32);
          if (i != this.serverViewDistance) {
              this.serverViewDistance = i;
@@ -274,7 +274,7 @@
 +            return;
 +        }
 +        // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
-+        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delayadding to tracker until after list packets
++        if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
          if (!(entity instanceof EnderDragonPart)) {
              EntityType<?> type = entity.getType();
              int i = type.clientTrackingRange() * 16;
@@ -327,14 +327,14 @@
              if (player != this.entity) {
 -                Vec3 vec3 = player.position().subtract(this.entity.position());
 +                // Paper start - remove allocation of Vec3D here
-+                // Vec3 vec3d = player.position().subtract(this.entity.position());
-+                double vec3d_dx = player.getX() - this.entity.getX();
-+                double vec3d_dz = player.getZ() - this.entity.getZ();
++                // Vec3 vec3 = player.position().subtract(this.entity.position());
++                double vec3_dx = player.getX() - this.entity.getX();
++                double vec3_dz = player.getZ() - this.entity.getZ();
 +                // Paper end - remove allocation of Vec3D here
                  int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
                  double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16);
 -                double d1 = vec3.x * vec3.x + vec3.z * vec3.z;
-+                double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
++                double d1 = vec3_dx * vec3_dx + vec3_dz * vec3_dz; // Paper
                  double d2 = d * d;
 -                boolean flag = d1 <= d2
 -                    && this.entity.broadcastToPlayer(player)
@@ -344,8 +344,8 @@
 +                if (flag && level.paperConfig().entities.trackingRangeY.enabled) {
 +                    double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
 +                    if (rangeY != -1) {
-+                        double vec3d_dy = player.getY() - this.entity.getY();
-+                        flag = vec3d_dy * vec3d_dy <= rangeY * rangeY;
++                        double vec3_dy = player.getY() - this.entity.getY();
++                        flag = vec3_dy * vec3_dy <= rangeY * rangeY;
 +                    }
 +                }
 +                flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
index 725c75ec09..ec01e0da0f 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -84,7 +84,7 @@
                  if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) {
                      ChunkAccess chunkAccess = this.lastChunk[i];
 -                    if (chunkAccess != null || !requireChunk) {
-+                    if (chunkAccess != null) {  // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
++                    if (chunkAccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
                          return chunkAccess;
                      }
                  }
@@ -178,7 +178,7 @@
          this.lastSpawnState = spawnState;
          profiler.popPush("spawnAndTick");
 -        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
-+        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit;
++        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
          int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
          List<MobCategory> filteredSpawningCategories;
          if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 0eaf3ea736..99d27344d3 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -200,7 +200,7 @@
              this.registryAccess(),
              server.getStructureManager(),
 -            dimension,
-+            getTypeKey(),  // Paper - Fix missing CB diff
++            getTypeKey(), // Paper - Fix missing CB diff
              chunkGenerator,
              this.chunkSource.randomState(),
              this,
@@ -302,7 +302,7 @@
                  DifficultyInstance currentDifficultyAt = this.getCurrentDifficultyAt(blockPos);
                  boolean flag = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
 -                    && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * 0.01
-+                    && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) // Paper - Configurable spawn chances for skeleton horses
++                    && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses
                      && !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
                  if (flag) {
                      SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
@@ -409,23 +409,23 @@
              this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
          }
 +        */
-+        for (int idx = 0; idx < this.players.size(); ++idx) {
-+            if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                ((ServerPlayer) this.players.get(idx)).tickWeather();
++        for (ServerPlayer player : this.players) {
++            if (player.level() == this) {
++                player.tickWeather();
 +            }
 +        }
 +
 +        if (isRaining != this.isRaining()) {
 +            // Only send weather packets to those affected
-+            for (int idx = 0; idx < this.players.size(); ++idx) {
-+                if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                    ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false);
++            for (ServerPlayer player : this.players) {
++                if (player.level() == this) {
++                    player.setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false);
 +                }
 +            }
 +        }
-+        for (int idx = 0; idx < this.players.size(); ++idx) {
-+            if (((ServerPlayer) this.players.get(idx)).level() == this) {
-+                ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
++        for (ServerPlayer player : this.players) {
++            if (player.level() == this) {
++                player.updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
 +            }
 +        }
 +        // CraftBukkit end
@@ -600,19 +600,19 @@
      }
  
      public void unload(LevelChunk chunk) {
-+        // Spigot Start
-+        for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) {
-+            if (tileentity instanceof net.minecraft.world.Container) {
++        // Spigot start
++        for (net.minecraft.world.level.block.entity.BlockEntity blockEntity : chunk.getBlockEntities().values()) {
++            if (blockEntity instanceof net.minecraft.world.Container) {
 +                // Paper start - this area looks like it can load chunks, change the behavior
 +                // chests for example can apply physics to the world
 +                // so instead we just change the active container and call the event
-+                for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) {
-+                    ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
++                for (org.bukkit.entity.HumanEntity human : Lists.newArrayList(((net.minecraft.world.Container) blockEntity).getViewers())) {
++                    ((org.bukkit.craftbukkit.entity.CraftHumanEntity) human).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
 +                }
 +                // Paper end - this area looks like it can load chunks, change the behavior
 +            }
 +        }
-+        // Spigot End
++        // Spigot end
          chunk.clearAllBlockEntities();
          chunk.unregisterTickContainerFromLevel(this);
      }
@@ -776,7 +776,7 @@
 +        ParticleOptions smallExplosionParticles,
 +        ParticleOptions largeExplosionParticles,
 +        Holder<SoundEvent> explosionSound,
-+        java.util.function.Consumer<ServerExplosion> configurator
++        @Nullable java.util.function.Consumer<ServerExplosion> configurator
 +    ) {
 +        // CraftBukkit end
          Explosion.BlockInteraction blockInteraction = switch (explosionInteraction) {
@@ -865,7 +865,7 @@
 -        for (int i1 = 0; i1 < this.players.size(); i1++) {
 -            ServerPlayer serverPlayer = this.players.get(i1);
 +        for (int i1 = 0; i1 < receivers.size(); i1++) { // Paper - particle API
-+            ServerPlayer serverPlayer = receivers.get(i1);  // Paper - particle API
++            ServerPlayer serverPlayer = receivers.get(i1); // Paper - particle API
 +            if (sender != null && !serverPlayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
              if (this.sendParticles(serverPlayer, overrideLimiter, posX, posY, posZ, clientboundLevelParticlesPacket)) {
                  i++;
@@ -889,11 +889,11 @@
 +
 +        final Optional<net.minecraft.world.level.saveddata.SavedData> cacheEntry = storage.cache.get(mapId.key());
 +        if (cacheEntry == null) { // Cache did not contain, try to load and may init
-+            final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache
-+            if (worldmap != null) { // map was read, init it and return
-+                worldmap.id = mapId;
-+                new org.bukkit.event.server.MapInitializeEvent(worldmap.mapView).callEvent();
-+                return worldmap;
++            final MapItemSavedData mapData = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache
++            if (mapData != null) { // map was read, init it and return
++                mapData.id = mapId;
++                new org.bukkit.event.server.MapInitializeEvent(mapData.mapView).callEvent();
++                return mapData;
 +            }
 +
 +            return null; // Map does not exist, reading failed.
@@ -1096,7 +1096,7 @@
 +                }
 +            }
 +            // Spigot end
-+            // Spigot Start
++            // Spigot start
 +            if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
 +                // Paper start - Fix merchant inventory not closing on entity removal
 +                if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
@@ -1107,7 +1107,7 @@
 +                    h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
 +                }
 +            }
-+            // Spigot End
++            // Spigot end
              ServerLevel.this.getChunkSource().removeEntity(entity);
              if (entity instanceof ServerPlayer serverPlayer) {
                  ServerLevel.this.players.remove(serverPlayer);
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index d506c224e1..b6c60e488c 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -334,7 +334,7 @@
      private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) {
 -        this.getScoreboard().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
 -    }
-+        this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
++        this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points)); // CraftBukkit - Use our scores instead
 +    }
 +
 +    // Paper start - PlayerDeathEvent#getItemsToKeep
@@ -571,11 +571,11 @@
 +        );
 +        // Paper end - respawn flags
 +        this.level().getCraftServer().getPluginManager().callEvent(respawnEvent);
-+        // Spigot Start
++        // Spigot start
 +        if (this.connection.isDisconnected()) {
 +            return null;
 +        }
-+        // Spigot End
++        // Spigot end
 +
 +        location = respawnEvent.getRespawnLocation();
 +
@@ -613,7 +613,7 @@
              boolean isPossibleToRespawnInThis1 = blockState1.getBlock().isPossibleToRespawnInThis(blockState1);
              return isPossibleToRespawnInThis && isPossibleToRespawnInThis1
 -                ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle))
-+                ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle, false, false))  // CraftBukkit
++                ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle, false, false)) // CraftBukkit
                  : Optional.empty();
          }
      }
@@ -1159,7 +1159,7 @@
          }
  
 -        boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera);
-+        boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, cause);  // CraftBukkit
++        boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, cause); // CraftBukkit
          if (flag) {
              this.setYHeadRot(relativeMovements.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw);
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index d0b244566b..fd053c54f5 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -59,7 +59,7 @@
  
      public void tick() {
 -        this.gameTicks++;
-+        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit;
++        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
          if (this.hasDelayedDestroy) {
 -            BlockState blockState = this.level.getBlockState(this.delayedDestroyPos);
 -            if (blockState.isAir()) {
@@ -102,7 +102,7 @@
 -                    return;
 -                }
 +                    // Update any tile entity data for this block
-+                    capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
++                    this.capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
 +                    // CraftBukkit end
 +                    return;
 +                }
@@ -113,7 +113,7 @@
 +                    // Let the client know the block still exists
 +                    // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
 +                    // Update any tile entity data for this block
-+                    capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
++                    this.capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
 +                    return;
 +                }
 +                // CraftBukkit end
@@ -207,7 +207,7 @@
              }
          }
      }
-@@ -235,36 +_,125 @@
+@@ -235,36 +_,127 @@
  
      public boolean destroyBlock(BlockPos pos) {
          BlockState blockState = this.level.getBlockState(pos);
@@ -258,12 +258,14 @@
 +                // Paper end - Don't resync blocks
 +
 +                // Update any tile entity data for this block
-+                if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
-+                    BlockEntity tileentity = this.level.getBlockEntity(pos);
-+                    if (tileentity != null) {
-+                        this.player.connection.send(tileentity.getUpdatePacket());
++                if (!this.captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
++                    BlockEntity blockEntity = this.level.getBlockEntity(pos);
++                    if (blockEntity != null) {
++                        this.player.connection.send(blockEntity.getUpdatePacket());
 +                    }
-+                } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction
++                } else {
++                    this.capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
++                }
 +                return false;
 +            }
 +        }
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 7058df616f..39098db25c 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -1785,21 +1785,20 @@
      }
  
      public void sendDisguisedChatMessage(Component message, ChatType.Bound boundType) {
-@@ -1579,6 +_,18 @@
+@@ -1579,6 +_,17 @@
          return this.connection.getRemoteAddress();
      }
  
-+    // Spigot Start
-+    public SocketAddress getRawAddress()
-+    {
++    // Spigot start
++    public SocketAddress getRawAddress() {
 +        // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something
-+        if (connection.channel.remoteAddress() == null) {
++        if (this.connection.channel.remoteAddress() == null) {
 +            return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0);
 +        }
 +        // Paper end - Unix domain socket support
 +        return this.connection.channel.remoteAddress();
 +    }
-+    // Spigot End
++    // Spigot end
 +
      public void switchToConfig() {
          this.waitingForSwitchToConfig = true;
@@ -1812,12 +1811,12 @@
          if (this.player.hasClientLoaded()) {
              final ServerLevel serverLevel = this.player.serverLevel();
              final Entity target = packet.getTarget(serverLevel);
-+            // Spigot Start
++            // Spigot start
 +            if (target == this.player && !this.player.isSpectator()) {
 +                this.disconnect(Component.literal("Cannot interact with self!"), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION); // Paper - kick event cause
 +                return;
 +            }
-+            // Spigot End
++            // Spigot end
              this.player.resetLastActionTime();
              this.player.setShiftKeyDown(packet.isUsingSecondaryAction());
              if (target != null) {
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
index 64f87203f2..218905a0aa 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
@@ -107,9 +107,9 @@
 +                if (event.callEvent()) {
 +                    // If we've failed somehow, let the client know so and go no further.
 +                    if (event.isFailed()) {
-+                        Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.failMessage());
-+                        this.connection.send(new ClientboundLoginDisconnectPacket(component));
-+                        this.connection.disconnect(component);
++                        Component message = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.failMessage());
++                        this.connection.send(new ClientboundLoginDisconnectPacket(message));
++                        this.connection.disconnect(message);
 +                        return;
 +                    }
 +
@@ -129,7 +129,7 @@
 +                }
 +            }
 +            // Paper end
-+            // Spigot Start
++            // Spigot start
 +            String[] split = packet.hostName().split("\00");
 +            if (!handledByEvent && proxyLogicEnabled) { // Paper
 +                // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
@@ -141,20 +141,20 @@
 +                    // Paper end - Unix domain socket support
 +                    this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
 +                } else {
-+                    Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
-+                    this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
-+                    this.connection.disconnect(chatmessage);
++                    Component message = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
++                    this.connection.send(new ClientboundLoginDisconnectPacket(message));
++                    this.connection.disconnect(message);
 +                    return;
 +                }
 +                if (split.length == 4) {
 +                    this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class);
 +                }
 +            } else if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) {
-+                Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
-+                this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
-+                this.connection.disconnect(chatmessage);
++                Component message = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
++                this.connection.send(new ClientboundLoginDisconnectPacket(message));
++                this.connection.disconnect(message);
 +            }
-+            // Spigot End
++            // Spigot end
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
index 37842106ce..2336810f52 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch
@@ -47,7 +47,7 @@
          GameProfileCache.GameProfileInfo gameProfileInfo = new GameProfileCache.GameProfileInfo(gameProfile, time);
          this.safeAdd(gameProfileInfo);
 -        this.save();
-+        if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving
++        if (!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving
      }
  
      private long getNextOperation() {
@@ -130,11 +130,11 @@
  
              return (List<GameProfileCache.GameProfileInfo>)var9;
          } catch (FileNotFoundException var7) {
-+        // Spigot Start
++        // Spigot start
 +        } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) {
-+            GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." );
++            LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." );
 +            this.file.delete();
-+        // Spigot End
++        // Spigot end
          } catch (JsonParseException | IOException var8) {
              LOGGER.warn("Failed to load profile cache {}", this.file, var8);
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
index 80fad33f9d..8239aa71e1 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -671,11 +671,11 @@
 +        } else {
 +            teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(location), Vec3.ZERO, location.getYaw(), location.getPitch(), TeleportTransition.DO_NOTHING);
 +        }
-+        // Spigot Start
++        // Spigot start
 +        if (teleportTransition == null) { // Paper - Add PlayerPostRespawnEvent - diff on change - spigot early returns if respawn pos is null, that is how they handle disconnected player in respawn event
 +            return player;
 +        }
-+        // Spigot End
++        // Spigot end
 +        ServerLevel level = teleportTransition.newLevel();
 +        serverPlayer.spawnIn(level);
 +        serverPlayer.unsetRemoved();
diff --git a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
index 88ab6f0915..1e359d0ca2 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
@@ -68,14 +68,14 @@
                          this.map.put(this.getKeyForUser(storedUserEntry.getUser()), (V)storedUserEntry);
                      }
                  }
-+            // Spigot Start
++            // Spigot start
 +            } catch (com.google.gson.JsonParseException | NullPointerException ex) {
-+                org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex);
++                LOGGER.warn("Unable to read file {}, backing it up to {0}.backup and creating new copy.", this.file, ex);
 +                File backup = new File(this.file + ".backup");
 +                this.file.renameTo(backup);
 +                this.file.delete();
              }
-+            // Spigot End
++            // Spigot end
          }
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
index 6e3ece9070..7d07f44004 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/players/UserBanListEntry.java
 +++ b/net/minecraft/server/players/UserBanListEntry.java
-@@ -37,19 +_,29 @@
+@@ -37,19 +_,27 @@
  
      @Nullable
      private static GameProfile createGameProfile(JsonObject json) {
@@ -21,16 +21,14 @@
  
 -            return new GameProfile(uuid, json.get("name").getAsString());
 +        }
-+        if ( json.has("name"))
-+        {
++        if (json.has("name")) {
 +            name = json.get("name").getAsString();
 +        }
-+        if ( uuid != null || name != null )
-+        {
-+            return new GameProfile( uuid, name );
++        if (uuid != null || name != null) {
++            return new GameProfile(uuid, name);
          } else {
              return null;
          }
-+        // Spigot End
++        // Spigot end
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
index c4644730ac..d289aee489 100644
--- a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch
@@ -5,7 +5,7 @@
          for (RecipeHolder<?> recipeHolder : recipes) {
              ResourceKey<Recipe<?>> resourceKey = recipeHolder.id();
 -            if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial()) {
-+            if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial()  && org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, resourceKey.location())) { // CraftBukkit{
++            if (!this.known.contains(resourceKey) && !recipeHolder.value().isSpecial() && org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, resourceKey.location())) { // CraftBukkit
                  this.add(resourceKey);
                  this.addHighlight(resourceKey);
                  this.displayResolver
diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
index 5e1b12c3cb..4d4c9288fb 100644
--- a/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
+++ b/paper-server/patches/sources/net/minecraft/stats/ServerStatsCounter.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/stats/ServerStatsCounter.java
 +++ b/net/minecraft/stats/ServerStatsCounter.java
-@@ -51,9 +_,22 @@
+@@ -51,9 +_,21 @@
                  LOGGER.error("Couldn't parse statistics file {}", file, var5);
              }
          }
@@ -9,17 +9,16 @@
 +        // Disables saving any forced stats, so it stays at the same value (without enabling disableStatSaving)
 +        // Fixes stat initialization to not cause a NullPointerException
 +        // Spigot start
-+        for ( Map.Entry<ResourceLocation, Integer> entry : org.spigotmc.SpigotConfig.forcedStats.entrySet() )
-+        {
++        for (Map.Entry<ResourceLocation, Integer> entry : org.spigotmc.SpigotConfig.forcedStats.entrySet()) {
 +            Stat<ResourceLocation> wrapper = Stats.CUSTOM.get(java.util.Objects.requireNonNull(BuiltInRegistries.CUSTOM_STAT.getValue(entry.getKey()))); // Paper - ensured by SpigotConfig#stats
-+            this.stats.put( wrapper, entry.getValue().intValue() );
++            this.stats.put(wrapper, entry.getValue().intValue());
 +        }
 +        // Spigot end
 +        // Paper end - Moved after stat fetching for player state file
      }
  
      public void save() {
-+        if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot
++        if (org.spigotmc.SpigotConfig.disableStatSaving) return; // Spigot
          try {
              FileUtils.writeStringToFile(this.file, this.toJson());
          } catch (IOException var2) {
@@ -27,7 +26,7 @@
  
      @Override
      public void setValue(Player player, Stat<?> stat, int i) {
-+        if ( org.spigotmc.SpigotConfig.disableStatSaving ) return; // Spigot
++        if (org.spigotmc.SpigotConfig.disableStatSaving) return; // Spigot
 +        if (stat.getType() == Stats.CUSTOM && stat.getValue() instanceof final ResourceLocation resourceLocation && org.spigotmc.SpigotConfig.forcedStats.get(resourceLocation) != null) return; // Paper - disable saving forced stats
          super.setValue(player, stat, i);
          this.dirty.add(stat);
diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
index adc2c80c8e..f5611a0039 100644
--- a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
@@ -5,7 +5,7 @@
      public final Container container2;
  
 +    // Paper start - add fields and methods
-+    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +
 +    public java.util.List<ItemStack> getContents() {
 +        java.util.List<ItemStack> result = new java.util.ArrayList<>(this.getContainerSize());
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index da4d2fc24d..a6132cf6b8 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -136,6 +_,114 @@
+@@ -136,6 +_,108 @@
  import org.slf4j.Logger;
  
  public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder {
@@ -105,12 +105,6 @@
 +        return this.bukkitEntity;
 +    }
 +    // Paper end
-+
-+    // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-+    public int getDefaultMaxAirSupply() {
-+        return Entity.TOTAL_AIR_SUPPLY;
-+    }
-+    // CraftBukkit end
 +
      private static final Logger LOGGER = LogUtils.getLogger();
      public static final String ID_TAG = "id";
@@ -205,6 +199,19 @@
      }
  
      public boolean isColliding(BlockPos pos, BlockState state) {
+@@ -284,6 +_,12 @@
+         return team != null && team.getColor().getColor() != null ? team.getColor().getColor() : 16777215;
+     }
+ 
++    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++    public int getDefaultMaxAirSupply() {
++        return Entity.TOTAL_AIR_SUPPLY;
++    }
++    // CraftBukkit end
++
+     public boolean isSpectator() {
+         return false;
+     }
 @@ -324,7 +_,7 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
index 632c00d653..ffaffc46db 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LightningBolt.java.patch
@@ -44,7 +44,7 @@
              if (!(this.level() instanceof ServerLevel)) {
                  this.level().setSkyFlashTime(2);
 -            } else if (!this.visualOnly) {
-+            } else if (!this.visualOnly && !this.isEffect) {  // Paper - Properly handle lightning effects api
++            } else if (!this.visualOnly && !this.isEffect) { // Paper - Properly handle lightning effects api
                  List<Entity> entities = this.level()
                      .getEntities(
                          this,
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index 6a08fbc658..ea57a78e0b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -66,7 +66,7 @@
 -        this.setHealth(this.getMaxHealth());
 +        this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
 +        // CraftBukkit - this.setHealth(this.getMaxHealth()) inlined and simplified to skip the instanceof check for Player, as getBukkitEntity() is not initialized in constructor
-+        this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
++        this.entityData.set(LivingEntity.DATA_HEALTH_ID, this.getMaxHealth());
          this.blocksBuilding = true;
          this.rotA = (float)((Math.random() + 1.0) * 0.01F);
          this.reapplyPosition();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
index 0fab13edb2..f73e97c903 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch
@@ -4,7 +4,7 @@
  
      public FloatGoal(Mob mob) {
          this.mob = mob;
-+        if (mob.getCommandSenderWorld().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float
++        if (mob.level().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float
          this.setFlags(EnumSet.of(Goal.Flag.JUMP));
          mob.getNavigation().setCanFloat(true);
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
index bd9b6ee1ee..5cc08f5dc8 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch
@@ -50,7 +50,7 @@
 +            // Paper start - EntityPathfindEvent
 +            boolean copiedSet = false;
 +            for (BlockPos possibleTarget : targets) {
-+                if (!this.mob.getCommandSenderWorld().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border
++                if (!this.mob.level().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border
 +                    io.papermc.paper.util.MCUtil.toLocation(this.mob.level(), possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) {
 +                    if (!copiedSet) {
 +                        copiedSet = true;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
index 450e26f925..94cfc94803 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/AbstractSchoolingFish.java.patch
@@ -4,7 +4,7 @@
      }
  
      public void stopFollowing() {
-+        if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called
++        if (this.leader == null) return; // Paper - Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called
          this.leader.removeFollower();
          this.leader = null;
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
index a8935bd393..4f63402c67 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
@@ -14,8 +14,8 @@
      @Override
 -    protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
 +    // CraftBukkit start - void -> boolean
-+    public boolean actuallyHurt(ServerLevel worldserver, DamageSource damagesource, float f, org.bukkit.event.entity.EntityDamageEvent event) {
-+        boolean damageResult = super.actuallyHurt(worldserver, damagesource, f, event);
++    public boolean actuallyHurt(ServerLevel level, DamageSource damageSource, float amount, org.bukkit.event.entity.EntityDamageEvent event) {
++        boolean damageResult = super.actuallyHurt(level, damageSource, amount, event);
 +        if (!damageResult) return false;
          this.resetLove();
 -        super.actuallyHurt(level, damageSource, amount);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
index d8e007e816..5e9da74414 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
@@ -1,6 +1,10 @@
 --- a/net/minecraft/world/entity/animal/Bee.java
 +++ b/net/minecraft/world/entity/animal/Bee.java
-@@ -144,7 +_,22 @@
+@@ -141,10 +_,26 @@
+     Bee.BeeGoToHiveGoal goToHiveGoal;
+     private Bee.BeeGoToKnownFlowerGoal goToKnownFlowerGoal;
+     private int underWaterTicks;
++    public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override
  
      public Bee(EntityType<? extends Bee> entityType, Level level) {
          super(entityType, level);
@@ -65,24 +69,18 @@
 -            return this.isTooFarAway(this.hivePos) ? null : this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
 +        // Paper start - move over logic to accommodate isTooFarAway with chunk load check
 +        if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) {
-+            return (BeehiveBlockEntity) this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
++            return this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
          }
 +        return null;
 +        // Paper end
      }
  
      boolean isHiveValid() {
-@@ -520,11 +_,13 @@
-         this.setFlag(4, hasStung);
-     }
- 
-+    public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override
-     public boolean isRolling() {
-         return this.getFlag(2);
+@@ -525,6 +_,7 @@
      }
  
      public void setRolling(boolean isRolling) {
-+        isRolling = rollingOverride.toBooleanOrElse(isRolling); // Paper - Rolling override
++        isRolling = this.rollingOverride.toBooleanOrElse(isRolling); // Paper - Rolling override
          this.setFlag(2, isRolling);
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
index a9f720be0d..f0e77a24be 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Dolphin.java.patch
@@ -1,18 +1,19 @@
 --- a/net/minecraft/world/entity/animal/Dolphin.java
 +++ b/net/minecraft/world/entity/animal/Dolphin.java
-@@ -63,6 +_,12 @@
- import net.minecraft.world.phys.Vec3;
+@@ -96,6 +_,13 @@
+         return EntityType.DOLPHIN.create(level, EntitySpawnReason.BREEDING);
+     }
  
- public class Dolphin extends AgeableWaterCreature {
 +    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
 +    @Override
 +    public int getDefaultMaxAirSupply() {
 +        return TOTAL_AIR_SUPPLY;
 +    }
 +    // CraftBukkit end
-     private static final EntityDataAccessor<BlockPos> TREASURE_POS = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BLOCK_POS);
-     private static final EntityDataAccessor<Boolean> GOT_FISH = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BOOLEAN);
-     private static final EntityDataAccessor<Integer> MOISTNESS_LEVEL = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.INT);
++
+     @Override
+     public float getAgeScale() {
+         return this.isBaby() ? 0.65F : 1.0F;
 @@ -196,7 +_,7 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
index 302c9dd462..b39c81cfbc 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
@@ -22,9 +22,9 @@
      }
  
      @Override
-@@ -620,4 +_,11 @@
-             return Util.getRandom(variants, random);
-         }
+@@ -521,6 +_,13 @@
+     ) {
+         return level.getBlockState(pos.below()).is(BlockTags.AXOLOTLS_SPAWNABLE_ON);
      }
 +
 +    // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
@@ -33,4 +33,6 @@
 +        return Axolotl.AXOLOTL_TOTAL_AIR_SUPPLY;
 +    }
 +    // CraftBukkit end
- }
+ 
+     public static enum AnimationState {
+         PLAYING_DEAD,
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
index 0c41c3aea7..af11b98e94 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/goat/Goat.java.patch
@@ -34,10 +34,11 @@
          }
      }
  
-@@ -381,4 +_,14 @@
+@@ -381,4 +_,15 @@
      ) {
          return level.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(level, pos);
      }
++
 +    // Paper start - Goat ram API
 +    public void ram(net.minecraft.world.entity.LivingEntity entity) {
 +        Brain<Goat> brain = this.getBrain();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
index 4eadfd7df1..bc6f934031 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
@@ -1,12 +1,13 @@
 --- a/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
 +++ b/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
-@@ -69,9 +_,16 @@
+@@ -69,9 +_,17 @@
          super.dropEquipment(level);
          if (this.hasChest()) {
              this.spawnAtLocation(level, Blocks.CHEST);
 +            //this.setChest(false); // Paper - moved to post death logic
 +        }
 +    }
++
 +    // Paper start
 +    protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {
 +        if (this.hasChest() && (event == null || !event.isCancelled())) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
index 19135714d8..2679d44397 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -24,7 +24,7 @@
          }
 +
 +        // CraftBukkit start - add fields and methods
-+        public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++        public final List<HumanEntity> transaction = new java.util.ArrayList<>();
 +        private int maxStack = MAX_STACK;
 +
 +        @Override
@@ -132,7 +132,7 @@
              }
  
              if (this.canEatGrass()) {
-@@ -690,6 +_,15 @@
+@@ -690,6 +_,16 @@
          }
      }
  
@@ -140,6 +140,7 @@
 +    public void setMouthOpen(boolean open) {
 +        this.setFlag(FLAG_OPEN_MOUTH, open);
 +    }
++
 +    public boolean isMouthOpen() {
 +        return this.getFlag(FLAG_OPEN_MOUTH);
 +    }
@@ -148,7 +149,7 @@
      @Override
      public InteractionResult mobInteract(Player player, InteractionHand hand) {
          if (this.isVehicle() || this.isBaby()) {
-@@ -727,6 +_,11 @@
+@@ -727,6 +_,12 @@
          this.setFlag(16, eating);
      }
  
@@ -157,6 +158,7 @@
 +        this.setFlag(FLAG_STANDING, standing);
 +    }
 +    // Paper end - Horse API
++
      public void setStanding(boolean standing) {
          if (standing) {
              this.setEating(false);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
index 0e059e7e5c..14f23439db 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
@@ -1,9 +1,9 @@
 --- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
 +++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
-@@ -266,6 +_,13 @@
-             BlockPos headBlock = this.getHeadBlock();
+@@ -267,6 +_,13 @@
              this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.SNIFFER_DIGGING, (serverLevel1, itemStack) -> {
                  ItemEntity itemEntity = new ItemEntity(this.level(), headBlock.getX(), headBlock.getY(), headBlock.getZ(), itemStack);
+                 itemEntity.setDefaultPickUpDelay();
 +                // CraftBukkit start - handle EntityDropItemEvent
 +                org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
 +                org.bukkit.Bukkit.getPluginManager().callEvent(event);
@@ -11,9 +11,9 @@
 +                    return;
 +                }
 +                // CraftBukkit end
-                 itemEntity.setDefaultPickUpDelay();
                  serverLevel1.addFreshEntity(itemEntity);
              });
+             this.playSound(SoundEvents.SNIFFER_DROP_SEED, 1.0F, 1.0F);
 @@ -325,12 +_,17 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
index 48b66a876e..bb629ff9a6 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -48,16 +48,14 @@
                          double d = flyTargetLocation.x - this.getX();
                          double d1 = flyTargetLocation.y - this.getY();
                          double d2 = flyTargetLocation.z - this.getZ();
-@@ -369,7 +_,14 @@
+@@ -369,7 +_,12 @@
              if (this.nearestCrystal.isRemoved()) {
                  this.nearestCrystal = null;
              } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
 -                this.setHealth(this.getHealth() + 1.0F);
 +                // CraftBukkit start
 +                org.bukkit.event.entity.EntityRegainHealthEvent event = new org.bukkit.event.entity.EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
-+                this.level().getCraftServer().getPluginManager().callEvent(event);
-+
-+                if (!event.isCancelled()) {
++                if (event.callEvent()) {
 +                    this.setHealth((float) (this.getHealth() + event.getAmount()));
 +                }
 +                // CraftBukkit end
@@ -94,7 +92,7 @@
                          } else {
                              flag = true;
                          }
-@@ -450,6 +_,54 @@
+@@ -450,6 +_,58 @@
              }
          }
  
@@ -116,32 +114,36 @@
 +            }
 +        } else {
 +            for (org.bukkit.block.Block block : event.blockList()) {
-+                org.bukkit.Material blockId = block.getType();
-+                if (blockId.isAir()) {
++                org.bukkit.Material blockType = block.getType();
++                if (blockType.isAir()) {
 +                    continue;
 +                }
 +
 +                org.bukkit.craftbukkit.block.CraftBlock craftBlock = ((org.bukkit.craftbukkit.block.CraftBlock) block);
-+                BlockPos blockposition = craftBlock.getPosition();
++                BlockPos pos = craftBlock.getPosition();
 +
 +                net.minecraft.world.level.block.Block nmsBlock = craftBlock.getNMS().getBlock();
 +                if (nmsBlock.dropFromExplosion(this.explosionSource)) {
-+                    net.minecraft.world.level.block.entity.BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null;
-+                    net.minecraft.world.level.storage.loot.LootParams.Builder loottableinfo_builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((ServerLevel) this.level())).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.TOOL, net.minecraft.world.item.ItemStack.EMPTY).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.BLOCK_ENTITY, tileentity);
++                    net.minecraft.world.level.block.entity.BlockEntity blockEntity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(pos) : null;
++                    net.minecraft.world.level.storage.loot.LootParams.Builder builder = new net.minecraft.world.level.storage.loot.LootParams.Builder((ServerLevel) this.level())
++                        .withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
++                        .withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.TOOL, net.minecraft.world.item.ItemStack.EMPTY)
++                        .withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield())
++                        .withOptionalParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.BLOCK_ENTITY, blockEntity);
 +
-+                    craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> {
-+                        net.minecraft.world.level.block.Block.popResource(this.level(), blockposition, itemstack);
++                    craftBlock.getNMS().getDrops(builder).forEach((stack) -> {
++                        net.minecraft.world.level.block.Block.popResource(this.level(), pos, stack);
 +                    });
-+                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, net.minecraft.world.item.ItemStack.EMPTY, false);
++                    craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), pos, net.minecraft.world.item.ItemStack.EMPTY, false);
 +                }
 +                // Paper start - TNTPrimeEvent
-+                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level(), blockposition);
++                org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level(), pos);
 +                if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent())
 +                    continue;
 +                // Paper end - TNTPrimeEvent
-+                nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource);
++                nmsBlock.wasExploded((ServerLevel) this.level(), pos, this.explosionSource);
 +
-+                this.level().removeBlock(blockposition, false);
++                this.level().removeBlock(pos, false);
 +            }
 +        }
 +        // CraftBukkit end
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
index 5556c6a0ae..86f2d47cd1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch
@@ -6,7 +6,7 @@
                          DragonFireball dragonFireball = new DragonFireball(level, this.dragon, vec32.normalize());
 +                        dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported
                          dragonFireball.moveTo(d2, d3, d4, 0.0F, 0.0F);
-+                        if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events
++                        if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events
                          level.addFreshEntity(dragonFireball);
 +                        else dragonFireball.discard(null); // Paper - EnderDragon Events
                          this.fireballCharge = 0;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
index a99fa7b9c1..c9272090aa 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
 +++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
-@@ -23,6 +_,19 @@
+@@ -23,6 +_,18 @@
                  this.currentPhase.end();
              }
  
@@ -10,8 +10,7 @@
 +                (this.currentPhase == null) ? null : org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()),
 +                org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(phase)
 +            );
-+            this.dragon.level().getCraftServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
++            if (!event.callEvent()) {
 +                return;
 +            }
 +            phase = org.bukkit.craftbukkit.entity.CraftEnderDragon.getMinecraftPhase(event.getNewPhase());
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
index 9d6f177cef..936e924c09 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -1,18 +1,13 @@
 --- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
 +++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
-@@ -77,6 +_,12 @@
-         && entity.attackable();
-     private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR);
- 
-+    // Paper start
-+    private boolean canPortal = false;
-+
-+    public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
-+    // Paper end
-+
-     public WitherBoss(EntityType<? extends WitherBoss> entityType, Level level) {
-         super(entityType, level);
-         this.moveControl = new FlyingMoveControl(this, 10, false);
+@@ -69,6 +_,7 @@
+     private final int[] nextHeadUpdate = new int[2];
+     private final int[] idleHeadUpdates = new int[2];
+     private int destroyBlocksTick;
++    private boolean canPortal = false; // Paper
+     public final ServerBossEvent bossEvent = (ServerBossEvent)new ServerBossEvent(
+             this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS
+         )
 @@ -260,15 +_,40 @@
              int i = this.getInvulnerableTicks() - 1;
              this.bossEvent.setProgress(1.0F - i / 220.0F);
@@ -106,13 +101,20 @@
          } else {
              this.noActionTime = 0;
          }
-@@ -547,12 +_,12 @@
+@@ -547,12 +_,18 @@
  
      @Override
      public boolean canUsePortal(boolean allowPassengers) {
 -        return false;
+-    }
 +        return this.canPortal; // Paper
-     }
++    }
++
++    // Paper start
++    public void setCanTravelThroughPortals(boolean canPortal) {
++        this.canPortal = canPortal;
++    }
++    // Paper end
  
      @Override
      public boolean canBeAffected(MobEffectInstance potioneffect) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
index 146db06871..36711a9189 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/ArmorStand.java.patch
@@ -155,7 +155,7 @@
 +            // CraftBukkit end
 +            // Paper start - avoid duplicate event call
 +            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
-+            if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit
++            if (!event.isCancelled()) this.kill(level, damageSource, false); // CraftBukkit
 +            // Paper end
              return false;
          } else if (damageSource.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
@@ -204,7 +204,7 @@
 +                    org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(level, damageSource); // Paper
                      this.showBreakingParticles();
 -                    this.kill(level);
-+                    if (!event.isCancelled()) this.kill(damageSource, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...)
++                    if (!event.isCancelled()) this.kill(level, damageSource, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...)
                  }
  
                  return true;
@@ -215,7 +215,7 @@
 -            this.kill(level);
 +            // Paper start - avoid duplicate event call
 +            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
-+            if (!event.isCancelled()) this.kill(damageSource, false); // CraftBukkit
++            if (!event.isCancelled()) this.kill(level, damageSource, false); // CraftBukkit
 +            // Paper end
          } else {
              this.setHealth(health);
@@ -289,7 +289,7 @@
          Rotations rotations = this.entityData.get(DATA_HEAD_POSE);
          if (!this.headPose.equals(rotations)) {
              this.setHeadPose(rotations);
-@@ -587,9 +_,31 @@
+@@ -587,9 +_,32 @@
          return this.isSmall();
      }
  
@@ -309,9 +309,10 @@
 +
 +    public void kill(ServerLevel level, @Nullable DamageSource damageSource) {
 +        // Paper start - make cancellable
-+        this.kill(damageSource, true);
++        this.kill(level, damageSource, true);
 +    }
-+    public void kill(@Nullable DamageSource damageSource, boolean callEvent) {
++
++    public void kill(ServerLevel level, @Nullable DamageSource damageSource, boolean callEvent) {
 +        if (callEvent) {
 +            org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event
 +            if (event.isCancelled()) return;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
index 397795f3c0..96b38cb9de 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -4,7 +4,7 @@
  import net.minecraft.world.phys.Vec3;
  import org.slf4j.Logger;
  
-+// CraftBukkit start;
++// CraftBukkit start
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.event.entity.EntityRemoveEvent;
 +// CraftBukkit end
@@ -20,28 +20,14 @@
  
      public FallingBlockEntity(EntityType<? extends FallingBlockEntity> entityType, Level level) {
          super(entityType, level);
-@@ -80,6 +_,10 @@
-     }
- 
-     public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState) {
-+        // CraftBukkit start
-+        return FallingBlockEntity.fall(level, pos, blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
-+    }
-+    public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
-         FallingBlockEntity fallingBlockEntity = new FallingBlockEntity(
-             level,
-             pos.getX() + 0.5,
-@@ -89,8 +_,9 @@
+@@ -89,6 +_,7 @@
                  ? blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false))
                  : blockState
          );
 +        if (!CraftEventFactory.callEntityChangeBlockEvent(fallingBlockEntity, pos, blockState.getFluidState().createLegacyBlock())) return fallingBlockEntity; // CraftBukkit
          level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
--        level.addFreshEntity(fallingBlockEntity);
-+        level.addFreshEntity(fallingBlockEntity, creatureSpawnReason); // CraftBukkit
+         level.addFreshEntity(fallingBlockEntity);
          return fallingBlockEntity;
-     }
- 
 @@ -139,13 +_,22 @@
      @Override
      public void tick() {
@@ -147,9 +133,9 @@
          }
 +
 +        // Paper start - Expand FallingBlock API
-+         if (compound.contains("Paper.AutoExpire")) {
++        if (compound.contains("Paper.AutoExpire")) {
 +            this.autoExpire = compound.getBoolean("Paper.AutoExpire");
-+         }
++        }
 +        // Paper end - Expand FallingBlock API
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 8700616d07..487f6205a1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -142,18 +142,18 @@
              }
  
              return true;
-@@ -308,6 +_,11 @@
- 
-     @Override
-     public void addAdditionalSaveData(CompoundTag compound) {
+@@ -322,6 +_,11 @@
+         if (!this.getItem().isEmpty()) {
+             compound.put("Item", this.getItem().save(this.registryAccess()));
+         }
 +        // Paper start - Friction API
 +        if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) {
 +            compound.putString("Paper.FrictionState", this.frictionState.toString());
 +        }
 +        // Paper end - Friction API
-         compound.putShort("Health", (short)this.health);
-         compound.putShort("Age", (short)this.age);
-         compound.putShort("PickupDelay", (short)this.pickupDelay);
+     }
+ 
+     @Override
 @@ -347,9 +_,19 @@
          } else {
              this.setItem(ItemStack.EMPTY);
@@ -164,7 +164,7 @@
 +            try {
 +                frictionState = net.kyori.adventure.util.TriState.valueOf(fs);
 +            } catch (Exception ignored) {
-+                com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this);
++                com.mojang.logging.LogUtils.getLogger().error("Unknown friction state {} for {}", fs, this);
 +            }
 +        }
 +        // Paper end - Friction API
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
index 49714990c2..1bbdb4c9b6 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
@@ -4,7 +4,7 @@
  import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.level.portal.TeleportTransition;
  
-+// CraftBukkit start;
++// CraftBukkit start
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.event.entity.EntityRemoveEvent;
 +import org.bukkit.event.entity.ExplosionPrimeEvent;
@@ -95,7 +95,7 @@
 +        if (event.isCancelled()) {
 +            return;
 +        }
-+        // Craftbukkit end
++        // CraftBukkit end
          this.level()
              .explode(
                  this,
@@ -118,7 +118,7 @@
 +    // Paper start - Option to prevent TNT from moving in water
 +    @Override
 +    public boolean isPushedByFluid() {
-+        return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
++        return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
 +    }
 +    // Paper end - Option to prevent TNT from moving in water
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
index 2098a78455..a7203574ef 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/entity/monster/Bogged.java
 +++ b/net/minecraft/world/entity/monster/Bogged.java
-@@ -72,7 +_,20 @@
+@@ -72,7 +_,19 @@
          ItemStack itemInHand = player.getItemInHand(hand);
          if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
              if (this.level() instanceof ServerLevel serverLevel) {
@@ -11,18 +11,17 @@
 +                org.bukkit.event.player.PlayerShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemInHand, hand, drops);
 +                if (event != null) {
 +                    if (event.isCancelled()) {
-+                        // this.getEntityData().markDirty(Bogged.DATA_SHEARED); // CraftBukkit - mark dirty to restore sheared state to clients // Paper - no longer needed
 +                        return InteractionResult.PASS;
 +                    }
 +                    drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
-+                // Paper end - custom shear drops
++                    // Paper end - custom shear drops
 +                }
 +                // CraftBukkit end
 +                this.shear(serverLevel, SoundSource.PLAYERS, itemInHand, drops); // Paper - custom shear drops
                  this.gameEvent(GameEvent.SHEAR, player);
                  itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
              }
-@@ -125,15 +_,34 @@
+@@ -125,15 +_,33 @@
  
      @Override
      public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
@@ -53,10 +52,9 @@
 -            level, BuiltInLootTables.BOGGED_SHEAR, stack, (serverLevel, itemStack) -> this.spawnAtLocation(serverLevel, itemStack, this.getBbHeight())
 -        );
 +    // Paper start - custom shear drops
-+    private void spawnShearedMushrooms(ServerLevel world, ItemStack shears, java.util.List<ItemStack> drops) {
-+        final ServerLevel worldserver1 = world; // Named for lambda consumption
++    private void spawnShearedMushrooms(ServerLevel level, ItemStack stack, java.util.List<ItemStack> drops) {
 +        this.forceDrops = true; // Paper - Add missing forceDrop toggles
-+        drops.forEach(itemstack1 -> this.spawnAtLocation(world, shears, this.getBbHeight()));
++        drops.forEach(itemstack1 -> this.spawnAtLocation(level, stack, this.getBbHeight()));
 +        this.forceDrops = false; // Paper - Add missing forceDrop toggles
 +        // Paper end - custom shear drops
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
index 30d6bab42b..6a3c837696 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Creeper.java.patch
@@ -13,7 +13,7 @@
  
          if (compound.getBoolean("ignited")) {
 -            this.ignite();
-+            this.entityData.set(Creeper.DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event
++            this.entityData.set(DATA_IS_IGNITED, true); // Paper - set directly to avoid firing event
          }
      }
  
@@ -66,7 +66,7 @@
 +            // CraftBukkit start
 +            } else {
 +                this.swell = 0;
-+                this.entityData.set(DATA_IS_IGNITED, Boolean.valueOf(false)); // Paper
++                this.entityData.set(DATA_IS_IGNITED, false); // Paper
 +            }
 +            // CraftBukkit end
          }
@@ -81,7 +81,7 @@
              areaEffectCloud.setRadius(2.5F);
              areaEffectCloud.setRadiusOnUse(-0.5F);
              areaEffectCloud.setWaitTime(10);
-@@ -254,16 +_,26 @@
+@@ -254,16 +_,27 @@
                  areaEffectCloud.addEffect(new MobEffectInstance(mobEffectInstance));
              }
  
@@ -91,12 +91,13 @@
 +            this.level().addFreshEntity(areaEffectCloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit
 +        }
 +    }
++
 +    // Paper start - CreeperIgniteEvent
 +    public void setIgnited(boolean ignited) {
 +        if (isIgnited() != ignited) {
 +            com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
 +            if (event.callEvent()) {
-+                this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited());
++                this.entityData.set(DATA_IS_IGNITED, event.isIgnited());
 +            }
 +        }
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
index 5989e4cd04..d68e68e4c3 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/EnderMan.java.patch
@@ -71,22 +71,23 @@
  
                  return flag1;
              }
-@@ -400,6 +_,15 @@
-     public void setBeingStaredAt() {
+@@ -401,6 +_,16 @@
          this.entityData.set(DATA_STARED_AT, true);
      }
+ 
 +    // Paper start
 +    public void setCreepy(boolean creepy) {
-+        this.entityData.set(EnderMan.DATA_CREEPY, creepy);
++        this.entityData.set(DATA_CREEPY, creepy);
 +    }
 +
 +    public void setHasBeenStaredAt(boolean hasBeenStaredAt) {
-+        this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt);
++        this.entityData.set(DATA_STARED_AT, hasBeenStaredAt);
 +    }
 +    // Paper end
- 
++
      @Override
      public boolean requiresCustomPersistence() {
+         return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
 @@ -460,16 +_,19 @@
              int floor1 = Mth.floor(this.enderman.getY() + random.nextDouble() * 2.0);
              int floor2 = Mth.floor(this.enderman.getZ() - 1.0 + random.nextDouble() * 2.0);
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
index 9f53b5d8a3..20e8a59f9f 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Ghast.java.patch
@@ -17,7 +17,7 @@
                          }
  
                          LargeFireball largeFireball = new LargeFireball(level, this.ghast, vec3.normalize(), this.ghast.getExplosionPower());
-+                        largeFireball.bukkitYield = largeFireball.explosionPower = this.ghast.getExplosionPower(); // CraftBukkit - set bukkitYield when setting explosionpower
++                        largeFireball.bukkitYield = largeFireball.explosionPower = this.ghast.getExplosionPower(); // CraftBukkit - set bukkitYield when setting explosionPower
                          largeFireball.setPos(this.ghast.getX() + viewVector.x * 4.0, this.ghast.getY(0.5) + 0.5, largeFireball.getZ() + viewVector.z * 4.0);
                          level.addFreshEntity(largeFireball);
                          this.chargeTime = -40;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
index ed1eed8680..3b9061d838 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Phantom.java.patch
@@ -1,5 +1,17 @@
 --- a/net/minecraft/world/entity/monster/Phantom.java
 +++ b/net/minecraft/world/entity/monster/Phantom.java
+@@ -47,6 +_,11 @@
+     Vec3 moveTargetPoint = Vec3.ZERO;
+     public BlockPos anchorPoint = BlockPos.ZERO;
+     Phantom.AttackPhase attackPhase = Phantom.AttackPhase.CIRCLE;
++    // Paper start
++    @Nullable
++    public java.util.UUID spawningEntity;
++    public boolean shouldBurnInDay = true;
++    // Paper end
+ 
+     public Phantom(EntityType<? extends Phantom> entityType, Level level) {
+         super(entityType, level);
 @@ -141,7 +_,7 @@
  
      @Override
@@ -9,10 +21,11 @@
              this.igniteForSeconds(8.0F);
          }
  
-@@ -165,6 +_,14 @@
+@@ -165,6 +_,15 @@
          }
  
          this.setPhantomSize(compound.getInt("Size"));
++
 +        // Paper start
 +        if (compound.hasUUID("Paper.SpawningEntity")) {
 +            this.spawningEntity = compound.getUUID("Paper.SpawningEntity");
@@ -32,31 +45,11 @@
 +        if (this.spawningEntity != null) {
 +            compound.putUUID("Paper.SpawningEntity", this.spawningEntity);
 +        }
-+        compound.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
++        compound.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
 +        // Paper end
      }
  
      @Override
-@@ -222,6 +_,19 @@
-         return targetingConditions.test(level, this, entity);
-     }
- 
-+    // Paper start
-+    @Nullable
-+    java.util.UUID spawningEntity;
-+
-+    @Nullable
-+    public java.util.UUID getSpawningEntity() {
-+        return this.spawningEntity;
-+    }
-+    public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
-+    private boolean shouldBurnInDay = true;
-+    public boolean shouldBurnInDay() { return shouldBurnInDay; }
-+    public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
-+    // Paper end
-     static enum AttackPhase {
-         CIRCLE,
-         SWOOP;
 @@ -247,7 +_,8 @@
  
                      for (Player player : nearbyPlayers) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
index fc7af2dd2c..6f42d379d6 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
@@ -175,10 +175,11 @@
          }
  
          @Override
-@@ -398,6 +_,12 @@
+@@ -398,6 +_,13 @@
                  slimeMoveControl.setDirection(this.slime.getYRot(), this.slime.isDealsDamage());
              }
          }
++
 +        // Paper start - Slime pathfinder events; clear timer and target when goal resets
 +        public void stop() {
 +            this.growTiredTimer = 0;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
index 9e1d1a6ace..6c74e0007b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch
@@ -30,7 +30,7 @@
                      PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY),
                      SoundEvents.WANDERING_TRADER_DISAPPEARED,
 -                    wanderingTrader -> this.level().isNight() && !wanderingTrader.isInvisible()
-+                    wanderingTrader -> this.canDrinkPotion && this.level().isNight() && !wanderingTrader.isInvisible()  // Paper - Add more WanderingTrader API
++                    wanderingTrader -> this.canDrinkPotion && this.level().isNight() && !wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API
                  )
              );
          this.goalSelector
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
index e1f470371c..f28508dd93 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
@@ -106,7 +106,7 @@
 +                    org.bukkit.inventory.InventoryView view = this.getBukkitView();
 +                    org.bukkit.inventory.ItemStack newcursor = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack);
 +                    newcursor.setAmount(count);
-+                    java.util.Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new java.util.HashMap<Integer, org.bukkit.inventory.ItemStack>();
++                    java.util.Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new java.util.HashMap<>();
 +                    for (java.util.Map.Entry<Integer, ItemStack> ditem : draggedSlots.entrySet()) {
 +                        eventmap.put(ditem.getKey(), org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(ditem.getValue()));
 +                    }
@@ -147,12 +147,11 @@
                  if (!this.getCarried().isEmpty()) {
                      if (clickAction == ClickAction.PRIMARY) {
 -                        player.drop(this.getCarried(), true);
--                        this.setCarried(ItemStack.EMPTY);
-+                            // CraftBukkit start
-+                            ItemStack carried = this.getCarried();
-+                            this.setCarried(ItemStack.EMPTY);
-+                            player.drop(carried, true);
-+                            // CraftBukkit start
++                        // CraftBukkit start
++                        ItemStack carried = this.getCarried();
+                         this.setCarried(ItemStack.EMPTY);
++                        player.drop(carried, true);
++                        // CraftBukkit end
                      } else {
                          player.drop(this.getCarried().split(1), true);
                      }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
index feccf70be9..1c0ac6136a 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -18,7 +18,8 @@
          super(MenuType.BREWING_STAND, containerId);
 +        this.player = playerInventory; // CraftBukkit
          checkContainerSize(brewingStandContainer, 5);
-         checkContainerDataCount(brewingStandData, 2);
+-        checkContainerDataCount(brewingStandData, 2);
++        checkContainerDataCount(brewingStandData, 3); // Paper - Add recipeBrewTime
          this.brewingStand = brewingStandContainer;
          this.brewingStandData = brewingStandData;
          PotionBrewing potionBrewing = playerInventory.player.level().potionBrewing();
@@ -61,7 +62,7 @@
                          return ItemStack.EMPTY;
                      }
 -                } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemStack)) {
-+                } else if (PotionSlot.mayPlaceItem(itemStack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes
++                } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemStack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes
                      if (!this.moveItemStackTo(item, 0, 3, false)) {
                          return ItemStack.EMPTY;
                      }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
index 854683fadb..2e2aa4a423 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CartographyTableMenu.java.patch
@@ -49,7 +49,7 @@
  
      public CartographyTableMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
          super(MenuType.CARTOGRAPHY_TABLE, containerId);
-+        // Paper Start - Add missing InventoryHolders - move down
++        // Paper start - Add missing InventoryHolders - move down
 +        this.container = new SimpleContainer(this.createBlockHolder(access), 2) { // Paper - Add missing InventoryHolders
 +            @Override
 +            public void setChanged() {
@@ -66,7 +66,7 @@
 +        this.resultContainer = new ResultContainer(this.createBlockHolder(access)) { // Paper - Add missing InventoryHolders
 +            @Override
 +            public void setChanged() {
-+                CartographyTableMenu.this.slotsChanged(this);
++                // CartographyTableMenu.this.slotsChanged(this); // Paper - Add CartographyItemEvent - do not recompute results if the result slot changes - allows to set the result slot via api
 +                super.setChanged();
 +            }
 +            // CraftBukkit start
@@ -76,7 +76,7 @@
 +            }
 +            // CraftBukkit end
 +        };
-+        // Paper Start - Add missing InventoryHolders - move down
++        // Paper end - Add missing InventoryHolders - move down
          this.access = access;
          this.addSlot(new Slot(this.container, 0, 15, 15) {
              @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
index bb590914a6..e37648a0fb 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/EnchantmentMenu.java.patch
@@ -20,7 +20,7 @@
      public final int[] levelClue = new int[]{-1, -1, -1};
 +    // CraftBukkit start
 +    private org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView bukkitEntity = null;
-+    private org.bukkit.entity.Player player;
++    private final org.bukkit.entity.Player player;
 +    // CraftBukkit end
  
      public EnchantmentMenu(int containerId, Inventory playerInventory) {
@@ -48,13 +48,11 @@
          this.access = access;
          this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) {
              @Override
-@@ -80,13 +_,16 @@
+@@ -80,13 +_,14 @@
          this.addDataSlot(DataSlot.shared(this.levelClue, 0));
          this.addDataSlot(DataSlot.shared(this.levelClue, 1));
          this.addDataSlot(DataSlot.shared(this.levelClue, 2));
-+        // CraftBukkit start
-+        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity();
-+        // CraftBukkit end
++        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
      }
  
      @Override
@@ -180,10 +178,11 @@
          return stillValid(this.access, player, Blocks.ENCHANTING_TABLE);
      }
  
-@@ -261,4 +_,22 @@
+@@ -261,4 +_,23 @@
  
          return itemStack;
      }
++
 +    // CraftBukkit start
 +    @Override
 +    public org.bukkit.craftbukkit.inventory.view.CraftEnchantmentView getBukkitView() {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
index eb58b2f0ef..11190b8898 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/LecternMenu.java.patch
@@ -26,13 +26,13 @@
 +    }
 +    // CraftBukkit end
 +
-+    // CraftBukkit start - add player
++    // CraftBukkit start - add player inventory
 +    public LecternMenu(int containerId, net.minecraft.world.entity.player.Inventory playerinventory) {
 +        this(containerId, new SimpleContainer(1), new SimpleContainerData(1), playerinventory);
-+    // CraftBukkit end - add player
 +    }
 +
 +    public LecternMenu(int containerId, Container lectern, ContainerData lecternData, net.minecraft.world.entity.player.Inventory playerinventory) {
++        // CraftBukkit end - add player inventory
          super(MenuType.LECTERN, containerId);
          checkContainerSize(lectern, 1);
          checkContainerDataCount(lecternData, 1);
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
index c41d046425..3168efc559 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/MerchantContainer.java.patch
@@ -5,7 +5,7 @@
      public int selectionHint;
      private int futureXp;
 +    // CraftBukkit start - add fields and methods
-+    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<org.bukkit.entity.HumanEntity>();
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
 +    public java.util.List<ItemStack> getContents() {
@@ -18,7 +18,7 @@
 +
 +    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
 +        this.transaction.remove(player);
-+        this.merchant.setTradingPlayer((Player) null); // SPIGOT-4860
++        this.merchant.setTradingPlayer(null); // SPIGOT-4860
 +    }
 +
 +    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
@@ -35,12 +35,12 @@
 +    }
 +
 +    public org.bukkit.inventory.InventoryHolder getOwner() {
-+        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) ? (org.bukkit.craftbukkit.entity.CraftAbstractVillager) ((net.minecraft.world.entity.npc.AbstractVillager) this.merchant).getBukkitEntity() : null;
++        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) ? (org.bukkit.craftbukkit.entity.CraftAbstractVillager) abstractVillager.getBukkitEntity() : null;
 +    }
 +
 +    @Override
 +    public org.bukkit.Location getLocation() {
-+        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager) ? ((net.minecraft.world.entity.npc.AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations
++        return (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) ? abstractVillager.getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations
 +    }
 +    // CraftBukkit end
  
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
index 4f3754e2e4..5f9f27b0f0 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/StonecutterMenu.java.patch
@@ -23,7 +23,7 @@
 +    final ResultContainer resultContainer; // Paper - Add missing InventoryHolders - move down
 +    // CraftBukkit start
 +    private org.bukkit.craftbukkit.inventory.view.CraftStonecutterView bukkitEntity = null;
-+    private org.bukkit.entity.Player player;
++    private final org.bukkit.entity.Player player;
 +
 +    @Override
 +    public org.bukkit.craftbukkit.inventory.view.CraftStonecutterView getBukkitView() {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
index d733c3578d..8ab255c440 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
@@ -14,7 +14,7 @@
                  ),
                  serverLevel,
 -                itemInHand
-+                itemInHand, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID()  // Paper - firework api - assign spawning entity uuid
++                itemInHand, f -> f.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID() // Paper - firework api - assign spawning entity uuid
              );
 -            itemInHand.shrink(1);
 +            // Paper start - PlayerLaunchProjectileEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
index 9eba8654c6..2bd24dd054 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch
@@ -38,7 +38,7 @@
              }
          }
      };
-@@ -365,10 +_,178 @@
+@@ -365,10 +_,171 @@
              return InteractionResult.PASS;
          } else {
              Item item = this.getItem();
@@ -127,13 +127,6 @@
 +                    serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
 +                    serverLevel.preventPoiUpdated = false;
 +
-+                    // Brute force all possible updates
-+                    // Paper start - Don't resync blocks
-+                    // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
-+                    // for (Direction dir : Direction.values()) {
-+                    //     ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir)));
-+                    // }
-+                    // Paper end - Don't resync blocks
 +                    SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
 +                } else {
 +                    // Change the stack to its new contents if it hasn't been tampered with.
@@ -179,9 +172,9 @@
 +                    // SPIGOT-4678
 +                    if (this.item instanceof SignItem && SignItem.openSign != null) {
 +                        try {
-+                            if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity tileentitysign) {
-+                                if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock blocksign) {
-+                                    blocksign.openTextEdit(player, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Craftbukkit // Paper - Add PlayerOpenSignEvent
++                            if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) {
++                                if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) {
++                                    signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent
 +                                }
 +                            }
 +                        } finally {
@@ -229,7 +222,7 @@
 +        // Paper start - add force boolean overload
 +        this.hurtAndBreak(damage, level, player, onBreak, false);
 +    }
-+    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak, boolean force) {  // Paper - Add EntityDamageItemEvent
++    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak, boolean force) { // Paper - Add EntityDamageItemEvent
 +        // Paper end
 +        final int originalDamage = damage; // Paper - Expand PlayerItemDamageEvent
 +        int i = this.processDurabilityChange(damage, level, player, force); // Paper
diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
index 273c487386..a27f9ad41f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch
@@ -73,11 +73,11 @@
                              }
  
                              nextSpawnData.getEquipment().ifPresent(mob::equip);
-+                            // Spigot Start
++                            // Spigot start
 +                            if (mob.level().spigotConfig.nerfSpawnerMobs) {
 +                                mob.aware = false;
 +                            }
-+                            // Spigot End
++                            // Spigot end
                          }
  
 -                        if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
index 79f390f51b..0e251ca8c9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/GameRules.java.patch
@@ -140,7 +140,7 @@
          }
  
 -        public void set(boolean value, @Nullable MinecraftServer server) {
-+        public void set(boolean value, @Nullable ServerLevel level) {  // CraftBukkit - per-world
++        public void set(boolean value, @Nullable ServerLevel level) { // CraftBukkit - per-world
              this.value = value;
 -            this.onChanged(server);
 +            this.onChanged(level); // CraftBukkit - per-world
@@ -161,7 +161,7 @@
  
          @Override
 -        public void setFrom(GameRules.BooleanValue value, @Nullable MinecraftServer server) {
-+        public void setFrom(GameRules.BooleanValue value, @Nullable ServerLevel level) {  // CraftBukkit - per-world
++        public void setFrom(GameRules.BooleanValue value, @Nullable ServerLevel level) { // CraftBukkit - per-world
              this.value = value.value;
 -            this.onChanged(server);
 +            this.onChanged(level); // CraftBukkit - per-world
@@ -208,7 +208,7 @@
 +        public void set(int value, @Nullable ServerLevel level) { // CraftBukkit - per-world
              this.value = value;
 -            this.onChanged(server);
-+            this.onChanged(level) ;// CraftBukkit - per-world
++            this.onChanged(level); // CraftBukkit - per-world
          }
  
          @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index 82aaac77b4..66f1c0a2ed 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -530,30 +530,25 @@
              return chunk.getBlockState(pos);
          }
      }
-@@ -454,32 +_,54 @@
+@@ -454,32 +_,49 @@
              this.pendingBlockEntityTickers.clear();
          }
  
 -        Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
 +        // Spigot start
-+        // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
          boolean runsNormally = this.tickRateManager().runsNormally();
  
 -        while (iterator.hasNext()) {
 -            TickingBlockEntity tickingBlockEntity = iterator.next();
-+        int tilesThisCycle = 0;
 +        var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
 +        toRemove.add(null); // Paper - Fix MC-117075
 +        for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
 +            this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0;
-+            TickingBlockEntity tickingBlockEntity = (TickingBlockEntity) this.blockEntityTickers.get(this.tileTickPosition);
++            TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition);
 +            // Spigot end
              if (tickingBlockEntity.isRemoved()) {
 -                iterator.remove();
-+                // Spigot start
-+                    tilesThisCycle--;
-+                    toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
-+                // Spigot end
++                toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
              } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
                  tickingBlockEntity.tick();
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
index 5f188bd666..ef63e798ef 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -107,7 +107,7 @@
                      if (nearestPlayer != null) {
                          double d2 = nearestPlayer.distanceToSqr(d, y, d1);
 -                        if (isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) {
-+                        if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) {  // Paper - don't load chunks for mob spawn
++                        if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn
                              if (spawnerData == null) {
                                  Optional<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAt(
                                      level, structureManager, generator, category, level.random, mutableBlockPos
diff --git a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
index e16fda169d..263bafc98c 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/ServerExplosion.java
 +++ b/net/minecraft/world/level/ServerExplosion.java
-@@ -33,6 +_,18 @@
+@@ -33,6 +_,17 @@
  import net.minecraft.world.phys.HitResult;
  import net.minecraft.world.phys.Vec3;
  
@@ -8,7 +8,6 @@
 +import net.minecraft.world.entity.boss.EnderDragonPart;
 +import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
 +import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.state.BlockState;
 +import org.bukkit.craftbukkit.event.CraftEventFactory;
 +import org.bukkit.craftbukkit.util.CraftLocation;
 +import org.bukkit.event.entity.EntityExplodeEvent;
@@ -61,7 +60,7 @@
                              if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, f)) {
                                  set.add(blockPos);
 +                                // Paper start - prevent headless pistons from forming
-+                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.getBlock() == Blocks.MOVING_PISTON) {
++                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.is(Blocks.MOVING_PISTON)) {
 +                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockPos);
 +                                    if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
 +                                        net.minecraft.core.Direction direction = blockState.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
@@ -78,7 +77,7 @@
          int floor5 = Mth.floor(this.center.z + f + 1.0);
 -
 -        for (Entity entity : this.level.getEntities(this.source, new AABB(floor, floor2, floor4, floor1, floor3, floor5))) {
-+        List <Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB(floor, floor2, floor4, floor1, floor3, floor5), (com.google.common.base.Predicate<Entity>) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
++        List <Entity> list = this.level.getEntities(excludeSourceFromDamage ? this.source : null, new AABB(floor, floor2, floor4, floor1, floor3, floor5), entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities, Allow explosions to damage source
 +        for (Entity entity : list) { // Paper - used in loop
              if (!entity.ignoreExplosion(this)) {
                  double d = Math.sqrt(entity.distanceToSqr(this.center)) / f;
@@ -95,10 +94,10 @@
 +
 +                            // Special case ender dragon only give knockback if no damage is cancelled
 +                            // Thinks to note:
-+                            // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed)
-+                            // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon
++                            // - Setting a velocity to a EnderDragonPart is ignored (and therefore not needed)
++                            // - Damaging EnderDragonPart while forward the damage to EnderDragon
 +                            // - Damaging EntityEnderDragon does nothing
-+                            // - EntityEnderDragon hitbock always covers the other parts and is therefore always present
++                            // - EnderDragon hitbock always covers the other parts and is therefore always present
 +                            if (entity instanceof EnderDragonPart) {
 +                                continue;
 +                            }
@@ -106,10 +105,10 @@
 +                            entity.lastDamageCancelled = false;
 +
 +                            if (entity instanceof EnderDragon) {
-+                                for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
++                                for (EnderDragonPart dragonPart : ((EnderDragon) entity).getSubEntities()) {
 +                                    // Calculate damage separately for each EntityComplexPart
-+                                    if (list.contains(entityComplexPart)) {
-+                                        entityComplexPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
++                                    if (list.contains(dragonPart)) {
++                                        dragonPart.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f1));
 +                                    }
 +                                }
 +                            } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
index 70ed7bd204..e58b07c7a7 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/Block.java.patch
@@ -128,7 +128,7 @@
 +            // CraftBukkit start
 +            //this.popExperience(level, pos, i);
 +            return i;
-+            // Craftbukkit end
++            // CraftBukkit end
 +        }
 +        return 0; // CraftBukkit
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
index 64f4e71297..f965aa92d2 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
@@ -5,6 +5,6 @@
      @Override
      public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
 -        return createTickerHelper(blockEntityType, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
-+        return null; // Craftbukkit - remove unnecessary sign ticking
++        return null; // CraftBukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
index 2e9c06c89a..0897bcc22d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CropBlock.java.patch
@@ -7,7 +7,7 @@
 -                if (random.nextInt((int)(25.0F / growthSpeed) + 1) == 0) {
 -                    level.setBlock(pos, this.getStateForAge(age + 1), 2);
 +                // Spigot start
-+                int modifier;
++                int modifier = 100;
 +                if (this == Blocks.BEETROOTS) {
 +                    modifier = level.spigotConfig.beetrootModifier;
 +                } else if (this == Blocks.CARROTS) {
@@ -18,7 +18,7 @@
 +                } else if (this == Blocks.TORCHFLOWER_CROP) {
 +                    modifier = level.spigotConfig.torchFlowerModifier;
 +                // Paper end - Fix Spigot growth modifiers
-+                } else {
++                } else if (this == Blocks.WHEAT) {
 +                    modifier = level.spigotConfig.wheatModifier;
 +                }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
index cc9bee614b..82a4a56c86 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch
@@ -5,7 +5,7 @@
          if (enderChestInventory != null && level.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) {
              BlockPos blockPos = pos.above();
 -            if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) {
-+            if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) {  // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic
++            if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) { // Paper - diff on change; make sure that EnderChest#isBlocked uses the same logic
                  return InteractionResult.SUCCESS;
              } else {
 -                if (level instanceof ServerLevel serverLevel) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
index ddbe3daa0a..997e408b2f 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
@@ -1,23 +1,23 @@
 --- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
 +++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
-@@ -44,14 +_,31 @@
+@@ -44,13 +_,31 @@
  
      @Override
      protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
 -        if (state.getValue(AGE) < 25 && random.nextDouble() < this.growPerTickProbability) {
 +        // Spigot start
-+        int modifier;
++        int modifier = 100;
 +        if (this == Blocks.KELP) {
 +            modifier = level.spigotConfig.kelpModifier;
 +        } else if (this == Blocks.TWISTING_VINES) {
 +            modifier = level.spigotConfig.twistingVinesModifier;
 +        } else if (this == Blocks.WEEPING_VINES) {
 +            modifier = level.spigotConfig.weepingVinesModifier;
-+        } else {
++        } else if (this == Blocks.CAVE_VINES) {
 +            modifier = level.spigotConfig.caveVinesModifier;
 +        }
-+             if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
-+        // Spigot end
++        if (state.getValue(AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
++            // Spigot end
              BlockPos blockPos = pos.relative(this.growthDirection);
              if (this.canGrowInto(level.getBlockState(blockPos))) {
 -                level.setBlockAndUpdate(blockPos, this.getGrowIntoState(state, level.random));
@@ -25,12 +25,12 @@
              }
          }
      }
- 
++
 +    // Paper start - Fix Spigot growth modifiers
 +    protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
 +        return this.getGrowIntoState(state, random);
 +    }
 +    // Paper end - Fix Spigot growth modifiers
+ 
      protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
          return state.cycle(AGE);
-     }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
index cb2a17d059..fa161299ce 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch
@@ -86,7 +86,7 @@
 -    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder) {
 -        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, isNether, worldBorder);
 +    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit
-+        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // Craftbukkit
++        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // CraftBukkit
          BlockUtil.FoundRectangle largestRectangleAround;
          TeleportTransition.PostTeleportTransition postTeleportTransition;
          if (optional.isPresent()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
index 44b9126421..148aaa4fdc 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/ShulkerBoxBlock.java.patch
@@ -7,7 +7,7 @@
 -            && canOpen(state, level, pos, shulkerBoxBlockEntity)) {
 -            player.openMenu(shulkerBoxBlockEntity);
 +            && canOpen(state, level, pos, shulkerBoxBlockEntity) // Paper - Fix InventoryOpenEvent cancellation - expand if for belows check
-+            && player.openMenu(shulkerBoxBlockEntity).isPresent()) {  // Paper - Fix InventoryOpenEvent cancellation
++            && player.openMenu(shulkerBoxBlockEntity).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
              player.awardStat(Stats.OPEN_SHULKER_BOX);
              PiglinAi.angerNearbyPiglins(serverLevel, player, true);
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
index 36c68ab94b..8029d51d21 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SignBlock.java.patch
@@ -48,6 +48,6 @@
      @Override
      public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
 -        return createTickerHelper(blockEntityType, BlockEntityType.SIGN, SignBlockEntity::tick);
-+        return null; // Craftbukkit - remove unnecessary sign ticking
++        return null; // CraftBukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
index 0543306a35..ad86e1ce3a 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
@@ -5,6 +5,6 @@
      @Override
      public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
 -        return createTickerHelper(blockEntityType, BlockEntityType.HANGING_SIGN, SignBlockEntity::tick);
-+        return null; // Craftbukkit - remove unnecessary sign ticking
++        return null; // CraftBukkit - remove unnecessary sign ticking
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
index 7fb16a71ce..55e57b57f1 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
@@ -83,7 +83,7 @@
  
          if (blockEntity.lastCheckY >= height) {
              blockEntity.lastCheckY = level.getMinY() - 1;
-@@ -224,36 +_,99 @@
+@@ -224,35 +_,99 @@
  
      @Override
      public void setRemoved() {
@@ -177,7 +177,6 @@
              }
          }
 -    }
--
 +        return list;
 +    }
 +
@@ -195,13 +194,13 @@
 +                apiBlock, apiEffect, (org.bukkit.entity.Player) player.getBukkitEntity(), isPrimary
 +            );
 +            if (!event.callEvent()) continue;
-+            player.addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(event.getEffect()));
++            player.addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(event.getEffect()), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON);
 +        }
 +    }
 +    // Paper end - BeaconEffectEvent
+ 
      public static void playSound(Level level, BlockPos pos, SoundEvent sound) {
          level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
-     }
 @@ -282,7 +_,7 @@
      private static Holder<MobEffect> loadEffect(CompoundTag tag, String key) {
          if (tag.contains(key, 8)) {
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
index 4c305f64e5..f654073e2d 100644
--- 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
@@ -208,7 +208,7 @@
              boolean flag = false;
              boolean isEmpty = destination.isEmpty();
              if (item.isEmpty()) {
-+                // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
++                // Spigot start - SPIGOT-6693, SimpleContainer#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
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
index b70d49f1a0..baf2047d7c 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
@@ -35,7 +35,7 @@
          }
  
          replaceMissingSections(biomeRegistry, this.sections);
-+        this.biomeRegistry = biomeRegistry; // Craftbukkit
++        this.biomeRegistry = biomeRegistry; // CraftBukkit
      }
  
      private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
index 7d3779bd7c..0adecb3d87 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
@@ -106,7 +106,7 @@
  
      public FluidState getFluidState(int x, int y, int z) {
 -        try {
-+        // try {  // Paper start - Perf: Optimise Chunk#getFluid
++        // try { // Paper start - Perf: Optimise Chunk#getFluid
              int sectionIndex = this.getSectionIndex(y);
              if (sectionIndex >= 0 && sectionIndex < this.sections.length) {
                  LevelChunkSection levelChunkSection = this.sections[sectionIndex];
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
index 0e1624f5dc..b25a255606 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
@@ -57,7 +57,7 @@
 +            } else {
 +                long days;
 +                if (level.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) {
-+                    days = player.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang
++                    days = player.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_TIME is counting in ticks
 +                } else {
 +                    days = level.getDayTime() / 24000L;
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
index 80e3eeee2e..19c728a9ed 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch
@@ -55,7 +55,7 @@
 +                                                // Paper end - PhantomPreSpawnEvent
                                                  Phantom phantom = EntityType.PHANTOM.create(level, EntitySpawnReason.NATURAL);
                                                  if (phantom != null) {
-+                                                    phantom.setSpawningEntity(serverPlayer.getUUID()); // Paper - PhantomPreSpawnEvent
++                                                    phantom.spawningEntity = serverPlayer.getUUID(); // Paper - PhantomPreSpawnEvent
                                                      phantom.moveTo(blockPos1, 0.0F, 0.0F);
                                                      spawnGroupData = phantom.finalizeSpawn(
                                                          level, currentDifficultyAt, EntitySpawnReason.NATURAL, spawnGroupData
diff --git a/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
index 4ade7f8dfa..9457eaacf4 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/material/FlowingFluid.java.patch
@@ -41,7 +41,7 @@
          for (Direction direction : Direction.Plane.HORIZONTAL) {
              BlockPos blockPos = mutableBlockPos.setWithOffset(pos, direction);
 -            BlockState blockState = level.getBlockState(blockPos);
-+            BlockState blockState = level.getBlockStateIfLoaded(mutableBlockPos); // Paper - Prevent chunk loading from fluid flowing
++            BlockState blockState = level.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing
 +            if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing
              FluidState fluidState = blockState.getFluidState();
              if (fluidState.getType().isSame(this) && canPassThroughWall(direction, level, pos, state, blockPos, blockState)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
index e4a2c7c33c..7c2db6c3ac 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
@@ -23,10 +23,10 @@
 +            }
 +            // CraftBukkit end
              state.handleNeighborChanged(level, pos, neighborBlock, orientation, movedByPiston);
-+            // Spigot Start
++            // Spigot start
 +        } catch (StackOverflowError ex) {
 +            level.lastPhysicsProblem = new BlockPos(pos);
-+            // Spigot End
++            // Spigot end
          } catch (Throwable var9) {
              CrashReport crashReport = CrashReport.forThrowable(var9, "Exception while updating neighbours");
              CrashReportCategory crashReportCategory = crashReport.addCategory("Block being updated");
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
index f531362c07..23c6073c85 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
@@ -49,26 +49,26 @@
 -        File file = new File(this.playerDir, player.getStringUUID() + suffix);
 +    private Optional<CompoundTag> load(String name, String stringUuid, String suffix) { // CraftBukkit
 +        File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
-+        // Spigot Start
++        // Spigot start
 +        boolean usingWrongFile = false;
 +        if (org.bukkit.Bukkit.getOnlineMode() && !file.exists()) { // Paper - Check online mode first
 +            file = new File(file, java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString() + suffix);
 +            if (file.exists()) {
 +                usingWrongFile = true;
-+                org.bukkit.Bukkit.getServer().getLogger().warning("Using offline mode UUID file for player " + name + " as it is the only copy we can find.");
++                LOGGER.warn("Using offline mode UUID file for player {} as it is the only copy we can find.", name);
 +            }
 +        }
-+        // Spigot End
++        // Spigot end
          if (file.exists() && file.isFile()) {
              try {
 -                return Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
-+                // Spigot Start
++                // Spigot start
 +                Optional<CompoundTag> optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
 +                if (usingWrongFile) {
 +                    file.renameTo(new File(file.getPath() + ".offline-read"));
 +                }
 +                return optional;
-+                // Spigot End
++                // Spigot end
              } catch (Exception var5) {
 -                LOGGER.warn("Failed to load player data for {}", player.getName().getString());
 +                LOGGER.warn("Failed to load player data for {}", name); // CraftBukkit
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 2cdfb3ac17..d3ad9f5097 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -904,13 +904,13 @@ public final class CraftServer implements Server {
 
     @Override
     public long getConnectionThrottle() {
-        // Spigot Start - Automatically set connection throttle for bungee configurations
+        // Spigot start - Automatically set connection throttle for bungee configurations
         if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support
             return -1;
         } else {
             return this.configuration.getInt("settings.connection-throttle");
         }
-        // Spigot End
+        // Spigot end
     }
 
     @Override
@@ -2131,7 +2131,7 @@ public final class CraftServer implements Server {
         if (result == null) {
             GameProfile profile = null;
             // Only fetch an online UUID in online mode
-            if (this.getOnlineMode() || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status
+            if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status
                 // This is potentially blocking :(
                 profile = this.console.getProfileCache().get(name).orElse(null);
             }
@@ -2585,12 +2585,11 @@ public final class CraftServer implements Server {
     }
 
     public List<String> tabCompleteCommand(Player player, String message, ServerLevel world, Vec3 pos) {
-        // Spigot Start
-        if ( (org.spigotmc.SpigotConfig.tabComplete < 0 || message.length() <= org.spigotmc.SpigotConfig.tabComplete) && !message.contains( " " ) )
-        {
+        // Spigot start
+        if ((org.spigotmc.SpigotConfig.tabComplete < 0 || message.length() <= org.spigotmc.SpigotConfig.tabComplete) && !message.contains(" ")) {
             return ImmutableList.of();
         }
-        // Spigot End
+        // Spigot end
 
         List<String> completions = null;
         try {
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java b/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java
index 1c2439ffc1..ecb0fcd1f3 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -149,13 +149,13 @@ public class Main {
 
                 this.acceptsAll(Main.asList("initSettings"), "Only create configuration files and then exit"); // SPIGOT-5761: Add initSettings option
 
-                // Spigot Start
+                // Spigot start
                 this.acceptsAll(Main.asList("S", "spigot-settings"), "File for spigot settings")
                         .withRequiredArg()
                         .ofType(File.class)
                         .defaultsTo(new File("spigot.yml"))
                         .describedAs("Yml file");
-                // Spigot End
+                // Spigot end
 
                 // Paper start
                 acceptsAll(asList("paper-dir", "paper-settings-directory"), "Directory for Paper settings")
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
index 7b7b89e67d..1ef0ec7ed3 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
@@ -23,7 +23,7 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem
     public Set<ComplexEntityPart> getParts() {
         Builder<ComplexEntityPart> builder = ImmutableSet.builder();
 
-        for (EnderDragonPart part : this.getHandle().subEntities) {
+        for (EnderDragonPart part : this.getHandle().getSubEntities()) {
             builder.add((ComplexEntityPart) part.getBukkitEntity());
         }
 
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
index 83e77c6d28..429200b0b0 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
@@ -32,17 +32,17 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy {
     // Paper start
     @Override
     public java.util.UUID getSpawningEntity() {
-        return getHandle().getSpawningEntity();
+        return this.getHandle().spawningEntity;
     }
 
     @Override
     public boolean shouldBurnInDay() {
-        return getHandle().shouldBurnInDay();
+        return this.getHandle().shouldBurnInDay;
     }
 
     @Override
     public void setShouldBurnInDay(boolean shouldBurnInDay) {
-        getHandle().setShouldBurnInDay(shouldBurnInDay);
+        this.getHandle().shouldBurnInDay = shouldBurnInDay;
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index e9df37ff66..1bdad8088d 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -543,7 +543,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public String getDisplayName() {
-        if(true) return io.papermc.paper.adventure.DisplayNames.getLegacy(this); // Paper
+        if (true) return io.papermc.paper.adventure.DisplayNames.getLegacy(this); // Paper
         return this.getHandle().displayName;
     }
 
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
index 01ea7cdedd..eb7d90cfa9 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
@@ -2361,7 +2361,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
 
         for (Object object : addFrom) {
             // Paper start - support components
-            if(object instanceof net.md_5.bungee.api.chat.BaseComponent[] baseComponentArr) {
+            if (object instanceof net.md_5.bungee.api.chat.BaseComponent[] baseComponentArr) {
                 addTo.add(CraftChatMessage.fromJSON(net.md_5.bungee.chat.ComponentSerializer.toString(baseComponentArr)));
             } else
             // Paper end

From dce53e05b876e23aac4a2635dfeaf856fac9af15 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Wed, 18 Dec 2024 19:37:10 +0100
Subject: [PATCH 234/285] fix bogged dropping shears instead of mushrooms

---
 .../net/minecraft/world/entity/monster/Bogged.java.patch        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
index a7203574ef..63ee02e6cb 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Bogged.java.patch
@@ -54,7 +54,7 @@
 +    // Paper start - custom shear drops
 +    private void spawnShearedMushrooms(ServerLevel level, ItemStack stack, java.util.List<ItemStack> drops) {
 +        this.forceDrops = true; // Paper - Add missing forceDrop toggles
-+        drops.forEach(itemstack1 -> this.spawnAtLocation(level, stack, this.getBbHeight()));
++        drops.forEach(drop -> this.spawnAtLocation(level, drop, this.getBbHeight()));
 +        this.forceDrops = false; // Paper - Add missing forceDrop toggles
 +        // Paper end - custom shear drops
      }

From c71ada60db618f56d7614243a16d1ba5f8f97040 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Wed, 18 Dec 2024 21:54:15 +0100
Subject: [PATCH 235/285] get offline player data file from the right folder

---
 .../minecraft/world/level/pathfinder/Path.java.patch   | 10 ----------
 .../ExperimentalRedstoneWireEvaluator.java.patch       |  2 +-
 .../world/level/storage/LevelStorageSource.java.patch  |  2 +-
 .../world/level/storage/PlayerDataStorage.java.patch   |  2 +-
 .../destroystokyo/paper/entity/PaperPathfinder.java    |  2 +-
 5 files changed, 4 insertions(+), 14 deletions(-)
 delete mode 100644 paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch b/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch
deleted file mode 100644
index 7238ea6726..0000000000
--- a/paper-server/patches/sources/net/minecraft/world/level/pathfinder/Path.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/world/level/pathfinder/Path.java
-+++ b/net/minecraft/world/level/pathfinder/Path.java
-@@ -18,6 +_,7 @@
-     private final BlockPos target;
-     private final float distToTarget;
-     private final boolean reached;
-+    public boolean hasNext() { return getNextNodeIndex() < this.nodes.size(); } // Paper - Mob Pathfinding API
- 
-     public Path(List<Node> nodes, BlockPos target, boolean reached) {
-         this.nodes = nodes;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
index a5eebb497d..b83293fbd0 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator.java.patch
@@ -13,7 +13,7 @@
 +
 +                i = event.getNewCurrent();
 +            }
-+            if (blockState.is((net.minecraft.world.level.block.Block) this.wireBlock) && oldPower != i) {
++            if (blockState.is(this.wireBlock) && oldPower != i) {
 +                // CraftBukkit end
                  int i1 = 2;
                  if (!updateShape || !flag) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
index 599d60a432..1c0cac272a 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -72,7 +72,7 @@
  
          public Path getDimensionPath(ResourceKey<Level> dimensionPath) {
 -            return DimensionType.getStorageFolder(dimensionPath, this.levelDirectory.path());
-+            return getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
++            return LevelStorageSource.getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
          }
  
          private void checkLock() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
index 23c6073c85..e452ec4ba4 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
@@ -52,7 +52,7 @@
 +        // Spigot start
 +        boolean usingWrongFile = false;
 +        if (org.bukkit.Bukkit.getOnlineMode() && !file.exists()) { // Paper - Check online mode first
-+            file = new File(file, java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString() + suffix);
++            file = new File(this.playerDir, java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString() + suffix);
 +            if (file.exists()) {
 +                usingWrongFile = true;
 +                LOGGER.warn("Using offline mode UUID file for player {} as it is the only copy we can find.", name);
diff --git a/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java b/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java
index 946cbc9556..3e43beaaa2 100644
--- a/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java
+++ b/paper-server/src/main/java/com/destroystokyo/paper/entity/PaperPathfinder.java
@@ -135,7 +135,7 @@ public class PaperPathfinder implements com.destroystokyo.paper.entity.Pathfinde
         @Nullable
         @Override
         public Location getNextPoint() {
-            if (!path.hasNext()) {
+            if (path.isDone()) {
                 return null;
             }
             return toLoc(path.nodes.get(path.getNextNodeIndex()));

From 5922e6334b8f95eb563ae0ab62f2ec6cf8d95032 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Wed, 18 Dec 2024 23:35:47 +0100
Subject: [PATCH 236/285] fix allowPermanentBlockBreakExploits config

---
 .../bukkit/event/entity/EntityDeathEvent.java   |  2 +-
 .../bukkit/inventory/EquipmentSlotGroup.java    |  2 +-
 .../0006-Entity-Activation-Range-2.0.patch      |  8 ++++----
 ...17-Optimize-Bit-Operations-by-inlining.patch |  4 ++--
 .../critereon/SimpleCriterionTrigger.java.patch |  2 +-
 .../ClientboundBlockEntityDataPacket.java.patch |  2 +-
 .../server/level/ServerPlayer.java.patch        |  8 ++++----
 .../world/entity/item/PrimedTnt.java.patch      |  2 +-
 .../minecraft/world/entity/raid/Raid.java.patch |  2 +-
 .../world/item/FireworkRocketItem.java.patch    |  2 +-
 .../block/piston/PistonBaseBlock.java.patch     |  2 +-
 .../level/block/state/BlockBehaviour.java.patch |  2 +-
 .../world/level/chunk/ChunkGenerator.java.patch |  4 ++--
 .../world/level/chunk/LevelChunk.java.patch     |  8 +++-----
 .../structures/MineshaftPieces.java.patch       |  8 --------
 .../templatesystem/StructureTemplate.java.patch | 17 ++++-------------
 .../bukkit/craftbukkit/entity/CraftEntity.java  |  4 ++--
 .../craftbukkit/scheduler/CraftAsyncTask.java   |  2 +-
 18 files changed, 31 insertions(+), 50 deletions(-)

diff --git a/paper-api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java
index 086bec9daa..42ffb81708 100644
--- a/paper-api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * Thrown whenever a LivingEntity dies
  */
-public class EntityDeathEvent extends EntityEvent implements org.bukkit.event.Cancellable {  // Paper - make cancellable
+public class EntityDeathEvent extends EntityEvent implements org.bukkit.event.Cancellable { // Paper - make cancellable
     private static final HandlerList handlers = new HandlerList();
     private final DamageSource damageSource;
     private final List<ItemStack> drops;
diff --git a/paper-api/src/main/java/org/bukkit/inventory/EquipmentSlotGroup.java b/paper-api/src/main/java/org/bukkit/inventory/EquipmentSlotGroup.java
index 2c4353e8fa..83b5128f91 100644
--- a/paper-api/src/main/java/org/bukkit/inventory/EquipmentSlotGroup.java
+++ b/paper-api/src/main/java/org/bukkit/inventory/EquipmentSlotGroup.java
@@ -25,7 +25,7 @@ public final class EquipmentSlotGroup implements Predicate<EquipmentSlot> {
     public static final EquipmentSlotGroup LEGS = get("legs", EquipmentSlot.LEGS);
     public static final EquipmentSlotGroup CHEST = get("chest", EquipmentSlot.CHEST);
     public static final EquipmentSlotGroup HEAD = get("head", EquipmentSlot.HEAD);
-    public static final EquipmentSlotGroup ARMOR = get("armor", (test) -> test == EquipmentSlot.FEET || test == EquipmentSlot.LEGS || test == EquipmentSlot.CHEST || test == EquipmentSlot.HEAD || test == EquipmentSlot.BODY, EquipmentSlot.CHEST);  // Paper - add missing slot type
+    public static final EquipmentSlotGroup ARMOR = get("armor", (test) -> test == EquipmentSlot.FEET || test == EquipmentSlot.LEGS || test == EquipmentSlot.CHEST || test == EquipmentSlot.HEAD || test == EquipmentSlot.BODY, EquipmentSlot.CHEST); // Paper - add missing slot type
     public static final EquipmentSlotGroup BODY = get("body", EquipmentSlot.BODY); // Paper - add missing slot group
     //
     private final String key;
diff --git a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
index 811fc15914..a949aa3137 100644
--- a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
@@ -786,14 +786,14 @@ index 7c0862c50b44555fb27ce7dc46f4ec95a3eb0022..774ca9e0b56fd175ae246051de762d0c
      public void tick() {
          super.tick();
 diff --git a/net/minecraft/world/entity/vehicle/MinecartHopper.java b/net/minecraft/world/entity/vehicle/MinecartHopper.java
-index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c116aee9b35 100644
+index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..8341e7f01606fca90e69384c16fc19bb9e20d1b7 100644
 --- a/net/minecraft/world/entity/vehicle/MinecartHopper.java
 +++ b/net/minecraft/world/entity/vehicle/MinecartHopper.java
 @@ -47,6 +47,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper
          if (flag != this.isEnabled()) {
              this.setEnabled(flag);
          }
-+        this.immunize();  // Paper
++        this.immunize(); // Paper
      }
  
      public boolean isEnabled() {
@@ -801,13 +801,13 @@ index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..dec705ec57e4f63ef2ccaa87c5400c11
  
      public boolean suckInItems() {
          if (HopperBlockEntity.suckInItems(this.level(), this)) {
-+            this.immunize();  // Paper
++            this.immunize(); // Paper
              return true;
          } else {
              for (ItemEntity itemEntity : this.level()
                  .getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.25, 0.0, 0.25), EntitySelector.ENTITY_STILL_ALIVE)) {
                  if (HopperBlockEntity.addItem(this, itemEntity)) {
-+                    this.immunize();  // Paper
++                    this.immunize(); // Paper
                      return true;
                  }
              }
diff --git a/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch b/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
index 2958bae139..688e72d605 100644
--- a/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
+++ b/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
@@ -7,7 +7,7 @@ Inline bit operations and reduce instruction count to make these hot
 operations faster
 
 diff --git a/net/minecraft/core/BlockPos.java b/net/minecraft/core/BlockPos.java
-index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..0822a0faf7af9e746e7936ac17597b1f51e40a92 100644
+index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..a81694a22e94cca6f7110f7d5b205d1303f4e071 100644
 --- a/net/minecraft/core/BlockPos.java
 +++ b/net/minecraft/core/BlockPos.java
 @@ -51,15 +51,17 @@ public class BlockPos extends Vec3i {
@@ -62,7 +62,7 @@ index 98f0b1cf19d7a035849a9a2fa25e2be3a4c5a980..0822a0faf7af9e746e7936ac17597b1f
  
      public static int getZ(long packedPos) {
 -        return (int)(packedPos << 64 - Z_OFFSET - PACKED_HORIZONTAL_LENGTH >> 64 - PACKED_HORIZONTAL_LENGTH);
-+        return (int) ((packedPos << 26) >> 38);  // Paper - simplify/inline
++        return (int) ((packedPos << 26) >> 38); // Paper - simplify/inline
      }
  
      public static BlockPos of(long packedPos) {
diff --git a/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
index 1817a4cb37..c4d7639889 100644
--- a/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
+++ b/paper-server/patches/sources/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java.patch
@@ -10,7 +10,7 @@
      @Override
      public final void addPlayerListener(PlayerAdvancements playerAdvancements, CriterionTrigger.Listener<T> listener) {
 -        this.players.computeIfAbsent(playerAdvancements, advancements -> Sets.newHashSet()).add(listener);
-+        playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener);  // Paper - fix PlayerAdvancements leak
++        playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener); // Paper - fix PlayerAdvancements leak
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
index dad6ace870..92492a6835 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java.patch
@@ -5,7 +5,7 @@
      public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity, BiFunction<BlockEntity, RegistryAccess, CompoundTag> dataGetter) {
          RegistryAccess registryAccess = blockEntity.getLevel().registryAccess();
 -        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), dataGetter.apply(blockEntity, registryAccess));
-+        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(dataGetter.apply(blockEntity, registryAccess)));  // Paper - Sanitize sent data
++        return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(dataGetter.apply(blockEntity, registryAccess))); // Paper - Sanitize sent data
      }
  
      public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) {
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index b6c60e488c..eae0ca32da 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -511,7 +511,7 @@
  
      private boolean isPvpAllowed() {
 -        return this.server.isPvpAllowed();
-+        return this.level().pvpMode;  // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
++        return this.level().pvpMode; // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
      }
  
 -    public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean useCharge, TeleportTransition.PostTeleportTransition postTeleportTransition) {
@@ -541,7 +541,7 @@
          } else {
 -            return new TeleportTransition(this.server.overworld(), this, postTeleportTransition);
 -        }
-+            teleportTransition = new TeleportTransition(this.server.overworld(), this, postTeleportTransition);  // CraftBukkit
++            teleportTransition = new TeleportTransition(this.server.overworld(), this, postTeleportTransition); // CraftBukkit
 +        }
 +        // CraftBukkit start
 +        if (respawnReason == null) {
@@ -1123,7 +1123,7 @@
  
              for (MobEffectInstance mobEffectInstance : that.getActiveEffects()) {
 -                this.addEffect(new MobEffectInstance(mobEffectInstance));
-+                // this.addEffect(new MobEffectInstance(mobEffectInstance));  // CraftBukkit
++                // this.addEffect(new MobEffectInstance(mobEffectInstance)); // CraftBukkit
              }
  
              this.getInventory().replaceWith(that.getInventory());
@@ -1203,7 +1203,7 @@
 +        // Paper end
          if (this.acceptsChatMessages()) {
 -            message.sendToPlayer(this, filtered, boundType);
-+            message.sendToPlayer(this, filtered, boundType, unsigned);  // Paper
++            message.sendToPlayer(this, filtered, boundType, unsigned); // Paper
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
index 1bbdb4c9b6..45ed0bc03d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch
@@ -26,7 +26,7 @@
          this(EntityType.TNT, level);
          this.setPos(x, y, z);
 -        double d = level.random.nextDouble() * (float) (Math.PI * 2);
-+        double d = this.random.nextDouble() * (float) (Math.PI * 2);  // Paper - Don't use level random in entity constructors
++        double d = this.random.nextDouble() * (float) (Math.PI * 2); // Paper - Don't use level random in entity constructors
          this.setDeltaMovement(-Math.sin(d) * 0.02, 0.2F, -Math.cos(d) * 0.02);
          this.setFuse(80);
          this.xo = x;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
index 34ed85451b..f5c2c2d983 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/raid/Raid.java.patch
@@ -66,7 +66,7 @@
                      }
  
                      if (i > 5) {
-+                        org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE);  // CraftBukkit
++                        org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
                          this.stop();
                          break;
                      }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
index 8ab255c440..00153390e9 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/FireworkRocketItem.java.patch
@@ -34,7 +34,7 @@
 -                itemInHand.consume(1, player);
 -                player.awardStat(Stats.ITEM_USED.get(this));
 +                // Paper start - PlayerElytraBoostEvent
-+                final Projectile.Delayed<FireworkRocketEntity> delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(level, itemInHand, player), serverLevel, itemInHand, f -> f.spawningEntity = player.getUUID());  // Paper - firework api - assign spawning entity uuid
++                final Projectile.Delayed<FireworkRocketEntity> delayed = Projectile.spawnProjectileDelayed(new FireworkRocketEntity(level, itemInHand, player), serverLevel, itemInHand, f -> f.spawningEntity = player.getUUID()); // Paper - firework api - assign spawning entity uuid
 +                com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) player.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemInHand), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand));
 +                if (event.callEvent() && delayed.attemptSpawn()) {
 +                    player.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
index 239a88e07e..680447ba4e 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -25,7 +25,7 @@
          Direction direction = state.getValue(FACING);
 +        // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur)
 +        Direction directionQueuedAs = Direction.from3DDataValue(param & 7); // Paper - copied from below
-+        if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && directionQueuedAs != directionQueuedAs) {
++        if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && direction != directionQueuedAs) {
 +            return false;
 +        }
 +        // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
index f0c398822e..4f4759e0c9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -53,7 +53,7 @@
  
      protected boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) {
 -        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem()));
-+        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem()))&& (state.isDestroyable() || (useContext.getPlayer() != null && useContext.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
++        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (useContext.getPlayer() != null && useContext.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
      }
  
      protected boolean canBeReplaced(BlockState state, Fluid fluid) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
index 32ce3a4e7d..5fa4572bc5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -51,7 +51,7 @@
 -            boolean flag = i == -z || i == z;
 +            // Paper start - Perf: iterate over border chunks instead of entire square chunk area
 +            final int radius = z;
-+            boolean flag = i == -z || i == z; final boolean onBorderAlongZAxis = flag;  // Paper - OBFHELPER
++            boolean flag = i == -z || i == z; final boolean onBorderAlongZAxis = flag; // Paper - OBFHELPER
  
 -            for (int i1 = -z; i1 <= z; i1++) {
 -                boolean flag1 = i1 == -z || i1 == z;
@@ -82,7 +82,7 @@
 +                            long featurePopulationSeed = l;
 +                            final long configFeatureSeed = level.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedFeature.feature());
 +                            if (configFeatureSeed != -1) {
-+                                featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See seededrandom.setDecorationSeed from above
++                                featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See WorldgenRandom.setDecorationSeed from above
 +                            }
 +                            worldgenRandom.setFeatureSeed(featurePopulationSeed, i3, i);
 +                            // Paper end - Configurable feature seeds
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
index 0adecb3d87..dcd413b184 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch
@@ -265,7 +265,7 @@
      public boolean isEmpty() {
          return false;
      }
-@@ -711,23 +_,25 @@
+@@ -711,23 +_,24 @@
                          if (this.blockEntity.getType().isValid(blockState)) {
                              this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), blockState, this.blockEntity);
                              this.loggedInvalidBlockState = false;
@@ -278,17 +278,15 @@
 -                                    LogUtils.defer(this::getPos),
 -                                    blockState
 -                                );
--                        }
 +                        // Paper start - Remove the Block Entity if it's invalid
 +                        } else {
 +                            LevelChunk.this.removeBlockEntity(this.getPos());
 +                            if (!this.loggedInvalidBlockState) {
 +                                this.loggedInvalidBlockState = true;
-+                                LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), blockState});
++                                LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", LogUtils.defer(this::getType), LogUtils.defer(this::getPos), blockState);
 +                            }
 +                            // Paper end - Remove the Block Entity if it's invalid
-+                         }
-+
+                         }
  
                          profilerFiller.pop();
                      } catch (Throwable var5) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
index 50b84588ce..be468da02b 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
 +++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
-@@ -8,6 +_,7 @@
- import net.minecraft.core.Direction;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.NbtOps;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.resources.ResourceKey;
- import net.minecraft.tags.BiomeTags;
- import net.minecraft.util.RandomSource;
 @@ -401,10 +_,13 @@
                          BlockPos worldPos = this.getWorldPos(1, 0, i8);
                          if (box.isInside(worldPos) && this.isInterior(level, 1, 0, i8, box)) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
index 7839e3322c..c88b4e61e3 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
 +++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
-@@ -25,6 +_,7 @@
- import net.minecraft.nbt.IntTag;
- import net.minecraft.nbt.ListTag;
- import net.minecraft.nbt.NbtUtils;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.resources.ResourceLocation;
- import net.minecraft.util.RandomSource;
- import net.minecraft.world.Clearable;
 @@ -54,6 +_,10 @@
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
@@ -108,7 +100,7 @@
                  if (!settings.isIgnoreEntities()) {
                      this.placeEntities(
 -                        serverLevel,
-+                        wrappedAccess,  // CraftBukkit
++                        wrappedAccess, // CraftBukkit
                          offset,
                          settings.getMirror(),
                          settings.getRotation(),
@@ -143,15 +135,14 @@
          return NbtUtils.addCurrentDataVersion(tag);
      }
  
-@@ -720,6 +_,13 @@
+@@ -720,6 +_,12 @@
                  this.entityInfoList.add(new StructureTemplate.StructureEntityInfo(vec3, blockPos, compound1));
              }
          }
 +
 +        // CraftBukkit start - PDC
-+        Tag base = tag.get("BukkitValues");
-+        if (base instanceof CompoundTag) {
-+            this.persistentDataContainer.putAll((CompoundTag) base);
++        if (tag.get("BukkitValues") instanceof CompoundTag compoundTag) {
++            this.persistentDataContainer.putAll(compoundTag);
 +        }
 +        // CraftBukkit end
      }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 8c3a6a15dd..91b78711ec 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1252,12 +1252,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
 
     // Paper start - missing entity api
     @Override
-    public boolean isInvisible() {  // Paper - moved up from LivingEntity
+    public boolean isInvisible() { // Paper - moved up from LivingEntity
         return this.getHandle().isInvisible();
     }
 
     @Override
-    public void setInvisible(boolean invisible) {  // Paper - moved up from LivingEntity
+    public void setInvisible(boolean invisible) { // Paper - moved up from LivingEntity
         this.getHandle().persistentInvisibility = invisible;
         this.getHandle().setSharedFlag(Entity.FLAG_INVISIBLE, invisible);
     }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
index e97f6b76ef..e4e2e42d0c 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
@@ -28,7 +28,7 @@ class CraftAsyncTask extends CraftTask {
         // Paper start - name threads according to running plugin
         final String nameBefore = thread.getName();
         thread.setName(nameBefore + " - " + this.getOwner().getName());
-        try { synchronized (this.workers) {  // Paper end - name threads according to running plugin
+        try { synchronized (this.workers) { // Paper end - name threads according to running plugin
             if (this.getPeriod() == CraftTask.CANCEL) {
                 // Never continue running after cancelled.
                 // Checking this with the lock is important!

From 49f715fdda850619be57eeb621d9d80d7a3086c4 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 18 Dec 2024 18:30:29 -0800
Subject: [PATCH 237/285] update brig in the API and patch cleanup

---
 paper-api/build.gradle.kts                    |  2 +-
 .../argument/CustomArgumentType.java          | 43 +++++++++++++++++++
 ...y-type-tags-suggestions-in-selectors.patch |  4 +-
 .../minecraft/commands/Commands.java.patch    |  2 +-
 .../arguments/MessageArgument.java.patch      |  4 +-
 .../component/DataComponentPatch.java.patch   |  2 +-
 .../BoatDispenseItemBehavior.java.patch       |  4 +-
 .../dispenser/DispenseItemBehavior.java.patch | 40 ++++++++---------
 8 files changed, 72 insertions(+), 29 deletions(-)

diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts
index 832a2b3e86..5608f8d01d 100644
--- a/paper-api/build.gradle.kts
+++ b/paper-api/build.gradle.kts
@@ -39,7 +39,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
 // Paper end - configure mockito agent that is needed in newer java versions
 
 dependencies {
-    api("com.mojang:brigadier:1.2.9") // Paper - Brigadier command api
+    api("com.mojang:brigadier:1.3.10") // Paper - Brigadier command api
     // api dependencies are listed transitively to API consumers
     api("com.google.guava:guava:33.3.1-jre")
     api("com.google.code.gson:gson:2.11.0")
diff --git a/paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java b/paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java
index 91d40ef0bd..14be89fab5 100644
--- a/paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java
+++ b/paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java
@@ -37,10 +37,31 @@ public interface CustomArgumentType<T, N> extends ArgumentType<T> {
      * @param reader string reader input
      * @return parsed value
      * @throws CommandSyntaxException if an error occurs while parsing
+     * @see #parse(StringReader, Object)
      */
     @Override
     T parse(final StringReader reader) throws CommandSyntaxException;
 
+    /**
+     * Parses the argument into the custom type ({@code T}). Keep in mind
+     * that this parsing will be done on the server. This means that if
+     * you throw a {@link CommandSyntaxException} during parsing, this
+     * will only show up to the user after the user has executed the command
+     * not while they are still entering it.
+     * <p>
+     * This method provides the command source for additional context when parsing. You
+     * may have to do your own {@code instanceof} checks for {@link io.papermc.paper.command.brigadier.CommandSourceStack}.
+     *
+     * @param reader string reader input
+     * @param source source of the command
+     * @return parsed value
+     * @throws CommandSyntaxException if an error occurs while parsing
+     */
+    @Override
+    default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException {
+        return ArgumentType.super.parse(reader, source);
+    }
+
     /**
      * Gets the native type that this argument uses,
      * the type that is sent to the client.
@@ -95,13 +116,35 @@ public interface CustomArgumentType<T, N> extends ArgumentType<T> {
             return this.convert(this.getNativeType().parse(reader));
         }
 
+        @ApiStatus.NonExtendable
+        @Override
+        default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException {
+            return this.convert(this.getNativeType().parse(reader, source), source);
+        }
+
         /**
          * Converts the value from the native type to the custom argument type.
          *
          * @param nativeType native argument provided value
          * @return converted value
          * @throws CommandSyntaxException if an exception occurs while parsing
+         * @see #convert(Object, Object)
          */
         T convert(N nativeType) throws CommandSyntaxException;
+
+        /**
+         * Converts the value from the native type to the custom argument type.
+         * <p>
+         * This method provides the command source for additional context when converting. You
+         * may have to do your own {@code instanceof} checks for {@link io.papermc.paper.command.brigadier.CommandSourceStack}.
+         *
+         * @param nativeType native argument provided value
+         * @param source     source of the command
+         * @return converted value
+         * @throws CommandSyntaxException if an exception occurs while parsing
+         */
+        default <S> T convert(final N nativeType, final S source) throws CommandSyntaxException {
+            return this.convert(nativeType);
+        }
     }
 }
diff --git a/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch b/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
index 0f7eeec9d8..5e8077c3dd 100644
--- a/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
+++ b/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
@@ -35,10 +35,10 @@ index 704a63890a06d793f8ac3452838917e7c7335232..75262c8c9eaecb4a88a94f4076d67119
 +    // Paper end - tell clients to ask server for suggestions for EntityArguments
  }
 diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java
-index 45fa9cdc34e78613e346138aa92a6d3bbdee374c..e422a266de555bbf77eee201df9e4c5d89f7b801 100644
+index fa8c5ba4e0efd0c36613aaa8eaafba0cb70ceb87..19ccf3abf14c67f72a1ca065e4a304f50e645ef4 100644
 --- a/net/minecraft/commands/Commands.java
 +++ b/net/minecraft/commands/Commands.java
-@@ -510,6 +510,7 @@ public class Commands {
+@@ -509,6 +509,7 @@ public class Commands {
          Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> commandNodeToSuggestionNode
      ) {
          commandNodeToSuggestionNode.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains(":")); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
index f16c785fbd..0c4203d037 100644
--- a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch
@@ -183,7 +183,6 @@
 +    private void sendAsync(ServerPlayer player, java.util.Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
 +        // Paper end - Perf: Async command map building
 +        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
-+        // Paper - brigadier API removes the need to fill the map twice
          RootCommandNode<SharedSuggestionProvider> rootCommandNode = new RootCommandNode<>();
          map.put(this.dispatcher.getRoot(), rootCommandNode);
 -        this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
@@ -276,6 +275,7 @@
 -                    argumentBuilder.executes(commandContext -> 0);
 +                    // Paper start - fix suggestions due to falsely equal nodes
 +                    // Always create a new instance
++                    //noinspection Convert2Lambda
 +                    argumentBuilder.executes(new com.mojang.brigadier.Command<>() {
 +                        @Override
 +                        public int run(com.mojang.brigadier.context.CommandContext<SharedSuggestionProvider> commandContext) {
diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
index 9c9cb202be..c2bfa96065 100644
--- a/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/MessageArgument.java.patch
@@ -4,11 +4,11 @@
  
      public static void resolveChatMessage(CommandContext<CommandSourceStack> context, String key, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
          MessageArgument.Message message = context.getArgument(key, MessageArgument.Message.class);
-+        // Paper start
++        // Paper start - brig message argument support
 +        resolveChatMessage(message, context, key, callback);
 +    }
 +    public static void resolveChatMessage(MessageArgument.Message message, CommandContext<CommandSourceStack> context, String key, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
-+        // Paper end
++        // Paper end - brig message argument support
          CommandSourceStack commandSourceStack = context.getSource();
          Component component = message.resolveComponent(commandSourceStack);
          CommandSigningContext signingContext = commandSourceStack.getSigningContext();
diff --git a/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
index 272f5afdc5..9a4fca81b5 100644
--- a/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponentPatch.java.patch
@@ -7,7 +7,7 @@
 -            component.streamCodec().encode(buffer, (T)value);
 +            // Paper start - codec errors of random anonymous classes are useless
 +            try {
-+                component.streamCodec().encode(buffer, (T) value); // CraftBukkit - decompile error
++                component.streamCodec().encode(buffer, (T)value);
 +            } catch (final Exception e) {
 +                throw new RuntimeException("Error encoding component " + component, e);
 +            }
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
index bceaccc7b8..c40f77ee71 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
@@ -5,9 +5,9 @@
          }
  
 +        // CraftBukkit start
-+        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink at end and single item in event
++        ItemStack singleItemStack = item.copyWithCount(1); // Paper - shrink at end and single item in event
 +        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
-+        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
 +
 +        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
 +        if (!DispenserBlock.eventFired) {
diff --git a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
index fed8dc6488..c4e6ac4f58 100644
--- a/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
@@ -6,9 +6,9 @@
  
 +                // CraftBukkit start
 +                ServerLevel serverLevel = blockSource.level();
-+                ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                ItemStack singleItemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
 +                org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
-+                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
 +
 +                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
 +                if (!DispenserBlock.eventFired) {
@@ -25,20 +25,20 @@
 +                    shrink = false; // Paper - shrink below
 +                    // Chain to handler for new item
 +                    ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
-+                    DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                    if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
-+                        idispensebehavior.dispense(blockSource, eventStack);
++                    DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                    if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
++                        dispenseBehavior.dispense(blockSource, eventStack);
 +                        return item;
 +                    }
 +                    // Paper start - track changed items in the dispense event
-+                    itemstack1 = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified
-+                    type = ((SpawnEggItem) itemstack1.getItem()).getType(serverLevel.registryAccess(), itemstack1);
++                    singleItemStack = org.bukkit.craftbukkit.inventory.CraftItemStack.unwrap(event.getItem()); // unwrap is safe because the stack won't be modified
++                    type = ((SpawnEggItem) singleItemStack.getItem()).getType(serverLevel.registryAccess(), singleItemStack);
 +                    // Paper end - track changed item from dispense event
 +                }
                  try {
                      type.spawn(
 -                        blockSource.level(), item, null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false
-+                        blockSource.level(), itemstack1, null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false // Paper - track changed item in dispense event
++                        blockSource.level(), singleItemStack, null, blockSource.pos().relative(direction), EntitySpawnReason.DISPENSER, direction != Direction.UP, false // Paper - track changed item in dispense event
                      );
                  } catch (Exception var6) {
                      LOGGER.error("Error while dispensing spawn egg from dispenser at {}", blockSource.pos(), var6);
@@ -56,9 +56,9 @@
                      BlockPos blockPos = blockSource.pos().relative(direction);
                      ServerLevel serverLevel = blockSource.level();
 +                    // CraftBukkit start
-+                    ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                    ItemStack singleItemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
 +                    org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
-+                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++                    org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
 +
 +                    org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
 +                    if (!DispenserBlock.eventFired) {
@@ -100,10 +100,10 @@
                      if (!entitiesOfClass.isEmpty()) {
 -                        ((Saddleable)entitiesOfClass.get(0)).equipSaddle(item.split(1), SoundSource.BLOCKS);
 +                        // CraftBukkit start
-+                        ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink below and single item in event
++                        ItemStack singleItemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
 +                        ServerLevel world = blockSource.level();
 +                        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos());
-+                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++                        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
 +
 +                        org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity());
 +                        if (!DispenserBlock.eventFired) {
@@ -120,9 +120,9 @@
 +                            shrink = false; // Paper - shrink below
 +                            // Chain to handler for new item
 +                            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
-+                            DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                            if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
-+                                idispensebehavior.dispense(blockSource, eventStack);
++                            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) { // Paper - fix possible StackOverflowError
++                                dispenseBehavior.dispense(blockSource, eventStack);
 +                                return item;
 +                            }
 +                        }
@@ -379,9 +379,9 @@
                  BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
 -                PrimedTnt primedTnt = new PrimedTnt(level, blockPos.getX() + 0.5, blockPos.getY(), blockPos.getZ() + 0.5, null);
 +                // CraftBukkit start
-+                ItemStack itemstack1 = item.copyWithCount(1); // Paper - shrink at end and single item in event
++                ItemStack singleItemStack = item.copyWithCount(1); // Paper - shrink at end and single item in event
 +                org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockSource.pos());
-+                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1);
++                org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack);
 +
 +                org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D));
 +                if (!DispenserBlock.eventFired) {
@@ -557,9 +557,9 @@
 +                    if (!event.getItem().equals(craftItem)) {
 +                        // Chain to handler for new item
 +                        ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
-+                        DispenseItemBehavior idispensebehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
-+                        if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) { // Paper - fix possible StackOverflowError
-+                            idispensebehavior.dispense(blockSource, eventStack);
++                        DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
++                        if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) { // Paper - fix possible StackOverflowError
++                            dispenseBehavior.dispense(blockSource, eventStack);
 +                            return item;
 +                        }
 +                    }

From 15ad7cc156039cbcb2b3db0b861eb2d68c1e79a9 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Wed, 18 Dec 2024 18:54:31 -0800
Subject: [PATCH 238/285] improve comment and better local var name

---
 .../patches/sources/net/minecraft/core/Holder.java.patch    | 2 +-
 .../login/ServerboundCustomQueryAnswerPacket.java.patch     | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/core/Holder.java.patch b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch
index 51a81bf5f3..c1d6d57ceb 100644
--- a/paper-server/patches/sources/net/minecraft/core/Holder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Holder.java.patch
@@ -5,7 +5,7 @@
  
          void bindTags(Collection<TagKey<T>> tags) {
 -            this.tags = Set.copyOf(tags);
-+            this.tags = java.util.Collections.unmodifiableSet(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper
++            this.tags = it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper - use reference set because TagKey are interned
          }
  
          @Override
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
index 9596254f44..40b8fdbc78 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch
@@ -7,9 +7,9 @@
 -        return readUnknownPayload(buffer);
 +        // Paper start - MC Utils - default query payloads
 +        FriendlyByteBuf buf = buffer.readNullable((buf2) -> {
-+            int i = buf2.readableBytes();
-+            if (i >= 0 && i <= MAX_PAYLOAD_SIZE) {
-+                return new FriendlyByteBuf(buf2.readBytes(i));
++            int readableBytes = buf2.readableBytes();
++            if (readableBytes >= 0 && readableBytes <= MAX_PAYLOAD_SIZE) {
++                return new FriendlyByteBuf(buf2.readBytes(readableBytes));
 +            } else {
 +                throw new IllegalArgumentException("Payload may not be larger than " + MAX_PAYLOAD_SIZE + " bytes");
 +            }

From 8f5d9953f517f29159b852d1299587d6acd59657 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Thu, 19 Dec 2024 11:01:00 +0100
Subject: [PATCH 239/285] Fix DensityFunctions lvt

---
 .../dimension/end/EndDragonFight.java.patch   |  4 +--
 .../level/entity/EntityLookup.java.patch      |  2 +-
 .../levelgen/DensityFunctions.java.patch      | 33 ++++++++++---------
 3 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
index b93d5faef8..c1082899a2 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -4,10 +4,10 @@
      private static final int GATEWAY_DISTANCE = 96;
      public static final int DRAGON_SPAWN_Y = 128;
      private final Predicate<Entity> validPlayer;
-+    private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name
++    private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - reset EnderDragon boss event name
      public final ServerBossEvent dragonEvent = (ServerBossEvent)new ServerBossEvent(
 -            Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS
-+            DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS // Paper
++            DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS // Paper - reset EnderDragon boss event name
          )
          .setPlayBossMusic(true)
          .setCreateWorldFog(true);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
index 900da01b8e..27c389bc82 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntityLookup.java.patch
@@ -11,7 +11,7 @@
 +                    LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity);
 +                }
 +            }
-+            // Paper end
++            // Paper end - extra debug info
          } else {
              this.byUuid.put(uuid, entity);
              this.byId.put(entity.getId(), entity);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
index 96d7029c39..087b4b7d6d 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/DensityFunctions.java.patch
@@ -17,7 +17,7 @@
  
          public EndIslandDensityFunction(long seed) {
              RandomSource randomSource = new LegacyRandomSource(seed);
-@@ -518,15 +_,29 @@
+@@ -518,15 +_,31 @@
              int i1 = z / 2;
              int i2 = x % 2;
              int i3 = z % 2;
@@ -28,22 +28,25 @@
 +            NoiseCache cache = noiseCache.get().computeIfAbsent(noise, noiseKey -> new NoiseCache()); // Paper - Perf: Optimize end generation
              for (int i4 = -12; i4 <= 12; i4++) {
                  for (int i5 = -12; i5 <= 12; i5++) {
-                     long l = i + i4;
-                     long l1 = i1 + i5;
--                    if (l * l + l1 * l1 > 4096L && noise.getValue(l, l1) < -0.9F) {
--                        float f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
+-                    long l = i + i4;
+-                    long l1 = i1 + i5;
++                    long l = i + i4; final int chunkX = (int) l; // Paper - OBFHELPER
++                    long l1 = i1 + i5; final int chunkZ = (int) l1; // Paper - OBFHELPER
 +                    // Paper start - Perf: Optimize end generation by using a noise cache
-+                    long key = net.minecraft.world.level.ChunkPos.asLong((int) l, (int) l);
-+                    int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191;
-+                    float f1 = Float.MIN_VALUE;
-+                    if (cache.keys[index] == key) {
-+                        f1 = cache.values[index];
++                    final long chunkKey = net.minecraft.world.level.ChunkPos.asLong(chunkX, chunkZ);
++                    final int cacheIndex = (int) it.unimi.dsi.fastutil.HashCommon.mix(chunkKey) & 8191;
++                    float f1 = Float.MIN_VALUE; // noise value
++                    if (cache.keys[cacheIndex] == chunkKey) {
++                        // Use cache
++                        f1 = cache.values[cacheIndex];
 +                    } else {
-+                        if (l * l + l1 * l1 > 4096L && noise.getValue((double)l, (double)l1) < -0.9F) {
-+                            f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
-+                        }
-+                        cache.keys[index] = key;
-+                        cache.values[index] = f1;
++                        // Vanilla function
+                     if (l * l + l1 * l1 > 4096L && noise.getValue(l, l1) < -0.9F) {
+-                        float f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
++                        f1 = (Mth.abs((float)l) * 3439.0F + Mth.abs((float)l1) * 147.0F) % 13.0F + 9.0F;
++                    }
++                        cache.keys[cacheIndex] = chunkKey;
++                        cache.values[cacheIndex] = f1;
 +                    }
 +                    if (f1 != Float.MIN_VALUE) {
 +                        // Paper end - Perf: Optimize end generation

From 0eb8f95dec8f3f610e0140436501eca1bba2dd02 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Thu, 19 Dec 2024 11:30:22 +0100
Subject: [PATCH 240/285] Properly update diff in NaturalSpawner

---
 .../world/CompoundContainer.java.patch        |  6 +--
 .../net/minecraft/world/Container.java.patch  |  8 +--
 .../world/level/NaturalSpawner.java.patch     | 54 ++++++++-----------
 3 files changed, 29 insertions(+), 39 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
index f5611a0039..cf4dc60873 100644
--- a/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/CompoundContainer.java.patch
@@ -4,7 +4,7 @@
      public final Container container1;
      public final Container container2;
  
-+    // Paper start - add fields and methods
++    // CraftBukkit start - add fields and methods
 +    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +
 +    public java.util.List<ItemStack> getContents() {
@@ -44,7 +44,7 @@
 +    public org.bukkit.Location getLocation() {
 +        return this.container1.getLocation(); // TODO: right?
 +    }
-+    // Paper end
++    // CraftBukkit end
 +
      public CompoundContainer(Container container1, Container container2) {
          this.container1 = container1;
@@ -54,7 +54,7 @@
      @Override
      public int getMaxStackSize() {
 -        return this.container1.getMaxStackSize();
-+        return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // Paper - check both sides
++        return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // CraftBukkit - check both sides
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/Container.java.patch b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
index 1250bede66..e86d0c9b24 100644
--- a/paper-server/patches/sources/net/minecraft/world/Container.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/Container.java.patch
@@ -7,7 +7,7 @@
 -    default int getMaxStackSize() {
 -        return 99;
 -    }
-+    int getMaxStackSize(); // Paper
++    int getMaxStackSize(); // CraftBukkit
  
      default int getMaxStackSize(ItemStack stack) {
          return Math.min(this.getMaxStackSize(), stack.getMaxStackSize());
@@ -16,7 +16,7 @@
          return level != null && level.getBlockEntity(blockPos) == blockEntity && player.canInteractWithBlock(blockPos, distance);
      }
 +
-+    // Paper start
++    // CraftBukkit start
 +    java.util.List<ItemStack> getContents();
 +
 +    void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity player);
@@ -25,12 +25,12 @@
 +
 +    java.util.List<org.bukkit.entity.HumanEntity> getViewers();
 +
-+    org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation
++    org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner();
 +
 +    void setMaxStackSize(int size);
 +
 +    org.bukkit.Location getLocation();
 +
 +    int MAX_STACK = Item.ABSOLUTE_MAX_STACK_SIZE;
-+    // Paper end
++    // CraftBukkit end
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
index ef63e798ef..d5a3311160 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -28,7 +28,7 @@
                      BlockPos blockPos = entity.blockPosition();
                      chunkGetter.query(ChunkPos.asLong(blockPos), chunk -> {
                          MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
-@@ -96,17 +_,37 @@
+@@ -96,17 +_,34 @@
          return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
      }
  
@@ -40,22 +40,12 @@
 +        LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
 +        // CraftBukkit end
          List<MobCategory> list = new ArrayList<>(SPAWNING_CATEGORIES.length);
--
--        for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
--            if ((spawnFriendlies || !mobCategory.isFriendly())
--                && (spawnEnemies || mobCategory.isFriendly())
--                && (spawnPassives || !mobCategory.isPersistent())
--                && spawnState.canSpawnForCategoryGlobal(mobCategory)) {
--                list.add(mobCategory);
-+        MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES;
-+        int i = aenumcreaturetype.length;
-+
-+        for (int j = 0; j < i; ++j) {
-+            MobCategory enumcreaturetype = SPAWNING_CATEGORIES[j];
+ 
+         for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
 +            // CraftBukkit start - Use per-world spawn limits
 +            boolean spawnThisTick = true;
-+            int limit = enumcreaturetype.getMaxInstancesPerChunk();
-+            SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
++            int limit = mobCategory.getMaxInstancesPerChunk();
++            SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory);
 +            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
 +                spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
 +                limit = worldserver.getWorld().getSpawnLimit(spawnCategory);
@@ -65,11 +55,12 @@
 +                continue;
 +            }
 +
-+            if ((spawnFriendlies || !enumcreaturetype.isFriendly())
-+                && (spawnEnemies || enumcreaturetype.isFriendly())
-+                && (spawnPassives || !enumcreaturetype.isPersistent())
-+                && spawnState.canSpawnForCategoryGlobal(enumcreaturetype, limit)) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
-+                list.add(enumcreaturetype);
+             if ((spawnFriendlies || !mobCategory.isFriendly())
+                 && (spawnEnemies || mobCategory.isFriendly())
+                 && (spawnPassives || !mobCategory.isPersistent())
+-                && spawnState.canSpawnForCategoryGlobal(mobCategory)) {
++                && spawnState.canSpawnForCategoryGlobal(mobCategory, limit)) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
+                 list.add(mobCategory);
 +                // CraftBukkit end
              }
          }
@@ -164,20 +155,11 @@
          ServerLevel level,
          MobCategory category,
          StructureManager structureManager,
-@@ -235,16 +_,20 @@
+@@ -235,7 +_,20 @@
          double distance
      ) {
          EntityType<?> entityType = data.type;
 -        return entityType.getCategory() != MobCategory.MISC
--            && (
--                entityType.canSpawnFarFromPlayer()
--                    || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance())
--            )
--            && entityType.canSummon()
--            && canSpawnMobAt(level, structureManager, generator, category, data, pos)
--            && SpawnPlacements.isSpawnPositionOk(entityType, level, pos)
--            && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
--            && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
 +
 +        // Paper start - PreCreatureSpawnEvent
 +        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
@@ -190,8 +172,16 @@
 +            }
 +            return PreSpawnStatus.CANCELLED;
 +        }
-+        // Paper end - PreCreatureSpawnEvent
-+        return entityType.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entityType.canSpawnFarFromPlayer() && distance > (double) (entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entityType.canSummon() && NaturalSpawner.canSpawnMobAt(level, structureManager, generator, category, data, pos) ? (!SpawnPlacements.isSpawnPositionOk(entityType, level, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random) ? PreSpawnStatus.FAIL : level.noCollision(entityType.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent
++        final boolean success = entityType.getCategory() != MobCategory.MISC
++            // Paper end - PreCreatureSpawnEvent
+             && (
+                 entityType.canSpawnFarFromPlayer()
+                     || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance())
+@@ -245,6 +_,7 @@
+             && SpawnPlacements.isSpawnPositionOk(entityType, level, pos)
+             && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
+             && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
++        return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent
      }
  
      @Nullable

From 5f9265cc31daba293d6b7968ca0d212e74a89846 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Thu, 19 Dec 2024 12:03:16 +0100
Subject: [PATCH 241/285] Update more feature patches The incremental chunk
 saving patch has exactly one line that it expects to come from the moonrise
 patch, I just left the output as is

---
 ...-Incremental-chunk-and-player-saving.patch |  88 +++---
 .../1071-Optional-per-player-mob-spawns.patch | 288 +++++++++---------
 ...ng-PreCreatureSpawnEvent-with-per-pl.patch |  76 ++---
 .../world/level/NaturalSpawner.java.patch     |   8 +-
 4 files changed, 232 insertions(+), 228 deletions(-)

diff --git a/feature-patches/1066-Incremental-chunk-and-player-saving.patch b/feature-patches/1066-Incremental-chunk-and-player-saving.patch
index 9e117f351b..17ee1796ca 100644
--- a/feature-patches/1066-Incremental-chunk-and-player-saving.patch
+++ b/feature-patches/1066-Incremental-chunk-and-player-saving.patch
@@ -4,23 +4,23 @@ Date: Sun, 9 Jun 2019 03:53:22 +0100
 Subject: [PATCH] Incremental chunk and player saving
 
 
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203..a0a75c84379432ccc525ab22d476c358c77f663b 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -862,7 +862,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         boolean var4;
          try {
              this.isSaving = true;
 -            this.getPlayerList().saveAll();
 +            this.getPlayerList().saveAll(); // Paper - Incremental chunk and player saving; diff on change
-             flag3 = this.saveAllChunks(suppressLogs, flush, force);
+             var4 = this.saveAllChunks(suppressLog, flush, forced);
          } finally {
              this.isSaving = false;
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1409,9 +1409,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
  
-         --this.ticksUntilAutosave;
+         this.ticksUntilAutosave--;
 -        if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) { // CraftBukkit
 -            this.autoSave();
 +        // Paper start - Incremental chunk and player saving
@@ -47,22 +47,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        profiler.pop();
 +        // Paper end - Incremental chunk and player saving
  
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         ProfilerFiller profilerFiller = Profiler.get();
+         this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index c38eda42b33cfa4792625f40ebde6f30e591119b..c8129f0d8218daff9123f1ad2d8ca321a02e1c7e 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -1007,6 +1007,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos);
      }
  
 +    // Paper start - Incremental chunk and player saving
 +    public void saveIncrementally(boolean doFull) {
-+        ServerChunkCache chunkproviderserver = this.getChunkSource();
-+
 +        if (doFull) {
-+            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
++            org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
 +        }
 +
 +        if (doFull) {
@@ -72,8 +70,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Copied from save()
 +        // CraftBukkit start - moved from MinecraftServer.saveChunks
 +        if (doFull) { // Paper
-+            ServerLevel worldserver1 = this;
-+            this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
++            ServerLevel serverLevel1 = this;
++            this.serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
 +            this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
 +            this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
 +        }
@@ -81,34 +79,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Incremental chunk and player saving
 +
-     public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) {
-         // Paper start - add close param
-         this.save(progressListener, flush, savingDisabled, false);
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -0,0 +0,0 @@ import org.bukkit.inventory.MainHand;
- public class ServerPlayer extends net.minecraft.world.entity.player.Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
+     public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
+         ServerChunkCache chunkSource = this.getChunkSource();
+         if (!skipSave) {
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
+index 92bd46cca1956f327fb0b407e988d68782f441a4..0f00db82e85c9e510c2e4fe4065291971c408dad 100644
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -180,6 +180,7 @@ import org.slf4j.Logger;
  
+ public class ServerPlayer extends Player {
      private static final Logger LOGGER = LogUtils.getLogger();
 +    public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
      private static final int FLY_STAT_RECORDING_SPEED = 25;
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
+index bafeeab3edbc73f6f86474e18ab4a3d96ce17157..aaa6b8eee7b34fe6efa76f1fe997dcece827d5dd 100644
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -483,6 +483,7 @@ public abstract class PlayerList {
  
      protected void save(ServerPlayer player) {
          if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
 +        player.lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
          this.playerIo.save(player);
-         ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
- 
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+         ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
+         if (serverStatsCounter != null) {
+@@ -1070,9 +1071,23 @@ public abstract class PlayerList {
      }
  
      public void saveAll() {
@@ -116,18 +114,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.saveAll(-1);
 +    }
 +
-+    public void saveAll(int interval) {
++    public void saveAll(final int interval) {
          io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
 +        int numSaved = 0;
-+        long now = MinecraftServer.currentTick;
-         for (int i = 0; i < this.players.size(); ++i) {
--            this.save((ServerPlayer) this.players.get(i));
++        final long now = MinecraftServer.currentTick;
+         for (int i = 0; i < this.players.size(); i++) {
+-            this.save(this.players.get(i));
 +            final ServerPlayer player = this.players.get(i);
 +            if (interval == -1 || now - player.lastSave >= interval) {
 +                this.save(player);
-+                if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; }
++                if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) {
++                    break;
++                }
 +            }
 +            // Paper end - Incremental chunk and player saving
          }
- 
          return null; }); // Paper - ensure main
+     }
diff --git a/feature-patches/1071-Optional-per-player-mob-spawns.patch b/feature-patches/1071-Optional-per-player-mob-spawns.patch
index dda7294b2a..2604d7bb97 100644
--- a/feature-patches/1071-Optional-per-player-mob-spawns.patch
+++ b/feature-patches/1071-Optional-per-player-mob-spawns.patch
@@ -4,229 +4,233 @@ Date: Mon, 19 Aug 2019 01:27:58 +0500
 Subject: [PATCH] Optional per player mob spawns
 
 
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
+index 809f3fe1285e347f18709c2368923fcc8f953ded..6c5b2eb411fb60babbb0c74d5c075696ef70b38d 100644
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -237,11 +237,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         this.chunksToEagerlySave.add(chunkPos.toLong());
      }
  
-     // Paper start
+-    // Paper start
+-    public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+-        return -1;
 +    // Paper start - Optional per player mob spawns
 +    public void updatePlayerMobTypeMap(final Entity entity) {
 +        if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
 +            return;
 +        }
-+        final int index = entity.getType().getCategory().ordinal();
 +
++        final int index = entity.getType().getCategory().ordinal();
 +        final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> inRange =
 +            this.level.moonrise$getNearbyPlayers().getPlayers(entity.chunkPosition(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
 +        if (inRange == null) {
 +            return;
 +        }
++
 +        final ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
 +        for (int i = 0, len = inRange.size(); i < len; i++) {
 +            ++(backingSet[i].mobCounts[index]);
 +        }
-+    }
-     public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
--        return -1;
-+        return player.mobCounts[mobCategory.ordinal()];
-+        // Paper end - Optional per player mob spawns
      }
-     // Paper end
+-    // Paper end
++
++    public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
++        return player.mobCounts[mobCategory.ordinal()];
++     }
++    // Paper end - Optional per player mob spawns
  
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
-                     gameprofilerfiller.popPush("shuffleChunks");
-                     // Paper start - chunk tick iteration optimisation
-                     this.shuffleRandom.setSeed(this.level.random.nextLong());
--                    Util.shuffle(list, this.shuffleRandom);
+     protected ChunkGenerator generator() {
+         return this.worldGenContext.generator();
+diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
+index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..078f208e104a652ce48458150389d19ede6808ef 100644
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -435,7 +435,7 @@ public class ServerChunkCache extends ChunkSource {
+                     profilerFiller.push("filteringTickingChunks");
+                     this.collectTickingChunks(list);
+                     profilerFiller.popPush("shuffleChunks");
+-                    Util.shuffle(list, this.level.random);
 +                    if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled
-                     // Paper end - chunk tick iteration optimisation
-                     this.tickChunks(gameprofilerfiller, j, list);
-                     gameprofilerfiller.pop();
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
-     private void tickChunks(ProfilerFiller profiler, long timeDelta, List<LevelChunk> chunks) {
+                     this.tickChunks(profilerFiller, l, list);
+                     profilerFiller.pop();
+                 } finally {
+@@ -474,9 +474,18 @@ public class ServerChunkCache extends ChunkSource {
+     private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
          profiler.popPush("naturalSpawnCount");
-         int j = this.distanceManager.getNaturalSpawnChunkCount();
--        NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(j, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
+         int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
+-        NaturalSpawner.SpawnState spawnState = NaturalSpawner.createState(
+-            naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)
+-        );
 +        // Paper start - Optional per player mob spawns
-+        final int naturalSpawnChunkCount = j;
-+        NaturalSpawner.SpawnState spawnercreature_d; // moved down
++        NaturalSpawner.SpawnState spawnState;
 +        if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
 +            // re-set mob counts
 +            for (ServerPlayer player : this.level.players) {
 +                Arrays.fill(player.mobCounts, 0);
 +            }
-+            spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
++            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
 +        } else {
-+            spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
++            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
 +        }
 +        // Paper end - Optional per player mob spawns
- 
-         this.lastSpawnState = spawnercreature_d;
+         this.lastSpawnState = spawnState;
          profiler.popPush("spawnAndTick");
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
+         boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
+index 0f00db82e85c9e510c2e4fe4065291971c408dad..dab58457ed02d3f8153c07de101262b1a0182d71 100644
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -368,6 +368,10 @@ public class ServerPlayer extends Player {
      public boolean queueHealthUpdatePacket;
      public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
      // Paper end - cancellable death event
 +    // Paper start - Optional per player mob spawns
 +    public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
-+    public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper
++    public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS];
 +    // Paper end - Optional per player mob spawns
- 
      // CraftBukkit start
-     public CraftPlayer.TransferCookieConnection transferCookieConnection;
-diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-     private NaturalSpawner() {}
- 
-     public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper) {
+     public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
+     public String displayName;
+diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
+index 6e6e028621ccc4597b2c24f54f53cb7f3de603e2..14e99450a8522b79e4c3805bd91439a950bc8f99 100644
+--- a/net/minecraft/world/level/NaturalSpawner.java
++++ b/net/minecraft/world/level/NaturalSpawner.java
+@@ -72,6 +72,14 @@ public final class NaturalSpawner {
+     public static NaturalSpawner.SpawnState createState(
+         int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator
+     ) {
 +        // Paper start - Optional per player mob spawns
-+        return createState(spawningChunkCount, entities, chunkSource, densityCapper, false);
++        return createState(spawnableChunkCount, entities, chunkGetter, calculator, false);
 +    }
 +
-+    public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper, boolean countMobs) {
++    public static NaturalSpawner.SpawnState createState(
++        int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
++    ) {
 +        // Paper end - Optional per player mob spawns
-         PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator();
-         Object2IntOpenHashMap<MobCategory> object2intopenhashmap = new Object2IntOpenHashMap();
-         Iterator iterator = entities.iterator();
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-                         spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.charge());
-                     }
+         PotentialCalculator potentialCalculator = new PotentialCalculator();
+         Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
  
--                    if (entity instanceof Mob) {
-+                    if (densityCapper != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
-                         densityCapper.addMob(chunk.getPos(), enumcreaturetype);
-                     }
+@@ -93,11 +101,16 @@ public final class NaturalSpawner {
+                             potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
+                         }
  
-                     object2intopenhashmap.addTo(enumcreaturetype, 1);
-+                    // Paper start - Optional per player mob spawns
-+                    if (countMobs) {
-+                        chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
-+                    }
-+                    // Paper end - Optional per player mob spawns
-                 });
+-                        if (entity instanceof Mob) {
++                        if (calculator != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
+                             calculator.addMob(chunk.getPos(), category);
+                         }
+ 
+                         map.addTo(category, 1);
++                        // Paper start - Optional per player mob spawns
++                        if (countMobs) {
++                            chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
++                        }
++                        // Paper end - Optional per player mob spawns
+                     });
+                 }
              }
-         }
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-                 continue;
-             }
- 
--            if ((flag || !enumcreaturetype.isFriendly()) && (flag1 || enumcreaturetype.isFriendly()) && (flag2 || !enumcreaturetype.isPersistent()) && spawnercreature_d.canSpawnForCategoryGlobal(enumcreaturetype, limit)) {
-+            if ((flag || !enumcreaturetype.isFriendly()) && (flag1 || enumcreaturetype.isFriendly()) && (flag2 || !enumcreaturetype.isPersistent()) && (worldserver.paperConfig().entities.spawning.perPlayerMobSpawns || spawnercreature_d.canSpawnForCategoryGlobal(enumcreaturetype, limit))) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
+@@ -135,7 +148,7 @@ public final class NaturalSpawner {
+             if ((spawnFriendlies || !mobCategory.isFriendly())
+                 && (spawnEnemies || mobCategory.isFriendly())
+                 && (spawnPassives || !mobCategory.isPersistent())
+-                && spawnState.canSpawnForCategoryGlobal(mobCategory, limit)) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
++                && (level.paperConfig().entities.spawning.perPlayerMobSpawns || spawnState.canSpawnForCategoryGlobal(mobCategory, limit))) { // Paper - Optional per player mob spawns; remove global check, check later during the local one
+                 list.add(mobCategory);
                  // CraftBukkit end
-                 list.add(enumcreaturetype);
              }
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-         while (iterator.hasNext()) {
-             MobCategory enumcreaturetype = (MobCategory) iterator.next();
+@@ -149,8 +162,37 @@ public final class NaturalSpawner {
+         profilerFiller.push("spawner");
  
--            if (info.canSpawnForCategoryLocal(enumcreaturetype, chunk.getPos())) {
+         for (MobCategory mobCategory : categories) {
+-            if (spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos())) {
+-                spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn);
 +            // Paper start - Optional per player mob spawns
 +            final boolean canSpawn;
 +            int maxSpawns = Integer.MAX_VALUE;
-+            if (world.paperConfig().entities.spawning.perPlayerMobSpawns) {
++            if (level.paperConfig().entities.spawning.perPlayerMobSpawns) {
 +                // Copied from getFilteredSpawningCategories
-+                int limit = enumcreaturetype.getMaxInstancesPerChunk();
-+                SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
++                int limit = mobCategory.getMaxInstancesPerChunk();
++                SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory);
 +                if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
-+                    limit = world.getWorld().getSpawnLimit(spawnCategory);
++                    limit = level.getWorld().getSpawnLimit(spawnCategory);
 +                }
 +
 +                // Apply per-player limit
 +                int minDiff = Integer.MAX_VALUE;
 +                final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> inRange =
-+                    world.moonrise$getNearbyPlayers().getPlayers(chunk.getPos(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
++                    level.moonrise$getNearbyPlayers().getPlayers(chunk.getPos(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE);
 +                if (inRange != null) {
 +                    final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked();
 +                    for (int k = 0, len = inRange.size(); k < len; k++) {
-+                        minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(backingSet[k], enumcreaturetype), minDiff);
++                        minDiff = Math.min(limit - level.getChunkSource().chunkMap.getMobCountNear(backingSet[k], mobCategory), minDiff);
 +                    }
 +                }
 +
 +                maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
 +                canSpawn = maxSpawns > 0;
 +            } else {
-+                canSpawn = info.canSpawnForCategoryLocal(enumcreaturetype, chunk.getPos());
++                canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos());
 +            }
 +            if (canSpawn) {
-+                // Paper end - Optional per player mob spawns
-                 Objects.requireNonNull(info);
-                 NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn;
- 
-                 Objects.requireNonNull(info);
--                NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn);
-+                // Paper start - Optional per player mob spawns
-+                NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn,
-+                    maxSpawns, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
++                spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn,
++                    maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
 +                // Paper end - Optional per player mob spawns
              }
          }
  
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-     // Paper end - Add mobcaps commands
- 
-     public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
+@@ -170,9 +212,16 @@ public final class NaturalSpawner {
+     public static void spawnCategoryForChunk(
+         MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
+     ) {
 +        // Paper start - Optional per player mob spawns
-+        spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null);
++        spawnCategoryForChunk(category, level, chunk, filter, callback, Integer.MAX_VALUE, null);
 +    }
-+    public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
++    public static void spawnCategoryForChunk(
++        MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer<Entity> trackEntity
++    ) {
 +        // Paper end - Optional per player mob spawns
-         BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
- 
-         if (blockposition.getY() >= world.getMinY() + 1) {
--            NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner);
-+            NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper - Optional per player mob spawns
+         BlockPos randomPosWithin = getRandomPosWithin(level, chunk);
+         if (randomPosWithin.getY() >= level.getMinY() + 1) {
+-            spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback);
++            spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity); // Paper - Optional per player mob spawns
          }
      }
  
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-         });
-     }
- 
-+    // Paper start - Optional per player mob spawns
-     public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
-+        spawnCategoryForPosition(group, world,chunk, pos, checker, runner, Integer.MAX_VALUE, null);
+@@ -189,6 +238,12 @@ public final class NaturalSpawner {
+         NaturalSpawner.SpawnPredicate filter,
+         NaturalSpawner.AfterSpawnCallback callback
+     ) {
++        spawnCategoryForPosition(category, level, chunk, pos, filter, callback, Integer.MAX_VALUE, null);
 +    }
-+    public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer<Entity> trackEntity) {
-+    // Paper end - Optional per player mob spawns
-         StructureManager structuremanager = world.structureManager();
-         ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
-         int i = pos.getY();
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-                                                 ++j;
-                                                 ++k1;
-                                                 runner.run(entityinsentient, chunk);
-+                                                // Paper start - Optional per player mob spawns
-+                                                if (trackEntity != null) {
-+                                                    trackEntity.accept(entityinsentient);
-+                                                }
-+                                                // Paper end - Optional per player mob spawns
-                                             }
-                                             // CraftBukkit end
--                                            if (j >= entityinsentient.getMaxSpawnClusterSize()) {
-+                                            if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper - Optional per player mob spawns
-                                                 return;
-                                             }
++    public static void spawnCategoryForPosition(
++        MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer<Entity> trackEntity
++    ) {
++        // Paper end - Optional per player mob spawns
+         StructureManager structureManager = level.structureManager();
+         ChunkGenerator generator = level.getChunkSource().getGenerator();
+         int y = pos.getY();
+@@ -252,9 +307,14 @@ public final class NaturalSpawner {
+                                         ++i;
+                                         ++i3;
+                                         callback.run(mobForSpawn, chunk);
++                                        // Paper start - Optional per player mob spawns
++                                        if (trackEntity != null) {
++                                            trackEntity.accept(mobForSpawn);
++                                        }
++                                        // Paper end - Optional per player mob spawns
+                                     }
+                                     // CraftBukkit end
+-                                    if (i >= mobForSpawn.getMaxSpawnClusterSize()) {
++                                    if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns
+                                         return;
+                                     }
  
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
-             MobCategory enumcreaturetype = entitytypes.getCategory();
- 
-             this.mobCategoryCounts.addTo(enumcreaturetype, 1);
--            this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype);
-+            if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper - Optional per player mob spawns
+@@ -565,7 +625,7 @@ public final class NaturalSpawner {
+             this.spawnPotential.addCharge(blockPos, d);
+             MobCategory category = type.getCategory();
+             this.mobCategoryCounts.addTo(category, 1);
+-            this.localMobCapCalculator.addMob(new ChunkPos(blockPos), category);
++            if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockPos), category); // Paper - Optional per player mob spawns
          }
  
          public int getSpawnableChunkCount() {
diff --git a/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
index 46e601aaff..d94b29471c 100644
--- a/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
+++ b/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
@@ -5,14 +5,14 @@ Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob
  spawns
 
 
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-             ++(backingSet[i].mobCounts[index]);
+diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
+index 6c5b2eb411fb60babbb0c74d5c075696ef70b38d..cf439285e4ba9babda228c36aa81dfc49db2c22a 100644
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -256,8 +256,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
      }
+ 
 +    // Paper start - per player mob count backoff
 +    public void updateFailurePlayerMobTypeMap(int chunkX, int chunkZ, net.minecraft.world.entity.MobCategory mobCategory) {
 +        if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
@@ -33,14 +33,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
 -        return player.mobCounts[mobCategory.ordinal()];
 +        return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff
-         // Paper end - Optional per player mob spawns
-     }
-     // Paper end
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
+      }
+     // Paper end - Optional per player mob spawns
+ 
+diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
+index 078f208e104a652ce48458150389d19ede6808ef..aa141c00a41d49daee8e4ab7be70ce4e4767b105 100644
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -479,7 +479,17 @@ public class ServerChunkCache extends ChunkSource {
          if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
              // re-set mob counts
              for (ServerPlayer player : this.level.players) {
@@ -57,33 +57,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +                // Paper end - per player mob spawning backoff
              }
-             spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+             spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
          } else {
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
+index dab58457ed02d3f8153c07de101262b1a0182d71..2d20f42fbcfb67845323d994843d7b977aa867e0 100644
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -372,6 +372,7 @@ public class ServerPlayer extends Player {
      public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
-     public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper
+     public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS];
      // Paper end - Optional per player mob spawns
 +    public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff
- 
      // CraftBukkit start
-     public CraftPlayer.TransferCookieConnection transferCookieConnection;
-diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
-@@ -0,0 +0,0 @@ public final class NaturalSpawner {
+     public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
+     public String displayName;
+diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
+index 5e82a8fdaec5a6750040ebb687aa35bba4dcc2ba..23f7fb22906e49babc7784f4b3d1f8ea8e187b1d 100644
+--- a/net/minecraft/world/level/NaturalSpawner.java
++++ b/net/minecraft/world/level/NaturalSpawner.java
+@@ -285,6 +285,11 @@ public final class NaturalSpawner {
  
-                                     // Paper start - PreCreatureSpawnEvent
-                                     PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
-+                                    // Paper start - per player mob count backoff
-+                                    if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) {
-+                                        world.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(blockposition_mutableblockposition.getX() >> 4, blockposition_mutableblockposition.getZ() >> 4, group);
-+                                    }
-+                                    // Paper end - per player mob count backoff
-                                     if (doSpawning == PreSpawnStatus.ABORT) {
-                                         return;
-                                     }
+                             // Paper start - PreCreatureSpawnEvent
+                             PreSpawnStatus doSpawning = isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2);
++                            // Paper start - per player mob count backoff
++                            if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) {
++                                level.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(mutableBlockPos.getX() >> 4, mutableBlockPos.getZ() >> 4, category);
++                            }
++                            // Paper end - per player mob count backoff
+                             if (doSpawning == PreSpawnStatus.ABORT) {
+                                 return;
+                             }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
index d5a3311160..89849d7d70 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -35,9 +35,9 @@
 +    // CraftBukkit start - add server
      public static List<MobCategory> getFilteredSpawningCategories(
 -        NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives
-+        NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives, ServerLevel worldserver
++        NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives, ServerLevel level
      ) {
-+        LevelData worlddata = worldserver.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
++        LevelData worlddata = level.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
 +        // CraftBukkit end
          List<MobCategory> list = new ArrayList<>(SPAWNING_CATEGORIES.length);
  
@@ -47,8 +47,8 @@
 +            int limit = mobCategory.getMaxInstancesPerChunk();
 +            SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory);
 +            if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
-+                spawnThisTick = worldserver.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % worldserver.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
-+                limit = worldserver.getWorld().getSpawnLimit(spawnCategory);
++                spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
++                limit = level.getWorld().getSpawnLimit(spawnCategory);
 +            }
 +
 +            if (!spawnThisTick || limit == 0) {

From 4353c3321314127e53bd1363171373585ea3fd82 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Thu, 19 Dec 2024 12:26:28 +0100
Subject: [PATCH 242/285] Move flat bedrock generator to file patches

---
 ...1042-Flat-bedrock-generator-settings.patch | 292 ------------------
 .../noise_settings/amplified.json.patch       |  12 +
 .../worldgen/noise_settings/caves.json.patch  |  22 ++
 .../noise_settings/large_biomes.json.patch    |  12 +
 .../worldgen/noise_settings/nether.json.patch |  22 ++
 .../noise_settings/overworld.json.patch       |  12 +
 .../net/minecraft/server/Bootstrap.java.patch |   3 +-
 .../NoiseBasedChunkGenerator.java.patch       |  18 ++
 .../WorldGenerationContext.java.patch         |  32 ++
 .../levelgen/carver/CarvingContext.java.patch |  14 +
 .../placement/PlacementContext.java.patch     |  11 +
 .../OptionallyFlatBedrockConditionSource.java |  81 +++++
 12 files changed, 238 insertions(+), 293 deletions(-)
 delete mode 100644 feature-patches/1042-Flat-bedrock-generator-settings.patch
 create mode 100644 paper-server/patches/resources/data/minecraft/worldgen/noise_settings/amplified.json.patch
 create mode 100644 paper-server/patches/resources/data/minecraft/worldgen/noise_settings/caves.json.patch
 create mode 100644 paper-server/patches/resources/data/minecraft/worldgen/noise_settings/large_biomes.json.patch
 create mode 100644 paper-server/patches/resources/data/minecraft/worldgen/noise_settings/nether.json.patch
 create mode 100644 paper-server/patches/resources/data/minecraft/worldgen/noise_settings/overworld.json.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/WorldGenerationContext.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/carver/CarvingContext.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/placement/PlacementContext.java.patch
 create mode 100644 paper-server/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java

diff --git a/feature-patches/1042-Flat-bedrock-generator-settings.patch b/feature-patches/1042-Flat-bedrock-generator-settings.patch
deleted file mode 100644
index 4d8c54d243..0000000000
--- a/feature-patches/1042-Flat-bedrock-generator-settings.patch
+++ /dev/null
@@ -1,292 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Byteflux <byte@byteflux.net>
-Date: Wed, 2 Mar 2016 02:17:54 -0600
-Subject: [PATCH] Flat bedrock generator settings
-
-== AT ==
-public net.minecraft.world.level.levelgen.SurfaceRules$Condition
-public net.minecraft.world.level.levelgen.SurfaceRules$Context
-public net.minecraft.world.level.levelgen.SurfaceRules$Context blockX
-public net.minecraft.world.level.levelgen.SurfaceRules$Context blockY
-public net.minecraft.world.level.levelgen.SurfaceRules$Context blockZ
-public net.minecraft.world.level.levelgen.SurfaceRules$Context context
-public net.minecraft.world.level.levelgen.SurfaceRules$Context randomState
-public net.minecraft.world.level.levelgen.SurfaceRules$LazyYCondition
-public net.minecraft.world.level.levelgen.SurfaceRules$LazyCondition
-public net.minecraft.world.level.levelgen.SurfaceRules$VerticalGradientConditionSource
-public net.minecraft.world.level.levelgen.SurfaceRules$SurfaceRule
-
-Co-authored-by: Noah van der Aa <ndvdaa@gmail.com>
-
-diff --git a/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java b/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java
-@@ -0,0 +0,0 @@
-+package io.papermc.paper.world.worldgen;
-+
-+import com.mojang.serialization.Codec;
-+import com.mojang.serialization.MapCodec;
-+import com.mojang.serialization.codecs.RecordCodecBuilder;
-+import net.minecraft.core.Registry;
-+import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.resources.ResourceKey;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.util.KeyDispatchDataCodec;
-+import net.minecraft.util.Mth;
-+import net.minecraft.util.RandomSource;
-+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
-+import net.minecraft.world.level.levelgen.SurfaceRules;
-+import net.minecraft.world.level.levelgen.VerticalAnchor;
-+import org.checkerframework.checker.nullness.qual.NonNull;
-+import org.checkerframework.framework.qual.DefaultQualifier;
-+
-+// Modelled off of SurfaceRules$VerticalGradientConditionSource
-+@DefaultQualifier(NonNull.class)
-+public record OptionallyFlatBedrockConditionSource(ResourceLocation randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove, boolean isRoof) implements SurfaceRules.ConditionSource {
-+
-+    private static final ResourceKey<MapCodec<? extends SurfaceRules.ConditionSource>> CODEC_RESOURCE_KEY = ResourceKey.create(
-+        Registries.MATERIAL_CONDITION,
-+        ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "optionally_flat_bedrock_condition_source")
-+    );
-+    private static final KeyDispatchDataCodec<OptionallyFlatBedrockConditionSource> CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec((instance) -> {
-+        return instance.group(
-+            ResourceLocation.CODEC.fieldOf("random_name").forGetter(OptionallyFlatBedrockConditionSource::randomName),
-+            VerticalAnchor.CODEC.fieldOf("true_at_and_below").forGetter(OptionallyFlatBedrockConditionSource::trueAtAndBelow),
-+            VerticalAnchor.CODEC.fieldOf("false_at_and_above").forGetter(OptionallyFlatBedrockConditionSource::falseAtAndAbove),
-+            Codec.BOOL.fieldOf("is_roof").forGetter(OptionallyFlatBedrockConditionSource::isRoof)
-+        ).apply(instance, OptionallyFlatBedrockConditionSource::new);
-+    }));
-+
-+    public static void bootstrap() {
-+        Registry.register(BuiltInRegistries.MATERIAL_CONDITION, CODEC_RESOURCE_KEY, CODEC.codec());
-+    }
-+
-+    @Override
-+    public KeyDispatchDataCodec<? extends SurfaceRules.ConditionSource> codec() {
-+        return CODEC;
-+    }
-+
-+    @Override
-+    public SurfaceRules.Condition apply(final SurfaceRules.Context context) {
-+        boolean hasFlatBedrock = context.context.getWorld().paperConfig().environment.generateFlatBedrock;
-+        int tempTrueAtAndBelowY = this.trueAtAndBelow().resolveY(context.context);
-+        int tempFalseAtAndAboveY = this.falseAtAndAbove().resolveY(context.context);
-+
-+        int flatYLevel = this.isRoof ? Math.max(tempFalseAtAndAboveY, tempTrueAtAndBelowY) - 1 : Math.min(tempFalseAtAndAboveY, tempTrueAtAndBelowY);
-+        final int trueAtAndBelowY = hasFlatBedrock ? flatYLevel : tempTrueAtAndBelowY;
-+        final int falseAtAndAboveY = hasFlatBedrock ? flatYLevel : tempFalseAtAndAboveY;
-+
-+        final PositionalRandomFactory positionalRandomFactory = context.randomState.getOrCreateRandomFactory(this.randomName());
-+
-+        class VerticalGradientCondition extends SurfaceRules.LazyYCondition {
-+            VerticalGradientCondition(SurfaceRules.Context context) {
-+                super(context);
-+            }
-+
-+            @Override
-+            protected boolean compute() {
-+                int blockY = this.context.blockY;
-+                if (blockY <= trueAtAndBelowY) {
-+                    return true;
-+                } else if (blockY >= falseAtAndAboveY) {
-+                    return false;
-+                } else {
-+                    double d = Mth.map(blockY, trueAtAndBelowY, falseAtAndAboveY, 1.0D, 0.0D);
-+                    RandomSource randomSource = positionalRandomFactory.at(this.context.blockX, blockY, this.context.blockZ);
-+                    return (double)randomSource.nextFloat() < d;
-+                }
-+            }
-+        }
-+
-+        return new VerticalGradientCondition(context);
-+    }
-+}
-diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/Bootstrap.java
-+++ b/src/main/java/net/minecraft/server/Bootstrap.java
-@@ -0,0 +0,0 @@ public class Bootstrap {
-                     CauldronInteraction.bootStrap();
-                     // Paper start
-                     BuiltInRegistries.bootStrap(() -> {
-+                        io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings
-                     });
-                     // Paper end
-                     CreativeModeTabs.validate();
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-@@ -0,0 +0,0 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
-     @Override
-     public void buildSurface(WorldGenRegion region, StructureManager structures, RandomState noiseConfig, ChunkAccess chunk) {
-         if (!SharedConstants.debugVoidTerrain(chunk.getPos())) {
--            WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region);
-+            WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region, region.getMinecraftWorld()); // Paper - Flat bedrock generator settings
- 
-             this.buildSurface(chunk, worldgenerationcontext, noiseConfig, structures, region.getBiomeManager(), region.registryAccess().lookupOrThrow(Registries.BIOME), Blender.of(region));
-         }
-@@ -0,0 +0,0 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
-             return this.createNoiseChunk(ichunkaccess1, structureAccessor, Blender.of(chunkRegion), noiseConfig);
-         });
-         Aquifer aquifer = noisechunk.aquifer();
--        CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule());
-+        CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule(), chunkRegion.getMinecraftWorld()); // Paper - Flat bedrock generator settings
-         CarvingMask carvingmask = ((ProtoChunk) chunk).getOrCreateCarvingMask();
- 
-         for (int j = -8; j <= 8; ++j) {
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.ChunkGenerator;
- public class WorldGenerationContext {
-     private final int minY;
-     private final int height;
-+    private final @javax.annotation.Nullable net.minecraft.world.level.Level level; // Paper - Flat bedrock generator settings
- 
--    public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) {
-+    public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) { this(generator, world, null); } // Paper - Flat bedrock generator settings
-+    public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world, @org.jetbrains.annotations.Nullable net.minecraft.world.level.Level level) { // Paper - Flat bedrock generator settings
-         this.minY = Math.max(world.getMinY(), generator.getMinY());
-         this.height = Math.min(world.getHeight(), generator.getGenDepth());
-+        this.level = level; // Paper - Flat bedrock generator settings
-     }
- 
-     public int getMinGenY() {
-@@ -0,0 +0,0 @@ public class WorldGenerationContext {
-     public int getGenDepth() {
-         return this.height;
-     }
-+
-+    // Paper start - Flat bedrock generator settings
-+    public net.minecraft.world.level.Level getWorld() {
-+        if (this.level == null) {
-+            throw new NullPointerException("WorldGenerationContext was initialized without a Level, but WorldGenerationContext#getWorld was called");
-+        }
-+        return this.level;
-+    }
-+    // Paper end - Flat bedrock generator settings
- }
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java
-@@ -0,0 +0,0 @@ public class CarvingContext extends WorldGenerationContext {
-         LevelHeightAccessor heightLimitView,
-         NoiseChunk chunkNoiseSampler,
-         RandomState noiseConfig,
--        SurfaceRules.RuleSource materialRule
-+        SurfaceRules.RuleSource materialRule, @javax.annotation.Nullable net.minecraft.world.level.Level level  // Paper - Flat bedrock generator settings
-     ) {
--        super(noiseChunkGenerator, heightLimitView);
-+        super(noiseChunkGenerator, heightLimitView, level); // Paper - Flat bedrock generator settings
-         this.registryAccess = registryManager;
-         this.noiseChunk = chunkNoiseSampler;
-         this.randomState = noiseConfig;
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java
-@@ -0,0 +0,0 @@ public class PlacementContext extends WorldGenerationContext {
-     private final Optional<PlacedFeature> topFeature;
- 
-     public PlacementContext(WorldGenLevel world, ChunkGenerator generator, Optional<PlacedFeature> placedFeature) {
--        super(generator, world);
-+        super(generator, world, world.getLevel()); // Paper - Flat bedrock generator settings
-         this.level = world;
-         this.generator = generator;
-         this.topFeature = placedFeature;
-diff --git a/src/main/resources/data/minecraft/worldgen/noise_settings/amplified.json b/src/main/resources/data/minecraft/worldgen/noise_settings/amplified.json
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/data/minecraft/worldgen/noise_settings/amplified.json
-+++ b/src/main/resources/data/minecraft/worldgen/noise_settings/amplified.json
-@@ -0,0 +0,0 @@
-       {
-         "type": "minecraft:condition",
-         "if_true": {
--          "type": "minecraft:vertical_gradient",
-+          "type": "paper:optionally_flat_bedrock_condition_source",
-+          "is_roof": false,
-           "false_at_and_above": {
-             "above_bottom": 5
-           },
-diff --git a/src/main/resources/data/minecraft/worldgen/noise_settings/caves.json b/src/main/resources/data/minecraft/worldgen/noise_settings/caves.json
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/data/minecraft/worldgen/noise_settings/caves.json
-+++ b/src/main/resources/data/minecraft/worldgen/noise_settings/caves.json
-@@ -0,0 +0,0 @@
-         "if_true": {
-           "type": "minecraft:not",
-           "invert": {
--            "type": "minecraft:vertical_gradient",
-+            "type": "paper:optionally_flat_bedrock_condition_source",
-+            "is_roof": true,
-             "false_at_and_above": {
-               "below_top": 0
-             },
-@@ -0,0 +0,0 @@
-       {
-         "type": "minecraft:condition",
-         "if_true": {
--          "type": "minecraft:vertical_gradient",
-+          "type": "paper:optionally_flat_bedrock_condition_source",
-+          "is_roof": false,
-           "false_at_and_above": {
-             "above_bottom": 5
-           },
-diff --git a/src/main/resources/data/minecraft/worldgen/noise_settings/large_biomes.json b/src/main/resources/data/minecraft/worldgen/noise_settings/large_biomes.json
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/data/minecraft/worldgen/noise_settings/large_biomes.json
-+++ b/src/main/resources/data/minecraft/worldgen/noise_settings/large_biomes.json
-@@ -0,0 +0,0 @@
-       {
-         "type": "minecraft:condition",
-         "if_true": {
--          "type": "minecraft:vertical_gradient",
-+          "type": "paper:optionally_flat_bedrock_condition_source",
-+          "is_roof": false,
-           "false_at_and_above": {
-             "above_bottom": 5
-           },
-diff --git a/src/main/resources/data/minecraft/worldgen/noise_settings/nether.json b/src/main/resources/data/minecraft/worldgen/noise_settings/nether.json
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/data/minecraft/worldgen/noise_settings/nether.json
-+++ b/src/main/resources/data/minecraft/worldgen/noise_settings/nether.json
-@@ -0,0 +0,0 @@
-       {
-         "type": "minecraft:condition",
-         "if_true": {
--          "type": "minecraft:vertical_gradient",
-+          "type": "paper:optionally_flat_bedrock_condition_source",
-+          "is_roof": false,
-           "false_at_and_above": {
-             "above_bottom": 5
-           },
-@@ -0,0 +0,0 @@
-         "if_true": {
-           "type": "minecraft:not",
-           "invert": {
--            "type": "minecraft:vertical_gradient",
-+            "type": "paper:optionally_flat_bedrock_condition_source",
-+            "is_roof": true,
-             "false_at_and_above": {
-               "below_top": 0
-             },
-diff --git a/src/main/resources/data/minecraft/worldgen/noise_settings/overworld.json b/src/main/resources/data/minecraft/worldgen/noise_settings/overworld.json
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/resources/data/minecraft/worldgen/noise_settings/overworld.json
-+++ b/src/main/resources/data/minecraft/worldgen/noise_settings/overworld.json
-@@ -0,0 +0,0 @@
-       {
-         "type": "minecraft:condition",
-         "if_true": {
--          "type": "minecraft:vertical_gradient",
-+          "type": "paper:optionally_flat_bedrock_condition_source",
-+          "is_roof": false,
-           "false_at_and_above": {
-             "above_bottom": 5
-           },
diff --git a/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/amplified.json.patch b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/amplified.json.patch
new file mode 100644
index 0000000000..356d44998f
--- /dev/null
+++ b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/amplified.json.patch
@@ -0,0 +1,12 @@
+--- a/data/minecraft/worldgen/noise_settings/amplified.json
++++ b/data/minecraft/worldgen/noise_settings/amplified.json
+@@ -389,7 +_,8 @@
+       {
+         "type": "minecraft:condition",
+         "if_true": {
+-          "type": "minecraft:vertical_gradient",
++          "type": "paper:optionally_flat_bedrock_condition_source",
++          "is_roof": false,
+           "false_at_and_above": {
+             "above_bottom": 5
+           },
diff --git a/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/caves.json.patch b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/caves.json.patch
new file mode 100644
index 0000000000..64ecd45575
--- /dev/null
+++ b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/caves.json.patch
@@ -0,0 +1,22 @@
+--- a/data/minecraft/worldgen/noise_settings/caves.json
++++ b/data/minecraft/worldgen/noise_settings/caves.json
+@@ -110,7 +_,8 @@
+         "if_true": {
+           "type": "minecraft:not",
+           "invert": {
+-            "type": "minecraft:vertical_gradient",
++            "type": "paper:optionally_flat_bedrock_condition_source",
++            "is_roof": true,
+             "false_at_and_above": {
+               "below_top": 0
+             },
+@@ -130,7 +_,8 @@
+       {
+         "type": "minecraft:condition",
+         "if_true": {
+-          "type": "minecraft:vertical_gradient",
++          "type": "paper:optionally_flat_bedrock_condition_source",
++          "is_roof": false,
+           "false_at_and_above": {
+             "above_bottom": 5
+           },
diff --git a/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/large_biomes.json.patch b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/large_biomes.json.patch
new file mode 100644
index 0000000000..e82d43382c
--- /dev/null
+++ b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/large_biomes.json.patch
@@ -0,0 +1,12 @@
+--- a/data/minecraft/worldgen/noise_settings/large_biomes.json
++++ b/data/minecraft/worldgen/noise_settings/large_biomes.json
+@@ -389,7 +_,8 @@
+       {
+         "type": "minecraft:condition",
+         "if_true": {
+-          "type": "minecraft:vertical_gradient",
++          "type": "paper:optionally_flat_bedrock_condition_source",
++          "is_roof": false,
+           "false_at_and_above": {
+             "above_bottom": 5
+           },
diff --git a/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/nether.json.patch b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/nether.json.patch
new file mode 100644
index 0000000000..ad053aec9a
--- /dev/null
+++ b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/nether.json.patch
@@ -0,0 +1,22 @@
+--- a/data/minecraft/worldgen/noise_settings/nether.json
++++ b/data/minecraft/worldgen/noise_settings/nether.json
+@@ -108,7 +_,8 @@
+       {
+         "type": "minecraft:condition",
+         "if_true": {
+-          "type": "minecraft:vertical_gradient",
++          "type": "paper:optionally_flat_bedrock_condition_source",
++          "is_roof": false,
+           "false_at_and_above": {
+             "above_bottom": 5
+           },
+@@ -129,7 +_,8 @@
+         "if_true": {
+           "type": "minecraft:not",
+           "invert": {
+-            "type": "minecraft:vertical_gradient",
++            "type": "paper:optionally_flat_bedrock_condition_source",
++            "is_roof": true,
+             "false_at_and_above": {
+               "below_top": 0
+             },
diff --git a/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/overworld.json.patch b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/overworld.json.patch
new file mode 100644
index 0000000000..868ffa6476
--- /dev/null
+++ b/paper-server/patches/resources/data/minecraft/worldgen/noise_settings/overworld.json.patch
@@ -0,0 +1,12 @@
+--- a/data/minecraft/worldgen/noise_settings/overworld.json
++++ b/data/minecraft/worldgen/noise_settings/overworld.json
+@@ -389,7 +_,8 @@
+       {
+         "type": "minecraft:condition",
+         "if_true": {
+-          "type": "minecraft:vertical_gradient",
++          "type": "paper:optionally_flat_bedrock_condition_source",
++          "is_roof": false,
+           "false_at_and_above": {
+             "above_bottom": 5
+           },
diff --git a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
index 701d502fec..20a795954e 100644
--- a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
@@ -8,13 +8,14 @@
              if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
                  throw new IllegalStateException("Unable to load registries");
              } else {
-@@ -54,11 +_,77 @@
+@@ -54,11 +_,78 @@
                      EntitySelectorOptions.bootStrap();
                      DispenseItemBehavior.bootStrap();
                      CauldronInteraction.bootStrap();
 -                    BuiltInRegistries.bootStrap();
 +                    // Paper start
 +                    BuiltInRegistries.bootStrap(() -> {
++                        io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings
 +                    });
 +                    // Paper end
                      CreativeModeTabs.validate();
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
index 18943c3caa..c986ea09a9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
@@ -5,3 +5,21 @@
  package net.minecraft.world.level.levelgen;
  
  import com.google.common.annotations.VisibleForTesting;
+@@ -218,7 +_,7 @@
+     @Override
+     public void buildSurface(WorldGenRegion level, StructureManager structureManager, RandomState random, ChunkAccess chunk) {
+         if (!SharedConstants.debugVoidTerrain(chunk.getPos())) {
+-            WorldGenerationContext worldGenerationContext = new WorldGenerationContext(this, level);
++            WorldGenerationContext worldGenerationContext = new WorldGenerationContext(this, level, level.getMinecraftWorld()); // Paper - Flat bedrock generator settings
+             this.buildSurface(
+                 chunk,
+                 worldGenerationContext,
+@@ -260,7 +_,7 @@
+         NoiseChunk noiseChunk = chunk.getOrCreateNoiseChunk(chunkAccess -> this.createNoiseChunk(chunkAccess, structureManager, Blender.of(level), random));
+         Aquifer aquifer = noiseChunk.aquifer();
+         CarvingContext carvingContext = new CarvingContext(
+-            this, level.registryAccess(), chunk.getHeightAccessorForGeneration(), noiseChunk, random, this.settings.value().surfaceRule()
++            this, level.registryAccess(), chunk.getHeightAccessorForGeneration(), noiseChunk, random, this.settings.value().surfaceRule(), level.getMinecraftWorld() // Paper - Flat bedrock generator settings
+         );
+         CarvingMask carvingMask = ((ProtoChunk)chunk).getOrCreateCarvingMask();
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/WorldGenerationContext.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/WorldGenerationContext.java.patch
new file mode 100644
index 0000000000..a0993bf36a
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/WorldGenerationContext.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/levelgen/WorldGenerationContext.java
++++ b/net/minecraft/world/level/levelgen/WorldGenerationContext.java
+@@ -6,8 +_,15 @@
+ public class WorldGenerationContext {
+     private final int minY;
+     private final int height;
++    // Paper start - Flat bedrock generator settings
++    private final @javax.annotation.Nullable net.minecraft.world.level.Level serverLevel;
+ 
+     public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor level) {
++        this(generator, level, null);
++    }
++    public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor level, net.minecraft.world.level.Level serverLevel) {
++        this.serverLevel = serverLevel;
++        // Paper end - Flat bedrock generator settings
+         this.minY = Math.max(level.getMinY(), generator.getMinY());
+         this.height = Math.min(level.getHeight(), generator.getGenDepth());
+     }
+@@ -19,4 +_,13 @@
+     public int getGenDepth() {
+         return this.height;
+     }
++
++    // Paper start - Flat bedrock generator settings
++    public net.minecraft.world.level.Level level() {
++        if (this.serverLevel == null) {
++            throw new NullPointerException("WorldGenerationContext was initialized without a Level, but WorldGenerationContext#level was called");
++        }
++        return this.serverLevel;
++    }
++    // Paper end - Flat bedrock generator settings
+ }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/carver/CarvingContext.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/carver/CarvingContext.java.patch
new file mode 100644
index 0000000000..63d7237796
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/carver/CarvingContext.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/levelgen/carver/CarvingContext.java
++++ b/net/minecraft/world/level/levelgen/carver/CarvingContext.java
+@@ -27,9 +_,9 @@
+         LevelHeightAccessor level,
+         NoiseChunk noiseChunk,
+         RandomState randomState,
+-        SurfaceRules.RuleSource surfaceRule
++        SurfaceRules.RuleSource surfaceRule, @javax.annotation.Nullable net.minecraft.world.level.Level serverLevel  // Paper - Flat bedrock generator settings
+     ) {
+-        super(generator, level);
++        super(generator, level, serverLevel); // Paper - Flat bedrock generator settings
+         this.registryAccess = registryAccess;
+         this.noiseChunk = noiseChunk;
+         this.randomState = randomState;
diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/placement/PlacementContext.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/placement/PlacementContext.java.patch
new file mode 100644
index 0000000000..1d38b1ee7c
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/placement/PlacementContext.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/levelgen/placement/PlacementContext.java
++++ b/net/minecraft/world/level/levelgen/placement/PlacementContext.java
+@@ -17,7 +_,7 @@
+     private final Optional<PlacedFeature> topFeature;
+ 
+     public PlacementContext(WorldGenLevel level, ChunkGenerator generator, Optional<PlacedFeature> topFeature) {
+-        super(generator, level);
++        super(generator, level, level.getLevel()); // Paper - Flat bedrock generator settings
+         this.level = level;
+         this.generator = generator;
+         this.topFeature = topFeature;
diff --git a/paper-server/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java b/paper-server/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java
new file mode 100644
index 0000000000..60b6e040df
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/world/worldgen/OptionallyFlatBedrockConditionSource.java
@@ -0,0 +1,81 @@
+package io.papermc.paper.world.worldgen;
+
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.core.Registry;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.KeyDispatchDataCodec;
+import net.minecraft.util.Mth;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
+import net.minecraft.world.level.levelgen.SurfaceRules;
+import net.minecraft.world.level.levelgen.VerticalAnchor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+// Modelled off of SurfaceRules$VerticalGradientConditionSource
+// Flat bedrock generator settings
+@DefaultQualifier(NonNull.class)
+public record OptionallyFlatBedrockConditionSource(ResourceLocation randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove, boolean isRoof) implements SurfaceRules.ConditionSource {
+
+    private static final ResourceKey<MapCodec<? extends SurfaceRules.ConditionSource>> CODEC_RESOURCE_KEY = ResourceKey.create(
+        Registries.MATERIAL_CONDITION,
+        ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "optionally_flat_bedrock_condition_source")
+    );
+    private static final KeyDispatchDataCodec<OptionallyFlatBedrockConditionSource> CODEC = KeyDispatchDataCodec.of(RecordCodecBuilder.mapCodec((instance) -> {
+        return instance.group(
+            ResourceLocation.CODEC.fieldOf("random_name").forGetter(OptionallyFlatBedrockConditionSource::randomName),
+            VerticalAnchor.CODEC.fieldOf("true_at_and_below").forGetter(OptionallyFlatBedrockConditionSource::trueAtAndBelow),
+            VerticalAnchor.CODEC.fieldOf("false_at_and_above").forGetter(OptionallyFlatBedrockConditionSource::falseAtAndAbove),
+            Codec.BOOL.fieldOf("is_roof").forGetter(OptionallyFlatBedrockConditionSource::isRoof)
+        ).apply(instance, OptionallyFlatBedrockConditionSource::new);
+    }));
+
+    public static void bootstrap() {
+        Registry.register(BuiltInRegistries.MATERIAL_CONDITION, CODEC_RESOURCE_KEY, CODEC.codec());
+    }
+
+    @Override
+    public KeyDispatchDataCodec<? extends SurfaceRules.ConditionSource> codec() {
+        return CODEC;
+    }
+
+    @Override
+    public SurfaceRules.Condition apply(final SurfaceRules.Context context) {
+        boolean hasFlatBedrock = context.context.level().paperConfig().environment.generateFlatBedrock;
+        int tempTrueAtAndBelowY = this.trueAtAndBelow().resolveY(context.context);
+        int tempFalseAtAndAboveY = this.falseAtAndAbove().resolveY(context.context);
+
+        int flatYLevel = this.isRoof ? Math.max(tempFalseAtAndAboveY, tempTrueAtAndBelowY) - 1 : Math.min(tempFalseAtAndAboveY, tempTrueAtAndBelowY);
+        final int trueAtAndBelowY = hasFlatBedrock ? flatYLevel : tempTrueAtAndBelowY;
+        final int falseAtAndAboveY = hasFlatBedrock ? flatYLevel : tempFalseAtAndAboveY;
+
+        final PositionalRandomFactory positionalRandomFactory = context.randomState.getOrCreateRandomFactory(this.randomName());
+
+        class VerticalGradientCondition extends SurfaceRules.LazyYCondition {
+            VerticalGradientCondition(SurfaceRules.Context context) {
+                super(context);
+            }
+
+            @Override
+            protected boolean compute() {
+                int blockY = this.context.blockY;
+                if (blockY <= trueAtAndBelowY) {
+                    return true;
+                } else if (blockY >= falseAtAndAboveY) {
+                    return false;
+                } else {
+                    double d = Mth.map(blockY, trueAtAndBelowY, falseAtAndAboveY, 1.0D, 0.0D);
+                    RandomSource randomSource = positionalRandomFactory.at(this.context.blockX, blockY, this.context.blockZ);
+                    return (double)randomSource.nextFloat() < d;
+                }
+            }
+        }
+
+        return new VerticalGradientCondition(context);
+    }
+}

From a438cc45f60220d5cae88f9e1b3e015071cc1bb2 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Thu, 19 Dec 2024 15:55:01 +0100
Subject: [PATCH 243/285] Small diff cleanup

---
 .../level/ServerPlayerGameMode.java.patch     | 117 +++++-------------
 .../network/LegacyQueryHandler.java.patch     |  25 ++--
 .../ServerCommonPacketListenerImpl.java.patch |  27 ++--
 .../ServerConnectionListener.java.patch       |   2 +-
 4 files changed, 57 insertions(+), 114 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index fd053c54f5..99bc1d539e 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -91,7 +91,7 @@
              this.debugLogging(pos, false, sequence, "too far");
          } else if (pos.getY() > maxBuildHeight) {
              this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
-@@ -138,16 +_,40 @@
+@@ -138,16 +_,39 @@
          } else {
              if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
                  if (!this.level.mayInteract(this.player, pos)) {
@@ -126,7 +126,6 @@
 +                // Spigot start - handle debug stick left click for non-creative
 +                if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK)
 +                    && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) {
-+                    // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
 +                    return;
 +                }
 +                // Spigot end
@@ -134,45 +133,28 @@
                  if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
                      this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
                      this.debugLogging(pos, false, sequence, "block action restricted");
-@@ -157,7 +_,21 @@
+@@ -157,7 +_,7 @@
                  this.destroyProgressStart = this.gameTicks;
                  float f = 1.0F;
                  BlockState blockState = this.level.getBlockState(pos);
 -                if (!blockState.isAir()) {
-+                // CraftBukkit start - Swings at air do *NOT* exist.
-+                if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) {
-+                    // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
-+                    // Paper start - Don't resync blocks
-+                    //BlockState data = this.level.getBlockState(pos);
-+                    //if (data.getBlock() instanceof DoorBlock) {
-+                    //    // For some reason *BOTH* the bottom/top part have to be marked updated.
-+                    //    boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
-+                    //    this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
-+                    //    this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below()));
-+                    //} else if (data.getBlock() instanceof TrapDoorBlock) {
-+                    //    this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
-+                    //}
-+                    // Paper end - Don't resync blocks
-+                } else if (!blockState.isAir()) {
++                if (event.useInteractedBlock() != org.bukkit.event.Event.Result.DENY && !blockState.isAir()) { // Paper
                      EnchantmentHelper.onHitBlock(
                          this.level,
                          this.player.getMainHandItem(),
-@@ -172,6 +_,26 @@
+@@ -172,6 +_,23 @@
                      f = blockState.getDestroyProgress(this.player, this.player.level(), pos);
                  }
  
++                // CraftBukkit start
++                // Note that we don't need to resync blocks, block acks will handle it properly for everything but block entities already
 +                if (event.useItemInHand() == org.bukkit.event.Event.Result.DENY) {
-+                    // If we 'insta destroyed' then the client needs to be informed.
-+                    if (f > 1.0f) {
-+                        // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks
-+                    }
 +                    return;
 +                }
++
 +                org.bukkit.event.block.BlockDamageEvent blockEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageEvent(this.player, pos, face, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent
 +
 +                if (blockEvent.isCancelled()) {
-+                    // Let the client know the block still exists
-+                    // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block
 +                    return;
 +                }
 +
@@ -184,20 +166,22 @@
                  if (!blockState.isAir() && f >= 1.0F) {
                      this.destroyAndAck(pos, sequence, "insta mine");
                  } else {
-@@ -212,14 +_,18 @@
+@@ -212,14 +_,22 @@
                  this.debugLogging(pos, true, sequence, "stopped destroying");
              } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
                  this.isDestroyingBlock = false;
 -                if (!Objects.equals(this.destroyPos, pos)) {
 -                    LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
--                    this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
--                    this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
++                // Paper start - Don't allow digging into unloaded chunks
 +                if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper
 +                    ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled
-+                    BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here
-+                    if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
-+                    if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
-+                    this.destroyPos = BlockPos.ZERO; // Paper
++                    BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Don't load unloaded chunks for stale records here
++                    if (type != null) {
+                     this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
+                     this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
++                    }
++                    this.destroyPos = BlockPos.ZERO;
++                    // Paper end - Don't allow digging into unloaded chunks
                  }
  
                  this.level.destroyBlockProgress(this.player.getId(), pos, -1);
@@ -207,7 +191,7 @@
              }
          }
      }
-@@ -235,36 +_,127 @@
+@@ -235,36 +_,108 @@
  
      public boolean destroyBlock(BlockPos pos) {
          BlockState blockState = this.level.getBlockState(pos);
@@ -217,54 +201,36 @@
 +        org.bukkit.event.block.BlockBreakEvent event = null;
 +        if (this.player instanceof ServerPlayer) {
 +            // Sword + Creative mode pre-cancel
-+            boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player);
-+
-+            // Tell client the block is gone immediately then process events
-+            // Don't tell the client if its a creative sword break because its not broken!
-+            if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block
-+                ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState());
-+                this.player.connection.send(packet);
-+            }
-+
++            boolean canAttackBlock = !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player);
 +            event = new org.bukkit.event.block.BlockBreakEvent(bblock, this.player.getBukkitEntity());
 +
 +            // Sword + Creative mode pre-cancel
-+            event.setCancelled(isSwordNoBreak);
++            event.setCancelled(canAttackBlock);
 +
 +            // Calculate default block experience
-+            BlockState nmsData = this.level.getBlockState(pos);
-+            Block nmsBlock = nmsData.getBlock();
++            BlockState updatedBlockState = this.level.getBlockState(pos);
++            Block block = updatedBlockState.getBlock();
 +
-+            ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
-+
-+            if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) {
-+                event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true));
++            if (!event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(block.defaultBlockState())) {
++                ItemStack itemInHand = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
++                event.setExpToDrop(block.getExpDrop(updatedBlockState, this.level, pos, itemInHand, true));
 +            }
 +
 +            this.level.getCraftServer().getPluginManager().callEvent(event);
 +
 +            if (event.isCancelled()) {
-+                if (isSwordNoBreak) {
++                if (canAttackBlock) {
 +                    return false;
 +                }
-+                // Paper start - Don't resync blocks
-+                // Let the client know the block still exists
-+                //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
 +
-+                // Brute force all possible updates
-+                //for (Direction dir : Direction.values()) {
-+                //    this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir)));
-+                //}
-+                // Paper end - Don't resync blocks
-+
-+                // Update any tile entity data for this block
-+                if (!this.captureSentBlockEntities) { // Paper - Send block entities after destroy prediction
++                // Block entity data is not reset by the block acks, send after destroy prediction
++                if (!this.captureSentBlockEntities) {
 +                    BlockEntity blockEntity = this.level.getBlockEntity(pos);
 +                    if (blockEntity != null) {
 +                        this.player.connection.send(blockEntity.getUpdatePacket());
 +                    }
 +                } else {
-+                    this.capturedBlockEntity = true; // Paper - Send block entities after destroy prediction
++                    this.capturedBlockEntity = true;
 +                }
 +                return false;
 +            }
@@ -325,10 +291,9 @@
 +                if (event.isDropItems()) {
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world
 +                }
-+                //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up
 +
 +                // Drop event experience
-+                if (flag && event != null) {
++                if (flag) {
 +                    blockState.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
 +                }
 +                // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy)
@@ -344,7 +309,7 @@
              }
          }
      }
-@@ -307,15 +_,61 @@
+@@ -307,15 +_,47 @@
          }
      }
  
@@ -379,32 +344,18 @@
 +        this.interactItemStack = stack.copy();
 +
 +        if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) {
-+            // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
-+            if (blockState.getBlock() instanceof net.minecraft.world.level.block.DoorBlock) {
-+                // Paper start - Don't resync blocks
-+                // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER;
-+                // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below()));
-+                // Paper end - Don't resync blocks
-+            } else if (blockState.getBlock() instanceof net.minecraft.world.level.block.CakeBlock) {
++            // Block acks will take care of most of it, just handle some special cases here
++            if (blockState.getBlock() instanceof net.minecraft.world.level.block.CakeBlock) {
 +                player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
-+            } else if (this.interactItemStack.getItem() instanceof net.minecraft.world.item.DoubleHighBlockItem) {
-+                // send a correcting update to the client, as it already placed the upper half of the bisected item
-+                //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks
-+
-+                // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc)
-+                //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks
-+                // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method
 +            } else if (blockState.is(net.minecraft.world.level.block.Blocks.JIGSAW) || blockState.is(net.minecraft.world.level.block.Blocks.STRUCTURE_BLOCK) || blockState.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) {
 +                player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId));
 +            }
-+            // Paper end - extend Player Interact cancellation
 +            player.getBukkitEntity().updateInventory(); // SPIGOT-2867
 +            this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items
 +            return (event.useItemInHand() != org.bukkit.event.Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
 +        } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
-+            MenuProvider itileinventory = blockState.getMenuProvider(level, blockPos);
-+
-+            if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
++            MenuProvider menuProvider = blockState.getMenuProvider(level, blockPos);
++            if (menuProvider != null && player.openMenu(menuProvider).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation
                  return InteractionResult.CONSUME;
              } else {
                  return InteractionResult.PASS;
diff --git a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
index 5e6a096b22..31868aa929 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -48,30 +48,25 @@
                      sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
                  } else {
                      if (byteBuf.readUnsignedByte() != 1) {
-@@ -43,16 +_,35 @@
+@@ -43,16 +_,39 @@
                      }
  
                      if (byteBuf.isReadable()) {
 -                        if (!readCustomPayloadPacket(byteBuf)) {
-+                        // Paper start - Replace with improved version below
++                        // Paper start - Replace below
 +                        if (byteBuf.readUnsignedByte() != LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_ID) {
 +                            string = this.readLegacy1_6(context, byteBuf);
 +                            if (string == null) {
 +                                return;
 +                            }
 +                        }
-+                        // if (!readCustomPayloadPacket(byteBuf)) {
-+                        //     return;
-+                        // }
-+
-+                        // LOGGER.debug("Ping: (1.6) from {}", socketAddress);
-+                        // Paper end - Replace with improved version below
++                        // Paper end - Replace below
 +                    } else {
 +                        LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress : "<ip address withheld>"); // Paper - Respect logIPs option
 +                    }
 +
++                    // Paper start - Call PaperServerListPingEvent and use results
 +                    if (string == null) {
-+                        // Paper start - Call PaperServerListPingEvent and use results
 +                        com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper
 +                        if (event == null) {
 +                            context.close();
@@ -79,11 +74,19 @@
 +                            flag = false;
                              return;
                          }
--
+ 
 -                        LOGGER.debug("Ping: (1.6) from {}", socketAddress);
 -                    } else {
 -                        LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
-+                        string = String.format(Locale.ROOT, "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit
++                        // See createVersion1Response
++                        string = String.format(
++                            Locale.ROOT,
++                            "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d",
++                            event.getProtocolVersion(), this.server.getServerVersion(),
++                            event.getMotd(),
++                            event.getNumPlayers(),
++                            event.getMaxPlayers()
++                        );
 +                        // Paper end - Call PaperServerListPingEvent and use results
                      }
 -
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
index 81c6160738..4d1d822b0f 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -95,7 +95,7 @@
          }
      }
  
-@@ -88,37 +_,124 @@
+@@ -88,30 +_,117 @@
      public void handlePong(ServerboundPongPacket packet) {
      }
  
@@ -206,31 +206,20 @@
  
      protected void keepConnectionAlive() {
          Profiler.get().push("keepAlive");
--        long millis = Util.getMillis();
+         long millis = Util.getMillis();
 -        if (!this.isSingleplayerOwner() && millis - this.keepAliveTime >= 15000L) {
 -            if (this.keepAlivePending) {
 -                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
--            } else if (this.checkIfClosed(millis)) {
 +        // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
 +        // This should effectively place the keepalive handling back to "as it was" before 1.12.2
-+        long currentTime = Util.getMillis();
-+        long elapsedTime = currentTime - this.keepAliveTime;
-+        if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
-+            if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
++        final long elapsedTime = millis - this.keepAliveTime;
++        if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // use vanilla's 15000L between keep alive packets
++            if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected
++                // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings
 +                this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
-+            } else if (this.checkIfClosed(currentTime)) { // Paper
+             } else if (this.checkIfClosed(millis)) {
                  this.keepAlivePending = true;
--                this.keepAliveTime = millis;
--                this.keepAliveChallenge = millis;
-+                this.keepAliveTime = currentTime;
-+                this.keepAliveChallenge = currentTime;
-                 this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge));
-             }
-         }
-+        // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings
- 
-         Profiler.get().pop();
-     }
+                 this.keepAliveTime = millis;
 @@ -126,7 +_,7 @@
      private boolean checkIfClosed(long time) {
          if (this.closed) {
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
index 97e6b6ed34..78b623e0aa 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -68,7 +68,6 @@
                                          ? new RateKickingConnection(rateLimitPacketsPerSecond)
                                          : new Connection(PacketFlow.SERVERBOUND));
 -                                    ServerConnectionListener.this.connections.add(connection);
-+                                    // ServerConnectionListener.this.connections.add(connection); // Paper
 +                                    // Paper start - Add support for Proxy Protocol
 +                                    if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
 +                                        channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
@@ -98,6 +97,7 @@
 +                                        });
 +                                    }
 +                                    // Paper end - Add support for proxy protocol
++                                    // ServerConnectionListener.this.connections.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
 +                                    ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
                                      connection.configurePacketHandler(channelPipeline);
                                      connection.setListenerForServerboundHandshake(

From 1f86b55302987565028f96f8fe8e07d441cdb98b Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Thu, 19 Dec 2024 18:35:43 +0100
Subject: [PATCH 244/285] reapply SummonEntityEffect

---
 paper-server/patches/features/0007-Anti-Xray.patch   |  6 +++---
 .../item/enchantment/effects/ReplaceBlock.java.patch |  4 +---
 .../effects/SummonEntityEffect.java.patch            | 10 ++++++----
 .../level/block/piston/PistonBaseBlock.java.patch    |  4 ++--
 .../level/block/state/BlockBehaviour.java.patch      | 12 ++----------
 5 files changed, 14 insertions(+), 22 deletions(-)

diff --git a/paper-server/patches/features/0007-Anti-Xray.patch b/paper-server/patches/features/0007-Anti-Xray.patch
index 801b9fcda9..bb7e6f43a4 100644
--- a/paper-server/patches/features/0007-Anti-Xray.patch
+++ b/paper-server/patches/features/0007-Anti-Xray.patch
@@ -166,10 +166,10 @@ index 7702004b68b7735043914f93b54b4413cd21ba41..4d20bda4cba578c47216d450c99389b7
          this.levelStorageAccess = levelStorageAccess;
          this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index d7028ed8ebdecd647467b67f62f7431a6df59f76..9fdb825be2b04a5806bbb3d39948bad0863e7ae5 100644
+index 23d241e98f37979701f80fb6f7b76954bd699ad6..fd7ad2b1bffe3880def0f0c9a7ed8de5088ecd71 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -312,6 +312,7 @@ public class ServerPlayerGameMode {
+@@ -298,6 +298,7 @@ public class ServerPlayerGameMode {
                  org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
              }
          }
@@ -269,7 +269,7 @@ index 809b3c37d3749c76c3c243cd91c593d03693e9b3..860d1c9729c4ee97ec6f40f7aa969829
          }
      }
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index 2b5d99eb6860578cc1af8fbe2571c314846984dd..bc4260623dfb1d804f41593293a5c176c86df5a1 100644
+index 51a136cf015de730ca0d1b48cf618a2ed69ea89f..96b0342ab7b922aa16d07b6c00542e6cb66c974a 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
 @@ -109,7 +109,7 @@ public class LevelChunk extends ChunkAccess {
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
index 497dec7455..4e789c8e9d 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java.patch
@@ -1,10 +1,8 @@
 --- a/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
 +++ b/net/minecraft/world/item/enchantment/effects/ReplaceBlock.java
-@@ -29,8 +_,9 @@
-     @Override
+@@ -30,7 +_,7 @@
      public void apply(ServerLevel level, int enchantmentLevel, EnchantedItemInUse item, Entity entity, Vec3 origin) {
          BlockPos blockPos = BlockPos.containing(origin).offset(this.offset);
-+        // }).orElse(true) &&
          if (this.predicate.map(blockPredicate -> blockPredicate.test(level, blockPos)).orElse(true)
 -            && level.setBlockAndUpdate(blockPos, this.blockState.getState(entity.getRandom(), blockPos))) {
 +            && org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, blockPos, this.blockState.getState(entity.getRandom(), blockPos), entity)) { // CraftBukkit - Call EntityBlockFormEvent
diff --git a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
index 913d9aeec7..cf03e4582d 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
 +++ b/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
-@@ -34,11 +_,16 @@
+@@ -34,11 +_,18 @@
          if (Level.isInSpawnableBounds(blockPos)) {
              Optional<Holder<EntityType<?>>> randomElement = this.entityTypes().getRandomElement(level.getRandom());
              if (!randomElement.isEmpty()) {
@@ -11,9 +11,11 @@
                          lightningBolt.setCause(serverPlayer);
                      }
 +                    // CraftBukkit start
-+                    level.strikeLightning(entity1, (item.itemStack().getItem() == net.minecraft.world.item.Items.TRIDENT) ? org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT : org.bukkit.event.weather.LightningStrikeEvent.Cause.ENCHANTMENT);
-+                } else {
-+                    level.addFreshEntityWithPassengers(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENCHANTMENT);
++                    if (entity1 instanceof LightningBolt) {
++                        level.strikeLightning(entity1, (item.itemStack().is(net.minecraft.world.item.Items.TRIDENT)) ? org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT : org.bukkit.event.weather.LightningStrikeEvent.Cause.ENCHANTMENT);
++                    } else {
++                        level.addFreshEntityWithPassengers(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENCHANTMENT);
++                    }
 +                    // CraftBukkit end
  
                      if (this.joinTeam && entity.getTeam() != null) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
index 680447ba4e..9ee94df160 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -90,7 +90,7 @@
 +            final List<BlockPos> moved = pistonStructureResolver.getToPush();
 +            final List<BlockPos> broken = pistonStructureResolver.getToDestroy();
 +
-+            List<org.bukkit.block.Block> blocks = new java.util.AbstractList<org.bukkit.block.Block>() {
++            List<org.bukkit.block.Block> blocks = new java.util.AbstractList<>() {
 +
 +                @Override
 +                public int size() {
@@ -102,7 +102,7 @@
 +                    if (index >= this.size() || index < 0) {
 +                        throw new ArrayIndexOutOfBoundsException(index);
 +                    }
-+                    BlockPos pos = (BlockPos) (index < moved.size() ? moved.get(index) : broken.get(index - moved.size()));
++                    BlockPos pos = index < moved.size() ? moved.get(index) : broken.get(index - moved.size());
 +                    return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
 +                }
 +            };
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
index 4f4759e0c9..ee4aa83de5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -1,13 +1,5 @@
 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java
 +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
-@@ -46,6 +_,7 @@
- import net.minecraft.world.item.Item;
- import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.context.BlockPlaceContext;
-+import net.minecraft.world.item.context.UseOnContext;
- import net.minecraft.world.level.BlockGetter;
- import net.minecraft.world.level.EmptyBlockGetter;
- import net.minecraft.world.level.Explosion;
 @@ -167,16 +_,24 @@
      }
  
@@ -17,7 +9,7 @@
 +    }
 +
 +    // CraftBukkit start
-+    protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, @Nullable UseOnContext context) {
++    protected void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, @Nullable net.minecraft.world.item.context.UseOnContext context) {
 +        this.onPlace(iblockdata, world, blockposition, iblockdata1, flag);
 +    }
 +    // CraftBukkit end
@@ -152,7 +144,7 @@
 +            this.onPlace(level, pos, oldState, movedByPiston, null);
 +        }
 +
-+        public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, @Nullable UseOnContext context) {
++        public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, @Nullable net.minecraft.world.item.context.UseOnContext context) {
 +            this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston, context);
 +            // CraftBukkit end
          }

From 25a7793b9c3c07da9307d0d36af4d9bd15fde302 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Thu, 19 Dec 2024 22:12:09 +0100
Subject: [PATCH 245/285] drop duplicate block update

---
 .../0006-Entity-Activation-Range-2.0.patch         |  4 ++--
 ...014-Check-distance-in-entity-interactions.patch |  4 ++--
 .../features/0022-Lag-compensation-ticks.patch     | 14 +++++++-------
 .../minecraft/world/entity/LivingEntity.java.patch |  8 +++-----
 .../level/block/BuddingAmethystBlock.java.patch    |  2 +-
 .../world/level/block/CactusBlock.java.patch       |  4 ++--
 .../level/block/SweetBerryBushBlock.java.patch     |  3 +--
 7 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
index a949aa3137..e4ddc69055 100644
--- a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
@@ -529,10 +529,10 @@ index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855
              movement = this.maybeBackOffFromEdge(movement, type);
              Vec3 vec3 = this.collide(movement);
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index fa67721bc0aa2d5b920d691f34d81d0a1feec202..c853c5ff5a9422b9a17be34102f80348ca89cad6 100644
+index e76889ab9766294e64afa2b89006d2efd218027c..9bf5addb07f40cc50e80a46c0ee7944e938620fb 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -3089,6 +3089,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3086,6 +3086,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
          return false;
      }
  
diff --git a/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch b/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
index 45b999e2f7..39272633a7 100644
--- a/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
+++ b/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
@@ -17,7 +17,7 @@ index ae1d53cefb9cede1c93cb8b22122a4a2d2d9a40c..80a7a85e1a03a1ca406259207e1ae3b9
      public static <K, V> Collector<Entry<? extends K, ? extends V>, ?, Map<K, V>> toMap() {
          return Collectors.toMap(Entry::getKey, Entry::getValue);
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 9de400977ec33e485e87cdf1cf145588527e1e10..c83aeaf4e50dd7290c608dfe260a3bd2404b6004 100644
+index 9bf5addb07f40cc50e80a46c0ee7944e938620fb..635e6e49483194c43b0ab47b5e1bacfe84613c3a 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
 @@ -1385,7 +1385,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -44,7 +44,7 @@ index 9de400977ec33e485e87cdf1cf145588527e1e10..c83aeaf4e50dd7290c608dfe260a3bd2
  
                      this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
                      if (!flag) {
-@@ -2345,7 +2353,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -2342,7 +2350,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
                  Entity entity = damageSource.getDirectEntity();
  
diff --git a/paper-server/patches/features/0022-Lag-compensation-ticks.patch b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
index 1d23e4ff9d..65659dc68f 100644
--- a/paper-server/patches/features/0022-Lag-compensation-ticks.patch
+++ b/paper-server/patches/features/0022-Lag-compensation-ticks.patch
@@ -49,7 +49,7 @@ index 4d20bda4cba578c47216d450c99389b744a59008..47b7d487467225505e3f20cea92e1143
 +    // Paper end - lag compensation
  }
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index 9fdb825be2b04a5806bbb3d39948bad0863e7ae5..261445152f741b68f91cd8fc1eac17275ee52427 100644
+index fd7ad2b1bffe3880def0f0c9a7ed8de5088ecd71..e753849002b48d4a498dc93349e331bc26d39aff 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
 @@ -111,7 +111,7 @@ public class ServerPlayerGameMode {
@@ -62,10 +62,10 @@ index 9fdb825be2b04a5806bbb3d39948bad0863e7ae5..261445152f741b68f91cd8fc1eac1727
              BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
              if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be3879918a5d3 100644
+index 635e6e49483194c43b0ab47b5e1bacfe84613c3a..195e1151f7b2a32d6c4eb67edd1952e38f58b266 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -3831,6 +3831,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3828,6 +3828,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
          this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
      }
      // Paper end - Properly cancel usable items
@@ -76,7 +76,7 @@ index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be387
      private void updatingUsingItem() {
          if (this.isUsingItem()) {
              if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
-@@ -3844,7 +3848,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3841,7 +3845,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
  
      protected void updateUsingItem(ItemStack usingItem) {
          usingItem.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
@@ -90,7 +90,7 @@ index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be387
              this.completeUsingItem();
          }
      }
-@@ -3878,7 +3887,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3875,7 +3884,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
          ItemStack itemInHand = this.getItemInHand(hand);
          if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
              this.useItem = itemInHand;
@@ -102,7 +102,7 @@ index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be387
              if (!this.level().isClientSide) {
                  this.setLivingEntityFlag(1, true);
                  this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
-@@ -3902,7 +3914,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3899,7 +3911,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
                  }
              } else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
                  this.useItem = ItemStack.EMPTY;
@@ -114,7 +114,7 @@ index 228ceecd5ab9040dcc6710d1cdd0feda2f901016..884e1f8f72cc31520ba0c54af17be387
              }
          }
      }
-@@ -4026,7 +4041,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -4023,7 +4038,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
          }
  
          this.useItem = ItemStack.EMPTY;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index ea57a78e0b..df1aedb289 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -692,7 +692,7 @@
              if (killCredit != null) {
                  killCredit.awardKillScore(this, damageSource);
              }
-@@ -1373,68 +_,145 @@
+@@ -1373,68 +_,142 @@
              }
  
              if (!this.level().isClientSide && this.hasCustomName()) {
@@ -764,11 +764,9 @@
                      BlockPos blockPos = this.blockPosition();
                      BlockState blockState = Blocks.WITHER_ROSE.defaultBlockState();
                      if (this.level().getBlockState(blockPos).isAir() && blockState.canSurvive(this.level(), blockPos)) {
-                         this.level().setBlock(blockPos, blockState, 3);
+-                        this.level().setBlock(blockPos, blockState, 3);
 -                        var6 = true;
-+                        // CraftBukkit start - call EntityBlockFormEvent for Wither Rose
-+                        var6 = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockPos, blockState, 3, this);
-+                        // CraftBukkit end
++                        var6 = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockPos, blockState, 3, this); // CraftBukkit - call EntityBlockFormEvent for Wither Rose
                      }
                  }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
index e6ad3e837b..81ceb891c5 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
@@ -7,7 +7,7 @@
 -                level.setBlockAndUpdate(blockPos, blockState1);
 +                // Paper start - Have Amethyst throw both spread and grow events
 +                if (block == Blocks.SMALL_AMETHYST_BUD) {
-+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState1); // CraftBukkit
++                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, blockState1, 3); // CraftBukkit
 +                } else {
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, blockState1);
 +                }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
index 05f47c53f7..af19e96da9 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/CactusBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/CactusBlock.java
 +++ b/net/minecraft/world/level/block/CactusBlock.java
-@@ -56,14 +_,17 @@
+@@ -56,14 +_,16 @@
                  i++;
              }
  
@@ -8,11 +8,11 @@
 +            if (i < level.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height
                  int ageValue = state.getValue(AGE);
 -                if (ageValue == 15) {
+-                    level.setBlockAndUpdate(blockPos, this.defaultBlockState());
 +
 +                int modifier = level.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution
 +                if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier
 +                    org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, this.defaultBlockState()); // CraftBukkit
-                     level.setBlockAndUpdate(blockPos, this.defaultBlockState());
                      BlockState blockState = state.setValue(AGE, Integer.valueOf(0));
                      level.setBlock(pos, blockState, 4);
                      level.neighborChanged(blockState, blockPos, this, null, false);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
index 8b96fa4a77..a228885132 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/SweetBerryBushBlock.java
 +++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java
-@@ -68,15 +_,17 @@
+@@ -68,15 +_,16 @@
      @Override
      protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
          int ageValue = state.getValue(AGE);
@@ -8,7 +8,6 @@
 +        if (ageValue < 3 && random.nextFloat() < (level.spigotConfig.sweetBerryModifier / (100.0f * 5)) && level.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
              BlockState blockState = state.setValue(AGE, Integer.valueOf(ageValue + 1));
 -            level.setBlock(pos, blockState, 2);
-+            // level.setBlock(pos, blockState, 2);
 +            if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, 2)) return; // CraftBukkit
              level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
          }

From 413751ae2daf6e981363c76c81f27adcf6756a54 Mon Sep 17 00:00:00 2001
From: Jake <gabrieleluparelloj@gmail.com>
Date: Thu, 19 Dec 2024 23:14:21 +0100
Subject: [PATCH 246/285] Update Mob.java (#11757)

---
 paper-api/src/main/java/org/bukkit/entity/Mob.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/paper-api/src/main/java/org/bukkit/entity/Mob.java b/paper-api/src/main/java/org/bukkit/entity/Mob.java
index 9a10262a95..ca773a807e 100644
--- a/paper-api/src/main/java/org/bukkit/entity/Mob.java
+++ b/paper-api/src/main/java/org/bukkit/entity/Mob.java
@@ -191,7 +191,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
      * set by {@link #setAggressive(boolean)}. {@link Panda}'s are always
      * aggressive if their combined {@link Panda.Gene} is {@link Panda.Gene#AGGRESSIVE}.
      *
-     * @return wether the mob is aggressive or not
+     * @return whether the mob is aggressive or not
      */
     boolean isAggressive();
 
@@ -199,7 +199,7 @@ public interface Mob extends LivingEntity, Lootable, io.papermc.paper.entity.Lea
      * Some mobs will raise their arm(s) when aggressive,
      * see {@link #isAggressive()} for full list.
      *
-     * @param aggressive wether the mob should be aggressive or not
+     * @param aggressive whether the mob should be aggressive or not
      * @see #isAggressive()
      */
     void setAggressive(boolean aggressive);

From 2935905ced6c8543a87eb0f0971542c4531aed6e Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Thu, 19 Dec 2024 00:00:37 -0500
Subject: [PATCH 247/285] Small sculk cleanup

Don't check for empty items anymore in FishingHook.

Removes some diff + we handle it gracefully in add fresh entity
---
 .../entity/projectile/FishingHook.java.patch  | 24 ++++---------------
 .../world/level/block/SculkBlock.java.patch   | 11 ++++-----
 2 files changed, 9 insertions(+), 26 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
index 68d1d7f9e5..b433002c90 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch
@@ -171,20 +171,12 @@
              } else if (this.nibble > 0) {
                  LootParams lootParams = new LootParams.Builder((ServerLevel)this.level())
                      .withParameter(LootContextParams.ORIGIN, this.position())
-@@ -464,19 +_,43 @@
-                 CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)playerOwner, stack, this, randomItems);
+@@ -465,18 +_,32 @@
  
                  for (ItemStack itemStack : randomItems) {
--                    ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
-+                    // Paper start - new ItemEntity would throw if for whatever reason (mostly shitty datapacks) the itemStack turns out to be empty
-+                    // if the item stack is empty we instead just have our itemEntity as null
-+                    ItemEntity itemEntity = null;
-+                    if (!itemStack.isEmpty()) {
-+                        itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
-+                    }
-+                    // Paper end
+                     ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
 +                    // CraftBukkit start
-+                    org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), itemEntity != null ? itemEntity.getBukkitEntity() : null, (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.CAUGHT_FISH); // Paper - itemEntity may be null // Paper - Add hand parameter to PlayerFishEvent
++                    org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), itemEntity.getBukkitEntity(), (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.CAUGHT_FISH); // Paper - itemEntity may be null // Paper - Add hand parameter to PlayerFishEvent
 +                    playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1);
 +                    this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
 +
@@ -196,20 +188,14 @@
                      double d1 = playerOwner.getY() - this.getY();
                      double d2 = playerOwner.getZ() - this.getZ();
                      double d3 = 0.1;
--                    itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
--                    this.level().addFreshEntity(itemEntity);
+                     itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
+                     this.level().addFreshEntity(itemEntity);
 -                    playerOwner.level()
 -                        .addFreshEntity(
 -                            new ExperienceOrb(
 -                                playerOwner.level(), playerOwner.getX(), playerOwner.getY() + 0.5, playerOwner.getZ() + 0.5, this.random.nextInt(6) + 1
 -                            )
 -                        );
-+                    // Paper start - entity item can be null, so we need to check against this
-+                    if (itemEntity != null) {
-+                        itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
-+                        this.level().addFreshEntity(itemEntity);
-+                    }
-+                    // Paper end
 +                    // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
 +                    if (playerFishEvent.getExpToDrop() > 0) {
 +                        playerOwner.level()
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
index 681606e369..f17c4e7aa6 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/SculkBlock.java.patch
@@ -1,16 +1,13 @@
 --- a/net/minecraft/world/level/block/SculkBlock.java
 +++ b/net/minecraft/world/level/block/SculkBlock.java
-@@ -37,8 +_,11 @@
+@@ -37,8 +_,9 @@
                  if (random.nextInt(growthSpawnCost) < charge) {
                      BlockPos blockPos = pos1.above();
                      BlockState randomGrowthState = this.getRandomGrowthState(level, blockPos, random, spreader.isWorldGeneration());
 -                    level.setBlock(blockPos, randomGrowthState, 3);
--                    level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    // CraftBukkit start - Call BlockSpreadEvent
-+                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, randomGrowthState, 3)) {
-+                        level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
-+                    }
-+                    // CraftBukkit end
++                    if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockPos, randomGrowthState, 3)) { // CraftBukkit - Call BlockSpreadEvent
+                     level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++                    } // CraftBukkit - Call BlockSpreadEvent
                  }
  
                  return Math.max(0, charge - growthSpawnCost);

From 431303ff40cd62ec274eb8d1d6306cced77fb9c4 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Tue, 17 Dec 2024 20:50:41 +0100
Subject: [PATCH 248/285] Move ChunkMap setServerViewDistance to ATs

---
 build-data/paper.at                                      | 1 +
 .../net/minecraft/server/level/ChunkMap.java.patch       | 9 ---------
 2 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index 0c0e4084b9..d47d32c8e3 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -68,6 +68,7 @@ public net.minecraft.server.level.ChunkMap level
 public net.minecraft.server.level.ChunkMap progressListener
 public net.minecraft.server.level.ChunkMap save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z
 public net.minecraft.server.level.ChunkMap serverViewDistance
+public net.minecraft.server.level.ChunkMap setServerViewDistance(I)V
 public net.minecraft.server.level.ChunkMap toDrop
 public net.minecraft.server.level.ChunkMap updatingChunkMap
 public net.minecraft.server.level.ChunkMap visibleChunkMap
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
index 26c38d11c8..6c090dc5e0 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
@@ -143,15 +143,6 @@
              }
          }, this.unloadQueue::add).whenComplete((_void, error) -> {
              if (error != null) {
-@@ -818,7 +_,7 @@
-         }
-     }
- 
--    protected void setServerViewDistance(int viewDistance) {
-+    public void setServerViewDistance(int viewDistance) { // Paper - public
-         int i = Mth.clamp(viewDistance, 2, 32);
-         if (i != this.serverViewDistance) {
-             this.serverViewDistance = i;
 @@ -856,7 +_,7 @@
      }
  

From 993db46961dfe7e1a3617795f0df8205ff0b76e7 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Tue, 17 Dec 2024 20:59:43 +0100
Subject: [PATCH 249/285] Comment typo

---
 .../net/minecraft/server/level/DistanceManager.java.patch | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
index eadab866cb..2ac4d5befd 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch
@@ -57,18 +57,16 @@
  
      private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
          return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
-@@ -217,8 +_,12 @@
+@@ -217,8 +_,10 @@
          ChunkPos chunkPos = sectionPos.chunk();
          long packedChunkPos = chunkPos.toLong();
          ObjectSet<ServerPlayer> set = this.playersPerChunk.get(packedChunkPos);
 -        set.remove(player);
 -        if (set.isEmpty()) {
 +        // Paper start - some state corruption happens here, don't crash, clean up gracefully
-+        if (set != null) {
-+            set.remove(player);
-+        }
++        if (set != null) set.remove(player);
 +        if (set == null || set.isEmpty()) {
-+            // Paper end - some state corruption happens here, don't crash, clean up gracefully
++        // Paper end - some state corruption happens here, don't crash, clean up gracefully
              this.playersPerChunk.remove(packedChunkPos);
              this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
              this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);

From 68bbd2e202431b0f6ad968bd309e6522e87b8c2f Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Thu, 19 Dec 2024 21:48:24 +0100
Subject: [PATCH 250/285] Improve diff in ServerPlayer#openHorseInventory

---
 .../minecraft/server/level/ServerPlayer.java.patch | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index eae0ca32da..ce890ce4a0 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -966,12 +966,8 @@
  
      @Override
      public void openHorseInventory(AbstractHorse horse, Container inventory) {
--        if (this.containerMenu != this.inventoryMenu) {
--            this.closeContainer();
--        }
--
 +        // CraftBukkit start - Inventory open hook
-         this.nextContainerCounter();
++        this.nextContainerCounter(); // Moved up from below
 +        AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns());
 +        container.setTitle(horse.getDisplayName());
 +        container = org.bukkit.craftbukkit.event.CraftEventFactory.callInventoryOpenEvent(this, container);
@@ -981,10 +977,12 @@
 +            return;
 +        }
 +        // CraftBukkit end
-+        if (this.containerMenu != this.inventoryMenu) {
+         if (this.containerMenu != this.inventoryMenu) {
+-            this.closeContainer();
 +            this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason
-+        }
-+
+         }
+ 
+-        this.nextContainerCounter();
 +        // this.nextContainerCounter(); // CraftBukkit - moved up
          int inventoryColumns = horse.getInventoryColumns();
          this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventoryColumns, horse.getId()));

From fd1b6b1ae9246096332f56c08acff27196ddd4ad Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Fri, 20 Dec 2024 03:07:40 +0100
Subject: [PATCH 251/285] Readd configurable auth server down kick message

---
 build-data/paper.at                                   |  1 +
 .../server/level/ServerPlayerGameMode.java.patch      |  2 +-
 .../network/ServerCommonPacketListenerImpl.java.patch |  4 ++--
 .../network/ServerGamePacketListenerImpl.java.patch   |  9 +++++----
 .../network/ServerLoginPacketListenerImpl.java.patch  | 11 ++++-------
 5 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index d47d32c8e3..8e1b527ed3 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -105,6 +105,7 @@ public net.minecraft.server.level.ServerPlayer$RespawnPosAngle
 public net.minecraft.server.level.ServerPlayerGameMode level
 public net.minecraft.server.level.Ticket key
 public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z
+public net.minecraft.server.network.ServerLoginPacketListenerImpl authenticatedProfile
 public net.minecraft.server.network.ServerLoginPacketListenerImpl connection
 public net.minecraft.server.network.ServerLoginPacketListenerImpl state
 public net.minecraft.server.network.ServerLoginPacketListenerImpl$State
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index 99bc1d539e..f61fcfdd62 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -279,7 +279,7 @@
 -
 -                    return true;
 -                }
-+                    if (flag && hasCorrectToolForDrops/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
++                    if (flag && hasCorrectToolForDrops) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion
 +                        block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion
 +                    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
index 4d1d822b0f..e21dc4c54b 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -111,12 +111,12 @@
 +            this.player.clientBrandName = brand;
 +        }
 +        // Paper end - Brand support
-+        if (!(packet.payload() instanceof net.minecraft.network.protocol.common.custom.DiscardedPayload)) {
++        if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) {
 +            return;
 +        }
 +        PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
 +        net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id();
-+        io.netty.buffer.ByteBuf payload = ((net.minecraft.network.protocol.common.custom.DiscardedPayload)packet.payload()).data();
++        io.netty.buffer.ByteBuf payload = discardedPayload.data();
 +
 +        if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) {
 +            try {
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 39098db25c..fbea2e5964 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -167,7 +167,7 @@
          this.player.setLastClientInput(packet.input());
      }
  
-@@ -390,17 +_,29 @@
+@@ -390,25 +_,84 @@
      public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) {
@@ -201,16 +201,17 @@
                  float f = Mth.wrapDegrees(packet.yRot());
                  float f1 = Mth.wrapDegrees(packet.xRot());
                  double d3 = d - this.vehicleFirstGoodX;
-@@ -408,7 +_,54 @@
+                 double d4 = d1 - this.vehicleFirstGoodY;
                  double d5 = d2 - this.vehicleFirstGoodZ;
                  double d6 = rootVehicle.getDeltaMovement().lengthSqr();
-                 double d7 = d3 * d3 + d4 * d4 + d5 * d5;
+-                double d7 = d3 * d3 + d4 * d4 + d5 * d5;
 -                if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
++                double d7 = d3 * d3 + d4 * d4 + d5 * d5; final double vehicleFirstGoodDistanceSquared = d7; // Paper - OBFHELPER
 +                // Paper start - fix large move vectors killing the server
 +                double currDeltaX = toX - x;
 +                double currDeltaY = toY - y;
 +                double currDeltaZ = toZ - z;
-+                double d10 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
++                double d10 = Math.max(vehicleFirstGoodDistanceSquared, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
 +                double otherFieldX = d3 - this.vehicleLastGoodX;
 +                double otherFieldY = d4 - this.vehicleLastGoodY;
 +                double otherFieldZ = d5 - this.vehicleLastGoodZ;
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
index 579db13c3c..47090dba31 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -21,12 +21,8 @@
      private static final int MAX_TICKS_BEFORE_LOGIN = 600;
      private final byte[] challenge;
      final MinecraftServer server;
-@@ -56,9 +_,12 @@
-     @Nullable
-     String requestedUsername;
-     @Nullable
--    private GameProfile authenticatedProfile;
-+    public GameProfile authenticatedProfile; // Paper - public
+@@ -59,6 +_,9 @@
+     public GameProfile authenticatedProfile;
      private final String serverId = "";
      private final boolean transferred;
 +    private net.minecraft.server.level.ServerPlayer player; // CraftBukkit
@@ -220,7 +216,8 @@
 -                        ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
 +                        ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
                      } else {
-                         ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
+-                        ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
++                        ServerLoginPacketListenerImpl.this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.authenticationServersDown)); // Paper - Configurable kick message
                          ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable");
                      }
 +                    // CraftBukkit start - catch all exceptions

From 6e0c8776e67219ddc8e4e61364c8203d924fd23f Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 20 Dec 2024 00:37:00 -0800
Subject: [PATCH 252/285] Update paperweight to 2.0.0-beta.6

---
 build.gradle.kts              | 29 ++++++-----
 gradle.properties             |  3 +-
 paper-server/build.gradle.kts | 98 +++++++++++++++++------------------
 3 files changed, 66 insertions(+), 64 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index f2fae287ad..3ae1c411ae 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,5 @@
 import io.papermc.paperweight.util.*
+import io.papermc.paperweight.util.constants.*
 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
 import org.gradle.api.tasks.testing.logging.TestLogEvent
 import java.io.IOException
@@ -11,7 +12,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.5" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.6" apply false
 }
 
 subprojects {
@@ -146,19 +147,18 @@ tasks.register("checkWork") {
         return Path.of(path.replaceFirst("^~".toRegex(), System.getProperty("user.home")))
     }
 
-    val input = layout.cache.resolve("last-updating-folder").readText().trim()
-    val patchFolder = layout.projectDirectory.file("paper-server/patches/sources").convertToPath().resolve(input)
-    val sourceFolder = layout.projectDirectory.file("paper-server/src/vanilla/java/").convertToPath().resolve(input)
-    val targetFolder = expandUserHome(
-        providers.gradleProperty("cleanPaperRepo").orNull
-            ?: error("cleanPaperRepo is required, define it in gradle.properties")
-    ).resolve(input)
+    val input = providers.fileContents(layout.projectDirectory.file("$CACHE_PATH/last-updating-folder")).asText.map { it.trim() }
+    val patchFolder = layout.projectDirectory.dir("paper-server/patches/sources").dir(input)
+    val sourceFolder = layout.projectDirectory.dir("paper-server/src/vanilla/java").dir(input)
+    val targetFolder = providers.gradleProperty("cleanPaperRepo").map {
+        expandUserHome(it).resolve(input.get())
+    }
 
     fun copy(back: Boolean = false) {
-        patchFolder.listDirectoryEntries().forEach {
-            val relative = patchFolder.relativize(it).toString().replace(".patch", "")
-            val source = sourceFolder.resolve(relative)
-            val target = targetFolder.resolve(relative)
+        patchFolder.path.listDirectoryEntries().forEach {
+            val relative = patchFolder.path.relativize(it).toString().replace(".patch", "")
+            val source = sourceFolder.path.resolve(relative)
+            val target = targetFolder.get().resolve(relative)
             if (target.isDirectory()) { return@forEach }
             if (back) {
                 target.copyTo(source, overwrite = true)
@@ -169,8 +169,11 @@ tasks.register("checkWork") {
     }
 
     doLast {
+        if (!targetFolder.isPresent) {
+            error("cleanPaperRepo is required, define it in gradle.properties")
+        }
         copy()
-        val files = patchFolder.listDirectoryEntries().map { it.fileName.toString().replace(".patch", "") }
+        val files = patchFolder.path.listDirectoryEntries().map { it.fileName.toString().replace(".patch", "") }
         println("Copied $files from $sourceFolder to $targetFolder")
         println("Make the files compile, then press enter to copy them back!")
         System.`in`.read()
diff --git a/gradle.properties b/gradle.properties
index f39859fbab..27969fea17 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,8 +6,7 @@ mcVersion=1.21.4
 updatingMinecraft=false
 updateTaskListIssue=https://github.com/PaperMC/Paper/issues/11736
 
-# todo - bundler/paperclip tasks not compatible yet when using libraries
-org.gradle.configuration-cache=false
+org.gradle.configuration-cache=true
 org.gradle.caching=true
 org.gradle.parallel=true
 org.gradle.vfs.watch=false
diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index 8a25dc0885..dc2bce7b5a 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -13,7 +13,6 @@ val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
 dependencies {
     mache("io.papermc:mache:1.21.4+build.6")
     paperclip("io.papermc:paperclip:3.0.3")
-    remapper("net.fabricmc:tiny-remapper:0.10.3:fat")
 }
 
 paperweight {
@@ -23,23 +22,24 @@ paperweight {
 
     paper {
         reobfMappingsPatch = layout.projectDirectory.file("../build-data/reobf-mappings-patch.tiny")
-        reobfPackagesToFix.addAll(
-            "co.aikar.timings",
-            "com.destroystokyo.paper",
-            "com.mojang",
-            "io.papermc.paper",
-            "ca.spottedleaf",
-            "net.kyori.adventure.bossbar",
-            "net.minecraft",
-            "org.bukkit.craftbukkit",
-            "org.spigotmc",
-        )
     }
 
     spigot {
         buildDataRef = "3edaf46ec1eed4115ce1b18d2846cded42577e42"
         packageVersion = "v1_21_R3" // also needs to be updated in MappingEnvironment
     }
+
+    reobfPackagesToFix.addAll(
+        "co.aikar.timings",
+        "com.destroystokyo.paper",
+        "com.mojang",
+        "io.papermc.paper",
+        "ca.spottedleaf",
+        "net.kyori.adventure.bossbar",
+        "net.minecraft",
+        "org.bukkit.craftbukkit",
+        "org.spigotmc",
+    )
 }
 
 tasks.generateDevelopmentBundle {
@@ -58,45 +58,45 @@ abstract class Services {
 }
 val services = objects.newInstance<Services>()
 
-publishing {
-    if (project.providers.gradleProperty("publishDevBundle").isPresent) {
-        val devBundleComponent = services.softwareComponentFactory.adhoc("devBundle")
-        components.add(devBundleComponent)
+if (project.providers.gradleProperty("publishDevBundle").isPresent) {
+    val devBundleComponent = services.softwareComponentFactory.adhoc("devBundle")
+    components.add(devBundleComponent)
 
-        val devBundle = configurations.consumable("devBundle") {
-            attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.ZIP))
-            outgoing.artifact(tasks.generateDevelopmentBundle.flatMap { it.devBundleFile })
-        }
-        devBundleComponent.addVariantsFromConfiguration(devBundle.get()) {}
+    val devBundle = configurations.consumable("devBundle") {
+        attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.ZIP))
+        outgoing.artifact(tasks.generateDevelopmentBundle.flatMap { it.devBundleFile })
+    }
+    devBundleComponent.addVariantsFromConfiguration(devBundle.get()) {}
 
-        val runtime = configurations.consumable("serverRuntimeClasspath") {
-            attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.SERVER_DEPENDENCIES))
-            attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
-            extendsFrom(configurations.runtimeClasspath.get())
-        }
-        devBundleComponent.addVariantsFromConfiguration(runtime.get()) {
-            mapToMavenScope("runtime")
-        }
+    val runtime = configurations.consumable("serverRuntimeClasspath") {
+        attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.SERVER_DEPENDENCIES))
+        attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
+        extendsFrom(configurations.runtimeClasspath.get())
+    }
+    devBundleComponent.addVariantsFromConfiguration(runtime.get()) {
+        mapToMavenScope("runtime")
+    }
 
-        val compile = configurations.consumable("serverCompileClasspath") {
-            attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.SERVER_DEPENDENCIES))
-            attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API))
-            extendsFrom(configurations.compileClasspath.get())
-        }
-        devBundleComponent.addVariantsFromConfiguration(compile.get()) {
-            mapToMavenScope("compile")
-        }
+    val compile = configurations.consumable("serverCompileClasspath") {
+        attributes.attribute(DevBundleOutput.ATTRIBUTE, objects.named(DevBundleOutput.SERVER_DEPENDENCIES))
+        attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API))
+        extendsFrom(configurations.compileClasspath.get())
+    }
+    devBundleComponent.addVariantsFromConfiguration(compile.get()) {
+        mapToMavenScope("compile")
+    }
 
-        tasks.withType(GenerateMavenPom::class).configureEach {
-            doLast {
-                val text = destination.readText()
-                // Remove dependencies from pom, dev bundle is designed for gradle module metadata consumers
-                destination.writeText(
-                    text.substringBefore("<dependencies>") + text.substringAfter("</dependencies>")
-                )
-            }
+    tasks.withType(GenerateMavenPom::class).configureEach {
+        doLast {
+            val text = destination.readText()
+            // Remove dependencies from pom, dev bundle is designed for gradle module metadata consumers
+            destination.writeText(
+                text.substringBefore("<dependencies>") + text.substringAfter("</dependencies>")
+            )
         }
+    }
 
+    publishing {
         publications.create<MavenPublication>("devBundle") {
             artifactId = "dev-bundle"
             from(devBundleComponent)
@@ -317,21 +317,21 @@ tasks.registerRunTask("runDevServer") {
 
 tasks.registerRunTask("runBundler") {
     description = "Spin up a test server from the Mojang mapped bundler jar"
-    classpath(tasks.named<io.papermc.paperweight.tasks.CreateBundlerJar>("createMojmapBundlerJar").flatMap { it.outputZip })
+    classpath(tasks.createMojmapBundlerJar.flatMap { it.outputZip })
     mainClass.set(null as String?)
 }
 tasks.registerRunTask("runReobfBundler") {
     description = "Spin up a test server from the reobf bundler jar"
-    classpath(tasks.named<io.papermc.paperweight.tasks.CreateBundlerJar>("createReobfBundlerJar").flatMap { it.outputZip })
+    classpath(tasks.createReobfBundlerJar.flatMap { it.outputZip })
     mainClass.set(null as String?)
 }
 tasks.registerRunTask("runPaperclip") {
     description = "Spin up a test server from the Mojang mapped Paperclip jar"
-    classpath(tasks.named<io.papermc.paperweight.tasks.CreatePaperclipJar>("createMojmapPaperclipJar").flatMap { it.outputZip })
+    classpath(tasks.createMojmapPaperclipJar.flatMap { it.outputZip })
     mainClass.set(null as String?)
 }
 tasks.registerRunTask("runReobfPaperclip") {
     description = "Spin up a test server from the reobf Paperclip jar"
-    classpath(tasks.named<io.papermc.paperweight.tasks.CreatePaperclipJar>("createReobfPaperclipJar").flatMap { it.outputZip })
+    classpath(tasks.createReobfPaperclipJar.flatMap { it.outputZip })
     mainClass.set(null as String?)
 }

From 42a2ccff550ee239e4c7a4b19c9e297563285cc7 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 20 Dec 2024 12:00:24 +0100
Subject: [PATCH 253/285] Remove dead diff, some name/diff cleanup

---
 .../net/minecraft/server/Bootstrap.java.patch |  8 +--
 .../server/ServerFunctionManager.java.patch   | 11 ----
 .../server/ServerTickRateManager.java.patch   | 50 -------------------
 .../util/worldupdate/WorldUpgrader.java.patch | 10 ++--
 .../damagesource/DamageSources.java.patch     |  4 +-
 .../world/effect/MobEffectUtil.java.patch     | 25 ++++++----
 .../entity/ai/behavior/AcquirePoi.java.patch  |  2 +-
 .../PrepareRamNearestTarget.java.patch        |  8 +--
 .../horse/AbstractChestedHorse.java.patch     | 13 ++---
 9 files changed, 34 insertions(+), 97 deletions(-)
 delete mode 100644 paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
 delete mode 100644 paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch

diff --git a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
index 20a795954e..c2cbc6a238 100644
--- a/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Bootstrap.java.patch
@@ -8,7 +8,7 @@
              if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
                  throw new IllegalStateException("Unable to load registries");
              } else {
-@@ -54,11 +_,78 @@
+@@ -54,11 +_,80 @@
                      EntitySelectorOptions.bootStrap();
                      DispenseItemBehavior.bootStrap();
                      CauldronInteraction.bootStrap();
@@ -22,7 +22,9 @@
                      wrapStreams();
                      bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
                  }
-+                // CraftBukkit start - easier than fixing the decompile
++                // CraftBukkit start
++                // TODO Check what of this is needed, maybe report it to Mojira. if deemed relevant, move to the respective classes
++                //  Used in CraftLegacy
 +                net.minecraft.util.datafix.fixes.BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
 +                net.minecraft.util.datafix.fixes.BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}");
 +                net.minecraft.util.datafix.fixes.BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}");
@@ -41,7 +43,7 @@
 +                net.minecraft.util.datafix.fixes.BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}");
 +                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign");
 +
-+                net.minecraft.util.datafix.fixes.BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"});
++                net.minecraft.util.datafix.fixes.BlockStateData.register(1440, "{Name:'minecraft:portal',Properties:{axis:'x'}}", "{Name:'minecraft:portal',Properties:{axis:'x'}}");
 +
 +                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard");
 +                net.minecraft.util.datafix.fixes.ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals");
diff --git a/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
deleted file mode 100644
index f78aa0e5e9..0000000000
--- a/paper-server/patches/sources/net/minecraft/server/ServerFunctionManager.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/server/ServerFunctionManager.java
-+++ b/net/minecraft/server/ServerFunctionManager.java
-@@ -34,7 +_,7 @@
-     }
- 
-     public CommandDispatcher<CommandSourceStack> getDispatcher() {
--        return this.server.getCommands().getDispatcher();
-+        return this.server.getCommands().getDispatcher(); // CraftBukkit // Paper - Don't override command dispatcher
-     }
- 
-     public void tick() {
diff --git a/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch b/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
deleted file mode 100644
index 2c1e737818..0000000000
--- a/paper-server/patches/sources/net/minecraft/server/ServerTickRateManager.java.patch
+++ /dev/null
@@ -1,50 +0,0 @@
---- a/net/minecraft/server/ServerTickRateManager.java
-+++ b/net/minecraft/server/ServerTickRateManager.java
-@@ -58,8 +_,14 @@
-     }
- 
-     public boolean stopSprinting() {
-+        // CraftBukkit start - add sendLog parameter
-+        return this.stopSprinting(true);
-+    }
-+
-+    public boolean stopSprinting(boolean sendLog) {
-+        // CraftBukkit end - add sendLog parameter
-         if (this.remainingSprintTicks > 0L) {
--            this.finishTickSprint();
-+            this.finishTickSprint(sendLog); // CraftBukkit - add sendLog parameter
-             return true;
-         } else {
-             return false;
-@@ -76,14 +_,20 @@
-         return flag;
-     }
- 
--    private void finishTickSprint() {
-+    private void finishTickSprint(boolean sendLog) { // CraftBukkit - add sendLog parameter
-         long l = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
-         double d = Math.max(1.0, (double)this.sprintTimeSpend) / TimeUtil.NANOSECONDS_PER_MILLISECOND;
-         int i = (int)(TimeUtil.MILLISECONDS_PER_SECOND * l / d);
-         String string = String.format("%.2f", l == 0L ? this.millisecondsPerTick() : d / l);
-         this.scheduledCurrentSprintTicks = 0L;
-         this.sprintTimeSpend = 0L;
--        this.server.createCommandSourceStack().sendSuccess(() -> Component.translatable("commands.tick.sprint.report", i, string), true);
-+        // CraftBukkit start - add sendLog parameter
-+        if (sendLog) {
-+            this.server.createCommandSourceStack().sendSuccess(() -> {
-+                return Component.translatable("commands.tick.sprint.report", i, string);
-+            }, true);
-+        }
-+        // CraftBukkit end
-         this.remainingSprintTicks = 0L;
-         this.setFrozen(this.previousIsFrozen);
-         this.server.onTickRateChanged();
-@@ -97,7 +_,7 @@
-             this.remainingSprintTicks--;
-             return true;
-         } else {
--            this.finishTickSprint();
-+            this.finishTickSprint(true); // CraftBukkit - add sendLog parameter
-             return false;
-         }
-     }
diff --git a/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
index 5f0dc70ca1..ac9d8ccaca 100644
--- a/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
@@ -9,14 +9,12 @@
          this.eraseCache = eraseCache;
          this.dataFixer = dataFixer;
          this.levelStorage = levelStorage;
-@@ -357,9 +_,7 @@
-             if (compoundTag != null) {
+@@ -358,7 +_,7 @@
                  int version = ChunkStorage.getVersion(compoundTag);
                  ChunkGenerator chunkGenerator = WorldUpgrader.this.dimensions.getValueOrThrow(Registries.levelToLevelStem(dimension)).generator();
--                CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(
+                 CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(
 -                    dimension, () -> WorldUpgrader.this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer()
--                );
-+                CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(Registries.levelToLevelStem(dimension), () -> WorldUpgrader.this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer(), chunkPos, null); // CraftBukkit
++                    Registries.levelToLevelStem(dimension), () -> WorldUpgrader.this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer(), chunkPos, null // CraftBukkit
+                 );
                  ChunkPos chunkPos1 = new ChunkPos(compoundTag1.getInt("xPos"), compoundTag1.getInt("zPos"));
                  if (!chunkPos1.equals(chunkPos)) {
-                     WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", chunkPos, chunkPos1);
diff --git a/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
index 9e1a0a8d8b..f13f6629d4 100644
--- a/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/damagesource/DamageSources.java.patch
@@ -42,8 +42,8 @@
 +        return this.badRespawnPointExplosion(position, null);
 +    }
 +
-+    public DamageSource badRespawnPointExplosion(Vec3 vec3d, org.bukkit.block.BlockState blockState) {
-+        return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), vec3d).directBlockState(blockState);
++    public DamageSource badRespawnPointExplosion(Vec3 position, org.bukkit.block.BlockState blockState) {
++        return new DamageSource(this.damageTypes.getOrThrow(DamageTypes.BAD_RESPAWN_POINT), position).directBlockState(blockState);
 +        // CraftBukkit end
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
index eeeb7fd665..16be8c5818 100644
--- a/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectUtil.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/effect/MobEffectUtil.java
 +++ b/net/minecraft/world/effect/MobEffectUtil.java
-@@ -47,18 +_,31 @@
+@@ -47,18 +_,38 @@
      public static List<ServerPlayer> addEffectToPlayersAround(
          ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance effect, int duration
      ) {
@@ -17,28 +17,31 @@
 +        // Paper end - Add ElderGuardianAppearanceEvent
 +        // CraftBukkit end
          Holder<MobEffect> effect1 = effect.getEffect();
--        List<ServerPlayer> players = level.getPlayers(
+         List<ServerPlayer> players = level.getPlayers(
 -            serverPlayer -> serverPlayer.gameMode.isSurvival()
--                && (source == null || !source.isAlliedTo(serverPlayer))
--                && pos.closerThan(serverPlayer.position(), radius)
--                && (
++            // Paper start - Add ElderGuardianAppearanceEvent
++            serverPlayer -> {
++            final boolean condition = serverPlayer.gameMode.isSurvival()
+                 && (source == null || !source.isAlliedTo(serverPlayer))
+                 && pos.closerThan(serverPlayer.position(), radius)
+                 && (
 -                    !serverPlayer.hasEffect(effect1)
 -                        || serverPlayer.getEffect(effect1).getAmplifier() < effect.getAmplifier()
 -                        || serverPlayer.getEffect(effect1).endsWithin(duration - 1)
 -                )
 -        );
 -        players.forEach(serverPlayer -> serverPlayer.addEffect(new MobEffectInstance(effect), source));
-+        List<ServerPlayer> players = level.getPlayers((entityplayer) -> {
-+            // Paper start - Add ElderGuardianAppearanceEvent
-+            boolean condition = entityplayer.gameMode.isSurvival() && (source == null || !source.isAlliedTo((Entity) entityplayer)) && pos.closerThan(entityplayer.position(), radius) && (!entityplayer.hasEffect(effect1) || entityplayer.getEffect(effect1).getAmplifier() < effect.getAmplifier() || entityplayer.getEffect(effect1).endsWithin(duration - 1));
++                !serverPlayer.hasEffect(effect1)
++                    || serverPlayer.getEffect(effect1).getAmplifier() < effect.getAmplifier()
++                    || serverPlayer.getEffect(effect1).endsWithin(duration - 1)
++            );
 +            if (condition) {
-+                return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true
++                return playerPredicate == null || playerPredicate.test(serverPlayer); // Only test the player AFTER it is true
 +            } else {
 +                return false;
 +            }
-+            // Paper end - Add ElderGuardianAppearanceEvent
 +        });
-+
++        // Paper end - Add ElderGuardianAppearanceEvent
 +        players.forEach(serverPlayer -> serverPlayer.addEffect(new MobEffectInstance(effect), source, cause)); // CraftBukkit
          return players;
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
index 512a8a9953..777259cb86 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/AcquirePoi.java.patch
@@ -4,7 +4,7 @@
                              return false;
                          } else {
                              mutableLong.setValue(time + 20L + level.getRandom().nextInt(20));
-+                            if (mob.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck
++                            if (mob.getNavigation().isStuck()) mutableLong.add(200); // Paper - Perf: Wait an additional 10s to check again if they're stuck // TODO Modifies Vanilla behavior, add config option
                              PoiManager poiManager = level.getPoiManager();
                              map.long2ObjectEntrySet().removeIf(entry -> !entry.getValue().isStillValid(time));
                              Predicate<BlockPos> predicate1 = pos -> {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
index 6df8611d59..41b07e6724 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
@@ -5,13 +5,13 @@
                  nearestVisibleLivingEntities -> nearestVisibleLivingEntities.findClosest(livingEntity -> this.ramTargeting.test(level, entity, livingEntity))
              )
 +            // CraftBukkit start
-+            .map((entityliving) -> {
-+                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof net.minecraft.server.level.ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++            .map((livingEntity) -> {
++                org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(entity, livingEntity, (livingEntity instanceof net.minecraft.server.level.ServerPlayer) ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
 +                if (event.isCancelled() || event.getTarget() == null) {
 +                    return null;
 +                }
-+                entityliving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
-+                return entityliving;
++                livingEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++                return livingEntity;
 +            })
 +            // CraftBukkit end
              .ifPresent(entity1 -> this.chooseRamPosition(entity, entity1));
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
index bc6f934031..9fbe146a0c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java.patch
@@ -1,20 +1,15 @@
 --- a/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
 +++ b/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
-@@ -69,9 +_,17 @@
+@@ -69,6 +_,12 @@
          super.dropEquipment(level);
          if (this.hasChest()) {
              this.spawnAtLocation(level, Blocks.CHEST);
-+            //this.setChest(false); // Paper - moved to post death logic
++            // Paper start - moved to post death logic
 +        }
 +    }
-+
-+    // Paper start
 +    protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {
-+        if (this.hasChest() && (event == null || !event.isCancelled())) {
++        if (this.hasChest() && !event.isCancelled()) {
++            // Paper end - moved to post death logic
              this.setChest(false);
          }
      }
-+    // Paper end
- 
-     @Override
-     public void addAdditionalSaveData(CompoundTag compound) {

From f3c1eb3dc0bf54fa51caff89031c28a1bfa2e306 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 20 Dec 2024 12:45:19 +0100
Subject: [PATCH 254/285] More param name fixes

---
 .../world/entity/AreaEffectCloud.java.patch   | 17 --------
 .../minecraft/world/entity/Entity.java.patch  |  4 +-
 .../world/entity/TamableAnimal.java.patch     |  2 +-
 .../world/entity/animal/Animal.java.patch     | 16 ++++----
 .../world/entity/animal/Bee.java.patch        | 22 ++++------
 .../world/entity/animal/Fox.java.patch        |  2 +-
 .../entity/animal/MushroomCow.java.patch      |  5 +--
 .../world/entity/animal/Sheep.java.patch      |  4 +-
 .../world/entity/animal/SnowGolem.java.patch  |  4 +-
 .../world/entity/item/ItemEntity.java.patch   |  2 +-
 .../world/entity/monster/Skeleton.java.patch  |  2 +-
 .../world/entity/monster/Slime.java.patch     |  8 ++--
 .../world/entity/player/Inventory.java.patch  | 41 +++++++++++--------
 .../world/entity/player/Player.java.patch     |  4 +-
 .../projectile/ShulkerBullet.java.patch       |  4 +-
 .../entity/vehicle/AbstractBoat.java.patch    |  7 ++--
 .../vehicle/NewMinecartBehavior.java.patch    |  2 +-
 .../vehicle/OldMinecartBehavior.java.patch    |  2 +-
 .../AbstractContainerMenu.java.patch          |  6 +--
 19 files changed, 68 insertions(+), 86 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
index adddf98077..ae128ef1a7 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -79,20 +79,3 @@
                                                  return;
                                              }
                                          }
-@@ -299,14 +_,14 @@
-         this.waitTime = waitTime;
-     }
- 
--    public void setOwner(@Nullable LivingEntity owner) {
-+    public void setOwner(@Nullable net.minecraft.world.entity.LivingEntity owner) {
-         this.owner = owner;
-         this.ownerUUID = owner == null ? null : owner.getUUID();
-     }
- 
-     @Nullable
-     @Override
--    public LivingEntity getOwner() {
-+    public net.minecraft.world.entity.LivingEntity getOwner() {
-         if (this.owner != null && !this.owner.isRemoved()) {
-             return this.owner;
-         } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index a6132cf6b8..6bd4be5cfe 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -300,8 +300,8 @@
 +        this.setRemoved(reason, null);
 +    }
 +
-+    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
-+        this.setRemoved(entity_removalreason, cause);
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {
++        this.setRemoved(reason, eventCause);
 +        // CraftBukkit end
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
index 878f738ed0..b00c96d54c 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch
@@ -61,7 +61,7 @@
          } else {
 -            this.moveTo(x + 0.5, y, z + 0.5, this.getYRot(), this.getXRot());
 +            // CraftBukkit start
-+            org.bukkit.event.entity.EntityTeleportEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, x + 0.5D, y, z + 0.5D);
++            org.bukkit.event.entity.EntityTeleportEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, x + 0.5, y, z + 0.5);
 +            if (event.isCancelled() || event.getTo() == null) { // Paper - prevent NP on null event to location
 +                return false;
 +            }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
index 4f63402c67..47de924357 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Animal.java.patch
@@ -85,21 +85,19 @@
  
      public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby) {
 -        Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(animal.getLoveCause())).ifPresent(player -> {
--            player.awardStat(Stats.ANIMALS_BRED);
--            CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, baby);
--        });
 +        // CraftBukkit start
 +        this.finalizeSpawnChildFromBreeding(level, animal, baby, this.getRandom().nextInt(7) + 1);
 +    }
 +    public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby, int experience) {
 +        // CraftBukkit end
 +        // Paper start
-+        ServerPlayer entityplayer = this.getLoveCause();
-+        if (entityplayer == null) entityplayer = animal.getLoveCause();
-+        if (entityplayer != null) {
++        ServerPlayer player = this.getLoveCause();
++        if (player == null) player = animal.getLoveCause();
++        if (player != null) {
 +            // Paper end
-+            entityplayer.awardStat(Stats.ANIMALS_BRED);
-+            CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, animal, baby);
+             player.awardStat(Stats.ANIMALS_BRED);
+             CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, baby);
+-        });
 +        } // Paper
          this.setAge(6000);
          animal.setAge(6000);
@@ -110,7 +108,7 @@
 -            level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
 +            // CraftBukkit start - use event experience
 +            if (experience > 0) {
-+                level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, baby)); // Paper
++                level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, player, baby)); // Paper
 +            }
 +            // CraftBukkit end
          }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
index 5e9da74414..94a18bec62 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Bee.java.patch
@@ -59,23 +59,19 @@
                  }
              }
  
-@@ -489,11 +_,12 @@
- 
-     @Nullable
-     BeehiveBlockEntity getBeehiveBlockEntity() {
--        if (this.hivePos == null) {
--            return null;
--        } else {
+@@ -492,7 +_,11 @@
+         if (this.hivePos == null) {
+             return null;
+         } else {
 -            return this.isTooFarAway(this.hivePos) ? null : this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
-+        // Paper start - move over logic to accommodate isTooFarAway with chunk load check
-+        if (this.hivePos != null && !this.isTooFarAway(this.hivePos) && this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) != null) {
-+            return this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
++            // Paper start - move over logic to accommodate isTooFarAway with chunk load check
++            return this.isTooFarAway(this.hivePos) || this.level().getChunkIfLoadedImmediately(this.hivePos.getX() >> 4, this.hivePos.getZ() >> 4) == null
++                ? null
++                : this.level().getBlockEntity(this.hivePos, BlockEntityType.BEEHIVE).orElse(null);
++            // Paper end
          }
-+        return null;
-+        // Paper end
      }
  
-     boolean isHiveValid() {
 @@ -525,6 +_,7 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
index 2db8810362..aa6fdd9654 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Fox.java.patch
@@ -115,7 +115,7 @@
 -                fox.setAge(-24000);
 -                fox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
 -                serverLevel.addFreshEntityWithPassengers(fox);
-+                level.addFreshEntityWithPassengers(fox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
++                serverLevel.addFreshEntityWithPassengers(fox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
                  this.level.broadcastEntityEvent(this.animal, (byte)18);
                  if (serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
 -                    this.level
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
index 3b9b4c2f06..adb68967c7 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/MushroomCow.java.patch
@@ -19,7 +19,7 @@
                  this.gameEvent(GameEvent.SHEAR, player);
                  itemInHand.hurtAndBreak(1, player, getSlotForHand(hand));
              }
-@@ -163,15 +_,32 @@
+@@ -163,15 +_,31 @@
  
      @Override
      public void shear(ServerLevel level, SoundSource soundSource, ItemStack shears) {
@@ -48,8 +48,7 @@
 -                }
 +            // Paper start - custom shear drops; moved drop generation to separate method
 +            drops.forEach(drop -> {
-+                ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop);
-+                this.spawnAtLocation(level, entityitem);
++                this.spawnAtLocation(level, new ItemEntity(this.level(), this.getX(), this.getY(1.0), this.getZ(), drop));
              });
 -        });
 +            // Paper end - custom shear drops
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
index c85c408ef3..2a1505ea58 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/Sheep.java.patch
@@ -49,10 +49,10 @@
 -            (serverLevel, itemStack) -> {
 -                for (int i = 0; i < itemStack.getCount(); i++) {
 -                    ItemEntity itemEntity = this.spawnAtLocation(serverLevel, itemStack.copyWithCount(1), 1.0F);
-+        drops.forEach(itemstack1 -> { // Paper - custom drops - loop in generated default drops
++        drops.forEach(itemStack -> { // Paper - custom drops - loop in generated default drops
 +            if (true) { // Paper - custom drops - loop in generated default drops
 +                this.forceDrops = true; // CraftBukkit
-+                ItemEntity itemEntity = this.spawnAtLocation(level, itemstack1, 1.0F); // Paper - custom drops - copy already done above
++                ItemEntity itemEntity = this.spawnAtLocation(level, itemStack, 1.0F); // Paper - custom drops - copy already done above
 +                this.forceDrops = false; // CraftBukkit
                      if (itemEntity != null) {
                          itemEntity.setDeltaMovement(
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
index c168577210..e647b528c6 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/animal/SnowGolem.java.patch
@@ -64,9 +64,9 @@
 -        this.dropFromShearingLootTable(
 -            level, BuiltInLootTables.SHEAR_SNOW_GOLEM, shears, (serverLevel, itemStack) -> this.spawnAtLocation(serverLevel, itemStack, this.getEyeHeight())
 -        );
-+        drops.forEach(itemstack1 -> { // Paper - custom shear drops
++        drops.forEach(itemStack -> { // Paper - custom shear drops
 +            this.forceDrops = true; // CraftBukkit
-+            this.spawnAtLocation(level, itemstack1, this.getEyeHeight());
++            this.spawnAtLocation(level, itemStack, this.getEyeHeight());
 +            this.forceDrops = false; // CraftBukkit
 +        });
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
index 487f6205a1..22c2ed163b 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -18,7 +18,7 @@
 +        // Paper start - Don't use level random in entity constructors (to make them thread-safe)
 +        this(EntityType.ITEM, level);
 +        this.setPos(posX, posY, posZ);
-+        this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D);
++        this.setDeltaMovement(this.random.nextDouble() * 0.2 - 0.1, 0.2, this.random.nextDouble() * 0.2 - 0.1);
 +        this.setItem(itemStack);
 +        // Paper end - Don't use level random in entity constructors
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
index 0f01620a40..49812bbeee 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Skeleton.java.patch
@@ -5,7 +5,7 @@
  
      protected void doFreezeConversion() {
 -        this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), mob -> {
-+        final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam
++        final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), mob -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam
              if (!this.isSilent()) {
                  this.level().levelEvent(null, 1048, this.blockPosition(), 0);
              }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
index 6f42d379d6..07a1abbeea 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Slime.java.patch
@@ -37,7 +37,7 @@
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {
 +        // CraftBukkit end
          int size = this.getSize();
          if (!this.level().isClientSide && size > 1 && this.isDeadOrDying()) {
@@ -53,7 +53,7 @@
 +            if (!event.isCancelled() && event.getCount() > 0) {
 +                i1 = event.getCount();
 +            } else {
-+                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++                super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
 +                return;
 +            }
 +            java.util.List<net.minecraft.world.entity.LivingEntity> slimes = new java.util.ArrayList<>(i1);
@@ -78,7 +78,7 @@
 +            }
 +            // CraftBukkit start
 +            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, slimes, org.bukkit.event.entity.EntityTransformEvent.TransformReason.SPLIT).isCancelled()) {
-+                super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++                super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
 +                return;
 +            }
 +            for (LivingEntity living : slimes) {
@@ -88,7 +88,7 @@
          }
  
 -        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++        super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
index 5a84bf452f..af826eead1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Inventory.java.patch
@@ -55,7 +55,7 @@
      public Inventory(Player player) {
          this.player = player;
      }
-@@ -50,10 +_,32 @@
+@@ -50,10 +_,39 @@
  
      private boolean hasRemainingSpaceForItem(ItemStack destination, ItemStack origin) {
          return !destination.isEmpty()
@@ -68,24 +68,31 @@
 +    }
 +
 +    // CraftBukkit start - Watch method above! :D
-+    public int canHold(ItemStack itemstack) {
-+        int remains = itemstack.getCount();
-+        for (int i = 0; i < this.items.size(); ++i) {
-+            ItemStack itemstack1 = this.getItem(i);
-+            if (itemstack1.isEmpty()) return itemstack.getCount();
-+
-+            if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) {
-+                remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
++    public int canHold(ItemStack itemStack) {
++        int remains = itemStack.getCount();
++        for (int slot = 0; slot < this.items.size(); ++slot) {
++            ItemStack itemInSlot = this.getItem(slot);
++            if (itemInSlot.isEmpty()) {
++                return itemStack.getCount();
 +            }
-+            if (remains <= 0) return itemstack.getCount();
-+        }
-+        ItemStack offhandItemStack = this.getItem(this.items.size() + this.armor.size());
-+        if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) {
-+            remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount();
-+        }
-+        if (remains <= 0) return itemstack.getCount();
 +
-+        return itemstack.getCount() - remains;
++            if (this.hasRemainingSpaceForItem(itemInSlot, itemStack)) {
++                remains -= (itemInSlot.getMaxStackSize() < this.getMaxStackSize() ? itemInSlot.getMaxStackSize() : this.getMaxStackSize()) - itemInSlot.getCount();
++            }
++            if (remains <= 0) {
++                return itemStack.getCount();
++            }
++        }
++
++        ItemStack itemInOffhand = this.getItem(this.items.size() + this.armor.size());
++        if (this.hasRemainingSpaceForItem(itemInOffhand, itemStack)) {
++            remains -= (itemInOffhand.getMaxStackSize() < this.getMaxStackSize() ? itemInOffhand.getMaxStackSize() : this.getMaxStackSize()) - itemInOffhand.getCount();
++        }
++        if (remains <= 0) {
++            return itemStack.getCount();
++        }
++
++        return itemStack.getCount() - remains;
 +    }
 +    // CraftBukkit end
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
index 02d43bc4f7..4d20a5178a 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/player/Player.java.patch
@@ -462,8 +462,8 @@
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
-+        super.remove(entity_removalreason, cause);
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {
++        super.remove(reason, eventCause);
 +        // CraftBukkit end
          this.inventoryMenu.removed(this);
          if (this.containerMenu != null && this.hasContainerOpen()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
index 310b6cb4c4..8f79e335e3 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
@@ -14,8 +14,8 @@
 +        return this.finalTarget;
 +    }
 +
-+    public void setTarget(Entity e) {
-+        this.finalTarget = e;
++    public void setTarget(Entity finalTarget) {
++        this.finalTarget = finalTarget;
 +        this.currentMoveDirection = Direction.UP;
 +        this.selectNextMoveDirection(Direction.Axis.X);
 +    }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
index a0e7f6a45f..a5d91baad1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
@@ -79,20 +79,19 @@
  
      @Override
      public void remove(Entity.RemovalReason reason) {
--        if (!this.level().isClientSide && reason.shouldDestroy() && this.isLeashed()) {
 +        // CraftBukkit start - add Bukkit remove cause
 +        this.remove(reason, null);
 +    }
 +
 +    @Override
-+    public void remove(Entity.RemovalReason entity_removalreason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
++    public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {
 +        // CraftBukkit end
-+        if (!this.level().isClientSide && entity_removalreason.shouldDestroy() && this.isLeashed()) {
+         if (!this.level().isClientSide && reason.shouldDestroy() && this.isLeashed()) {
              this.dropLeash();
          }
  
 -        super.remove(reason);
-+        super.remove(entity_removalreason, cause); // CraftBukkit - add Bukkit remove cause
++        super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
index 510d8fd645..31506e8555 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java.patch
@@ -19,7 +19,7 @@
      public double getSlowdownFactor() {
 -        return this.minecart.isVehicle() ? 0.997 : 0.975;
 +        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
-+        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.975D; // CraftBukkit - add !this.slowWhenEmpty
++        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997 : 0.975; // CraftBukkit - add !this.slowWhenEmpty
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
index 71af93aa6c..cc3a0cf21d 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java.patch
@@ -53,6 +53,6 @@
      public double getSlowdownFactor() {
 -        return this.minecart.isVehicle() ? 0.997 : 0.96;
 +        if (this.minecart.frictionState == net.kyori.adventure.util.TriState.FALSE) return 1; // Paper
-+        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
++        return this.minecart.isVehicle() || !this.minecart.slowWhenEmpty ? 0.997 : 0.96; // CraftBukkit - add !this.slowWhenEmpty
      }
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
index f28508dd93..e33c611feb 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
@@ -160,11 +160,11 @@
  
                  slot.setChanged();
 +                // CraftBukkit start - Make sure the client has the right slot contents
-+                if (player instanceof ServerPlayer && slot.getMaxStackSize() != 64) {
-+                    ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem()));
++                if (player instanceof ServerPlayer serverPlayer && slot.getMaxStackSize() != 64) {
++                    serverPlayer.connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem()));
 +                    // Updating a crafting inventory makes the client reset the result slot, have to send it again
 +                    if (this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.WORKBENCH || this.getBukkitView().getType() == org.bukkit.event.inventory.InventoryType.CRAFTING) {
-+                        ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem()));
++                        serverPlayer.connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem()));
 +                    }
 +                }
 +                // CraftBukkit end

From 09aea75701460317fe418b4f378e8e005894bf35 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 09:41:08 -0800
Subject: [PATCH 255/285] Move Moonrise patch

---
 .../patches/features/0018-Moonrise-optimisation-patches.patch     | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename feature-patches/1059-Moonrise-optimisation-patches.patch => paper-server/patches/features/0018-Moonrise-optimisation-patches.patch (100%)

diff --git a/feature-patches/1059-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
similarity index 100%
rename from feature-patches/1059-Moonrise-optimisation-patches.patch
rename to paper-server/patches/features/0018-Moonrise-optimisation-patches.patch

From f25d4a6b28a6c0a43c9bcb3ee3b384f8d5ed19c4 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 09:42:29 -0800
Subject: [PATCH 256/285] Fix indices/line numbers in Moonrise patch

---
 .../0018-Moonrise-optimisation-patches.patch  | 1244 ++++++++---------
 1 file changed, 622 insertions(+), 622 deletions(-)

diff --git a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
index 5373ef9f77..7f86f8c5a1 100644
--- a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
@@ -19,10 +19,10 @@ See https://github.com/Tuinity/Moonrise
 
 diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d93e7d855d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,273 @@
 +package ca.spottedleaf.moonrise.common.misc;
 +
 +import ca.spottedleaf.moonrise.common.list.ReferenceList;
@@ -297,10 +297,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5b4b8abea 100644
 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
 +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-@@ -0,0 +0,0 @@ package ca.spottedleaf.moonrise.common.util;
+@@ -2,11 +2,17 @@ package ca.spottedleaf.moonrise.common.util;
  
  import ca.spottedleaf.concurrentutil.util.Priority;
  import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -318,7 +318,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import net.minecraft.world.entity.Entity;
  import net.minecraft.world.level.chunk.ChunkAccess;
  import net.minecraft.world.level.chunk.LevelChunk;
-@@ -0,0 +0,0 @@ import java.util.function.Consumer;
+@@ -18,203 +24,46 @@ import java.util.function.Consumer;
  public final class ChunkSystem {
  
      private static final Logger LOGGER = LogUtils.getLogger();
@@ -530,7 +530,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static boolean hasAnyChunkHolders(final ServerLevel level) {
-@@ -0,0 +0,0 @@ public final class ChunkSystem {
+@@ -233,55 +82,96 @@ public final class ChunkSystem {
      }
  
      public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
@@ -645,10 +645,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private ChunkSystem() {}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508656ed7ac 100644
 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
 +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.levelgen.PositionalRandomFactory;
+@@ -9,7 +9,7 @@ import net.minecraft.world.level.levelgen.PositionalRandomFactory;
  /**
   * Avoid costly CAS of superclass
   */
@@ -658,10 +658,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private static final long MULTIPLIER = 25214903917L;
      private static final long ADDEND = 11L;
 diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..de22cfd2da4782072584d5140ce5567780d6feaa 100644
 --- a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -0,0 +0,0 @@ public final class PaperHooks implements PlatformHooks {
+@@ -267,7 +267,7 @@ public final class PaperHooks implements PlatformHooks {
  
      @Override
      public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
@@ -672,10 +672,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      @Override
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4a30e800b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.block_counting;
 +
 +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -688,10 +688,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0d1443a113c07d7655e7b927a899447f70db8fa9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.block_counting;
 +
 +import ca.spottedleaf.moonrise.common.list.ShortList;
@@ -705,10 +705,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb923a2d9a7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
 +
 +public interface PropertyAccess<T> {
@@ -723,10 +723,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..01da52b9e8a786824f199a057b62ce0431ecbc43
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
 +
 +public interface PropertyAccessStateHolder {
@@ -736,10 +736,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..866f38eb0f379ffbe2888023a7d1c290f521a231
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,230 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util;
 +
 +import ca.spottedleaf.concurrentutil.util.IntegerUtil;
@@ -972,10 +972,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..44bb25554634af2ec0b2e9b3d9231304d5dff034
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -1017,10 +1017,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c7da23900228aab3a5673eb5adfada5091140319
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,44 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.entity;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
@@ -1067,10 +1067,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a814512fcfb85312474ae2c2c21443843bf57831
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,31 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io;
 +
 +import net.minecraft.nbt.CompoundTag;
@@ -1104,10 +1104,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1acea58838f057ab87efd103cbecb6f5aeaef393
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1700 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io;
 +
 +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -2810,10 +2810,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a36ab89f5c37f5f9ab0152f087bb4cf3560f8581
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,50 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage;
@@ -2866,10 +2866,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..828c868f68c2a20bf90d0f7ec253fdeb591f15f6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,73 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage;
@@ -2945,10 +2945,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bd0d782852f9cfe5bc0b5339ecf4d82c10332ec9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,45 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage;
@@ -2996,10 +2996,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..47a4d3376d08dde94a39254bec21473ff27f53e6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
 +import net.minecraft.world.level.ChunkPos;
@@ -3012,10 +3012,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5d4d650186b18eb00782429d53d861564d8e4ba9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,33 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
@@ -3051,10 +3051,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..0b58701342d573fa43cdd06681534854a0e51d77
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
 +import net.minecraft.world.level.chunk.ChunkAccess;
@@ -3067,10 +3067,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..c278f8ef806f0b45c28cc3040c7db052cb51e053
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,62 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
 +import ca.spottedleaf.concurrentutil.util.Priority;
@@ -3135,10 +3135,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8b9dc582627b46843f4b5ea6f8c3df2d8cac46fa
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,21 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
 +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
@@ -3162,10 +3162,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7d049d750df88762566f13a9c4fc7574a2df4825
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,26 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
@@ -3194,10 +3194,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f4bc44bb266763345c4e6f859c89352c769a104d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,26 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
 +import net.minecraft.world.level.chunk.status.ChunkStatus;
@@ -3226,10 +3226,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..aacd543f03b35908011d0c2891e978cc093ebcf5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
@@ -3244,10 +3244,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5b092bca7027e37aeee8f4b852ad896dd0d5febc
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
 +import net.minecraft.server.level.ServerChunkCache;
@@ -3263,10 +3263,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7aea4e343581b977d11af90f9f65eac3532eade1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,569 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -3838,10 +3838,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7554c109c35397bc1a43dd80e87764fd78645bbf
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1002 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
 +
 +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
@@ -4846,10 +4846,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a038215156a163b0b1cbc870ada5b4ac85ed1335
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,129 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -4981,10 +4981,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2ff58cf753c60913ee73aae015182e9c5560d529
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,114 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl;
 +
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
@@ -5101,10 +5101,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e069bf1bf5
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,116 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -5223,10 +5223,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..458d1fc5e1222912512e6c59b56f6fca347d9ee9
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,17 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
@@ -5246,10 +5246,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..89b956b8fdf1a0d862a843104511005e2990a897
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
 +import net.minecraft.world.entity.ai.village.poi.PoiSection;
@@ -5264,10 +5264,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bbf9d6c1c9525d97160806819a57be03eca290f1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,204 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
@@ -5474,10 +5474,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..524752744e37a2db0e3ea089468bdf497129bfef
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.storage;
 +
 +import net.minecraft.nbt.CompoundTag;
@@ -5493,10 +5493,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..003a857e70ead858e8437e3c1bfaf22f4daba0df
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.player;
 +
 +public interface ChunkSystemServerPlayer {
@@ -5514,10 +5514,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..dd2509996bfd08e8c3f9f2be042229eac6d7692d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1092 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.player;
 +
 +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
@@ -6612,10 +6612,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,144 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.queue;
 +
 +import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
@@ -6763,10 +6763,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 \ No newline at end of file
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0fd989d97d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1457 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
@@ -8226,10 +8226,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..67532b85073b7978254a0b04caadfe822679e61f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1055 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
@@ -9287,10 +9287,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48fbb5e1a4c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1998 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.completable.CallbackCompletable;
@@ -11291,10 +11291,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6b468c621b74449a6218391f6477cf63cfc98c7c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,215 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
@@ -11512,10 +11512,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..310a8f80debadd64c2d962ebf83b7d0505ce6e42
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1457 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -12975,10 +12975,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5f4b99d8c5453f8ad2e600a57ea4e7dafa2d45f8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,729 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor;
 +
 +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
@@ -13710,10 +13710,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,151 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
@@ -13867,10 +13867,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4538ccfaea83d217ed85eaf16e82393c7f286489
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,181 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.util.Priority;
@@ -14054,10 +14054,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..1440c9e2b106616884edcb20201113320817ed9f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,494 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -14554,10 +14554,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..002ee365aa70d8e6a6e6bd5c95988bd17db4395a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,101 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -14662,10 +14662,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 \ No newline at end of file
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..25d8da4773dcee5096053e7e3788bfc224d705a7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,218 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
@@ -14886,10 +14886,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..bdcd1879457bafcca4e76523aac0555968f37c0b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,674 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
 +import ca.spottedleaf.concurrentutil.completable.CallbackCompletable;
@@ -15566,10 +15566,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cb6af3712bf9f6f6b8f7a459c309c75dabe83a50
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.server;
 +
 +public interface ChunkSystemMinecraftServer {
@@ -15581,10 +15581,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ea759ce6f10f2a5a4e107ab7528030fe931ba223
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.status;
 +
 +import net.minecraft.world.level.chunk.status.ChunkStatus;
@@ -15596,10 +15596,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..51c126735ace8fdde89ad97b5cab62f244212db0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
 +import net.minecraft.world.level.chunk.storage.RegionFile;
@@ -15614,10 +15614,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..129a35ff2db5b3bb6736810fc180796ce55e1875
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
 +import net.minecraft.world.level.chunk.storage.RegionFileStorage;
@@ -15629,10 +15629,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..3bd1b59250dbab15097a64d515999b278636795a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
@@ -15647,10 +15647,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..786e6ad17cd6216ef0aadaa7cf10044a0c19c933
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.ticket;
 +
 +public interface ChunkSystemTicket<T> {
@@ -15662,10 +15662,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2add7fd15a2210286aeb9af5024263333340d34c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.ticks;
 +
 +public interface ChunkSystemLevelChunkTicks {
@@ -15677,10 +15677,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ce3bb903c9ccb7efa0f004cf79b291dcb1cb7a23
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util;
 +
 +import net.minecraft.util.SortedArraySet;
@@ -15698,10 +15698,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..93fd23027c00cef76562098306737272fda1350a
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,321 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util;
 +
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
@@ -16025,10 +16025,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7ef3dcca89ed7578c6c0f5565131889110063056
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,37 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util.stream;
 +
 +import java.io.DataInputStream;
@@ -16068,10 +16068,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ea6b6ed27b212719feb31610faac974899688839
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.world;
 +
 +import net.minecraft.world.entity.Entity;
@@ -16086,10 +16086,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4b9e2fa963c14f65f15407c1814c543c2999ea32
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.world;
 +
 +import net.minecraft.world.level.chunk.LevelChunk;
@@ -16103,10 +16103,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e97e7d276faf055c89207385d3820debffb06463
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
 +public final class ChunkTickConstants {
@@ -16116,10 +16116,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f28fd0e01e2bdda0daf9d775e514a7253d32d8d0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,16 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
 +import net.minecraft.core.SectionPos;
@@ -16138,10 +16138,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..6af03fd7807d4c71dbf85028d18dc850978ef429
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,19 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
 +import ca.spottedleaf.moonrise.common.list.ReferenceList;
@@ -16163,10 +16163,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..e04bd54744335fb5398c6e4f7ce8b981f35bfb7d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,2183 @@
 +package ca.spottedleaf.moonrise.patches.collisions;
 +
 +import ca.spottedleaf.moonrise.common.util.WorldUtil;
@@ -18352,10 +18352,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..35c8aaf0bfa42717f45eed1d1072e1614874de91
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,28 @@
 +package ca.spottedleaf.moonrise.patches.collisions;
 +
 +import net.minecraft.core.BlockPos;
@@ -18386,10 +18386,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..a38ab583200ebf68ca68fdddf2d12077720b72b7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.moonrise.patches.collisions.block;
 +
 +import net.minecraft.world.phys.shapes.VoxelShape;
@@ -18421,10 +18421,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5a6b16be4b8c0cc92d017bc592bc4818dba17da7
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
 +public record CachedShapeData(
@@ -18437,10 +18437,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..9d33ead3a97d86b371e4d9ad9fed80d789bed844
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,39 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
 +import net.minecraft.world.phys.AABB;
@@ -18482,10 +18482,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..07fe5e02c2d0a27d2fe37bb45761654dc2d02e5d
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
 +public interface CollisionDiscreteVoxelShape {
@@ -18495,10 +18495,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..05d7b3f9d8659c259f3ed0537c57e6e43eb6e288
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,40 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
 +import net.minecraft.core.Direction;
@@ -18541,10 +18541,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..44831fc18efb7534dc6e4822f3c9b5cdc4dcc33e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
 +import net.minecraft.world.phys.shapes.VoxelShape;
@@ -18557,10 +18557,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f62359e5d6aa9a9cdb015441dbdb6182dc302f02
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.collisions.util;
 +
 +public interface CollisionDirection {
@@ -18572,10 +18572,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..cf9ffdeff6bf0b62a45f7a44dbfe0dd7d17dc4f4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.collisions.util;
 +
 +import net.minecraft.core.Direction;
@@ -18585,10 +18585,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..5f5734c00ce8245a1ff69b2d4c3036579d5392e0
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.entity_tracker;
 +
 +import net.minecraft.server.level.ChunkMap;
@@ -18602,10 +18602,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8e7472157a98de607c03769a91f64c8369fd3ea6
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.entity_tracker;
 +
 +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
@@ -18623,10 +18623,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4a7abd239a9c59aa98947e7993962d75e9051902
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.fast_palette;
 +
 +public interface FastPalette<T> {
@@ -18638,10 +18638,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4503f3495846a7d7ed082b9e24636044e4fbccd1
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.fast_palette;
 +
 +public interface FastPaletteData<T> {
@@ -18653,10 +18653,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..107c97089354edd35f330582f5e0c8a18e792a6e
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,5 @@
 +package ca.spottedleaf.moonrise.patches.fluid;
 +
 +public interface FluidFluidState {
@@ -18664,10 +18664,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..540c14a6d2c216cd3ef2a9c4056e15712bf8cb8c
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.getblock;
 +
 +import net.minecraft.world.level.block.state.BlockState;
@@ -18679,10 +18679,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8e6d79b7c10ef25f5478b72c53c555423d615a2f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.starlight.blockstate;
 +
 +public interface StarlightAbstractBlockState {
@@ -18692,10 +18692,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..ed80017c8f257b981d626a37ffc5480d9b326558
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,18 @@
 +package ca.spottedleaf.moonrise.patches.starlight.chunk;
 +
 +import ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray;
@@ -18716,10 +18716,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..fa7b784a89626e8528c249d7889a598bd7ee3d49
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,280 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
@@ -19002,10 +19002,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..4ca68a903e67606fc4ef0bfa9862a73797121c8b
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,440 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import net.minecraft.world.level.chunk.DataLayer;
@@ -19448,10 +19448,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f9aef289e9a2d6f63c98c72c56ef32b8793f57f4
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,681 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import ca.spottedleaf.moonrise.common.util.WorldUtil;
@@ -20135,10 +20135,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8aeb5fb87f94a35659347a09a638420699b52a6f
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,1438 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import ca.spottedleaf.concurrentutil.util.IntegerUtil;
@@ -21579,10 +21579,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..571db5f9bf94745a8afe2cd313e593fb15db5e37
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,931 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -22516,10 +22516,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..7fe59ab70557aa6a484a02db2b2007fdd9e4bbb8
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,29 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
 +import net.minecraft.core.SectionPos;
@@ -22551,10 +22551,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..40d004afdc6449530f5bb2d7c7638b8ee3e3a577
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.starlight.storage;
 +
 +public interface StarlightSectionData {
@@ -22570,10 +22570,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc521d4bc742
 --- /dev/null
 +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,189 @@
 +package ca.spottedleaf.moonrise.patches.starlight.util;
 +
 +import ca.spottedleaf.moonrise.common.util.WorldUtil;
@@ -22764,10 +22764,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private SaveUtil() {}
 +}
 diff --git a/src/main/java/io/papermc/paper/FeatureHooks.java b/src/main/java/io/papermc/paper/FeatureHooks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index a6779295bff446ee79e7c9d41e405447becc2966..efc7f4071655201c59c912e9c84e35a8da66e34c 100644
 --- a/src/main/java/io/papermc/paper/FeatureHooks.java
 +++ b/src/main/java/io/papermc/paper/FeatureHooks.java
-@@ -0,0 +0,0 @@
+@@ -1,6 +1,8 @@
  package io.papermc.paper;
  
  import io.papermc.paper.command.PaperSubcommand;
@@ -22776,7 +22776,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
  import it.unimi.dsi.fastutil.longs.LongSet;
  import it.unimi.dsi.fastutil.longs.LongSets;
-@@ -0,0 +0,0 @@ import org.bukkit.World;
+@@ -29,9 +31,12 @@ import org.bukkit.World;
  public final class FeatureHooks {
  
      public static void initChunkTaskScheduler(final boolean useParallelGen) {
@@ -22791,10 +22791,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
 diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,277 @@
 +package io.papermc.paper.command.subcommands;
 +
 +import ca.spottedleaf.moonrise.common.util.JsonUtil;
@@ -23074,10 +23074,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de140e1a172e
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,116 @@
 +package io.papermc.paper.command.subcommands;
 +
 +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider;
@@ -23196,10 +23196,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +}
 diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..8424cf9d4617b4732d44cc460d25b04481068989
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java
-@@ -0,0 +0,0 @@
+@@ -0,0 +1,10 @@
 +package io.papermc.paper.threadedregions;
 +
 +// placeholder class for Folia
@@ -23211,10 +23211,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +}
 diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f7507519a39b 100644
 --- a/src/main/java/net/minecraft/core/Direction.java
 +++ b/src/main/java/net/minecraft/core/Direction.java
-@@ -0,0 +0,0 @@ import org.joml.Quaternionf;
+@@ -28,7 +28,7 @@ import org.joml.Quaternionf;
  import org.joml.Vector3f;
  import org.joml.Vector4f;
  
@@ -23223,7 +23223,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      DOWN(0, 1, -1, "down", Direction.AxisDirection.NEGATIVE, Direction.Axis.Y, new Vec3i(0, -1, 0)),
      UP(1, 0, -1, "up", Direction.AxisDirection.POSITIVE, Direction.Axis.Y, new Vec3i(0, 1, 0)),
      NORTH(2, 3, 2, "north", Direction.AxisDirection.NEGATIVE, Direction.Axis.Z, new Vec3i(0, 0, -1)),
-@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable {
+@@ -62,6 +62,46 @@ public enum Direction implements StringRepresentable {
      private final int adjY;
      private final int adjZ;
      // Paper end - Perf: Inline shift direction fields
@@ -23270,7 +23270,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private Direction(
          final int id,
-@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable {
+@@ -147,14 +187,13 @@ public enum Direction implements StringRepresentable {
      }
  
      public Quaternionf getRotation() {
@@ -23292,7 +23292,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public int get3DDataValue() {
-@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable {
+@@ -178,7 +217,7 @@ public enum Direction implements StringRepresentable {
      }
  
      public Direction getOpposite() {
@@ -23301,7 +23301,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public Direction getClockWise(Direction.Axis axis) {
-@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable {
+@@ -600,4 +639,17 @@ public enum Direction implements StringRepresentable {
              return this.faces.length;
          }
      }
@@ -23320,10 +23320,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end - optimise collisions
  }
 diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 063630c1ffcce099139c59d598fc5a210e21f640..a61153c5d99bdc26f37a10f33baf839e943e17e1 100644
 --- a/src/main/java/net/minecraft/core/MappedRegistry.java
 +++ b/src/main/java/net/minecraft/core/MappedRegistry.java
-@@ -0,0 +0,0 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
+@@ -50,6 +50,19 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
          return this.getTags();
      }
  
@@ -23343,7 +23343,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public MappedRegistry(ResourceKey<? extends Registry<T>> key, Lifecycle lifecycle) {
          this(key, lifecycle, false);
      }
-@@ -0,0 +0,0 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
+@@ -114,6 +127,7 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
              this.toId.put(value, i);
              this.registrationInfos.put(key, info);
              this.registryLifecycle = this.registryLifecycle.add(info.lifecycle());
@@ -23352,10 +23352,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
      }
 diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 731bdabd53fd4a3d17494f26781223097a5d6e16..42d46c7a7437bea5335a23cbee5708ac57131474 100644
 --- a/src/main/java/net/minecraft/server/Main.java
 +++ b/src/main/java/net/minecraft/server/Main.java
-@@ -0,0 +0,0 @@ public class Main {
+@@ -322,6 +322,7 @@ public class Main {
  
              convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
              */
@@ -23364,10 +23364,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
  
 diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd923cd0b1e 100644
 --- a/src/main/java/net/minecraft/server/MinecraftServer.java
 +++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.server.ServerLoadEvent;
+@@ -204,7 +204,7 @@ import org.bukkit.event.server.ServerLoadEvent;
  // CraftBukkit end
  
  
@@ -23376,7 +23376,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static MinecraftServer SERVER; // Paper
      public static final Logger LOGGER = LogUtils.getLogger();
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -333,7 +333,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
          AtomicReference<S> atomicreference = new AtomicReference();
@@ -23385,7 +23385,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              ((MinecraftServer) atomicreference.get()).runServer();
          }, "Server thread");
  
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -352,6 +352,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          return s0;
      }
  
@@ -23463,7 +23463,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
          super("Server");
          SERVER = this; // Paper - better singleton
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -672,7 +743,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          this.forceDifficulty();
          for (ServerLevel worldserver : this.getAllLevels()) {
              this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
@@ -23472,7 +23472,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
          }
  
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -888,6 +959,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public abstract boolean shouldRconBroadcast();
  
      public boolean saveAllChunks(boolean suppressLogs, boolean flush, boolean force) {
@@ -23484,7 +23484,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          boolean flag3 = false;
  
          for (Iterator iterator = this.getAllLevels().iterator(); iterator.hasNext(); flag3 = true) {
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -897,7 +973,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  MinecraftServer.LOGGER.info("Saving chunks for level '{}'/{}", worldserver, worldserver.dimension().location());
              }
  
@@ -23493,7 +23493,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
          // CraftBukkit start - moved to WorldServer.save
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -998,7 +1074,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              }
          }
  
@@ -23502,7 +23502,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return worldserver1.getChunkSource().chunkMap.hasWork();
          })) {
              this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1015,19 +1091,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.waitUntilNextTick();
          }
  
@@ -23523,7 +23523,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          this.isSaving = false;
          this.resources.close();
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1047,6 +1111,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
          // Spigot end
  
@@ -23538,7 +23538,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public String getLocalIp() {
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1228,6 +1300,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                          this.tickServer(flag ? () -> {
                              return false;
                          } : this::haveTime);
@@ -23552,7 +23552,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                          this.tickFrame.end();
                          gameprofilerfiller.popPush("nextTickWait");
                          this.mayHaveDelayedTasks = true;
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1432,6 +1511,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      private boolean pollTaskInternal() {
          if (super.pollTask()) {
@@ -23560,7 +23560,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return true;
          } else {
              boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
-@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2713,6 +2793,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      }
  
@@ -23575,10 +23575,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public boolean isDebugging() {
          return false;
 diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 2f47d95943c00020a24ea3ff1a49e64e114de675..0dd9ed7465d222505d5368781654ec4954f6e5c3 100644
 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -458,7 +458,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
          return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
      }
  
@@ -23613,10 +23613,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f977e09e7e0 100644
 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
 +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
+@@ -32,46 +32,125 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
  import net.minecraft.server.MinecraftServer;
  // CraftBukkit end
  
@@ -23764,7 +23764,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.setTicketLevel(level);
          this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
      }
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -79,7 +158,7 @@ public class ChunkHolder extends GenerationChunkHolder {
      // CraftBukkit start
      public LevelChunk getFullChunkNow() {
          // Note: We use the oldTicketLevel for isLoaded checks.
@@ -23773,7 +23773,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return this.getFullChunkNowUnchecked();
      }
  
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -89,64 +168,65 @@ public class ChunkHolder extends GenerationChunkHolder {
      // CraftBukkit end
  
      public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
@@ -23862,7 +23862,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          if (chunk == null) {
              return false;
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -172,7 +252,7 @@ public class ChunkHolder extends GenerationChunkHolder {
              return false;
          } else {
              ichunkaccess.markUnsaved();
@@ -23871,7 +23871,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              if (chunk == null) {
                  return false;
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -207,7 +287,7 @@ public class ChunkHolder extends GenerationChunkHolder {
              List list;
  
              if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
@@ -23880,7 +23880,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  if (!list.isEmpty()) {
                      ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter);
  
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -219,7 +299,7 @@ public class ChunkHolder extends GenerationChunkHolder {
              }
  
              if (this.hasChangedSections) {
@@ -23889,7 +23889,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
                  for (int i = 0; i < this.changedBlocksPerSection.length; ++i) {
                      ShortSet shortset = this.changedBlocksPerSection[i];
-@@ -0,0 +0,0 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -285,201 +365,48 @@ public class ChunkHolder extends GenerationChunkHolder {
  
      @Override
      public int getTicketLevel() {
@@ -24102,10 +24102,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @FunctionalInterface
 diff --git a/src/main/java/net/minecraft/server/level/ChunkLevel.java b/src/main/java/net/minecraft/server/level/ChunkLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb9652bb66 100644
 --- a/src/main/java/net/minecraft/server/level/ChunkLevel.java
 +++ b/src/main/java/net/minecraft/server/level/ChunkLevel.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.status.ChunkStep;
+@@ -7,8 +7,8 @@ import net.minecraft.world.level.chunk.status.ChunkStep;
  import org.jetbrains.annotations.Contract;
  
  public class ChunkLevel {
@@ -24117,10 +24117,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
 diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a604b8573 100644
 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java
 +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -0,0 +0,0 @@ import org.slf4j.Logger;
+@@ -108,7 +108,7 @@ import org.slf4j.Logger;
  import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
  // CraftBukkit end
  
@@ -24129,7 +24129,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
      private static final CompletableFuture<ChunkResult<List<ChunkAccess>>> UNLOADED_CHUNK_LIST_FUTURE = CompletableFuture.completedFuture(ChunkMap.UNLOADED_CHUNK_LIST_RESULT);
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -123,10 +123,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      public static final int MIN_VIEW_DISTANCE = 2;
      public static final int MAX_VIEW_DISTANCE = 32;
      public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
@@ -24141,7 +24141,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public final ServerLevel level;
      private final ThreadedLevelLightEngine lightEngine;
      private final BlockableEventLoop<Runnable> mainThreadExecutor;
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -136,22 +133,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      private final PoiManager poiManager;
      public final LongSet toDrop;
      private boolean modified;
@@ -24168,7 +24168,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
      public final CallbackExecutor callbackExecutor = new CallbackExecutor();
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -176,24 +169,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
      // Paper start
      public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
@@ -24203,7 +24203,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Path path = session.getDimensionPath(world.dimension());
  
          this.storageName = path.getFileName().toString();
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -221,18 +216,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          this.chunkStatusListener = chunkStatusChangeListener;
          ConsecutiveExecutor consecutiveexecutor1 = new ConsecutiveExecutor(executor, "light");
  
@@ -24225,7 +24225,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // Paper start
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -263,23 +256,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) {
@@ -24251,7 +24251,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      protected ThreadedLevelLightEngine getLightEngine() {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -288,20 +269,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
      @Nullable
      protected ChunkHolder getUpdatingChunkIfPresent(long pos) {
@@ -24281,7 +24281,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public String getChunkDebugData(ChunkPos chunkPos) {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -330,56 +313,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
@@ -24339,7 +24339,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -409,104 +343,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder holder) {
@@ -24453,7 +24453,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -524,143 +384,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public boolean hasWork() {
@@ -24604,7 +24604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos chunkPos) {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -716,139 +462,43 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
      @Override
      public GenerationChunkHolder acquireGeneration(long pos) {
@@ -24753,7 +24753,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public int getTickingGenerated() {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -856,144 +506,80 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      private boolean saveChunkIfNeeded(ChunkHolder chunkHolder, long currentTime) {
@@ -24946,7 +24946,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      @Nullable
      public LevelChunk getChunkToSend(long pos) {
          ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1059,7 +645,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      // CraftBukkit start
@@ -24955,7 +24955,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
          // CraftBukkit end
      }
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1069,7 +655,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
          while (longiterator.hasNext()) {
              long i = longiterator.nextLong();
@@ -24964,7 +24964,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              if (playerchunk != null && this.anyPlayerCloseEnoughForSpawningInternal(playerchunk.getPos())) {
                  callback.accept(playerchunk);
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1084,7 +670,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
@@ -24973,7 +24973,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          // Spigot end
      }
  
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1096,16 +682,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
          double blockRange; // Paper - use from event
          // Spigot end
@@ -25002,7 +25002,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              // Paper start - PlayerNaturallySpawnCreaturesEvent
              com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
              blockRange = 16384.0D;
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1115,33 +705,47 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                  blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
              }
              // Paper end - PlayerNaturallySpawnCreaturesEvent
@@ -25065,7 +25065,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (entityplayer.isSpectator()) {
              return false;
          } else {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1164,19 +768,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
              this.updatePlayerPos(player);
              if (!flag1) {
                  this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player);
@@ -25089,7 +25089,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
      }
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1188,17 +794,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public void move(ServerPlayer player) {
@@ -25108,7 +25108,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          SectionPos sectionposition = player.getLastSectionPos();
          SectionPos sectionposition1 = SectionPos.of((EntityAccess) player);
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1208,6 +804,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
          if (flag2 || flag != flag1) {
              this.updatePlayerPos(player);
@@ -25116,7 +25116,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              if (!flag) {
                  this.distanceManager.removePlayer(sectionposition, player);
              }
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1224,70 +821,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                  this.playerMap.unIgnorePlayer(player);
              }
  
@@ -25198,7 +25198,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void addEntity(Entity entity) {
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1314,6 +871,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                      ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas());
  
                      this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
@@ -25211,7 +25211,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      playerchunkmap_entitytracker.updatePlayers(this.level.players());
                      if (entity instanceof ServerPlayer) {
                          ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1354,16 +917,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
              playerchunkmap_entitytracker1.broadcastRemoved();
          }
  
@@ -25255,7 +25255,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          List<ServerPlayer> list = Lists.newArrayList();
          List<ServerPlayer> list1 = this.level.players();
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1466,27 +1051,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public void waitForLightBeforeSending(ChunkPos centerPos, int radius) {
@@ -25293,7 +25293,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
          @Nullable
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1502,7 +1085,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
      }
  
@@ -25302,7 +25302,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          public final ServerEntity serverEntity;
          final Entity entity;
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1510,6 +1093,89 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          SectionPos lastSectionPos;
          public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
  
@@ -25392,7 +25392,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
              this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
              this.entity = entity;
-@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1612,20 +1278,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
  
          private int getEffectiveRange() {
@@ -25428,10 +25428,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          public void updatePlayers(List<ServerPlayer> players) {
 diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57fbca45b2 100644
 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java
 +++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos;
+@@ -34,58 +34,57 @@ import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.chunk.LevelChunk;
  import org.slf4j.Logger;
  
@@ -25525,7 +25525,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -102,105 +101,15 @@ public abstract class DistanceManager {
      protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
  
      public boolean runAllUpdates(ChunkMap chunkLoadingManager) {
@@ -25634,7 +25634,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T argument) {
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -219,13 +128,7 @@ public abstract class DistanceManager {
      }
  
      public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
@@ -25649,7 +25649,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -234,32 +137,21 @@ public abstract class DistanceManager {
      }
  
      public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
@@ -25688,7 +25688,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -270,9 +162,8 @@ public abstract class DistanceManager {
          ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> {
              return new ObjectOpenHashSet();
          })).add(player);
@@ -25700,7 +25700,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void removePlayer(SectionPos pos, ServerPlayer player) {
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -284,160 +175,93 @@ public abstract class DistanceManager {
          if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
          if (objectset == null || objectset.isEmpty()) { // Paper
              this.playersPerChunk.remove(i);
@@ -25890,7 +25890,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private class ChunkTicketTracker extends ChunkTracker {
  
          private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -483,7 +307,7 @@ public abstract class DistanceManager {
          public int runDistanceUpdates(int distance) {
              return this.runUpdates(distance);
          }
@@ -25899,7 +25899,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private class FixedPlayerDistanceChunkTracker extends ChunkTracker {
  
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -563,6 +387,7 @@ public abstract class DistanceManager {
          }
      }
  
@@ -25907,7 +25907,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker {
  
          private int viewDistance = 0;
-@@ -0,0 +0,0 @@ public abstract class DistanceManager {
+@@ -657,5 +482,5 @@ public abstract class DistanceManager {
          private boolean haveTicketFor(int distance) {
              return distance <= this.viewDistance;
          }
@@ -25915,10 +25915,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }*/  // Paper - rewrite chunk system
  }
 diff --git a/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java b/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a5fd7489a 100644
 --- a/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
 +++ b/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
-@@ -0,0 +0,0 @@ public abstract class GenerationChunkHolder {
+@@ -27,13 +27,7 @@ public abstract class GenerationChunkHolder {
      public static final ChunkResult<ChunkAccess> UNLOADED_CHUNK = ChunkResult.error("Unloaded chunk");
      public static final CompletableFuture<ChunkResult<ChunkAccess>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_CHUNK);
      protected final ChunkPos pos;
@@ -25933,7 +25933,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public GenerationChunkHolder(ChunkPos pos) {
          this.pos = pos;
-@@ -0,0 +0,0 @@ public abstract class GenerationChunkHolder {
+@@ -43,243 +37,96 @@ public abstract class GenerationChunkHolder {
      }
  
      public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGenerationTask(ChunkStatus requestedStatus, ChunkMap chunkLoadingManager) {
@@ -26206,7 +26206,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public ChunkPos getPos() {
-@@ -0,0 +0,0 @@ public abstract class GenerationChunkHolder {
+@@ -287,7 +134,7 @@ public abstract class GenerationChunkHolder {
      }
  
      public FullChunkStatus getFullStatus() {
@@ -26215,7 +26215,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public abstract int getTicketLevel();
-@@ -0,0 +0,0 @@ public abstract class GenerationChunkHolder {
+@@ -296,26 +143,15 @@ public abstract class GenerationChunkHolder {
  
      @VisibleForDebug
      public List<Pair<ChunkStatus, CompletableFuture<ChunkResult<ChunkAccess>>>> getAllFutures() {
@@ -26248,10 +26248,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  }
 diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa7130c20f 100644
 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
 +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
+@@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
  import net.minecraft.world.level.storage.LevelStorageSource;
  import org.slf4j.Logger;
  
@@ -26260,7 +26260,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static final Logger LOGGER = LogUtils.getLogger();
      private final DistanceManager distanceManager;
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -81,6 +81,107 @@ public class ServerChunkCache extends ChunkSource {
      }
      long chunkFutureAwaitCounter;
      // Paper end
@@ -26368,7 +26368,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
          this.level = world;
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -112,13 +213,7 @@ public class ServerChunkCache extends ChunkSource {
      }
      // CraftBukkit end
      // Paper start
@@ -26383,7 +26383,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Nullable
      public ChunkAccess getChunkAtImmediately(int x, int z) {
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -189,59 +284,42 @@ public class ServerChunkCache extends ChunkSource {
      @Nullable
      @Override
      public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) {
@@ -26469,7 +26469,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private void clearCache() {
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -272,56 +350,59 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      private CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
@@ -26567,7 +26567,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -334,30 +415,18 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      public boolean runDistanceManagerUpdates() { // Paper - public
@@ -26604,7 +26604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.chunkMap.saveAllChunks(flush);
      }
  
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -368,17 +437,15 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      public void close(boolean save) throws IOException {
@@ -26625,7 +26625,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
          gameprofilerfiller.push("purge");
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -403,6 +470,7 @@ public class ServerChunkCache extends ChunkSource {
          this.runDistanceManagerUpdates();
          gameprofilerfiller.popPush("chunks");
          if (tickChunks) {
@@ -26633,7 +26633,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              this.tickChunks();
              this.chunkMap.tick();
          }
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -429,7 +497,10 @@ public class ServerChunkCache extends ChunkSource {
                      gameprofilerfiller.push("filteringTickingChunks");
                      this.collectTickingChunks(list);
                      gameprofilerfiller.popPush("shuffleChunks");
@@ -26645,7 +26645,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      this.tickChunks(gameprofilerfiller, j, list);
                      gameprofilerfiller.pop();
                  } finally {
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -448,7 +519,7 @@ public class ServerChunkCache extends ChunkSource {
  
          while (iterator.hasNext()) {
              ChunkHolder playerchunk = (ChunkHolder) iterator.next();
@@ -26654,7 +26654,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              if (chunk != null) {
                  playerchunk.broadcastChanges(chunk);
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -460,14 +531,26 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      private void collectTickingChunks(List<LevelChunk> chunks) {
@@ -26686,7 +26686,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private void tickChunks(ProfilerFiller profiler, long timeDelta, List<LevelChunk> chunks) {
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -508,7 +591,7 @@ public class ServerChunkCache extends ChunkSource {
                  NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1);
              }
  
@@ -26695,7 +26695,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  this.level.tickChunk(chunk, k);
              }
          }
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -521,11 +604,13 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {
@@ -26713,7 +26713,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -619,6 +704,12 @@ public class ServerChunkCache extends ChunkSource {
          this.chunkMap.setServerViewDistance(watchDistance);
      }
  
@@ -26726,7 +26726,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public void setSimulationDistance(int simulationDistance) {
          this.distanceManager.updateSimulationDistance(simulationDistance);
      }
-@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
+@@ -710,21 +801,19 @@ public class ServerChunkCache extends ChunkSource {
          @Override
          // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
          public boolean pollTask() {
@@ -26755,10 +26755,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  }
 diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index d5bc702f2676b1b7a32c8f3a4a349fc2710ee825..301e8d6599d200cb0f1328f0e386af2f9a619939 100644
 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java
 +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -0,0 +0,0 @@ public class ServerEntity {
+@@ -101,6 +101,11 @@ public class ServerEntity {
      }
  
      public void sendChanges() {
@@ -26771,10 +26771,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          if (!list.equals(this.lastPassengers)) {
 diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905d8325618 100644
 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java
 +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.weather.LightningStrikeEvent;
+@@ -186,7 +186,7 @@ import org.bukkit.event.weather.LightningStrikeEvent;
  import org.bukkit.event.world.TimeSkipEvent;
  // CraftBukkit end
  
@@ -26783,7 +26783,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0);
      public static final IntProvider RAIN_DELAY = UniformInt.of(12000, 180000);
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -202,7 +202,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      public final PrimaryLevelData serverLevelData; // CraftBukkit - type
      private int lastSpawnChunkRadius;
      final EntityTickList entityTickList = new EntityTickList();
@@ -26792,7 +26792,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final GameEventDispatcher gameEventDispatcher;
      public boolean noSave;
      private final SleepStatus sleepStatus;
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -273,12 +273,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority,
                                               java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
@@ -26806,7 +26806,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
          int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
  
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -297,32 +292,159 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ,
                                   ca.spottedleaf.concurrentutil.util.Priority priority,
                                   java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
@@ -26982,7 +26982,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      }
                  }
              }
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -330,22 +452,137 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
          for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
              for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
@@ -27130,7 +27130,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
      public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -379,14 +616,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          DataFixer datafixer = minecraftserver.getFixerUpper();
          EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver);
  
@@ -27148,7 +27148,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return minecraftserver.overworld().getDataStorage();
          });
          this.chunkSource.getGeneratorState().ensureStructuresGenerated();
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -414,6 +650,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
              return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
          });
@@ -27169,7 +27169,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
      }
  
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -536,7 +786,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                          gameprofilerfiller.push("checkDespawn");
                          entity.checkDespawn();
                          gameprofilerfiller.pop();
@@ -27178,7 +27178,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                              Entity entity1 = entity.getVehicle();
  
                              if (entity1 != null) {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -559,13 +809,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          }
  
          gameprofilerfiller.push("entityManagement");
@@ -27197,7 +27197,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      protected void tickTime() {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -605,7 +858,60 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          });
      }
  
@@ -27258,7 +27258,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ChunkPos chunkcoordintpair = chunk.getPos();
          boolean flag = this.isRaining();
          int j = chunkcoordintpair.getMinBlockX();
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -613,7 +919,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
          gameprofilerfiller.push("thunder");
@@ -27267,7 +27267,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
  
              if (this.isRainingAt(blockposition)) {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -645,7 +951,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
          if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
          for (int l = 0; l < randomTickSpeed; ++l) {
@@ -27276,7 +27276,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15));
              }
          }
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -653,35 +959,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
          gameprofilerfiller.popPush("tickBlocks");
          if (randomTickSpeed > 0) {
@@ -27313,7 +27313,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
          gameprofilerfiller.pop();
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -954,6 +1232,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          if (fluid1.is(fluid)) {
              fluid1.tick(this, pos, iblockdata);
          }
@@ -27325,7 +27325,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -963,6 +1246,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          if (iblockdata.is(block)) {
              iblockdata.tick(this, pos, this.random);
          }
@@ -27337,7 +27337,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1041,6 +1329,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      }
  
      public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) {
@@ -27349,7 +27349,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ServerChunkCache chunkproviderserver = this.getChunkSource();
  
          if (!savingDisabled) {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1054,14 +1347,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                  progressListener.progressStage(Component.translatable("menu.savingChunks"));
              }
  
@@ -27375,7 +27375,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          // CraftBukkit start - moved from MinecraftServer.saveChunks
          ServerLevel worldserver1 = this;
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1201,7 +1499,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED);
          }
  
@@ -27384,7 +27384,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // CraftBukkit start
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1232,7 +1530,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              }
              // CraftBukkit end
  
@@ -27393,7 +27393,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
      }
  
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1243,11 +1541,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
          // CraftBukkit end
@@ -27406,7 +27406,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return false;
          } else {
              this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1924,7 +2218,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                  }
              }
  
@@ -27415,7 +27415,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
              bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
              bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1973,7 +2267,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1);
  
          try {
@@ -27424,7 +27424,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          } catch (Throwable throwable4) {
              if (bufferedwriter2 != null) {
                  try {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1994,7 +2288,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2);
  
          try {
@@ -27433,7 +27433,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          } catch (Throwable throwable6) {
              if (bufferedwriter3 != null) {
                  try {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2136,7 +2430,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      @VisibleForTesting
      public String getWatchdogStats() {
@@ -27442,7 +27442,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
          }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats());
      }
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2166,15 +2460,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public LevelEntityGetter<Entity> getEntities() {
          org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -27471,7 +27471,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void startTickingChunk(LevelChunk chunk) {
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2194,34 +2498,47 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public void close() throws IOException {
          super.close();
@@ -27526,7 +27526,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2277,7 +2594,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report);
  
          crashreportsystemdetails.setDetail("Loaded entity count", () -> {
@@ -27536,10 +27536,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return crashreportsystemdetails;
      }
 diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index f6e3073e1f1ff99f6917d84974a18e3e756fa9ea..ba873bcc183f9b3f64ba39be08cb88a95ff52b0e 100644
 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
 +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
+@@ -217,7 +217,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
  import org.bukkit.inventory.MainHand;
  // CraftBukkit end
  
@@ -27548,7 +27548,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
-@@ -0,0 +0,0 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+@@ -322,6 +322,36 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
      public @Nullable String clientBrandName = null; // Paper - Brand support
      public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
  
@@ -27586,10 +27586,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
          this.chatVisibility = ChatVisiblity.FULL;
 diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157bddf99a7 100644
 --- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
 +++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
+@@ -22,23 +22,134 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
  import net.minecraft.world.level.lighting.LevelLightEngine;
  import org.slf4j.Logger;
  
@@ -27731,7 +27731,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
+@@ -52,164 +163,73 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
  
      @Override
      public void checkBlock(BlockPos pos) {
@@ -27920,10 +27920,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      static enum TaskType {
 diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466f5512bcd 100644
 --- a/src/main/java/net/minecraft/server/level/Ticket.java
 +++ b/src/main/java/net/minecraft/server/level/Ticket.java
-@@ -0,0 +0,0 @@ package net.minecraft.server.level;
+@@ -2,13 +2,25 @@ package net.minecraft.server.level;
  
  import java.util.Objects;
  
@@ -27952,7 +27952,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.type = type;
          this.ticketLevel = level;
          this.key = argument;
-@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
+@@ -41,7 +53,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
  
      @Override
      public String toString() {
@@ -27961,7 +27961,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public TicketType<T> getType() {
-@@ -0,0 +0,0 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
+@@ -53,11 +65,10 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
      }
  
      protected void setCreatedTick(long tickCreated) {
@@ -27976,10 +27976,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  }
 diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b7d29389a357f142237cecd75f8ca91cf1eb6b5b..e4b0dc3121101d54394a0c3a413dabf8103b2ea6 100644
 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
 +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-@@ -0,0 +0,0 @@ public class WorldGenRegion implements WorldGenLevel {
+@@ -85,6 +85,36 @@ public class WorldGenRegion implements WorldGenLevel {
      private final AtomicLong subTickCount = new AtomicLong();
      private static final ResourceLocation WORLDGEN_REGION_RANDOM = ResourceLocation.withDefaultNamespace("worldgen_region_random");
  
@@ -28017,10 +28017,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.generatingStep = generationStep;
          this.cache = chunks;
 diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index c68040a59fa8aa9b8b9f1e0b4fdded565ea592d9..7913c41aac1f9dd53a2b49da2a17fd894bcb6b3a 100644
 --- a/src/main/java/net/minecraft/server/players/PlayerList.java
 +++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+@@ -1426,7 +1426,7 @@ public abstract class PlayerList {
  
      public void setViewDistance(int viewDistance) {
          this.viewDistance = viewDistance;
@@ -28029,7 +28029,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Iterator iterator = this.server.getAllLevels().iterator();
  
          while (iterator.hasNext()) {
-@@ -0,0 +0,0 @@ public abstract class PlayerList {
+@@ -1441,7 +1441,7 @@ public abstract class PlayerList {
  
      public void setSimulationDistance(int simulationDistance) {
          this.simulationDistance = simulationDistance;
@@ -28039,10 +28039,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          while (iterator.hasNext()) {
 diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907cf68b15c 100644
 --- a/src/main/java/net/minecraft/util/BitStorage.java
 +++ b/src/main/java/net/minecraft/util/BitStorage.java
-@@ -0,0 +0,0 @@ package net.minecraft.util;
+@@ -2,7 +2,7 @@ package net.minecraft.util;
  
  import java.util.function.IntConsumer;
  
@@ -28051,7 +28051,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      int getAndSet(int index, int value);
  
      void set(int index, int value);
-@@ -0,0 +0,0 @@ public interface BitStorage {
+@@ -20,4 +20,22 @@ public interface BitStorage {
      void unpack(int[] out);
  
      BitStorage copy();
@@ -28075,10 +28075,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end - block counting
  }
 diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17b2fc8db5 100644
 --- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
 +++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-@@ -0,0 +0,0 @@ import java.util.Iterator;
+@@ -7,7 +7,7 @@ import java.util.Iterator;
  import javax.annotation.Nullable;
  import net.minecraft.core.IdMap;
  
@@ -28087,7 +28087,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private static final int NOT_FOUND = -1;
      private static final Object EMPTY_SLOT = null;
      private static final float LOADFACTOR = 0.8F;
-@@ -0,0 +0,0 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
+@@ -17,6 +17,16 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
      private int nextId;
      private int size;
  
@@ -28104,7 +28104,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private CrudeIncrementalIntIdentityHashBiMap(int size) {
          this.keys = (K[])(new Object[size]);
          this.values = new int[size];
-@@ -0,0 +0,0 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
+@@ -88,6 +98,12 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
          this.byId = crudeIncrementalIntIdentityHashBiMap.byId;
          this.nextId = crudeIncrementalIntIdentityHashBiMap.nextId;
          this.size = crudeIncrementalIntIdentityHashBiMap.size;
@@ -28118,10 +28118,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public void addMapping(K value, int id) {
 diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a6428b20c38 100644
 --- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
 +++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
-@@ -0,0 +0,0 @@ public class SimpleBitStorage implements BitStorage {
+@@ -208,6 +208,20 @@ public class SimpleBitStorage implements BitStorage {
      private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage
      private final int divideShift;
  
@@ -28142,7 +28142,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public SimpleBitStorage(int elementBits, int size, int[] data) {
          this(elementBits, size);
          int i = 0;
-@@ -0,0 +0,0 @@ public class SimpleBitStorage implements BitStorage {
+@@ -261,6 +275,13 @@ public class SimpleBitStorage implements BitStorage {
          } else {
              this.data = new long[j];
          }
@@ -28156,7 +28156,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private int cellIndex(int index) {
-@@ -0,0 +0,0 @@ public class SimpleBitStorage implements BitStorage {
+@@ -273,31 +294,54 @@ public class SimpleBitStorage implements BitStorage {
      public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
          //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
          //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage
@@ -28225,7 +28225,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class SimpleBitStorage implements BitStorage {
+@@ -362,6 +406,67 @@ public class SimpleBitStorage implements BitStorage {
          return new SimpleBitStorage(this.bits, this.size, (long[])this.data.clone());
      }
  
@@ -28294,10 +28294,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          InitializationException(String message) {
              super(message);
 diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf857043e1 100644
 --- a/src/main/java/net/minecraft/util/SortedArraySet.java
 +++ b/src/main/java/net/minecraft/util/SortedArraySet.java
-@@ -0,0 +0,0 @@ import java.util.Iterator;
+@@ -8,12 +8,89 @@ import java.util.Iterator;
  import java.util.NoSuchElementException;
  import javax.annotation.Nullable;
  
@@ -28389,10 +28389,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.comparator = comparator;
          if (initialCapacity < 0) {
 diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7eb80334e 100644
 --- a/src/main/java/net/minecraft/util/ZeroBitStorage.java
 +++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java
-@@ -0,0 +0,0 @@ public class ZeroBitStorage implements BitStorage {
+@@ -62,4 +62,22 @@ public class ZeroBitStorage implements BitStorage {
      public BitStorage copy() {
          return this;
      }
@@ -28416,10 +28416,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end - block counting
  }
 diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3b5d9b6ce 100644
 --- a/src/main/java/net/minecraft/world/entity/Entity.java
 +++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.player.PlayerTeleportEvent;
+@@ -176,7 +176,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
  import org.bukkit.plugin.PluginManager;
  // CraftBukkit end
  
@@ -28428,7 +28428,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // CraftBukkit start
      private static final int CURRENT_LEVEL = 2;
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -187,7 +187,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      // Paper start - Share random for entities to make them more random
      public static RandomSource SHARED_RANDOM = new RandomRandomSource();
@@ -28447,7 +28447,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          private boolean locked = false;
  
          @Override
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -200,61 +210,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              }
          }
  
@@ -28510,7 +28510,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
      // Paper end - Share random for entities to make them more random
      public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -462,6 +418,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return this.dimensions.makeBoundingBox(x, y, z);
      }
      // Paper end
@@ -28667,7 +28667,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public Entity(EntityType<?> type, Level world) {
          this.id = Entity.ENTITY_COUNTER.incrementAndGet();
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1387,41 +1493,76 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      private Vec3 collide(Vec3 movement) {
@@ -28772,7 +28772,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private static float[] collectCandidateStepUpHeights(AABB collisionBox, List<VoxelShape> collisions, float f, float stepHeight) {
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -2821,18 +2962,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public boolean isInWall() {
@@ -28890,7 +28890,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public InteractionResult interact(Player player, InteractionHand hand) {
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4310,14 +4543,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public Iterable<Entity> getIndirectPassengers() {
@@ -28915,7 +28915,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
      private Iterable<Entity> getIndirectPassengers_old() {
          // Paper end - Optimize indirect passenger iteration
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4475,82 +4711,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return Mth.lerp(delta, this.yRotO, this.yRot);
      }
  
@@ -29110,7 +29110,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public boolean touchingUnloadedChunk() {
          AABB axisalignedbb = this.getBoundingBox().inflate(1.0D);
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4702,6 +4992,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          this.setPosRaw(x, y, z, false);
      }
      public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29126,7 +29126,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (!checkPosition(this, x, y, z)) {
              return;
          }
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4831,6 +5130,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      @Override
      public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
@@ -29139,7 +29139,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          CraftEventFactory.callEntityRemoveEvent(this, cause);
          // CraftBukkit end
          final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4842,7 +5147,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              this.stopRiding();
          }
  
@@ -29148,7 +29148,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.levelCallback.onRemove(entity_removalreason);
          this.onRemoval(entity_removalreason);
          // Paper start - Folia schedulers
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4874,7 +5179,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      @Override
      public boolean shouldBeSaved() {
@@ -29158,10 +29158,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462ca1b78cd 100644
 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
+@@ -38,12 +38,137 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
  import net.minecraft.world.level.chunk.storage.SectionStorage;
  import net.minecraft.world.level.chunk.storage.SimpleRegionStorage;
  
@@ -29300,7 +29300,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public PoiManager(
          RegionStorageInfo storageKey,
          Path directory,
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
+@@ -64,6 +189,7 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
              world
          );
          this.distanceTracker = new PoiManager.DistanceTracker();
@@ -29308,7 +29308,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void add(BlockPos pos, Holder<PoiType> type) {
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
+@@ -197,8 +323,10 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
      }
  
      public int sectionsToVillage(SectionPos pos) {
@@ -29321,7 +29321,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      boolean isVillageCenter(long pos) {
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
+@@ -212,19 +340,26 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
  
      @Override
      public void tick(BooleanSupplier shouldKeepTicking) {
@@ -29354,7 +29354,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) {
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
+@@ -263,7 +398,7 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
              .map(sectionPos -> Pair.of(sectionPos, this.getOrLoad(sectionPos.asLong())))
              .filter(pair -> !pair.getSecond().map(PoiSection::isValid).orElse(false))
              .map(pair -> pair.getFirst().chunk())
@@ -29364,10 +29364,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57f892a768 100644
 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
 +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-@@ -0,0 +0,0 @@ import net.minecraft.core.SectionPos;
+@@ -23,13 +23,27 @@ import net.minecraft.core.SectionPos;
  import net.minecraft.util.VisibleForDebug;
  import org.slf4j.Logger;
  
@@ -29397,10 +29397,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this(updateListener, true, ImmutableList.of());
      }
 diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 63f02cdc67d9e88cc6998d0ae9d139c83e85b447..70b8023c3badc745f342d5b0ab54699e3923826a 100644
 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
 +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-@@ -0,0 +0,0 @@ public class ArmorStand extends LivingEntity {
+@@ -364,7 +364,7 @@ public class ArmorStand extends LivingEntity {
      @Override
      protected void pushEntities() {
          if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
@@ -29410,10 +29410,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          while (iterator.hasNext()) {
 diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda6757b4477c 100644
 --- a/src/main/java/net/minecraft/world/level/ClipContext.java
 +++ b/src/main/java/net/minecraft/world/level/ClipContext.java
-@@ -0,0 +0,0 @@ public class ClipContext {
+@@ -18,7 +18,7 @@ public class ClipContext {
      private final Vec3 from;
      private final Vec3 to;
      private final ClipContext.Block block;
@@ -29423,10 +29423,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) {
 diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b150013e940ee6 100644
 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java
 +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.phys.shapes.BooleanOp;
+@@ -15,7 +15,7 @@ import net.minecraft.world.phys.shapes.BooleanOp;
  import net.minecraft.world.phys.shapes.Shapes;
  import net.minecraft.world.phys.shapes.VoxelShape;
  
@@ -29435,7 +29435,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate);
  
      <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate);
-@@ -0,0 +0,0 @@ public interface EntityGetter {
+@@ -30,21 +30,44 @@ public interface EntityGetter {
          return this.getEntities(except, box, EntitySelector.NO_SPECTATORS);
      }
  
@@ -29491,7 +29491,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      default <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB box) {
-@@ -0,0 +0,0 @@ public interface EntityGetter {
+@@ -52,23 +75,41 @@ public interface EntityGetter {
      }
  
      default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
@@ -29547,10 +29547,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Paper start - Affects Spawning API
 diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b827401434bcfff1 100644
 --- a/src/main/java/net/minecraft/world/level/Level.java
 +++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.LevelData;
+@@ -84,6 +84,7 @@ import net.minecraft.world.level.storage.LevelData;
  import net.minecraft.world.level.storage.WritableLevelData;
  import net.minecraft.world.phys.AABB;
  import net.minecraft.world.phys.Vec3;
@@ -29558,7 +29558,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  import net.minecraft.world.scores.Scoreboard;
  
  // CraftBukkit start
-@@ -0,0 +0,0 @@ import org.bukkit.entity.SpawnCategory;
+@@ -105,7 +106,7 @@ import org.bukkit.entity.SpawnCategory;
  import org.bukkit.event.block.BlockPhysicsEvent;
  // CraftBukkit end
  
@@ -29567,7 +29567,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
      public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -131,7 +132,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public float rainLevel;
      protected float oThunderLevel;
      public float thunderLevel;
@@ -29576,7 +29576,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      /** @deprecated */
      @Deprecated
      private final RandomSource threadSafeRandom = RandomSource.createThreadSafe();
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -207,7 +208,639 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
  
      public abstract ResourceKey<LevelStem> getTypeKey();
  
@@ -30216,7 +30216,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
          this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
          this.generator = gen;
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -288,6 +921,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
          this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
          this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
@@ -30224,7 +30224,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // Paper start - Cancel hit for vanished players
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -557,7 +1191,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                  this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
              }
  
@@ -30233,7 +30233,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
              }
  
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -820,6 +1454,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
          boolean flag = this.tickRateManager().runsNormally();
  
@@ -30242,7 +30242,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          int tilesThisCycle = 0;
          var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
          toRemove.add(null); // Paper - Fix MC-117075
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -835,6 +1471,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                  // Spigot end
              } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
                  tickingblockentity.tick();
@@ -30254,7 +30254,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              }
          }
          this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -855,12 +1496,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
              entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
              // Paper end - Prevent block entity and entity crashes
          }
@@ -30276,7 +30276,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
      // Paper end - Option to prevent armor stands from doing entity lookups
  
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -912,7 +1561,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          }
          // Paper end - Perf: Optimize capturedTileEntities lookup
          // CraftBukkit end
@@ -30285,7 +30285,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void setBlockEntity(BlockEntity blockEntity) {
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1004,23 +1653,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          Profiler.get().incrementCounter("getEntities");
          List<Entity> list = Lists.newArrayList();
  
@@ -30315,7 +30315,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1035,36 +1676,94 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE);
      }
  
@@ -30432,10 +30432,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      @Nullable
      public abstract Entity getEntity(int id);
 diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 5eb8982678110fabb82a93c5ec67c666b7fde017..ade435de0af4ee3566fa4a490df53cddd2f6531c 100644
 --- a/src/main/java/net/minecraft/world/level/LevelReader.java
 +++ b/src/main/java/net/minecraft/world/level/LevelReader.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.dimension.DimensionType;
+@@ -22,7 +22,18 @@ import net.minecraft.world.level.dimension.DimensionType;
  import net.minecraft.world.level.levelgen.Heightmap;
  import net.minecraft.world.phys.AABB;
  
@@ -30456,10 +30456,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
  
 diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f81daa428 100644
 --- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
 +++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
-@@ -0,0 +0,0 @@ public class ServerExplosion implements Explosion {
+@@ -64,6 +64,249 @@ public class ServerExplosion implements Explosion {
      public float yield;
      // CraftBukkit end
      public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
@@ -30709,7 +30709,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
          this.level = world;
-@@ -0,0 +0,0 @@ public class ServerExplosion implements Explosion {
+@@ -127,65 +370,101 @@ public class ServerExplosion implements Explosion {
      }
  
      private List<BlockPos> calculateExplodedPositions() {
@@ -30860,7 +30860,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private void hurtEntities() {
-@@ -0,0 +0,0 @@ public class ServerExplosion implements Explosion {
+@@ -391,6 +670,14 @@ public class ServerExplosion implements Explosion {
              return;
          }
          // CraftBukkit end
@@ -30875,7 +30875,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center);
          List<BlockPos> list = this.calculateExplodedPositions();
  
-@@ -0,0 +0,0 @@ public class ServerExplosion implements Explosion {
+@@ -406,6 +693,13 @@ public class ServerExplosion implements Explosion {
          if (this.fire) {
              this.createFire(list);
          }
@@ -30889,7 +30889,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      }
  
-@@ -0,0 +0,0 @@ public class ServerExplosion implements Explosion {
+@@ -499,12 +793,12 @@ public class ServerExplosion implements Explosion {
      // Paper start - Optimize explosions
      private float getBlockDensity(Vec3 vec3d, Entity entity) {
          if (!this.level.paperConfig().environment.optimizeExplosions) {
@@ -30905,10 +30905,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
 diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 9f86b69d8c93a63e0b408ea52519f1fc2e798226..78afd8e51e03cd53c12b64db8a817da457f81bef 100644
 --- a/src/main/java/net/minecraft/world/level/biome/Biome.java
 +++ b/src/main/java/net/minecraft/world/level/biome/Biome.java
-@@ -0,0 +0,0 @@ public final class Biome {
+@@ -113,20 +113,7 @@ public final class Biome {
  
      @Deprecated
      public float getTemperature(BlockPos blockPos, int seaLevel) {
@@ -30931,10 +30931,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public boolean shouldFreeze(LevelReader world, BlockPos blockPos) {
 diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8bfa94b498 100644
 --- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
 +++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
-@@ -0,0 +0,0 @@ public class BiomeManager {
+@@ -98,8 +98,7 @@ public class BiomeManager {
      }
  
      private static double getFiddle(long l) {
@@ -30945,10 +30945,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public interface NoiseBiomeSource {
 diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 1aa69f4a7005242925124c74b8229e6fa7362717..c0b1f903962b25d8ff6c2b4fcd2be0e45de09b35 100644
 --- a/src/main/java/net/minecraft/world/level/block/Block.java
 +++ b/src/main/java/net/minecraft/world/level/block/Block.java
-@@ -0,0 +0,0 @@ public class Block extends BlockBehaviour implements ItemLike {
+@@ -271,7 +271,7 @@ public class Block extends BlockBehaviour implements ItemLike {
      }
  
      public static boolean isShapeFullBlock(VoxelShape shape) {
@@ -30958,10 +30958,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {}
 diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe4fe74166 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
-@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -797,7 +797,7 @@ public abstract class BlockBehaviour implements FeatureElement {
          boolean test(BlockState state, BlockGetter world, BlockPos pos);
      }
  
@@ -30970,7 +30970,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          private static final Direction[] DIRECTIONS = Direction.values();
          private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = (VoxelShape[]) Util.make(new VoxelShape[BlockBehaviour.BlockStateBase.DIRECTIONS.length], (avoxelshape) -> {
-@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -841,6 +841,76 @@ public abstract class BlockBehaviour implements FeatureElement {
          private boolean propagatesSkylightDown;
          private int lightBlock;
  
@@ -31047,7 +31047,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          protected BlockStateBase(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
              super(block, propertyMap, codec);
              this.fluidState = Fluids.EMPTY.defaultFluidState();
-@@ -0,0 +0,0 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -925,6 +995,41 @@ public abstract class BlockBehaviour implements FeatureElement {
  
              this.propagatesSkylightDown = ((Block) this.owner).propagatesSkylightDown(this.asState());
              this.lightBlock = ((Block) this.owner).getLightBlock(this.asState());
@@ -31090,10 +31090,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          public Block getBlock() {
 diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d769478de477bd 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-@@ -0,0 +0,0 @@ import java.util.stream.Collectors;
+@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
  import javax.annotation.Nullable;
  import net.minecraft.world.level.block.state.properties.Property;
  
@@ -31102,7 +31102,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public static final String NAME_TAG = "Name";
      public static final String PROPERTIES_TAG = "Properties";
      public static final Function<Entry<Property<?>, Comparable<?>>, String> PROPERTY_ENTRY_TO_STRING_FUNCTION = new Function<Entry<Property<?>, Comparable<?>>, String>() {
-@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
+@@ -34,14 +34,28 @@ public abstract class StateHolder<O, S> {
          }
      };
      protected final O owner;
@@ -31132,7 +31132,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public <T extends Comparable<T>> S cycle(Property<T> property) {
-@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
+@@ -67,20 +81,21 @@ public abstract class StateHolder<O, S> {
      }
  
      public Collection<Property<?>> getProperties() {
@@ -31161,7 +31161,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public <T extends Comparable<T>> Optional<T> getOptionalValue(Property<T> property) {
-@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
+@@ -93,22 +108,30 @@ public abstract class StateHolder<O, S> {
  
      @Nullable
      public <T extends Comparable<T>> T getNullableValue(Property<T> property) {
@@ -31201,7 +31201,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private <T extends Comparable<T>, V extends T> S setValueInternal(Property<T> property, V newValue, Comparable<?> oldValue) {
-@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
+@@ -125,18 +148,27 @@ public abstract class StateHolder<O, S> {
      }
  
      public void populateNeighbours(Map<Map<Property<?>, Comparable<?>>, S> states) {
@@ -31238,7 +31238,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private Map<Property<?>, Comparable<?>> makeNeighbourValues(Property<?> property, Comparable<?> value) {
-@@ -0,0 +0,0 @@ public abstract class StateHolder<O, S> {
+@@ -146,7 +178,11 @@ public abstract class StateHolder<O, S> {
      }
  
      public Map<Property<?>, Comparable<?>> getValues() {
@@ -31252,10 +31252,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      protected static <O, S extends StateHolder<O, S>> Codec<S> codec(Codec<O> codec, Function<O, S> ownerToStateFunction) {
 diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196713b14a7 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
-@@ -0,0 +0,0 @@ package net.minecraft.world.level.block.state.properties;
+@@ -3,13 +3,23 @@ package net.minecraft.world.level.block.state.properties;
  import java.util.List;
  import java.util.Optional;
  
@@ -31281,10 +31281,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5640127a7 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
-@@ -0,0 +0,0 @@ import java.util.function.Predicate;
+@@ -10,11 +10,39 @@ import java.util.function.Predicate;
  import java.util.stream.Collectors;
  import net.minecraft.util.StringRepresentable;
  
@@ -31325,7 +31325,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private EnumProperty(String name, Class<T> type, List<T> values) {
          super(name, type);
          if (values.isEmpty()) {
-@@ -0,0 +0,0 @@ public final class EnumProperty<T extends Enum<T> & StringRepresentable> extends
+@@ -37,6 +65,7 @@ public final class EnumProperty<T extends Enum<T> & StringRepresentable> extends
  
              this.names = builder.buildOrThrow();
          }
@@ -31334,10 +31334,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0efe1506d 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-@@ -0,0 +0,0 @@ import java.util.List;
+@@ -5,11 +5,33 @@ import java.util.List;
  import java.util.Optional;
  import java.util.stream.IntStream;
  
@@ -31372,7 +31372,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private IntegerProperty(String name, int min, int max) {
          super(name, Integer.class);
          if (min < 0) {
-@@ -0,0 +0,0 @@ public final class IntegerProperty extends Property<Integer> {
+@@ -21,6 +43,7 @@ public final class IntegerProperty extends Property<Integer> {
              this.max = max;
              this.values = IntImmutableList.toList(IntStream.range(min, max + 1));
          }
@@ -31381,10 +31381,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276f0386e4d 100644
 --- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
 +++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
-@@ -0,0 +0,0 @@ import java.util.stream.Stream;
+@@ -10,7 +10,7 @@ import java.util.stream.Stream;
  import javax.annotation.Nullable;
  import net.minecraft.world.level.block.state.StateHolder;
  
@@ -31393,7 +31393,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Class<T> clazz;
      private final String name;
      @Nullable
-@@ -0,0 +0,0 @@ public abstract class Property<T extends Comparable<T>> {
+@@ -24,9 +24,38 @@ public abstract class Property<T extends Comparable<T>> {
          );
      private final Codec<Property.Value<T>> valueCodec = this.codec.xmap(this::value, Property.Value::value);
  
@@ -31433,10 +31433,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public Property.Value<T> value(T value) {
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95c9ac2dbb 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.ticks.SavedTick;
+@@ -57,7 +57,7 @@ import net.minecraft.world.ticks.SavedTick;
  import net.minecraft.world.ticks.TickContainerAccess;
  import org.slf4j.Logger;
  
@@ -31445,7 +31445,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static final int NO_FILLED_SECTION = -1;
      private static final Logger LOGGER = LogUtils.getLogger();
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -77,7 +77,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      @Nullable
      protected BlendingData blendingData;
      public final Map<Heightmap.Types, Heightmap> heightmaps = Maps.newEnumMap(Heightmap.Types.class);
@@ -31454,7 +31454,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Map<Structure, StructureStart> structureStarts = Maps.newHashMap();
      private final Map<Structure, LongSet> structuresRefences = Maps.newHashMap();
      protected final Map<BlockPos, CompoundTag> pendingBlockEntities = Maps.newHashMap();
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -90,6 +90,57 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
      // CraftBukkit end
  
@@ -31512,7 +31512,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
          this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
          this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -99,7 +150,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
          this.inhabitedTime = inhabitedTime;
          this.postProcessing = new ShortList[heightLimitView.getSectionsCount()];
          this.blendingData = blendingData;
@@ -31521,7 +31521,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (sectionArray != null) {
              if (this.sections.length == sectionArray.length) {
                  System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length);
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -111,6 +162,16 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
          this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method
          // CraftBukkit start
          this.biomeRegistry = biomeRegistry;
@@ -31538,7 +31538,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
      public final Registry<Biome> biomeRegistry;
      // CraftBukkit end
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -457,22 +518,22 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
  
      @Override
      public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
@@ -31576,7 +31576,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // CraftBukkit start
-@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+@@ -529,12 +590,12 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      }
  
      public void initializeLightSources() {
@@ -31592,10 +31592,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static record PackedTicks(List<SavedTick<Block>> blocks, List<SavedTick<Fluid>> fluids) {
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ca6928f959eb63ac9183ba6c95738609839a7d32..e0cb360ece042c4fc6aa0d10106923fe25288f5c 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-@@ -0,0 +0,0 @@ public abstract class ChunkGenerator {
+@@ -120,7 +120,7 @@ public abstract class ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
              chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler());
              return chunk;
@@ -31604,7 +31604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public abstract void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, StructureManager structureAccessor, ChunkAccess chunk);
-@@ -0,0 +0,0 @@ public abstract class ChunkGenerator {
+@@ -315,7 +315,7 @@ public abstract class ChunkGenerator {
                          return Pair.of(placement.getLocatePos(pos), holder);
                      }
  
@@ -31614,10 +31614,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess);
                  } while (structurestart == null);
 diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95a960cb19 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.state.BlockState;
+@@ -13,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState;
  import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.level.material.Fluids;
  
@@ -31626,7 +31626,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Holder<Biome> biome;
  
      public EmptyLevelChunk(Level world, ChunkPos pos, Holder<Biome> biomeEntry) {
-@@ -0,0 +0,0 @@ public class EmptyLevelChunk extends LevelChunk {
+@@ -21,6 +21,40 @@ public class EmptyLevelChunk extends LevelChunk {
          this.biome = biomeEntry;
      }
  
@@ -31668,10 +31668,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public BlockState getBlockState(BlockPos pos) {
          return Blocks.VOID_AIR.defaultBlockState();
 diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5708f2c4a 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
-@@ -0,0 +0,0 @@ import net.minecraft.network.FriendlyByteBuf;
+@@ -8,12 +8,19 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap;
  
@@ -31693,10 +31693,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this(idList, bits, listener);
          entries.forEach(this.values::add);
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc9a11bbec 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.material.FluidState;
+@@ -30,7 +30,7 @@ import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.ticks.BlackholeTickAccess;
  import net.minecraft.world.ticks.TickContainerAccess;
  
@@ -31705,7 +31705,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final LevelChunk wrapped;
      private final boolean allowWrites;
  
-@@ -0,0 +0,0 @@ public class ImposterProtoChunk extends ProtoChunk {
+@@ -46,6 +46,48 @@ public class ImposterProtoChunk extends ProtoChunk {
          this.allowWrites = propagateToWrapped;
      }
  
@@ -31755,10 +31755,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
 diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf191005112080b 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.ticks.LevelChunkTicks;
+@@ -55,7 +55,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
  import net.minecraft.world.ticks.TickContainerAccess;
  import org.slf4j.Logger;
  
@@ -31767,7 +31767,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      static final Logger LOGGER = LogUtils.getLogger();
      private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() {
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -114,6 +114,14 @@ public class LevelChunk extends ChunkAccess {
          this.postLoad = entityLoader;
          this.blockTicks = blockTickScheduler;
          this.fluidTicks = fluidTickScheduler;
@@ -31782,7 +31782,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // CraftBukkit start
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -124,6 +132,39 @@ public class LevelChunk extends ChunkAccess {
      // Paper start
      boolean loadedTicketLevel;
      // Paper end
@@ -31822,7 +31822,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
          this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -157,13 +198,19 @@ public class LevelChunk extends ChunkAccess {
              }
          }
  
@@ -31843,7 +31843,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -366,7 +413,7 @@ public class LevelChunk extends ChunkAccess {
                      ProfilerFiller gameprofilerfiller = Profiler.get();
  
                      gameprofilerfiller.push("updateSkyLightSources");
@@ -31852,7 +31852,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      gameprofilerfiller.popPush("queueCheckLight");
                      this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
                      gameprofilerfiller.pop();
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -632,11 +679,12 @@ public class LevelChunk extends ChunkAccess {
  
      // CraftBukkit start
      public void loadCallback() {
@@ -31866,7 +31866,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (server != null) {
              /*
               * If it's a new world, the first few chunks are generated inside
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -645,6 +693,7 @@ public class LevelChunk extends ChunkAccess {
               */
              org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
              server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
@@ -31874,7 +31874,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              if (this.needsDecoration) {
                  this.needsDecoration = false;
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -671,13 +720,15 @@ public class LevelChunk extends ChunkAccess {
      }
  
      public void unloadCallback() {
@@ -31892,7 +31892,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          // Paper start
          this.loadedTicketLevel = false;
          // Paper end
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -685,8 +736,31 @@ public class LevelChunk extends ChunkAccess {
  
      @Override
      public boolean isUnsaved() {
@@ -31925,7 +31925,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // CraftBukkit end
  
      public boolean isEmpty() {
-@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
+@@ -794,6 +868,7 @@ public class LevelChunk extends ChunkAccess {
  
          this.pendingBlockEntities.clear();
          this.upgradeData.upgrade(this);
@@ -31934,10 +31934,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Nullable
 diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef63cdb8511 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.Blocks;
+@@ -13,7 +13,7 @@ import net.minecraft.world.level.block.Blocks;
  import net.minecraft.world.level.block.state.BlockState;
  import net.minecraft.world.level.material.FluidState;
  
@@ -31946,7 +31946,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static final int SECTION_WIDTH = 16;
      public static final int SECTION_HEIGHT = 16;
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -25,6 +25,30 @@ public class LevelChunkSection {
      public final PalettedContainer<BlockState> states;
      private PalettedContainer<Holder<Biome>> biomes; // CraftBukkit - read/write
  
@@ -31977,7 +31977,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private LevelChunkSection(LevelChunkSection section) {
          this.nonEmptyBlockCount = section.nonEmptyBlockCount;
          this.tickingBlockCount = section.tickingBlockCount;
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -67,6 +91,45 @@ public class LevelChunkSection {
          return this.setBlockState(x, y, z, state, true);
      }
  
@@ -32023,7 +32023,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
          BlockState iblockdata1;
  
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -86,7 +149,7 @@ public class LevelChunkSection {
              }
          }
  
@@ -32032,7 +32032,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              --this.tickingFluidCount;
          }
  
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -97,10 +160,12 @@ public class LevelChunkSection {
              }
          }
  
@@ -32046,7 +32046,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return iblockdata1;
      }
  
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -121,40 +186,70 @@ public class LevelChunkSection {
      }
  
      public void recalcBlockCounts() {
@@ -32138,7 +32138,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public PalettedContainer<BlockState> getStates() {
-@@ -0,0 +0,0 @@ public class LevelChunkSection {
+@@ -172,6 +267,11 @@ public class LevelChunkSection {
  
          datapaletteblock.read(buf);
          this.biomes = datapaletteblock;
@@ -32151,10 +32151,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public void readBiomes(FriendlyByteBuf buf) {
 diff --git a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf34354ce1195 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
-@@ -0,0 +0,0 @@ import net.minecraft.network.FriendlyByteBuf;
+@@ -7,13 +7,20 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import org.apache.commons.lang3.Validate;
  
@@ -32177,10 +32177,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.registry = idList;
          this.values = (T[])(new Object[1 << bits]);
 diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be2f08ecd1 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/Palette.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java
-@@ -0,0 +0,0 @@ import java.util.function.Predicate;
+@@ -5,7 +5,7 @@ import java.util.function.Predicate;
  import net.minecraft.core.IdMap;
  import net.minecraft.network.FriendlyByteBuf;
  
@@ -32190,10 +32190,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      boolean maybeHas(Predicate<T> predicate);
 diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48a1cc6873 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -29,7 +29,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
      public final IdMap<T> registry;
      private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
@@ -32202,7 +32202,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final PalettedContainer.Strategy strategy;
      // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
  
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -77,6 +77,33 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              );
      }
  
@@ -32236,7 +32236,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      // Paper start - Anti-Xray - Add preset values
      @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); }
      public PalettedContainer(
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -113,6 +140,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              }
          }
          // Paper end
@@ -32244,7 +32244,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // Paper start - Anti-Xray - Add preset values
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -122,6 +150,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          this.registry = idList;
          this.strategy = paletteProvider;
          this.data = data;
@@ -32252,7 +32252,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private PalettedContainer(PalettedContainer<T> container, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -140,6 +169,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          this.registry = idList;
          this.data = this.createOrReuseData(null, 0);
          this.data.palette.idFor(object);
@@ -32260,7 +32260,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private PalettedContainer.Data<T> createOrReuseData(@Nullable PalettedContainer.Data<T> previousData, int bits) {
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -166,6 +196,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          data2.copyFrom(data.palette, data.storage);
          this.data = data2;
          this.addPresetValues();
@@ -32268,7 +32268,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return object == null ? -1 : data2.palette.idFor(object);
          // Paper end
      }
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -198,9 +229,12 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      }
  
      private synchronized T getAndSet(int index, T value) { // Paper - synchronize
@@ -32284,7 +32284,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void set(int x, int y, int z, T value) {
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -223,9 +257,11 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          return this.get(this.strategy.getIndex(x, y, z));
      }
  
@@ -32299,7 +32299,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -246,6 +282,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              buf.readLongArray(data.storage.getRaw());
              this.data = data;
              this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server)
@@ -32307,7 +32307,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          } finally {
              this.release();
          }
-@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -394,7 +431,44 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          void accept(T object, int count);
      }
  
@@ -32354,10 +32354,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              for (int i = 0; i < storage.getSize(); i++) {
                  T object = palette.valueFor(storage.get(i));
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 15e14f5d006389c823fa6baf8c1a4f22804d4aa8..759adee51bad99bd4bbee4f44247e8c8486cfbd6 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -0,0 +0,0 @@ public class ProtoChunk extends ChunkAccess {
+@@ -149,7 +149,7 @@ public class ProtoChunk extends ChunkAccess {
                      }
  
                      if (LightEngine.hasDifferentLightProperties(blockState, state)) {
@@ -32367,10 +32367,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      }
                  }
 diff --git a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce7895529918073c 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
-@@ -0,0 +0,0 @@ import net.minecraft.network.FriendlyByteBuf;
+@@ -8,12 +8,24 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import org.apache.commons.lang3.Validate;
  
@@ -32396,7 +32396,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public SingleValuePalette(IdMap<T> idList, PaletteResize<T> listener, List<T> entries) {
          this.registry = idList;
          this.resizeHandler = listener;
-@@ -0,0 +0,0 @@ public class SingleValuePalette<T> implements Palette<T> {
+@@ -33,6 +45,11 @@ public class SingleValuePalette<T> implements Palette<T> {
              return this.resizeHandler.onResize(1, object);
          } else {
              this.value = object;
@@ -32408,7 +32408,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              return 0;
          }
      }
-@@ -0,0 +0,0 @@ public class SingleValuePalette<T> implements Palette<T> {
+@@ -58,6 +75,11 @@ public class SingleValuePalette<T> implements Palette<T> {
      @Override
      public void read(FriendlyByteBuf buf) {
          this.value = this.registry.byIdOrThrow(buf.readVarInt());
@@ -32421,10 +32421,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d6755a0d931a 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
-@@ -0,0 +0,0 @@ public record ChunkPyramid(ImmutableList<ChunkStep> steps) {
+@@ -54,7 +54,7 @@ public record ChunkPyramid(ImmutableList<ChunkStep> steps) {
          .step(ChunkStatus.CARVERS, builder -> builder)
          .step(ChunkStatus.FEATURES, builder -> builder)
          .step(ChunkStatus.INITIALIZE_LIGHT, builder -> builder.setTask(ChunkStatusTasks::initializeLight))
@@ -32434,10 +32434,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          .step(ChunkStatus.FULL, builder -> builder.setTask(ChunkStatusTasks::full))
          .build();
 diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eaedbf4b976 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
-@@ -0,0 +0,0 @@ import net.minecraft.resources.ResourceLocation;
+@@ -11,7 +11,7 @@ import net.minecraft.resources.ResourceLocation;
  import net.minecraft.world.level.levelgen.Heightmap;
  import org.jetbrains.annotations.VisibleForTesting;
  
@@ -32446,7 +32446,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public static final int MAX_STRUCTURE_DISTANCE = 8;
      private static final EnumSet<Heightmap.Types> WORLDGEN_HEIGHTMAPS = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG);
      public static final EnumSet<Heightmap.Types> FINAL_HEIGHTMAPS = EnumSet.of(
-@@ -0,0 +0,0 @@ public class ChunkStatus {
+@@ -51,8 +51,68 @@ public class ChunkStatus {
          return list;
      }
  
@@ -32516,10 +32516,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.chunkType = chunkType;
          this.heightmapsAfter = heightMapTypes;
 diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 3d8a35d8cf29447ee7ac750dbc6ffcdb0f89b81b..9a3900e970f22892d8a3da8a28f922aa9b62765f 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-@@ -0,0 +0,0 @@ public class ChunkStatusTasks {
+@@ -152,7 +152,7 @@ public class ChunkStatusTasks {
                  chunk1 = protochunkextension.getWrapped();
              } else {
                  chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix
@@ -32528,7 +32528,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  });
                  generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false));
              }
-@@ -0,0 +0,0 @@ public class ChunkStatusTasks {
+@@ -168,7 +168,7 @@ public class ChunkStatusTasks {
          }, context.mainThreadExecutor());
      }
  
@@ -32537,7 +32537,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (!entities.isEmpty()) {
              // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
              world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
-@@ -0,0 +0,0 @@ public class ChunkStatusTasks {
+@@ -180,7 +180,7 @@ public class ChunkStatusTasks {
                  }
                  checkDupeUUID(world, entity); // Paper - duplicate uuid resolving
                  return !needsRemoval;
@@ -32547,10 +32547,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
 diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4fff0f3ed5 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
-@@ -0,0 +0,0 @@ import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
+@@ -11,9 +11,50 @@ import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
  import net.minecraft.world.level.chunk.ChunkAccess;
  import net.minecraft.world.level.chunk.ProtoChunk;
  
@@ -32604,7 +32604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public int getAccumulatedRadiusOf(ChunkStatus status) {
          return status == this.targetStatus ? 0 : this.accumulatedDependencies.getRadiusOf(status);
      }
-@@ -0,0 +0,0 @@ public record ChunkStep(
+@@ -39,6 +80,56 @@ public record ChunkStep(
          return chunk;
      }
  
@@ -32662,10 +32662,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          private final ChunkStatus status;
          @Nullable
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a067109819f 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.dimension.LevelStem;
+@@ -28,21 +28,31 @@ import net.minecraft.world.level.dimension.LevelStem;
  import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
  import net.minecraft.world.level.storage.DimensionDataStorage;
  
@@ -32701,7 +32701,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // CraftBukkit start
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -102,7 +112,9 @@ public class ChunkStorage implements AutoCloseable {
                      if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
                          LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
  
@@ -32711,7 +32711,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      }
                  }
  
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -169,7 +181,13 @@ public class ChunkStorage implements AutoCloseable {
      }
  
      public CompletableFuture<Optional<CompoundTag>> read(ChunkPos chunkPos) {
@@ -32726,7 +32726,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public CompletableFuture<Void> write(ChunkPos chunkPos, Supplier<CompoundTag> nbtSupplier) {
-@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
+@@ -185,29 +203,54 @@ public class ChunkStorage implements AutoCloseable {
          };
          // Paper end - guard against possible chunk pos desync
          this.handleLegacyStructureIndex(chunkPos);
@@ -32788,10 +32788,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  }
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index a0cbccd2cf1ac785745d86c42b6f58fb8bad7ffa..16ca1c8672e5f0a27f8a30498c754a81cdec5191 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-@@ -0,0 +0,0 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
+@@ -71,12 +71,12 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
          }
      }
  
@@ -32807,10 +32807,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 1f2997cf5367200084f32c437f77040c8c6a18e6..a8a9e59a9721a76e34f78c1baa5026e5fe1d2bda 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
-@@ -0,0 +0,0 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable {
+@@ -30,7 +30,7 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable {
      private static final Logger LOGGER = LogUtils.getLogger();
      private final AtomicBoolean shutdownRequested = new AtomicBoolean();
      private final PriorityConsecutiveExecutor consecutiveExecutor;
@@ -32820,10 +32820,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
      private static final int REGION_CACHE_SIZE = 1024;
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a129514760edb32a2 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -0,0 +0,0 @@ import net.minecraft.nbt.NbtIo; // Paper
+@@ -28,7 +28,7 @@ import net.minecraft.nbt.NbtIo; // Paper
  import net.minecraft.world.level.ChunkPos;
  import org.slf4j.Logger;
  
@@ -32832,7 +32832,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int SECTOR_BYTES = 4096;
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
+@@ -52,6 +52,21 @@ public class RegionFile implements AutoCloseable {
      @VisibleForTesting
      protected final RegionBitmap usedSectors;
  
@@ -32854,7 +32854,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
          this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
      }
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
+@@ -224,6 +239,16 @@ public class RegionFile implements AutoCloseable {
  
      @Nullable
      private DataInputStream createExternalChunkInputStream(ChunkPos pos, byte flags) throws IOException {
@@ -32871,7 +32871,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Path path = this.getExternalChunkPath(pos);
  
          if (!Files.isRegularFile(path, new LinkOption[0])) {
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
+@@ -514,10 +539,29 @@ public class RegionFile implements AutoCloseable {
  
      }
      // Paper end
@@ -32902,7 +32902,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          public ChunkBuffer(final ChunkPos chunkcoordintpair) {
              super(8096);
              super.write(0);
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
+@@ -534,7 +578,7 @@ public class RegionFile implements AutoCloseable {
  
              JvmProfiler.INSTANCE.onRegionFileWrite(RegionFile.this.info, this.pos, RegionFile.this.version, i);
              bytebuffer.putInt(0, i);
@@ -32912,10 +32912,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3306a32e3 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -0,0 +0,0 @@ import net.minecraft.nbt.StreamTagVisitor;
+@@ -17,7 +17,7 @@ import net.minecraft.nbt.StreamTagVisitor;
  import net.minecraft.util.ExceptionCollector;
  import net.minecraft.world.level.ChunkPos;
  
@@ -32924,7 +32924,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public static final String ANVIL_EXTENSION = ".mca";
      private static final int MAX_CACHE_SIZE = 256;
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
+@@ -26,33 +26,219 @@ public final class RegionFileStorage implements AutoCloseable {
      private final Path folder;
      private final boolean sync;
  
@@ -33160,7 +33160,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      // Paper start
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
+@@ -175,8 +361,14 @@ public final class RegionFileStorage implements AutoCloseable {
  
      }
  
@@ -33177,7 +33177,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          if (nbt == null) {
              regionfile.clear(pos);
-@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
+@@ -206,30 +398,37 @@ public final class RegionFileStorage implements AutoCloseable {
      }
  
      public void close() throws IOException {
@@ -33233,10 +33233,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e5106056728 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos;
+@@ -40,10 +40,10 @@ import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.LevelHeightAccessor;
  import org.slf4j.Logger;
  
@@ -33249,7 +33249,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Long2ObjectMap<Optional<R>> storage = new Long2ObjectOpenHashMap<>();
      private final LongLinkedOpenHashSet dirtyChunks = new LongLinkedOpenHashSet();
      private final Codec<P> codec;
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -57,6 +57,18 @@ public class SectionStorage<R, P> implements AutoCloseable {
      private final Long2ObjectMap<CompletableFuture<Optional<SectionStorage.PackedChunk<P>>>> pendingLoads = new Long2ObjectOpenHashMap<>();
      private final Object loadLock = new Object();
  
@@ -33268,7 +33268,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public SectionStorage(
          SimpleRegionStorage storageAccess,
          Codec<P> codec,
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -67,7 +79,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
          ChunkIOErrorReporter errorHandler,
          LevelHeightAccessor world
      ) {
@@ -33277,7 +33277,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.codec = codec;
          this.packer = serializer;
          this.unpacker = deserializer;
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -75,6 +87,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
          this.registryAccess = registryManager;
          this.errorReporter = errorHandler;
          this.levelHeightAccessor = world;
@@ -33285,7 +33285,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      protected void tick(BooleanSupplier shouldKeepTicking) {
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -188,64 +201,15 @@ public class SectionStorage<R, P> implements AutoCloseable {
      }
  
      private CompletableFuture<Optional<SectionStorage.PackedChunk<P>>> tryRead(ChunkPos chunkPos) {
@@ -33353,7 +33353,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private <T> Dynamic<T> writeChunk(ChunkPos chunkPos, DynamicOps<T> ops) {
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -281,7 +245,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
      protected void onSectionLoad(long pos) {
      }
  
@@ -33362,7 +33362,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          Optional<R> optional = this.storage.get(pos);
          if (optional != null && !optional.isEmpty()) {
              this.dirtyChunks.add(ChunkPos.asLong(SectionPos.x(pos), SectionPos.z(pos)));
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable {
+@@ -302,7 +266,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
  
      @Override
      public void close() throws IOException {
@@ -33372,10 +33372,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      static record PackedChunk<T>(Int2ObjectMap<T> sectionsByY, boolean versionChanged) {
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77fab826cd7 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -129,7 +129,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
              long j = nbt.getLong("InhabitedTime");
              ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
              UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
@@ -33384,7 +33384,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              DataResult dataresult;
              Logger logger;
              BlendingData.Packed blendingdata_d;
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -246,7 +246,17 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
                  DataLayer nibblearray = nbttagcompound3.contains("BlockLight", 7) ? new DataLayer(nbttagcompound3.getByteArray("BlockLight")) : null;
                  DataLayer nibblearray1 = nbttagcompound3.contains("SkyLight", 7) ? new DataLayer(nbttagcompound3.getByteArray("SkyLight")) : null;
  
@@ -33403,7 +33403,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              }
  
              // CraftBukkit - ChunkBukkitValues
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -254,6 +264,59 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
          }
      }
  
@@ -33463,7 +33463,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public ProtoChunk read(ServerLevel world, PoiManager poiStorage, RegionStorageInfo key, ChunkPos expectedPos) {
          if (!Objects.equals(expectedPos, this.chunkPos)) {
              SerializableChunkData.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{expectedPos, expectedPos, this.chunkPos});
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -275,7 +338,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
  
              if (serializablechunkdata_b.chunkSection != null) {
                  achunksection[world.getSectionIndexFromSectionY(serializablechunkdata_b.y)] = serializablechunkdata_b.chunkSection;
@@ -33472,7 +33472,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              }
  
              boolean flag2 = serializablechunkdata_b.blockLight != null;
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -352,7 +415,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
          }
  
          if (chunktype == ChunkType.LEVELCHUNK) {
@@ -33481,7 +33481,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          } else {
              ProtoChunk protochunk1 = (ProtoChunk) object;
              Iterator iterator2 = this.entities.iterator();
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -382,7 +445,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
                  protochunk1.setCarvingMask(new CarvingMask(this.carvingMask, ((ChunkAccess) object).getMinY()));
              }
  
@@ -33490,7 +33490,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
      }
  
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -405,24 +468,48 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
              throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk));
          } else {
              ChunkPos chunkcoordintpair = chunk.getPos();
@@ -33550,7 +33550,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              List<CompoundTag> list1 = new ArrayList(chunk.getBlockEntitiesPos().size());
              Iterator iterator = chunk.getBlockEntitiesPos().iterator();
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -521,8 +608,8 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
          Iterator iterator = this.sectionData.iterator();
  
          while (iterator.hasNext()) {
@@ -33561,7 +33561,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              LevelChunkSection chunksection = serializablechunkdata_b.chunkSection;
  
              if (chunksection != null) {
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -538,6 +625,19 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
                  nbttagcompound1.putByteArray("SkyLight", serializablechunkdata_b.skyLight.getData());
              }
  
@@ -33581,7 +33581,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              if (!nbttagcompound1.isEmpty()) {
                  nbttagcompound1.putByte("Y", (byte) serializablechunkdata_b.y);
                  nbttaglist.add(nbttagcompound1);
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -577,6 +677,14 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
              nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer);
          }
          // CraftBukkit end
@@ -33596,7 +33596,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return nbttagcompound;
      }
  
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+@@ -763,7 +871,67 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
          return nbttaglist;
      }
  
@@ -33666,10 +33666,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
 diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 578d270d5b7efb9ac8f5dde539170f6021e2b786..c5085ebf4e801837010f3750c5e89576bb0c27a5 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-@@ -0,0 +0,0 @@ import net.minecraft.util.datafix.DataFixTypes;
+@@ -14,7 +14,7 @@ import net.minecraft.util.datafix.DataFixTypes;
  import net.minecraft.world.level.ChunkPos;
  
  public class SimpleRegionStorage implements AutoCloseable {
@@ -33679,10 +33679,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final DataFixTypes dataFixType;
  
 diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2d9c609d9 100644
 --- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
 +++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
+@@ -9,52 +9,38 @@ import javax.annotation.Nullable;
  import net.minecraft.world.entity.Entity;
  
  public class EntityTickList {
@@ -33751,10 +33751,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  }
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 1fcc2b287ed723cf51720f80e68f18f4a15cf429..3f39d6c786d9dfdd9ad591e08ff05fcbb41a1df6 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 +++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-@@ -0,0 +0,0 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+@@ -86,7 +86,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
              this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk);
              return chunk;
@@ -33763,7 +33763,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private void doCreateBiomes(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) {
-@@ -0,0 +0,0 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+@@ -311,7 +311,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
              }
  
              return ichunkaccess1;
@@ -33773,10 +33773,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private ChunkAccess doFill(Blender blender, StructureManager structureAccessor, RandomState noiseConfig, ChunkAccess chunk, int minimumCellY, int cellHeight) {
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index c3586281c9594769593a6027ea0a78f7c76c0262..decdb275e83fa6244aa3a24458872b42c49d04ed 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 +++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-@@ -0,0 +0,0 @@ public class StructureCheck {
+@@ -47,8 +47,13 @@ public class StructureCheck {
      private final BiomeSource biomeSource;
      private final long seed;
      private final DataFixer fixerUpper;
@@ -33792,7 +33792,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public StructureCheck(
          ChunkScanAccess chunkIoWorker,
-@@ -0,0 +0,0 @@ public class StructureCheck {
+@@ -90,7 +95,7 @@ public class StructureCheck {
  
      public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) {
          long l = pos.toLong();
@@ -33801,7 +33801,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (object2IntMap != null) {
              return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures);
          } else {
-@@ -0,0 +0,0 @@ public class StructureCheck {
+@@ -100,9 +105,11 @@ public class StructureCheck {
              } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs
                  return StructureCheckResult.START_NOT_PRESENT;
              } else {
@@ -33816,7 +33816,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED;
              }
          }
-@@ -0,0 +0,0 @@ public class StructureCheck {
+@@ -228,15 +235,25 @@ public class StructureCheck {
      }
  
      private void storeFullResults(long pos, Object2IntMap<Structure> referencesByStructure) {
@@ -33847,10 +33847,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              referencesByStructure.computeInt(structure, (feature, references) -> references == null ? 1 : references + 1);
              return referencesByStructure;
 diff --git a/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede341297bc58dba 100644
 --- a/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
 +++ b/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.LightLayer;
+@@ -9,151 +9,111 @@ import net.minecraft.world.level.LightLayer;
  import net.minecraft.world.level.chunk.DataLayer;
  import net.minecraft.world.level.chunk.LightChunkGetter;
  
@@ -34053,10 +34053,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public int getLightSectionCount() {
 diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1 100644
 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
 +++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
-@@ -0,0 +0,0 @@ public abstract class FlowingFluid extends Fluid {
+@@ -55,6 +55,48 @@ public abstract class FlowingFluid extends Fluid {
      });
      private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
  
@@ -34105,7 +34105,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public FlowingFluid() {}
  
      @Override
-@@ -0,0 +0,0 @@ public abstract class FlowingFluid extends Fluid {
+@@ -246,65 +288,70 @@ public abstract class FlowingFluid extends Fluid {
          }
      }
  
@@ -34217,10 +34217,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      protected abstract boolean canConvertToSource(ServerLevel world);
 diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc1859379bfd805 100644
 --- a/src/main/java/net/minecraft/world/level/material/FluidState.java
 +++ b/src/main/java/net/minecraft/world/level/material/FluidState.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.state.properties.Property;
+@@ -22,12 +22,30 @@ import net.minecraft.world.level.block.state.properties.Property;
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.phys.shapes.VoxelShape;
  
@@ -34252,7 +34252,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public FluidState(Fluid fluid, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<FluidState> codec) {
          super(fluid, propertyMap, codec);
          this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty()
-@@ -0,0 +0,0 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
+@@ -38,11 +56,11 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
      public boolean isSource() {
@@ -34266,7 +34266,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public boolean isEmpty() {
-@@ -0,0 +0,0 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
+@@ -54,11 +72,11 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
      public float getOwnHeight() {
@@ -34280,7 +34280,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public boolean shouldRenderBackwardUpFace(BlockGetter world, BlockPos pos) {
-@@ -0,0 +0,0 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
+@@ -84,7 +102,7 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
      public boolean isRandomlyTicking() {
@@ -34289,7 +34289,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void randomTick(ServerLevel world, BlockPos pos, RandomSource random) {
-@@ -0,0 +0,0 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
+@@ -96,7 +114,12 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
      public BlockState createLegacyBlock() {
@@ -34304,10 +34304,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Nullable
 diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 5dc2674b537f4a61b2e21a21bdb2e8dc090d3a3c..6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130 100644
 --- a/src/main/java/net/minecraft/world/phys/AABB.java
 +++ b/src/main/java/net/minecraft/world/phys/AABB.java
-@@ -0,0 +0,0 @@ public class AABB {
+@@ -331,7 +331,7 @@ public class AABB {
      }
  
      @Nullable
@@ -34317,10 +34317,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      ) {
          return getDirection(
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12243fea80 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
-@@ -0,0 +0,0 @@ public class ArrayVoxelShape extends VoxelShape {
+@@ -20,7 +20,7 @@ public class ArrayVoxelShape extends VoxelShape {
          );
      }
  
@@ -34329,7 +34329,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          super(shape);
          int i = shape.getXSize() + 1;
          int j = shape.getYSize() + 1;
-@@ -0,0 +0,0 @@ public class ArrayVoxelShape extends VoxelShape {
+@@ -34,6 +34,7 @@ public class ArrayVoxelShape extends VoxelShape {
                  new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")
              );
          }
@@ -34338,10 +34338,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f28cbaf30b 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
-@@ -0,0 +0,0 @@ import java.util.BitSet;
+@@ -4,13 +4,13 @@ import java.util.BitSet;
  import net.minecraft.core.Direction;
  
  public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
@@ -34362,7 +34362,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
          super(sizeX, sizeY, sizeZ);
-@@ -0,0 +0,0 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
+@@ -150,47 +150,109 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
          return bitSetDiscreteVoxelShape;
      }
  
@@ -34500,10 +34500,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private boolean isZStripFull(int z1, int z2, int x, int y) {
          return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2);
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7bbe0f4b38 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
-@@ -0,0 +0,0 @@ import net.minecraft.util.Mth;
+@@ -7,6 +7,7 @@ import net.minecraft.util.Mth;
  public final class CubeVoxelShape extends VoxelShape {
      protected CubeVoxelShape(DiscreteVoxelShape voxels) {
          super(voxels);
@@ -34512,10 +34512,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63cbb420f2 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
-@@ -0,0 +0,0 @@ package net.minecraft.world.phys.shapes;
+@@ -3,12 +3,79 @@ package net.minecraft.world.phys.shapes;
  import net.minecraft.core.AxisCycle;
  import net.minecraft.core.Direction;
  
@@ -34597,10 +34597,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) {
              this.xSize = sizeX;
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
-@@ -0,0 +0,0 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
+@@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
  import it.unimi.dsi.fastutil.doubles.DoubleList;
  
  public class OffsetDoubleList extends AbstractDoubleList {
@@ -34612,10 +34612,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public OffsetDoubleList(DoubleList oldList, double offset) {
          this.delegate = oldList;
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f175394c0 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-@@ -0,0 +0,0 @@ public final class Shapes {
+@@ -16,9 +16,15 @@ public final class Shapes {
      public static final double EPSILON = 1.0E-7;
      public static final double BIG_EPSILON = 1.0E-6;
      private static final VoxelShape BLOCK = Util.make(() -> {
@@ -34634,7 +34634,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      });
      public static final VoxelShape INFINITY = box(
          Double.NEGATIVE_INFINITY,
-@@ -0,0 +0,0 @@ public final class Shapes {
+@@ -43,6 +49,30 @@ public final class Shapes {
          return BLOCK;
      }
  
@@ -34665,7 +34665,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
          if (!(minX > maxX) && !(minY > maxY) && !(minZ > maxZ)) {
              return create(minX, minY, minZ, maxX, maxY, maxZ);
-@@ -0,0 +0,0 @@ public final class Shapes {
+@@ -52,39 +82,42 @@ public final class Shapes {
      }
  
      public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
@@ -34734,7 +34734,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public static VoxelShape create(AABB box) {
-@@ -0,0 +0,0 @@ public final class Shapes {
+@@ -119,80 +152,54 @@ public final class Shapes {
          return join(first, second, BooleanOp.OR);
      }
  
@@ -34854,7 +34854,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      private static boolean joinIsNotEmpty(
-@@ -0,0 +0,0 @@ public final class Shapes {
+@@ -219,51 +226,116 @@ public final class Shapes {
          return maxDist;
      }
  
@@ -35006,10 +35006,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @VisibleForTesting
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a004617eb53a2 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
-@@ -0,0 +0,0 @@ public class SliceShape extends VoxelShape {
+@@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape {
          super(makeSlice(shape.shape, axis, sliceWidth));
          this.delegate = shape;
          this.axis = axis;
@@ -35018,10 +35018,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) {
 diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9cffc3071 100644
 --- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
 +++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.phys.AABB;
+@@ -15,61 +15,546 @@ import net.minecraft.world.phys.AABB;
  import net.minecraft.world.phys.BlockHitResult;
  import net.minecraft.world.phys.Vec3;
  
@@ -35597,7 +35597,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public VoxelShape move(Vec3 vec3d) {
-@@ -0,0 +0,0 @@ public abstract class VoxelShape {
+@@ -77,24 +562,96 @@ public abstract class VoxelShape {
      }
  
      public VoxelShape move(double x, double y, double z) {
@@ -35709,7 +35709,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public void forAllEdges(Shapes.DoubleLineConsumer consumer) {
-@@ -0,0 +0,0 @@ public abstract class VoxelShape {
+@@ -131,9 +688,24 @@ public abstract class VoxelShape {
      }
  
      public List<AABB> toAabbs() {
@@ -35737,7 +35737,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public double min(Direction.Axis axis, double from, double to) {
-@@ -0,0 +0,0 @@ public abstract class VoxelShape {
+@@ -155,46 +727,92 @@ public abstract class VoxelShape {
      }
  
      protected int findIndex(Direction.Axis axis, double coord) {
@@ -35860,7 +35860,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public VoxelShape getFaceShape(Direction facing) {
-@@ -0,0 +0,0 @@ public abstract class VoxelShape {
+@@ -216,20 +834,24 @@ public abstract class VoxelShape {
          }
      }
  
@@ -35897,7 +35897,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      protected boolean isCubeLike() {
-@@ -0,0 +0,0 @@ public abstract class VoxelShape {
+@@ -249,9 +871,30 @@ public abstract class VoxelShape {
              && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0, 1.0E-7);
      }
  
@@ -35931,10 +35931,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) {
          if (this.isEmpty()) {
 diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2f26d593d 100644
 --- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
 +++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
-@@ -0,0 +0,0 @@ import net.minecraft.core.BlockPos;
+@@ -17,7 +17,7 @@ import net.minecraft.core.BlockPos;
  import net.minecraft.nbt.ListTag;
  import net.minecraft.world.level.ChunkPos;
  
@@ -35943,7 +35943,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Queue<ScheduledTick<T>> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER);
      @Nullable
      private List<SavedTick<T>> pendingTicks;
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -25,6 +25,30 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      @Nullable
      private BiConsumer<LevelChunkTicks<T>, ScheduledTick<T>> onTickAdded;
  
@@ -35974,7 +35974,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public LevelChunkTicks() {
      }
  
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -49,7 +73,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      public ScheduledTick<T> poll() {
          ScheduledTick<T> scheduledTick = this.tickQueue.poll();
          if (scheduledTick != null) {
@@ -35983,7 +35983,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
  
          return scheduledTick;
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -58,7 +82,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      @Override
      public void schedule(ScheduledTick<T> orderedTick) {
          if (this.ticksPerPosition.add(orderedTick)) {
@@ -35992,7 +35992,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          }
      }
  
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -80,7 +104,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
          while (iterator.hasNext()) {
              ScheduledTick<T> scheduledTick = iterator.next();
              if (predicate.test(scheduledTick)) {
@@ -36001,7 +36001,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  this.ticksPerPosition.remove(scheduledTick);
              }
          }
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -110,6 +134,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      }
  
      public ListTag save(long time, Function<T, String> typeToNameFunction) {
@@ -36009,7 +36009,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ListTag listTag = new ListTag();
  
          for (SavedTick<T> savedTick : this.pack(time)) {
-@@ -0,0 +0,0 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
+@@ -121,6 +146,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
  
      public void unpack(long time) {
          if (this.pendingTicks != null) {
@@ -36018,10 +36018,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
              for (SavedTick<T> savedTick : this.pendingTicks) {
 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index f3ab07e44e2e912ea66c6148cfdb2a4a528741b2..c2bffe3450ee9f768e00a23ec09df74d7a06d49b 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
 +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-@@ -0,0 +0,0 @@ public class CraftChunk implements Chunk {
+@@ -83,6 +83,12 @@ public class CraftChunk implements Chunk {
      }
  
      public ChunkAccess getHandle(ChunkStatus chunkStatus) {
@@ -36034,7 +36034,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ChunkAccess chunkAccess = this.worldServer.getChunk(this.x, this.z, chunkStatus);
  
          // SPIGOT-7332: Get unwrapped extension
-@@ -0,0 +0,0 @@ public class CraftChunk implements Chunk {
+@@ -117,60 +123,12 @@ public class CraftChunk implements Chunk {
  
      @Override
      public boolean isEntitiesLoaded() {
@@ -36098,10 +36098,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 5b64111bc8baca45ecc7bfa384e5f8a004163a0b..97b5d6ba2b19a7c730730c74175a29157aed1840 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
 +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
+@@ -1448,7 +1448,7 @@ public final class CraftServer implements Server {
          // Paper - Put world into worldlist before initing the world; move up
  
          this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal);
@@ -36110,7 +36110,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
          return internal.getWorld();
-@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
+@@ -1493,7 +1493,7 @@ public final class CraftServer implements Server {
              }
  
              handle.getChunkSource().close(save);
@@ -36119,7 +36119,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              handle.convertable.close();
          } catch (Exception ex) {
              this.getLogger().log(Level.SEVERE, null, ex);
-@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
+@@ -2531,7 +2531,7 @@ public final class CraftServer implements Server {
  
      @Override
      public boolean isPrimaryThread() {
@@ -36129,10 +36129,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Paper start - Adventure
 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ca62105a0ff0aa69385cbf2018f8fe6a4bb69fd4..92d9f0ea8f7810ae20d3996f49aefa539b4bcb69 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
 +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -507,15 +507,17 @@ public class CraftWorld extends CraftRegionAccessor implements World {
          ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
          if (playerChunk == null) return false;
  
@@ -36156,7 +36156,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          return true;
      }
  
-@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -618,20 +620,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
      @Override
      public Collection<Plugin> getPluginChunkTickets(int x, int z) {
          DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
@@ -36178,7 +36178,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -639,7 +629,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
          Map<Plugin, ImmutableList.Builder<Chunk>> ret = new HashMap<>();
          DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
  
@@ -36187,7 +36187,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              long chunkKey = chunkTickets.getLongKey();
              SortedArraySet<Ticket<?>> tickets = chunkTickets.getValue();
  
-@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -1342,12 +1332,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
  
      @Override
      public int getViewDistance() {
@@ -36202,7 +36202,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public BlockMetadataStore getBlockMetadata() {
-@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -2486,17 +2476,20 @@ public class CraftWorld extends CraftRegionAccessor implements World {
  
      @Override
      public void setSimulationDistance(final int simulationDistance) {
@@ -36227,10 +36227,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Paper start - implement pointers
 diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index e9df37ff66700278bc94ea1e42135b92d97d03f7..6a647cab8b2e476987931486e290703b8726f2c7 100644
 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
 +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+@@ -3527,7 +3527,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
  
      @Override
      public void setViewDistance(final int viewDistance) {
@@ -36241,7 +36241,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+@@ -3537,7 +3539,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
  
      @Override
      public void setSimulationDistance(final int simulationDistance) {
@@ -36252,7 +36252,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      @Override
-@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+@@ -3547,7 +3551,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
  
      @Override
      public void setSendViewDistance(final int viewDistance) {
@@ -36264,10 +36264,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Paper start - entity effect API
 diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 39377ba0739f9660567b38475f101672f7b5e035..c025a4ff42257a4e84f0f9574b84f6987ef8ac11 100644
 --- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
 +++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-@@ -0,0 +0,0 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
+@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
              return ichunkaccess1;
          };
  
@@ -36277,10 +36277,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      @Override
 diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index 54c4434662d057a08800918641b95708cda61207..37458e8fd5d57acbf90a6bea4e66797cb07f69fa 100644
 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
 +++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-@@ -0,0 +0,0 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
+@@ -810,6 +810,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
      public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) {
          return this.handle.getChunkIfLoadedImmediately(x, z);
      }
@@ -36295,10 +36295,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  }
  
 diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ef2598760458833021ef1bee92137f42c9fe591f..1f23e775eba1c34e01145bd91b0ce26fed6ca9de 100644
 --- a/src/main/java/org/spigotmc/AsyncCatcher.java
 +++ b/src/main/java/org/spigotmc/AsyncCatcher.java
-@@ -0,0 +0,0 @@ public class AsyncCatcher
+@@ -9,7 +9,7 @@ public class AsyncCatcher
  
      public static void catchOp(String reason)
      {
@@ -36308,10 +36308,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
              throw new IllegalStateException( "Asynchronous " + reason + "!" );
 diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+index ad282d34919716b75acd10426cd071da9d064a51..529df2a41dd93d6e1505053bd04032dbf0cdaa31 100644
 --- a/src/main/java/org/spigotmc/WatchdogThread.java
 +++ b/src/main/java/org/spigotmc/WatchdogThread.java
-@@ -0,0 +0,0 @@ import java.util.logging.Logger;
+@@ -8,7 +8,7 @@ import java.util.logging.Logger;
  import net.minecraft.server.MinecraftServer;
  import org.bukkit.Bukkit;
  
@@ -36320,7 +36320,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  {
  
      private static WatchdogThread instance;
-@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
+@@ -115,6 +115,7 @@ public class WatchdogThread extends Thread
                  // Paper end - Different message for short timeout
                  log.log( Level.SEVERE, "------------------------------" );
                  log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper

From 0ed399bb4163d0733fc537be6fb3680cf2bedcad Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 09:44:10 -0800
Subject: [PATCH 257/285] Fix file paths in Moonrise patch

---
 .../0018-Moonrise-optimisation-patches.patch  | 918 +++++++++---------
 1 file changed, 459 insertions(+), 459 deletions(-)

diff --git a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
index 7f86f8c5a1..dea5778bd2 100644
--- a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
@@ -17,11 +17,11 @@ Currently includes:
 
 See https://github.com/Tuinity/Moonrise
 
-diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
+diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d93e7d855d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 @@ -0,0 +1,273 @@
 +package ca.spottedleaf.moonrise.common.misc;
 +
@@ -296,10 +296,10 @@ index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d9
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
+diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
 index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5b4b8abea 100644
---- a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
+--- a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
++++ b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
 @@ -2,11 +2,17 @@ package ca.spottedleaf.moonrise.common.util;
  
  import ca.spottedleaf.concurrentutil.util.Priority;
@@ -644,10 +644,10 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5
      }
  
      private ChunkSystem() {}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
+diff --git a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
 index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508656ed7ac 100644
---- a/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
+--- a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
++++ b/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
 @@ -9,7 +9,7 @@ import net.minecraft.world.level.levelgen.PositionalRandomFactory;
  /**
   * Avoid costly CAS of superclass
@@ -657,10 +657,10 @@ index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508
  
      private static final long MULTIPLIER = 25214903917L;
      private static final long ADDEND = 11L;
-diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..de22cfd2da4782072584d5140ce5567780d6feaa 100644
---- a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
-+++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 @@ -267,7 +267,7 @@ public final class PaperHooks implements PlatformHooks {
  
      @Override
@@ -670,11 +670,11 @@ index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..de22cfd2da4782072584d5140ce55677
      }
  
      @Override
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
+diff --git a/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4a30e800b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
++++ b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
 @@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.block_counting;
 +
@@ -686,11 +686,11 @@ index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4
 +    public Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
+diff --git a/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0d1443a113c07d7655e7b927a899447f70db8fa9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
++++ b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingChunkSection.java
 @@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.block_counting;
 +
@@ -703,11 +703,11 @@ index 0000000000000000000000000000000000000000..0d1443a113c07d7655e7b927a899447f
 +    public ShortList moonrise$getTickingBlockList();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
+diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb923a2d9a7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
++++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccess.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
 +
@@ -721,11 +721,11 @@ index 0000000000000000000000000000000000000000..89e75b454695e174c5619104eeb15eb9
 +
 +    public void moonrise$setById(final T[] values);
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
+diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..01da52b9e8a786824f199a057b62ce0431ecbc43
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
++++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/PropertyAccessStateHolder.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
 +
@@ -734,11 +734,11 @@ index 0000000000000000000000000000000000000000..01da52b9e8a786824f199a057b62ce04
 +    public long moonrise$getTableIndex();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
+diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..866f38eb0f379ffbe2888023a7d1c290f521a231
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
++++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java
 @@ -0,0 +1,230 @@
 +package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util;
 +
@@ -970,11 +970,11 @@ index 0000000000000000000000000000000000000000..866f38eb0f379ffbe2888023a7d1c290
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java b/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..44bb25554634af2ec0b2e9b3d9231304d5dff034
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystemConverters.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system;
 +
@@ -1015,11 +1015,11 @@ index 0000000000000000000000000000000000000000..44bb25554634af2ec0b2e9b3d9231304
 +
 +    private ChunkSystemConverters() {}
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java b/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c7da23900228aab3a5673eb5adfada5091140319
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/entity/ChunkSystemEntity.java
 @@ -0,0 +1,44 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.entity;
 +
@@ -1065,11 +1065,11 @@ index 0000000000000000000000000000000000000000..c7da23900228aab3a5673eb5adfada50
 +
 +    public boolean moonrise$hasAnyPlayerPassengers();
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a814512fcfb85312474ae2c2c21443843bf57831
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java
 @@ -0,0 +1,31 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io;
 +
@@ -1102,11 +1102,11 @@ index 0000000000000000000000000000000000000000..a814512fcfb85312474ae2c2c2144384
 +            final int chunkX, final int chunkZ, final MoonriseRegionFileIO.RegionDataController.ReadData readData
 +    ) throws IOException;
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1acea58838f057ab87efd103cbecb6f5aeaef393
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java
 @@ -0,0 +1,1700 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io;
 +
@@ -2808,11 +2808,11 @@ index 0000000000000000000000000000000000000000..1acea58838f057ab87efd103cbecb6f5
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a36ab89f5c37f5f9ab0152f087bb4cf3560f8581
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/ChunkDataController.java
 @@ -0,0 +1,50 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
@@ -2864,11 +2864,11 @@ index 0000000000000000000000000000000000000000..a36ab89f5c37f5f9ab0152f087bb4cf3
 +        return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..828c868f68c2a20bf90d0f7ec253fdeb591f15f6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/EntityDataController.java
 @@ -0,0 +1,73 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
@@ -2943,11 +2943,11 @@ index 0000000000000000000000000000000000000000..828c868f68c2a20bf90d0f7ec253fdeb
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bd0d782852f9cfe5bc0b5339ecf4d82c10332ec9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/datacontroller/PoiDataController.java
 @@ -0,0 +1,45 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller;
 +
@@ -2994,11 +2994,11 @@ index 0000000000000000000000000000000000000000..bd0d782852f9cfe5bc0b5339ecf4d82c
 +        return ((ChunkSystemRegionFileStorage)this.getCache()).moonrise$finishRead(chunkX, chunkZ, readData);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..47a4d3376d08dde94a39254bec21473ff27f53e6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemChunkMap.java
 @@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
@@ -3010,11 +3010,11 @@ index 0000000000000000000000000000000000000000..47a4d3376d08dde94a39254bec21473f
 +    public void moonrise$writeFinishCallback(final ChunkPos pos) throws IOException;
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5d4d650186b18eb00782429d53d861564d8e4ba9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java
 @@ -0,0 +1,33 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
@@ -3049,11 +3049,11 @@ index 0000000000000000000000000000000000000000..5d4d650186b18eb00782429d53d86156
 +    public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0b58701342d573fa43cdd06681534854a0e51d77
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevelReader.java
 @@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
@@ -3065,11 +3065,11 @@ index 0000000000000000000000000000000000000000..0b58701342d573fa43cdd06681534854
 +    public ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..c278f8ef806f0b45c28cc3040c7db052cb51e053
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java
 @@ -0,0 +1,62 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level;
 +
@@ -3133,11 +3133,11 @@ index 0000000000000000000000000000000000000000..c278f8ef806f0b45c28cc3040c7db052
 +
 +    public ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getEntityTickingChunks();
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8b9dc582627b46843f4b5ea6f8c3df2d8cac46fa
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java
 @@ -0,0 +1,21 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
@@ -3160,11 +3160,11 @@ index 0000000000000000000000000000000000000000..8b9dc582627b46843f4b5ea6f8c3df2d
 +        return --this.referenceCount;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7d049d750df88762566f13a9c4fc7574a2df4825
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkHolder.java
 @@ -0,0 +1,26 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
@@ -3192,11 +3192,11 @@ index 0000000000000000000000000000000000000000..7d049d750df88762566f13a9c4fc7574
 +    public LevelChunk moonrise$getFullChunk();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f4bc44bb266763345c4e6f859c89352c769a104d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemChunkStatus.java
 @@ -0,0 +1,26 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
@@ -3224,11 +3224,11 @@ index 0000000000000000000000000000000000000000..f4bc44bb266763345c4e6f859c89352c
 +    public AtomicBoolean moonrise$getWarnedAboutNoImmediateComplete();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..aacd543f03b35908011d0c2891e978cc093ebcf5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemDistanceManager.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
@@ -3242,11 +3242,11 @@ index 0000000000000000000000000000000000000000..aacd543f03b35908011d0c2891e978cc
 +    public ChunkHolderManager moonrise$getChunkHolderManager();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5b092bca7027e37aeee8f4b852ad896dd0d5febc
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java
 @@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
 +
@@ -3261,11 +3261,11 @@ index 0000000000000000000000000000000000000000..5b092bca7027e37aeee8f4b852ad896d
 +    public void moonrise$setChunkAndHolder(final ServerChunkCache.ChunkAndHolder holder);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7aea4e343581b977d11af90f9f65eac3532eade1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
 @@ -0,0 +1,569 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
 +
@@ -3836,11 +3836,11 @@ index 0000000000000000000000000000000000000000..7aea4e343581b977d11af90f9f65eac3
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7554c109c35397bc1a43dd80e87764fd78645bbf
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java
 @@ -0,0 +1,1002 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
 +
@@ -4844,11 +4844,11 @@ index 0000000000000000000000000000000000000000..7554c109c35397bc1a43dd80e87764fd
 +        public void onRemove(final Entity.RemovalReason reason) {}
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a038215156a163b0b1cbc870ada5b4ac85ed1335
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/client/ClientEntityLookup.java
 @@ -0,0 +1,129 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.client;
 +
@@ -4979,11 +4979,11 @@ index 0000000000000000000000000000000000000000..a038215156a163b0b1cbc870ada5b4ac
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2ff58cf753c60913ee73aae015182e9c5560d529
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/dfl/DefaultEntityLookup.java
 @@ -0,0 +1,114 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl;
 +
@@ -5099,11 +5099,11 @@ index 0000000000000000000000000000000000000000..2ff58cf753c60913ee73aae015182e9c
 +        public void onSectionChange(final Entity entity) {}
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e069bf1bf5
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
 @@ -0,0 +1,116 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server;
 +
@@ -5221,11 +5221,11 @@ index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e0
 +        return ChunkSystem.screenEntity(this.serverWorld, entity, fromDisk, event);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..458d1fc5e1222912512e6c59b56f6fca347d9ee9
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
 @@ -0,0 +1,17 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
@@ -5244,11 +5244,11 @@ index 0000000000000000000000000000000000000000..458d1fc5e1222912512e6c59b56f6fca
 +    public void moonrise$checkConsistency(final ChunkAccess chunk);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..89b956b8fdf1a0d862a843104511005e2990a897
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiSection.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
@@ -5262,11 +5262,11 @@ index 0000000000000000000000000000000000000000..89b956b8fdf1a0d862a843104511005e
 +    public Optional<PoiSection> moonrise$asOptional();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bbf9d6c1c9525d97160806819a57be03eca290f1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java
 @@ -0,0 +1,204 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.poi;
 +
@@ -5472,11 +5472,11 @@ index 0000000000000000000000000000000000000000..bbf9d6c1c9525d97160806819a57be03
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..524752744e37a2db0e3ea089468bdf497129bfef
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/storage/ChunkSystemSectionStorage.java
 @@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.storage;
 +
@@ -5491,11 +5491,11 @@ index 0000000000000000000000000000000000000000..524752744e37a2db0e3ea089468bdf49
 +    public void moonrise$close() throws IOException;
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..003a857e70ead858e8437e3c1bfaf22f4daba0df
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/ChunkSystemServerPlayer.java
 @@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.player;
 +
@@ -5512,11 +5512,11 @@ index 0000000000000000000000000000000000000000..003a857e70ead858e8437e3c1bfaf22f
 +    public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..dd2509996bfd08e8c3f9f2be042229eac6d7692d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
 @@ -0,0 +1,1092 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.player;
 +
@@ -6610,11 +6610,11 @@ index 0000000000000000000000000000000000000000..dd2509996bfd08e8c3f9f2be042229ea
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java
 @@ -0,0 +1,144 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.queue;
 +
@@ -6761,11 +6761,11 @@ index 0000000000000000000000000000000000000000..7eafc5b7cba23d8dec92ecc1050afe3f
 +    }
 +}
 \ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0fd989d97d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
 @@ -0,0 +1,1457 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
@@ -8224,11 +8224,11 @@ index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0f
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..67532b85073b7978254a0b04caadfe822679e61f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java
 @@ -0,0 +1,1055 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
@@ -9285,11 +9285,11 @@ index 0000000000000000000000000000000000000000..67532b85073b7978254a0b04caadfe82
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48fbb5e1a4c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
 @@ -0,0 +1,1998 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
@@ -11289,11 +11289,11 @@ index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48f
 +        return ret;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6b468c621b74449a6218391f6477cf63cfc98c7c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/PriorityHolder.java
 @@ -0,0 +1,215 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
@@ -11510,11 +11510,11 @@ index 0000000000000000000000000000000000000000..6b468c621b74449a6218391f6477cf63
 +
 +    protected abstract void raisePriorityScheduled(final Priority priority);
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..310a8f80debadd64c2d962ebf83b7d0505ce6e42
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java
 @@ -0,0 +1,1457 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
@@ -12973,11 +12973,11 @@ index 0000000000000000000000000000000000000000..310a8f80debadd64c2d962ebf83b7d05
 +    }
 +     */
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5f4b99d8c5453f8ad2e600a57ea4e7dafa2d45f8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java
 @@ -0,0 +1,729 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor;
 +
@@ -13708,11 +13708,11 @@ index 0000000000000000000000000000000000000000..5f4b99d8c5453f8ad2e600a57ea4e7da
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java
 @@ -0,0 +1,151 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -13865,11 +13865,11 @@ index 0000000000000000000000000000000000000000..6ab353b0d2465c3680bb3c8d0852ba0f
 +        this.convertToFullTask.raisePriority(priority);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4538ccfaea83d217ed85eaf16e82393c7f286489
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLightTask.java
 @@ -0,0 +1,181 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -14052,11 +14052,11 @@ index 0000000000000000000000000000000000000000..4538ccfaea83d217ed85eaf16e82393c
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1440c9e2b106616884edcb20201113320817ed9f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkLoadTask.java
 @@ -0,0 +1,494 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -14552,11 +14552,11 @@ index 0000000000000000000000000000000000000000..1440c9e2b106616884edcb2020111332
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..002ee365aa70d8e6a6e6bd5c95988bd17db4395a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java
 @@ -0,0 +1,101 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -14660,11 +14660,11 @@ index 0000000000000000000000000000000000000000..002ee365aa70d8e6a6e6bd5c95988bd1
 +    }
 +}
 \ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..25d8da4773dcee5096053e7e3788bfc224d705a7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java
 @@ -0,0 +1,218 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -14884,11 +14884,11 @@ index 0000000000000000000000000000000000000000..25d8da4773dcee5096053e7e3788bfc2
 +        this.generateTask.raisePriority(priority);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..bdcd1879457bafcca4e76523aac0555968f37c0b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java
 @@ -0,0 +1,674 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task;
 +
@@ -15564,11 +15564,11 @@ index 0000000000000000000000000000000000000000..bdcd1879457bafcca4e76523aac05559
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java b/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cb6af3712bf9f6f6b8f7a459c309c75dabe83a50
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.server;
 +
@@ -15579,11 +15579,11 @@ index 0000000000000000000000000000000000000000..cb6af3712bf9f6f6b8f7a459c309c75d
 +    public void moonrise$executeMidTickTasks();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java b/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ea759ce6f10f2a5a4e107ab7528030fe931ba223
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/status/ChunkSystemChunkStep.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.status;
 +
@@ -15594,11 +15594,11 @@ index 0000000000000000000000000000000000000000..ea759ce6f10f2a5a4e107ab7528030fe
 +    public ChunkStatus moonrise$getRequiredStatusAtRadius(final int radius);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..51c126735ace8fdde89ad97b5cab62f244212db0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
@@ -15612,11 +15612,11 @@ index 0000000000000000000000000000000000000000..51c126735ace8fdde89ad97b5cab62f2
 +
 +    public void moonrise$write(final RegionFile regionFile) throws IOException;
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..129a35ff2db5b3bb6736810fc180796ce55e1875
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkStorage.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
@@ -15627,11 +15627,11 @@ index 0000000000000000000000000000000000000000..129a35ff2db5b3bb6736810fc180796c
 +    public RegionFileStorage moonrise$getRegionStorage();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..3bd1b59250dbab15097a64d515999b278636795a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemRegionFile.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.storage;
 +
@@ -15645,11 +15645,11 @@ index 0000000000000000000000000000000000000000..3bd1b59250dbab15097a64d515999b27
 +    public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final CompoundTag data, final ChunkPos pos) throws IOException;
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java b/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..786e6ad17cd6216ef0aadaa7cf10044a0c19c933
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/ticket/ChunkSystemTicket.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.ticket;
 +
@@ -15660,11 +15660,11 @@ index 0000000000000000000000000000000000000000..786e6ad17cd6216ef0aadaa7cf10044a
 +    public void moonrise$setRemoveDelay(final long removeDelay);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java b/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2add7fd15a2210286aeb9af5024263333340d34c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/ticks/ChunkSystemLevelChunkTicks.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.ticks;
 +
@@ -15675,11 +15675,11 @@ index 0000000000000000000000000000000000000000..2add7fd15a2210286aeb9af502426333
 +    public void moonrise$clearDirty();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java b/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ce3bb903c9ccb7efa0f004cf79b291dcb1cb7a23
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/util/ChunkSystemSortedArraySet.java
 @@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util;
 +
@@ -15696,11 +15696,11 @@ index 0000000000000000000000000000000000000000..ce3bb903c9ccb7efa0f004cf79b291dc
 +    public T moonrise$removeAndGet(final T object);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..93fd23027c00cef76562098306737272fda1350a
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java
 @@ -0,0 +1,321 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util;
 +
@@ -16023,11 +16023,11 @@ index 0000000000000000000000000000000000000000..93fd23027c00cef76562098306737272
 +        return ret.elements();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java b/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7ef3dcca89ed7578c6c0f5565131889110063056
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/util/stream/ExternalChunkStreamMarker.java
 @@ -0,0 +1,37 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.util.stream;
 +
@@ -16066,11 +16066,11 @@ index 0000000000000000000000000000000000000000..7ef3dcca89ed7578c6c0f55651318891
 +        super(getWrapped(in));
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java b/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ea6b6ed27b212719feb31610faac974899688839
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemEntityGetter.java
 @@ -0,0 +1,12 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.world;
 +
@@ -16084,11 +16084,11 @@ index 0000000000000000000000000000000000000000..ea6b6ed27b212719feb31610faac9748
 +    public List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java b/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4b9e2fa963c14f65f15407c1814c543c2999ea32
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/world/ChunkSystemServerChunkCache.java
 @@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.world;
 +
@@ -16101,11 +16101,11 @@ index 0000000000000000000000000000000000000000..4b9e2fa963c14f65f15407c1814c543c
 +    public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e97e7d276faf055c89207385d3820debffb06463
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
@@ -16114,11 +16114,11 @@ index 0000000000000000000000000000000000000000..e97e7d276faf055c89207385d3820deb
 +    public static final int PLAYER_SPAWN_TRACK_RANGE = 8;
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f28fd0e01e2bdda0daf9d775e514a7253d32d8d0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java
 @@ -0,0 +1,16 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
@@ -16136,11 +16136,11 @@ index 0000000000000000000000000000000000000000..f28fd0e01e2bdda0daf9d775e514a725
 +                                      final boolean oldIgnore, final boolean newIgnore);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..6af03fd7807d4c71dbf85028d18dc850978ef429
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickServerLevel.java
 @@ -0,0 +1,19 @@
 +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
 +
@@ -16161,11 +16161,11 @@ index 0000000000000000000000000000000000000000..6af03fd7807d4c71dbf85028d18dc850
 +    public void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..e04bd54744335fb5398c6e4f7ce8b981f35bfb7d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
 @@ -0,0 +1,2183 @@
 +package ca.spottedleaf.moonrise.patches.collisions;
 +
@@ -18350,11 +18350,11 @@ index 0000000000000000000000000000000000000000..e04bd54744335fb5398c6e4f7ce8b981
 +        throw new RuntimeException();
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..35c8aaf0bfa42717f45eed1d1072e1614874de91
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
 @@ -0,0 +1,28 @@
 +package ca.spottedleaf.moonrise.patches.collisions;
 +
@@ -18384,11 +18384,11 @@ index 0000000000000000000000000000000000000000..35c8aaf0bfa42717f45eed1d1072e161
 +        this.outOfWorld = outOfWorld;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..a38ab583200ebf68ca68fdddf2d12077720b72b7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.moonrise.patches.collisions.block;
 +
@@ -18419,11 +18419,11 @@ index 0000000000000000000000000000000000000000..a38ab583200ebf68ca68fdddf2d12077
 +
 +    public VoxelShape moonrise$getConstantContextCollisionShape();
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5a6b16be4b8c0cc92d017bc592bc4818dba17da7
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
 @@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
@@ -18435,11 +18435,11 @@ index 0000000000000000000000000000000000000000..5a6b16be4b8c0cc92d017bc592bc4818
 +        boolean isEmpty, boolean hasSingleAABB
 +) {
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..9d33ead3a97d86b371e4d9ad9fed80d789bed844
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
 @@ -0,0 +1,39 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
@@ -18480,11 +18480,11 @@ index 0000000000000000000000000000000000000000..9d33ead3a97d86b371e4d9ad9fed80d7
 +        return new CachedToAABBs(cache.aabbs, true, resX, resY, resZ);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..07fe5e02c2d0a27d2fe37bb45761654dc2d02e5d
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
@@ -18493,11 +18493,11 @@ index 0000000000000000000000000000000000000000..07fe5e02c2d0a27d2fe37bb45761654d
 +    public CachedShapeData moonrise$getOrCreateCachedShapeData();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..05d7b3f9d8659c259f3ed0537c57e6e43eb6e288
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
 @@ -0,0 +1,40 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
@@ -18539,11 +18539,11 @@ index 0000000000000000000000000000000000000000..05d7b3f9d8659c259f3ed0537c57e6e4
 +    // uses a cache internally
 +    public VoxelShape moonrise$orUnoptimized(final VoxelShape other);
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..44831fc18efb7534dc6e4822f3c9b5cdc4dcc33e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
 @@ -0,0 +1,10 @@
 +package ca.spottedleaf.moonrise.patches.collisions.shape;
 +
@@ -18555,11 +18555,11 @@ index 0000000000000000000000000000000000000000..44831fc18efb7534dc6e4822f3c9b5cd
 +) {
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java b/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f62359e5d6aa9a9cdb015441dbdb6182dc302f02
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.collisions.util;
 +
@@ -18570,11 +18570,11 @@ index 0000000000000000000000000000000000000000..f62359e5d6aa9a9cdb015441dbdb6182
 +    public int moonrise$uniqueId();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
+diff --git a/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..cf9ffdeff6bf0b62a45f7a44dbfe0dd7d17dc4f4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
++++ b/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.collisions.util;
 +
@@ -18583,11 +18583,11 @@ index 0000000000000000000000000000000000000000..cf9ffdeff6bf0b62a45f7a44dbfe0dd7
 +
 +public record FluidOcclusionCacheKey(BlockState first, BlockState second, Direction direction, boolean result) {
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
+diff --git a/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..5f5734c00ce8245a1ff69b2d4c3036579d5392e0
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
++++ b/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
 @@ -0,0 +1,11 @@
 +package ca.spottedleaf.moonrise.patches.entity_tracker;
 +
@@ -18600,11 +18600,11 @@ index 0000000000000000000000000000000000000000..5f5734c00ce8245a1ff69b2d4c303657
 +    public void moonrise$setTrackedEntity(final ChunkMap.TrackedEntity trackedEntity);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
+diff --git a/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java b/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8e7472157a98de607c03769a91f64c8369fd3ea6
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
++++ b/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerTrackedEntity.java
 @@ -0,0 +1,15 @@
 +package ca.spottedleaf.moonrise.patches.entity_tracker;
 +
@@ -18621,11 +18621,11 @@ index 0000000000000000000000000000000000000000..8e7472157a98de607c03769a91f64c83
 +    public boolean moonrise$hasPlayers();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
+diff --git a/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java b/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4a7abd239a9c59aa98947e7993962d75e9051902
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
++++ b/ca/spottedleaf/moonrise/patches/fast_palette/FastPalette.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.fast_palette;
 +
@@ -18636,11 +18636,11 @@ index 0000000000000000000000000000000000000000..4a7abd239a9c59aa98947e7993962d75
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
+diff --git a/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java b/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4503f3495846a7d7ed082b9e24636044e4fbccd1
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
++++ b/ca/spottedleaf/moonrise/patches/fast_palette/FastPaletteData.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.fast_palette;
 +
@@ -18651,22 +18651,22 @@ index 0000000000000000000000000000000000000000..4503f3495846a7d7ed082b9e24636044
 +    public void moonrise$setPalette(final T[] palette);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
+diff --git a/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java b/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..107c97089354edd35f330582f5e0c8a18e792a6e
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
++++ b/ca/spottedleaf/moonrise/patches/fluid/FluidFluidState.java
 @@ -0,0 +1,5 @@
 +package ca.spottedleaf.moonrise.patches.fluid;
 +
 +public interface FluidFluidState {
 +    public void moonrise$initCaches();
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
+diff --git a/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java b/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..540c14a6d2c216cd3ef2a9c4056e15712bf8cb8c
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
++++ b/ca/spottedleaf/moonrise/patches/getblock/GetBlockChunk.java
 @@ -0,0 +1,9 @@
 +package ca.spottedleaf.moonrise.patches.getblock;
 +
@@ -18677,11 +18677,11 @@ index 0000000000000000000000000000000000000000..540c14a6d2c216cd3ef2a9c4056e1571
 +    public BlockState moonrise$getBlock(final int x, final int y, final int z);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java b/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8e6d79b7c10ef25f5478b72c53c555423d615a2f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/blockstate/StarlightAbstractBlockState.java
 @@ -0,0 +1,7 @@
 +package ca.spottedleaf.moonrise.patches.starlight.blockstate;
 +
@@ -18690,11 +18690,11 @@ index 0000000000000000000000000000000000000000..8e6d79b7c10ef25f5478b72c53c55542
 +    public boolean starlight$isConditionallyFullOpaque();
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java b/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..ed80017c8f257b981d626a37ffc5480d9b326558
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/chunk/StarlightChunk.java
 @@ -0,0 +1,18 @@
 +package ca.spottedleaf.moonrise.patches.starlight.chunk;
 +
@@ -18714,11 +18714,11 @@ index 0000000000000000000000000000000000000000..ed80017c8f257b981d626a37ffc5480d
 +    public boolean[] starlight$getBlockEmptinessMap();
 +    public void starlight$setBlockEmptinessMap(final boolean[] emptinessMap);
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java b/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..fa7b784a89626e8528c249d7889a598bd7ee3d49
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/BlockStarLightEngine.java
 @@ -0,0 +1,280 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -19000,11 +19000,11 @@ index 0000000000000000000000000000000000000000..fa7b784a89626e8528c249d7889a598b
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..4ca68a903e67606fc4ef0bfa9862a73797121c8b
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
 @@ -0,0 +1,440 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -19446,11 +19446,11 @@ index 0000000000000000000000000000000000000000..4ca68a903e67606fc4ef0bfa9862a737
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java b/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f9aef289e9a2d6f63c98c72c56ef32b8793f57f4
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/SkyStarLightEngine.java
 @@ -0,0 +1,681 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -20133,11 +20133,11 @@ index 0000000000000000000000000000000000000000..f9aef289e9a2d6f63c98c72c56ef32b8
 +        return startY;
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8aeb5fb87f94a35659347a09a638420699b52a6f
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightEngine.java
 @@ -0,0 +1,1438 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -21577,11 +21577,11 @@ index 0000000000000000000000000000000000000000..8aeb5fb87f94a35659347a09a6384206
 +        this.performLightIncrease(lightAccess);
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..571db5f9bf94745a8afe2cd313e593fb15db5e37
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
 @@ -0,0 +1,931 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -22514,11 +22514,11 @@ index 0000000000000000000000000000000000000000..571db5f9bf94745a8afe2cd313e593fb
 +        }
 +    }
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..7fe59ab70557aa6a484a02db2b2007fdd9e4bbb8
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java
 @@ -0,0 +1,29 @@
 +package ca.spottedleaf.moonrise.patches.starlight.light;
 +
@@ -22549,11 +22549,11 @@ index 0000000000000000000000000000000000000000..7fe59ab70557aa6a484a02db2b2007fd
 +    }
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java b/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..40d004afdc6449530f5bb2d7c7638b8ee3e3a577
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/storage/StarlightSectionData.java
 @@ -0,0 +1,13 @@
 +package ca.spottedleaf.moonrise.patches.starlight.storage;
 +
@@ -22568,11 +22568,11 @@ index 0000000000000000000000000000000000000000..40d004afdc6449530f5bb2d7c7638b8e
 +    public void starlight$setSkyLightState(final int state);
 +
 +}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
+diff --git a/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc521d4bc742
 --- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java
 @@ -0,0 +1,189 @@
 +package ca.spottedleaf.moonrise.patches.starlight.util;
 +
@@ -22763,10 +22763,10 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +
 +    private SaveUtil() {}
 +}
-diff --git a/src/main/java/io/papermc/paper/FeatureHooks.java b/src/main/java/io/papermc/paper/FeatureHooks.java
+diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
 index a6779295bff446ee79e7c9d41e405447becc2966..efc7f4071655201c59c912e9c84e35a8da66e34c 100644
---- a/src/main/java/io/papermc/paper/FeatureHooks.java
-+++ b/src/main/java/io/papermc/paper/FeatureHooks.java
+--- a/io/papermc/paper/FeatureHooks.java
++++ b/io/papermc/paper/FeatureHooks.java
 @@ -1,6 +1,8 @@
  package io.papermc.paper;
  
@@ -22789,11 +22789,11 @@ index a6779295bff446ee79e7c9d41e405447becc2966..efc7f4071655201c59c912e9c84e35a8
      }
  
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
-diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
+diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
++++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
 @@ -0,0 +1,277 @@
 +package io.papermc.paper.command.subcommands;
 +
@@ -23072,11 +23072,11 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd
 +    }
 +
 +}
-diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
+diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de140e1a172e
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
++++ b/io/papermc/paper/command/subcommands/FixLightCommand.java
 @@ -0,0 +1,116 @@
 +package io.papermc.paper.command.subcommands;
 +
@@ -23194,11 +23194,11 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14
 +        sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks")));
 +    }
 +}
-diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java
+diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..8424cf9d4617b4732d44cc460d25b04481068989
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java
++++ b/io/papermc/paper/threadedregions/TickRegions.java
 @@ -0,0 +1,10 @@
 +package io.papermc.paper.threadedregions;
 +
@@ -23210,10 +23210,10 @@ index 0000000000000000000000000000000000000000..8424cf9d4617b4732d44cc460d25b044
 +    }
 +
 +}
-diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
+diff --git a/net/minecraft/core/Direction.java b/net/minecraft/core/Direction.java
 index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f7507519a39b 100644
---- a/src/main/java/net/minecraft/core/Direction.java
-+++ b/src/main/java/net/minecraft/core/Direction.java
+--- a/net/minecraft/core/Direction.java
++++ b/net/minecraft/core/Direction.java
 @@ -28,7 +28,7 @@ import org.joml.Quaternionf;
  import org.joml.Vector3f;
  import org.joml.Vector4f;
@@ -23319,10 +23319,10 @@ index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f750
 +    }
 +    // Paper end - optimise collisions
  }
-diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java
+diff --git a/net/minecraft/core/MappedRegistry.java b/net/minecraft/core/MappedRegistry.java
 index 063630c1ffcce099139c59d598fc5a210e21f640..a61153c5d99bdc26f37a10f33baf839e943e17e1 100644
---- a/src/main/java/net/minecraft/core/MappedRegistry.java
-+++ b/src/main/java/net/minecraft/core/MappedRegistry.java
+--- a/net/minecraft/core/MappedRegistry.java
++++ b/net/minecraft/core/MappedRegistry.java
 @@ -50,6 +50,19 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
          return this.getTags();
      }
@@ -23351,10 +23351,10 @@ index 063630c1ffcce099139c59d598fc5a210e21f640..a61153c5d99bdc26f37a10f33baf839e
              return reference;
          }
      }
-diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
+diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
 index 731bdabd53fd4a3d17494f26781223097a5d6e16..42d46c7a7437bea5335a23cbee5708ac57131474 100644
---- a/src/main/java/net/minecraft/server/Main.java
-+++ b/src/main/java/net/minecraft/server/Main.java
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
 @@ -322,6 +322,7 @@ public class Main {
  
              convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
@@ -23363,10 +23363,10 @@ index 731bdabd53fd4a3d17494f26781223097a5d6e16..42d46c7a7437bea5335a23cbee5708ac
              final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
                  DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
  
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
 index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd923cd0b1e 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
 @@ -204,7 +204,7 @@ import org.bukkit.event.server.ServerLoadEvent;
  // CraftBukkit end
  
@@ -23574,10 +23574,10 @@ index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd9
      // CraftBukkit start
      public boolean isDebugging() {
          return false;
-diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
 index 2f47d95943c00020a24ea3ff1a49e64e114de675..0dd9ed7465d222505d5368781654ec4954f6e5c3 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+--- a/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/net/minecraft/server/dedicated/DedicatedServer.java
 @@ -458,7 +458,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
          return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
      }
@@ -23612,10 +23612,10 @@ index 2f47d95943c00020a24ea3ff1a49e64e114de675..0dd9ed7465d222505d5368781654ec49
          this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue
      }
  
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+diff --git a/net/minecraft/server/level/ChunkHolder.java b/net/minecraft/server/level/ChunkHolder.java
 index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f977e09e7e0 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+--- a/net/minecraft/server/level/ChunkHolder.java
++++ b/net/minecraft/server/level/ChunkHolder.java
 @@ -32,46 +32,125 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
  import net.minecraft.server.MinecraftServer;
  // CraftBukkit end
@@ -24101,10 +24101,10 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
      }
  
      @FunctionalInterface
-diff --git a/src/main/java/net/minecraft/server/level/ChunkLevel.java b/src/main/java/net/minecraft/server/level/ChunkLevel.java
+diff --git a/net/minecraft/server/level/ChunkLevel.java b/net/minecraft/server/level/ChunkLevel.java
 index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb9652bb66 100644
---- a/src/main/java/net/minecraft/server/level/ChunkLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkLevel.java
+--- a/net/minecraft/server/level/ChunkLevel.java
++++ b/net/minecraft/server/level/ChunkLevel.java
 @@ -7,8 +7,8 @@ import net.minecraft.world.level.chunk.status.ChunkStep;
  import org.jetbrains.annotations.Contract;
  
@@ -24116,10 +24116,10 @@ index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb
      public static final int ENTITY_TICKING_LEVEL = 31;
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
 index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a604b8573 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
 @@ -108,7 +108,7 @@ import org.slf4j.Logger;
  import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
  // CraftBukkit end
@@ -25427,10 +25427,10 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a
          }
  
          public void updatePlayers(List<ServerPlayer> players) {
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
+diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java
 index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57fbca45b2 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
+--- a/net/minecraft/server/level/DistanceManager.java
++++ b/net/minecraft/server/level/DistanceManager.java
 @@ -34,58 +34,57 @@ import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.chunk.LevelChunk;
  import org.slf4j.Logger;
@@ -25914,10 +25914,10 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 -    }
 +    }*/  // Paper - rewrite chunk system
  }
-diff --git a/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java b/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
+diff --git a/net/minecraft/server/level/GenerationChunkHolder.java b/net/minecraft/server/level/GenerationChunkHolder.java
 index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a5fd7489a 100644
---- a/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/GenerationChunkHolder.java
+--- a/net/minecraft/server/level/GenerationChunkHolder.java
++++ b/net/minecraft/server/level/GenerationChunkHolder.java
 @@ -27,13 +27,7 @@ public abstract class GenerationChunkHolder {
      public static final ChunkResult<ChunkAccess> UNLOADED_CHUNK = ChunkResult.error("Unloaded chunk");
      public static final CompletableFuture<ChunkResult<ChunkAccess>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_CHUNK);
@@ -26247,10 +26247,10 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 +        // Paper end - rewrite chunk system
      }
  }
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
 index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa7130c20f 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
  import net.minecraft.world.level.storage.LevelStorageSource;
  import org.slf4j.Logger;
@@ -26754,10 +26754,10 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
  
      }
  }
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
+diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
 index d5bc702f2676b1b7a32c8f3a4a349fc2710ee825..301e8d6599d200cb0f1328f0e386af2f9a619939 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
 @@ -101,6 +101,11 @@ public class ServerEntity {
      }
  
@@ -26770,10 +26770,10 @@ index d5bc702f2676b1b7a32c8f3a4a349fc2710ee825..301e8d6599d200cb0f1328f0e386af2f
          List<Entity> list = this.entity.getPassengers();
  
          if (!list.equals(this.lastPassengers)) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
 index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905d8325618 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
 @@ -186,7 +186,7 @@ import org.bukkit.event.weather.LightningStrikeEvent;
  import org.bukkit.event.world.TimeSkipEvent;
  // CraftBukkit end
@@ -27535,10 +27535,10 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
          });
          return crashreportsystemdetails;
      }
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
 index f6e3073e1f1ff99f6917d84974a18e3e756fa9ea..ba873bcc183f9b3f64ba39be08cb88a95ff52b0e 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -217,7 +217,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
  import org.bukkit.inventory.MainHand;
  // CraftBukkit end
@@ -27585,10 +27585,10 @@ index f6e3073e1f1ff99f6917d84974a18e3e756fa9ea..ba873bcc183f9b3f64ba39be08cb88a9
      public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
          super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
          this.chatVisibility = ChatVisiblity.FULL;
-diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
+diff --git a/net/minecraft/server/level/ThreadedLevelLightEngine.java b/net/minecraft/server/level/ThreadedLevelLightEngine.java
 index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157bddf99a7 100644
---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
+--- a/net/minecraft/server/level/ThreadedLevelLightEngine.java
++++ b/net/minecraft/server/level/ThreadedLevelLightEngine.java
 @@ -22,23 +22,134 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
  import net.minecraft.world.level.lighting.LevelLightEngine;
  import org.slf4j.Logger;
@@ -27919,10 +27919,10 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
      }
  
      static enum TaskType {
-diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
+diff --git a/net/minecraft/server/level/Ticket.java b/net/minecraft/server/level/Ticket.java
 index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466f5512bcd 100644
---- a/src/main/java/net/minecraft/server/level/Ticket.java
-+++ b/src/main/java/net/minecraft/server/level/Ticket.java
+--- a/net/minecraft/server/level/Ticket.java
++++ b/net/minecraft/server/level/Ticket.java
 @@ -2,13 +2,25 @@ package net.minecraft.server.level;
  
  import java.util.Objects;
@@ -27975,10 +27975,10 @@ index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  }
-diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+diff --git a/net/minecraft/server/level/WorldGenRegion.java b/net/minecraft/server/level/WorldGenRegion.java
 index b7d29389a357f142237cecd75f8ca91cf1eb6b5b..e4b0dc3121101d54394a0c3a413dabf8103b2ea6 100644
---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+--- a/net/minecraft/server/level/WorldGenRegion.java
++++ b/net/minecraft/server/level/WorldGenRegion.java
 @@ -85,6 +85,36 @@ public class WorldGenRegion implements WorldGenLevel {
      private final AtomicLong subTickCount = new AtomicLong();
      private static final ResourceLocation WORLDGEN_REGION_RANDOM = ResourceLocation.withDefaultNamespace("worldgen_region_random");
@@ -28016,10 +28016,10 @@ index b7d29389a357f142237cecd75f8ca91cf1eb6b5b..e4b0dc3121101d54394a0c3a413dabf8
      public WorldGenRegion(ServerLevel world, StaticCache2D<GenerationChunkHolder> chunks, ChunkStep generationStep, ChunkAccess centerPos) {
          this.generatingStep = generationStep;
          this.cache = chunks;
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
 index c68040a59fa8aa9b8b9f1e0b4fdded565ea592d9..7913c41aac1f9dd53a2b49da2a17fd894bcb6b3a 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
 @@ -1426,7 +1426,7 @@ public abstract class PlayerList {
  
      public void setViewDistance(int viewDistance) {
@@ -28038,10 +28038,10 @@ index c68040a59fa8aa9b8b9f1e0b4fdded565ea592d9..7913c41aac1f9dd53a2b49da2a17fd89
          Iterator iterator = this.server.getAllLevels().iterator();
  
          while (iterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
+diff --git a/net/minecraft/util/BitStorage.java b/net/minecraft/util/BitStorage.java
 index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907cf68b15c 100644
---- a/src/main/java/net/minecraft/util/BitStorage.java
-+++ b/src/main/java/net/minecraft/util/BitStorage.java
+--- a/net/minecraft/util/BitStorage.java
++++ b/net/minecraft/util/BitStorage.java
 @@ -2,7 +2,7 @@ package net.minecraft.util;
  
  import java.util.function.IntConsumer;
@@ -28074,10 +28074,10 @@ index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907
 +    }
 +    // Paper end - block counting
  }
-diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
+diff --git a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
 index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17b2fc8db5 100644
---- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-+++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
+--- a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
++++ b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
 @@ -7,7 +7,7 @@ import java.util.Iterator;
  import javax.annotation.Nullable;
  import net.minecraft.core.IdMap;
@@ -28117,10 +28117,10 @@ index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17
      }
  
      public void addMapping(K value, int id) {
-diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java
+diff --git a/net/minecraft/util/SimpleBitStorage.java b/net/minecraft/util/SimpleBitStorage.java
 index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a6428b20c38 100644
---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
-+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
+--- a/net/minecraft/util/SimpleBitStorage.java
++++ b/net/minecraft/util/SimpleBitStorage.java
 @@ -208,6 +208,20 @@ public class SimpleBitStorage implements BitStorage {
      private final int divideAdd; private final long divideAddUnsigned; // Paper - Perf: Optimize SimpleBitStorage
      private final int divideShift;
@@ -28293,10 +28293,10 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
      public static class InitializationException extends RuntimeException {
          InitializationException(String message) {
              super(message);
-diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
+diff --git a/net/minecraft/util/SortedArraySet.java b/net/minecraft/util/SortedArraySet.java
 index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf857043e1 100644
---- a/src/main/java/net/minecraft/util/SortedArraySet.java
-+++ b/src/main/java/net/minecraft/util/SortedArraySet.java
+--- a/net/minecraft/util/SortedArraySet.java
++++ b/net/minecraft/util/SortedArraySet.java
 @@ -8,12 +8,89 @@ import java.util.Iterator;
  import java.util.NoSuchElementException;
  import javax.annotation.Nullable;
@@ -28388,10 +28388,10 @@ index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf
      private SortedArraySet(int initialCapacity, Comparator<T> comparator) {
          this.comparator = comparator;
          if (initialCapacity < 0) {
-diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java
+diff --git a/net/minecraft/util/ZeroBitStorage.java b/net/minecraft/util/ZeroBitStorage.java
 index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7eb80334e 100644
---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java
-+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java
+--- a/net/minecraft/util/ZeroBitStorage.java
++++ b/net/minecraft/util/ZeroBitStorage.java
 @@ -62,4 +62,22 @@ public class ZeroBitStorage implements BitStorage {
      public BitStorage copy() {
          return this;
@@ -28415,10 +28415,10 @@ index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7
 +    }
 +    // Paper end - block counting
  }
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
 index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3b5d9b6ce 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
 @@ -176,7 +176,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
  import org.bukkit.plugin.PluginManager;
  // CraftBukkit end
@@ -29157,10 +29157,10 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462ca1b78cd 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+--- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java
++++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 @@ -38,12 +38,137 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
  import net.minecraft.world.level.chunk.storage.SectionStorage;
  import net.minecraft.world.level.chunk.storage.SimpleRegionStorage;
@@ -29363,10 +29363,10 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462
              .forEach(chunkPos -> world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY));
      }
  
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+diff --git a/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
 index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57f892a768 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+--- a/net/minecraft/world/entity/ai/village/poi/PoiSection.java
++++ b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
 @@ -23,13 +23,27 @@ import net.minecraft.core.SectionPos;
  import net.minecraft.util.VisibleForDebug;
  import org.slf4j.Logger;
@@ -29396,10 +29396,10 @@ index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57
      public PoiSection(Runnable updateListener) {
          this(updateListener, true, ImmutableList.of());
      }
-diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java
 index 63f02cdc67d9e88cc6998d0ae9d139c83e85b447..70b8023c3badc745f342d5b0ab54699e3923826a 100644
---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+--- a/net/minecraft/world/entity/decoration/ArmorStand.java
++++ b/net/minecraft/world/entity/decoration/ArmorStand.java
 @@ -364,7 +364,7 @@ public class ArmorStand extends LivingEntity {
      @Override
      protected void pushEntities() {
@@ -29409,10 +29409,10 @@ index 63f02cdc67d9e88cc6998d0ae9d139c83e85b447..70b8023c3badc745f342d5b0ab54699e
          Iterator iterator = list.iterator();
  
          while (iterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java
+diff --git a/net/minecraft/world/level/ClipContext.java b/net/minecraft/world/level/ClipContext.java
 index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda6757b4477c 100644
---- a/src/main/java/net/minecraft/world/level/ClipContext.java
-+++ b/src/main/java/net/minecraft/world/level/ClipContext.java
+--- a/net/minecraft/world/level/ClipContext.java
++++ b/net/minecraft/world/level/ClipContext.java
 @@ -18,7 +18,7 @@ public class ClipContext {
      private final Vec3 from;
      private final Vec3 to;
@@ -29422,10 +29422,10 @@ index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda67
      private final CollisionContext collisionContext;
  
      public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) {
-diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
+diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java
 index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b150013e940ee6 100644
---- a/src/main/java/net/minecraft/world/level/EntityGetter.java
-+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
+--- a/net/minecraft/world/level/EntityGetter.java
++++ b/net/minecraft/world/level/EntityGetter.java
 @@ -15,7 +15,7 @@ import net.minecraft.world.phys.shapes.BooleanOp;
  import net.minecraft.world.phys.shapes.Shapes;
  import net.minecraft.world.phys.shapes.VoxelShape;
@@ -29546,10 +29546,10 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001
      }
  
      // Paper start - Affects Spawning API
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
 index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b827401434bcfff1 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
 @@ -84,6 +84,7 @@ import net.minecraft.world.level.storage.LevelData;
  import net.minecraft.world.level.storage.WritableLevelData;
  import net.minecraft.world.phys.AABB;
@@ -30431,10 +30431,10 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
  
      @Nullable
      public abstract Entity getEntity(int id);
-diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java
+diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java
 index 5eb8982678110fabb82a93c5ec67c666b7fde017..ade435de0af4ee3566fa4a490df53cddd2f6531c 100644
---- a/src/main/java/net/minecraft/world/level/LevelReader.java
-+++ b/src/main/java/net/minecraft/world/level/LevelReader.java
+--- a/net/minecraft/world/level/LevelReader.java
++++ b/net/minecraft/world/level/LevelReader.java
 @@ -22,7 +22,18 @@ import net.minecraft.world.level.dimension.DimensionType;
  import net.minecraft.world.level.levelgen.Heightmap;
  import net.minecraft.world.phys.AABB;
@@ -30455,10 +30455,10 @@ index 5eb8982678110fabb82a93c5ec67c666b7fde017..ade435de0af4ee3566fa4a490df53cdd
      @Nullable
      ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
  
-diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java
+diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
 index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f81daa428 100644
---- a/src/main/java/net/minecraft/world/level/ServerExplosion.java
-+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java
+--- a/net/minecraft/world/level/ServerExplosion.java
++++ b/net/minecraft/world/level/ServerExplosion.java
 @@ -64,6 +64,249 @@ public class ServerExplosion implements Explosion {
      public float yield;
      // CraftBukkit end
@@ -30904,10 +30904,10 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
              this.level.explosionDensityCache.put(key, blockDensity);
          }
  
-diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java
+diff --git a/net/minecraft/world/level/biome/Biome.java b/net/minecraft/world/level/biome/Biome.java
 index 9f86b69d8c93a63e0b408ea52519f1fc2e798226..78afd8e51e03cd53c12b64db8a817da457f81bef 100644
---- a/src/main/java/net/minecraft/world/level/biome/Biome.java
-+++ b/src/main/java/net/minecraft/world/level/biome/Biome.java
+--- a/net/minecraft/world/level/biome/Biome.java
++++ b/net/minecraft/world/level/biome/Biome.java
 @@ -113,20 +113,7 @@ public final class Biome {
  
      @Deprecated
@@ -30930,10 +30930,10 @@ index 9f86b69d8c93a63e0b408ea52519f1fc2e798226..78afd8e51e03cd53c12b64db8a817da4
      }
  
      public boolean shouldFreeze(LevelReader world, BlockPos blockPos) {
-diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
+diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java
 index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8bfa94b498 100644
---- a/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
-+++ b/src/main/java/net/minecraft/world/level/biome/BiomeManager.java
+--- a/net/minecraft/world/level/biome/BiomeManager.java
++++ b/net/minecraft/world/level/biome/BiomeManager.java
 @@ -98,8 +98,7 @@ public class BiomeManager {
      }
  
@@ -30944,10 +30944,10 @@ index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8b
      }
  
      public interface NoiseBiomeSource {
-diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java
 index 1aa69f4a7005242925124c74b8229e6fa7362717..c0b1f903962b25d8ff6c2b4fcd2be0e45de09b35 100644
---- a/src/main/java/net/minecraft/world/level/block/Block.java
-+++ b/src/main/java/net/minecraft/world/level/block/Block.java
+--- a/net/minecraft/world/level/block/Block.java
++++ b/net/minecraft/world/level/block/Block.java
 @@ -271,7 +271,7 @@ public class Block extends BlockBehaviour implements ItemLike {
      }
  
@@ -30957,10 +30957,10 @@ index 1aa69f4a7005242925124c74b8229e6fa7362717..c0b1f903962b25d8ff6c2b4fcd2be0e4
      }
  
      public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {}
-diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
 index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe4fe74166 100644
---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
 @@ -797,7 +797,7 @@ public abstract class BlockBehaviour implements FeatureElement {
          boolean test(BlockState state, BlockGetter world, BlockPos pos);
      }
@@ -31089,10 +31089,10 @@ index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe
          }
  
          public Block getBlock() {
-diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
+diff --git a/net/minecraft/world/level/block/state/StateHolder.java b/net/minecraft/world/level/block/state/StateHolder.java
 index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d769478de477bd 100644
---- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
+--- a/net/minecraft/world/level/block/state/StateHolder.java
++++ b/net/minecraft/world/level/block/state/StateHolder.java
 @@ -15,7 +15,7 @@ import java.util.stream.Collectors;
  import javax.annotation.Nullable;
  import net.minecraft.world.level.block.state.properties.Property;
@@ -31251,10 +31251,10 @@ index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d76947
      }
  
      protected static <O, S extends StateHolder<O, S>> Codec<S> codec(Codec<O> codec, Function<O, S> ownerToStateFunction) {
-diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
+diff --git a/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/net/minecraft/world/level/block/state/properties/BooleanProperty.java
 index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196713b14a7 100644
---- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java
+--- a/net/minecraft/world/level/block/state/properties/BooleanProperty.java
++++ b/net/minecraft/world/level/block/state/properties/BooleanProperty.java
 @@ -3,13 +3,23 @@ package net.minecraft.world.level.block.state.properties;
  import java.util.List;
  import java.util.Optional;
@@ -31280,10 +31280,10 @@ index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
+diff --git a/net/minecraft/world/level/block/state/properties/EnumProperty.java b/net/minecraft/world/level/block/state/properties/EnumProperty.java
 index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5640127a7 100644
---- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java
+--- a/net/minecraft/world/level/block/state/properties/EnumProperty.java
++++ b/net/minecraft/world/level/block/state/properties/EnumProperty.java
 @@ -10,11 +10,39 @@ import java.util.function.Predicate;
  import java.util.stream.Collectors;
  import net.minecraft.util.StringRepresentable;
@@ -31333,10 +31333,10 @@ index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
+diff --git a/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
 index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0efe1506d 100644
---- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java
+--- a/net/minecraft/world/level/block/state/properties/IntegerProperty.java
++++ b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
 @@ -5,11 +5,33 @@ import java.util.List;
  import java.util.Optional;
  import java.util.stream.IntStream;
@@ -31380,10 +31380,10 @@ index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
+diff --git a/net/minecraft/world/level/block/state/properties/Property.java b/net/minecraft/world/level/block/state/properties/Property.java
 index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276f0386e4d 100644
---- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
+--- a/net/minecraft/world/level/block/state/properties/Property.java
++++ b/net/minecraft/world/level/block/state/properties/Property.java
 @@ -10,7 +10,7 @@ import java.util.stream.Stream;
  import javax.annotation.Nullable;
  import net.minecraft.world.level.block.state.StateHolder;
@@ -31432,10 +31432,10 @@ index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276
      }
  
      public Property.Value<T> value(T value) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
 index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95c9ac2dbb 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+--- a/net/minecraft/world/level/chunk/ChunkAccess.java
++++ b/net/minecraft/world/level/chunk/ChunkAccess.java
 @@ -57,7 +57,7 @@ import net.minecraft.world.ticks.SavedTick;
  import net.minecraft.world.ticks.TickContainerAccess;
  import org.slf4j.Logger;
@@ -31591,10 +31591,10 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
      }
  
      public static record PackedTicks(List<SavedTick<Block>> blocks, List<SavedTick<Fluid>> fluids) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
 index ca6928f959eb63ac9183ba6c95738609839a7d32..e0cb360ece042c4fc6aa0d10106923fe25288f5c 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
 @@ -120,7 +120,7 @@ public abstract class ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
              chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler());
@@ -31613,10 +31613,10 @@ index ca6928f959eb63ac9183ba6c95738609839a7d32..e0cb360ece042c4fc6aa0d10106923fe
  
                      structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess);
                  } while (structurestart == null);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+diff --git a/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95a960cb19 100644
---- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+--- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java
++++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState;
  import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.level.material.Fluids;
@@ -31667,10 +31667,10 @@ index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95
      @Override
      public BlockState getBlockState(BlockPos pos) {
          return Blocks.VOID_AIR.defaultBlockState();
-diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
+diff --git a/net/minecraft/world/level/chunk/HashMapPalette.java b/net/minecraft/world/level/chunk/HashMapPalette.java
 index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5708f2c4a 100644
---- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
+--- a/net/minecraft/world/level/chunk/HashMapPalette.java
++++ b/net/minecraft/world/level/chunk/HashMapPalette.java
 @@ -8,12 +8,19 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap;
@@ -31692,10 +31692,10 @@ index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5
      public HashMapPalette(IdMap<T> idList, int bits, PaletteResize<T> listener, List<T> entries) {
          this(idList, bits, listener);
          entries.forEach(this.values::add);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+diff --git a/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
 index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc9a11bbec 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+--- a/net/minecraft/world/level/chunk/ImposterProtoChunk.java
++++ b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
 @@ -30,7 +30,7 @@ import net.minecraft.world.level.material.FluidState;
  import net.minecraft.world.ticks.BlackholeTickAccess;
  import net.minecraft.world.ticks.TickContainerAccess;
@@ -31754,10 +31754,10 @@ index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc
      @Nullable
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
 index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf191005112080b 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
 @@ -55,7 +55,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
  import net.minecraft.world.ticks.TickContainerAccess;
  import org.slf4j.Logger;
@@ -31933,10 +31933,10 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
      }
  
      @Nullable
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java
 index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef63cdb8511 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
 @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.Blocks;
  import net.minecraft.world.level.block.state.BlockState;
  import net.minecraft.world.level.material.FluidState;
@@ -32150,10 +32150,10 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
      }
  
      public void readBiomes(FriendlyByteBuf buf) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
+diff --git a/net/minecraft/world/level/chunk/LinearPalette.java b/net/minecraft/world/level/chunk/LinearPalette.java
 index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf34354ce1195 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LinearPalette.java
+--- a/net/minecraft/world/level/chunk/LinearPalette.java
++++ b/net/minecraft/world/level/chunk/LinearPalette.java
 @@ -7,13 +7,20 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import org.apache.commons.lang3.Validate;
@@ -32176,10 +32176,10 @@ index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf343
      private LinearPalette(IdMap<T> idList, int bits, PaletteResize<T> listener, List<T> list) {
          this.registry = idList;
          this.values = (T[])(new Object[1 << bits]);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/Palette.java b/src/main/java/net/minecraft/world/level/chunk/Palette.java
+diff --git a/net/minecraft/world/level/chunk/Palette.java b/net/minecraft/world/level/chunk/Palette.java
 index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be2f08ecd1 100644
---- a/src/main/java/net/minecraft/world/level/chunk/Palette.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/Palette.java
+--- a/net/minecraft/world/level/chunk/Palette.java
++++ b/net/minecraft/world/level/chunk/Palette.java
 @@ -5,7 +5,7 @@ import java.util.function.Predicate;
  import net.minecraft.core.IdMap;
  import net.minecraft.network.FriendlyByteBuf;
@@ -32189,10 +32189,10 @@ index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be
      int idFor(T object);
  
      boolean maybeHas(Predicate<T> predicate);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java
 index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48a1cc6873 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+--- a/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/net/minecraft/world/level/chunk/PalettedContainer.java
 @@ -29,7 +29,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
      public final IdMap<T> registry;
@@ -32353,10 +32353,10 @@ index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48
          public void copyFrom(Palette<T> palette, BitStorage storage) {
              for (int i = 0; i < storage.getSize(); i++) {
                  T object = palette.valueFor(storage.get(i));
-diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java
 index 15e14f5d006389c823fa6baf8c1a4f22804d4aa8..759adee51bad99bd4bbee4f44247e8c8486cfbd6 100644
---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+--- a/net/minecraft/world/level/chunk/ProtoChunk.java
++++ b/net/minecraft/world/level/chunk/ProtoChunk.java
 @@ -149,7 +149,7 @@ public class ProtoChunk extends ChunkAccess {
                      }
  
@@ -32366,10 +32366,10 @@ index 15e14f5d006389c823fa6baf8c1a4f22804d4aa8..759adee51bad99bd4bbee4f44247e8c8
                          this.lightEngine.checkBlock(pos);
                      }
                  }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
+diff --git a/net/minecraft/world/level/chunk/SingleValuePalette.java b/net/minecraft/world/level/chunk/SingleValuePalette.java
 index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce7895529918073c 100644
---- a/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/SingleValuePalette.java
+--- a/net/minecraft/world/level/chunk/SingleValuePalette.java
++++ b/net/minecraft/world/level/chunk/SingleValuePalette.java
 @@ -8,12 +8,24 @@ import net.minecraft.network.FriendlyByteBuf;
  import net.minecraft.network.VarInt;
  import org.apache.commons.lang3.Validate;
@@ -32420,10 +32420,10 @@ index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce789552
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
+diff --git a/net/minecraft/world/level/chunk/status/ChunkPyramid.java b/net/minecraft/world/level/chunk/status/ChunkPyramid.java
 index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d6755a0d931a 100644
---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkPyramid.java
+--- a/net/minecraft/world/level/chunk/status/ChunkPyramid.java
++++ b/net/minecraft/world/level/chunk/status/ChunkPyramid.java
 @@ -54,7 +54,7 @@ public record ChunkPyramid(ImmutableList<ChunkStep> steps) {
          .step(ChunkStatus.CARVERS, builder -> builder)
          .step(ChunkStatus.FEATURES, builder -> builder)
@@ -32433,10 +32433,10 @@ index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d675
          .step(ChunkStatus.SPAWN, builder -> builder)
          .step(ChunkStatus.FULL, builder -> builder.setTask(ChunkStatusTasks::full))
          .build();
-diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
+diff --git a/net/minecraft/world/level/chunk/status/ChunkStatus.java b/net/minecraft/world/level/chunk/status/ChunkStatus.java
 index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eaedbf4b976 100644
---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatus.java
+--- a/net/minecraft/world/level/chunk/status/ChunkStatus.java
++++ b/net/minecraft/world/level/chunk/status/ChunkStatus.java
 @@ -11,7 +11,7 @@ import net.minecraft.resources.ResourceLocation;
  import net.minecraft.world.level.levelgen.Heightmap;
  import org.jetbrains.annotations.VisibleForTesting;
@@ -32515,10 +32515,10 @@ index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eae
          this.parent = previous == null ? this : previous;
          this.chunkType = chunkType;
          this.heightmapsAfter = heightMapTypes;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+diff --git a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
 index 3d8a35d8cf29447ee7ac750dbc6ffcdb0f89b81b..9a3900e970f22892d8a3da8a28f922aa9b62765f 100644
---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+--- a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
++++ b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
 @@ -152,7 +152,7 @@ public class ChunkStatusTasks {
                  chunk1 = protochunkextension.getWrapped();
              } else {
@@ -32546,10 +32546,10 @@ index 3d8a35d8cf29447ee7ac750dbc6ffcdb0f89b81b..9a3900e970f22892d8a3da8a28f922aa
              // CraftBukkit end
          }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
+diff --git a/net/minecraft/world/level/chunk/status/ChunkStep.java b/net/minecraft/world/level/chunk/status/ChunkStep.java
 index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4fff0f3ed5 100644
---- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStep.java
+--- a/net/minecraft/world/level/chunk/status/ChunkStep.java
++++ b/net/minecraft/world/level/chunk/status/ChunkStep.java
 @@ -11,9 +11,50 @@ import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
  import net.minecraft.world.level.chunk.ChunkAccess;
  import net.minecraft.world.level.chunk.ProtoChunk;
@@ -32661,10 +32661,10 @@ index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4f
      public static class Builder {
          private final ChunkStatus status;
          @Nullable
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+diff --git a/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a067109819f 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 @@ -28,21 +28,31 @@ import net.minecraft.world.level.dimension.LevelStem;
  import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
  import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -32787,10 +32787,10 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
 +        return this.storage.info(); // Paper - rewrite chunk system
      }
  }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
+diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java
 index a0cbccd2cf1ac785745d86c42b6f58fb8bad7ffa..16ca1c8672e5f0a27f8a30498c754a81cdec5191 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
+--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java
++++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java
 @@ -71,12 +71,12 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
          }
      }
@@ -32806,10 +32806,10 @@ index a0cbccd2cf1ac785745d86c42b6f58fb8bad7ffa..16ca1c8672e5f0a27f8a30498c754a81
          chunkNbt.put("Position", new IntArrayTag(new int[]{pos.x, pos.z}));
      }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
+diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java
 index 1f2997cf5367200084f32c437f77040c8c6a18e6..a8a9e59a9721a76e34f78c1baa5026e5fe1d2bda 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/IOWorker.java
+--- a/net/minecraft/world/level/chunk/storage/IOWorker.java
++++ b/net/minecraft/world/level/chunk/storage/IOWorker.java
 @@ -30,7 +30,7 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable {
      private static final Logger LOGGER = LogUtils.getLogger();
      private final AtomicBoolean shutdownRequested = new AtomicBoolean();
@@ -32819,10 +32819,10 @@ index 1f2997cf5367200084f32c437f77040c8c6a18e6..a8a9e59a9721a76e34f78c1baa5026e5
      private final SequencedMap<ChunkPos, IOWorker.PendingStore> pendingWrites = new LinkedHashMap<>();
      private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
      private static final int REGION_CACHE_SIZE = 1024;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
 index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a129514760edb32a2 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
 @@ -28,7 +28,7 @@ import net.minecraft.nbt.NbtIo; // Paper
  import net.minecraft.world.level.ChunkPos;
  import org.slf4j.Logger;
@@ -32911,10 +32911,10 @@ index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a12951476
          }
      }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3306a32e3 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 @@ -17,7 +17,7 @@ import net.minecraft.nbt.StreamTagVisitor;
  import net.minecraft.util.ExceptionCollector;
  import net.minecraft.world.level.ChunkPos;
@@ -33232,10 +33232,10 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
  
      }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+diff --git a/net/minecraft/world/level/chunk/storage/SectionStorage.java b/net/minecraft/world/level/chunk/storage/SectionStorage.java
 index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e5106056728 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+--- a/net/minecraft/world/level/chunk/storage/SectionStorage.java
++++ b/net/minecraft/world/level/chunk/storage/SectionStorage.java
 @@ -40,10 +40,10 @@ import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.LevelHeightAccessor;
  import org.slf4j.Logger;
@@ -33371,10 +33371,10 @@ index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e51
      }
  
      static record PackedChunk<T>(Int2ObjectMap<T> sectionsByY, boolean versionChanged) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77fab826cd7 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 @@ -129,7 +129,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
              long j = nbt.getLong("InhabitedTime");
              ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
@@ -33665,10 +33665,10 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
  
      }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 index 578d270d5b7efb9ac8f5dde539170f6021e2b786..c5085ebf4e801837010f3750c5e89576bb0c27a5 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+--- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
++++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 @@ -14,7 +14,7 @@ import net.minecraft.util.datafix.DataFixTypes;
  import net.minecraft.world.level.ChunkPos;
  
@@ -33678,10 +33678,10 @@ index 578d270d5b7efb9ac8f5dde539170f6021e2b786..c5085ebf4e801837010f3750c5e89576
      private final DataFixer fixerUpper;
      private final DataFixTypes dataFixType;
  
-diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java
 index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2d9c609d9 100644
---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+--- a/net/minecraft/world/level/entity/EntityTickList.java
++++ b/net/minecraft/world/level/entity/EntityTickList.java
 @@ -9,52 +9,38 @@ import javax.annotation.Nullable;
  import net.minecraft.world.entity.Entity;
  
@@ -33750,10 +33750,10 @@ index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2
 +        // Paper end - rewrite chunk system
      }
  }
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
+diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 index 1fcc2b287ed723cf51720f80e68f18f4a15cf429..3f39d6c786d9dfdd9ad591e08ff05fcbb41a1df6 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
+--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 @@ -86,7 +86,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
              this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk);
@@ -33772,10 +33772,10 @@ index 1fcc2b287ed723cf51720f80e68f18f4a15cf429..3f39d6c786d9dfdd9ad591e08ff05fcb
      }
  
      private ChunkAccess doFill(Blender blender, StructureManager structureAccessor, RandomState noiseConfig, ChunkAccess chunk, int minimumCellY, int cellHeight) {
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+diff --git a/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 index c3586281c9594769593a6027ea0a78f7c76c0262..decdb275e83fa6244aa3a24458872b42c49d04ed 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+--- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java
++++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 @@ -47,8 +47,13 @@ public class StructureCheck {
      private final BiomeSource biomeSource;
      private final long seed;
@@ -33846,10 +33846,10 @@ index c3586281c9594769593a6027ea0a78f7c76c0262..decdb275e83fa6244aa3a24458872b42
  
              referencesByStructure.computeInt(structure, (feature, references) -> references == null ? 1 : references + 1);
              return referencesByStructure;
-diff --git a/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
+diff --git a/net/minecraft/world/level/lighting/LevelLightEngine.java b/net/minecraft/world/level/lighting/LevelLightEngine.java
 index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede341297bc58dba 100644
---- a/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/LevelLightEngine.java
+--- a/net/minecraft/world/level/lighting/LevelLightEngine.java
++++ b/net/minecraft/world/level/lighting/LevelLightEngine.java
 @@ -9,151 +9,111 @@ import net.minecraft.world.level.LightLayer;
  import net.minecraft.world.level.chunk.DataLayer;
  import net.minecraft.world.level.chunk.LightChunkGetter;
@@ -34052,10 +34052,10 @@ index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede34129
      }
  
      public int getLightSectionCount() {
-diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
 index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1 100644
---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
-+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+--- a/net/minecraft/world/level/material/FlowingFluid.java
++++ b/net/minecraft/world/level/material/FlowingFluid.java
 @@ -55,6 +55,48 @@ public abstract class FlowingFluid extends Fluid {
      });
      private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
@@ -34216,10 +34216,10 @@ index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c
      }
  
      protected abstract boolean canConvertToSource(ServerLevel world);
-diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java
+diff --git a/net/minecraft/world/level/material/FluidState.java b/net/minecraft/world/level/material/FluidState.java
 index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc1859379bfd805 100644
---- a/src/main/java/net/minecraft/world/level/material/FluidState.java
-+++ b/src/main/java/net/minecraft/world/level/material/FluidState.java
+--- a/net/minecraft/world/level/material/FluidState.java
++++ b/net/minecraft/world/level/material/FluidState.java
 @@ -22,12 +22,30 @@ import net.minecraft.world.level.block.state.properties.Property;
  import net.minecraft.world.phys.Vec3;
  import net.minecraft.world.phys.shapes.VoxelShape;
@@ -34303,10 +34303,10 @@ index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc18593
      }
  
      @Nullable
-diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
+diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java
 index 5dc2674b537f4a61b2e21a21bdb2e8dc090d3a3c..6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130 100644
---- a/src/main/java/net/minecraft/world/phys/AABB.java
-+++ b/src/main/java/net/minecraft/world/phys/AABB.java
+--- a/net/minecraft/world/phys/AABB.java
++++ b/net/minecraft/world/phys/AABB.java
 @@ -331,7 +331,7 @@ public class AABB {
      }
  
@@ -34316,10 +34316,10 @@ index 5dc2674b537f4a61b2e21a21bdb2e8dc090d3a3c..6cf6d4ec7b9e43c7b2b4c0e2fb080964
          AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ
      ) {
          return getDirection(
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+diff --git a/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
 index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12243fea80 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+--- a/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
++++ b/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
 @@ -20,7 +20,7 @@ public class ArrayVoxelShape extends VoxelShape {
          );
      }
@@ -34337,10 +34337,10 @@ index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
+diff --git a/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
 index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f28cbaf30b 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
+--- a/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
++++ b/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
 @@ -4,13 +4,13 @@ import java.util.BitSet;
  import net.minecraft.core.Direction;
  
@@ -34499,10 +34499,10 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
  
      private boolean isZStripFull(int z1, int z2, int x, int y) {
          return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2);
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
+diff --git a/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/net/minecraft/world/phys/shapes/CubeVoxelShape.java
 index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7bbe0f4b38 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
+--- a/net/minecraft/world/phys/shapes/CubeVoxelShape.java
++++ b/net/minecraft/world/phys/shapes/CubeVoxelShape.java
 @@ -7,6 +7,7 @@ import net.minecraft.util.Mth;
  public final class CubeVoxelShape extends VoxelShape {
      protected CubeVoxelShape(DiscreteVoxelShape voxels) {
@@ -34511,10 +34511,10 @@ index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7b
      }
  
      @Override
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
+diff --git a/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
 index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63cbb420f2 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
+--- a/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
++++ b/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
 @@ -3,12 +3,79 @@ package net.minecraft.world.phys.shapes;
  import net.minecraft.core.AxisCycle;
  import net.minecraft.core.Direction;
@@ -34596,10 +34596,10 @@ index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63
      protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
          if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) {
              this.xSize = sizeX;
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
+diff --git a/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/net/minecraft/world/phys/shapes/OffsetDoubleList.java
 index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
+--- a/net/minecraft/world/phys/shapes/OffsetDoubleList.java
++++ b/net/minecraft/world/phys/shapes/OffsetDoubleList.java
 @@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
  import it.unimi.dsi.fastutil.doubles.DoubleList;
  
@@ -34611,10 +34611,10 @@ index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e0
  
      public OffsetDoubleList(DoubleList oldList, double offset) {
          this.delegate = oldList;
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+diff --git a/net/minecraft/world/phys/shapes/Shapes.java b/net/minecraft/world/phys/shapes/Shapes.java
 index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f175394c0 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+--- a/net/minecraft/world/phys/shapes/Shapes.java
++++ b/net/minecraft/world/phys/shapes/Shapes.java
 @@ -16,9 +16,15 @@ public final class Shapes {
      public static final double EPSILON = 1.0E-7;
      public static final double BIG_EPSILON = 1.0E-6;
@@ -35005,10 +35005,10 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
      }
  
      @VisibleForTesting
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
+diff --git a/net/minecraft/world/phys/shapes/SliceShape.java b/net/minecraft/world/phys/shapes/SliceShape.java
 index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a004617eb53a2 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
+--- a/net/minecraft/world/phys/shapes/SliceShape.java
++++ b/net/minecraft/world/phys/shapes/SliceShape.java
 @@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape {
          super(makeSlice(shape.shape, axis, sliceWidth));
          this.delegate = shape;
@@ -35017,10 +35017,10 @@ index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a0046
      }
  
      private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) {
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+diff --git a/net/minecraft/world/phys/shapes/VoxelShape.java b/net/minecraft/world/phys/shapes/VoxelShape.java
 index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9cffc3071 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+--- a/net/minecraft/world/phys/shapes/VoxelShape.java
++++ b/net/minecraft/world/phys/shapes/VoxelShape.java
 @@ -15,61 +15,546 @@ import net.minecraft.world.phys.AABB;
  import net.minecraft.world.phys.BlockHitResult;
  import net.minecraft.world.phys.Vec3;
@@ -35930,10 +35930,10 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
  
      protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) {
          if (this.isEmpty()) {
-diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
+diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java
 index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2f26d593d 100644
---- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
-+++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
+--- a/net/minecraft/world/ticks/LevelChunkTicks.java
++++ b/net/minecraft/world/ticks/LevelChunkTicks.java
 @@ -17,7 +17,7 @@ import net.minecraft.core.BlockPos;
  import net.minecraft.nbt.ListTag;
  import net.minecraft.world.level.ChunkPos;
@@ -36017,10 +36017,10 @@ index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2
              int i = -this.pendingTicks.size();
  
              for (SavedTick<T> savedTick : this.pendingTicks) {
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+diff --git a/org/bukkit/craftbukkit/CraftChunk.java b/org/bukkit/craftbukkit/CraftChunk.java
 index f3ab07e44e2e912ea66c6148cfdb2a4a528741b2..c2bffe3450ee9f768e00a23ec09df74d7a06d49b 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+--- a/org/bukkit/craftbukkit/CraftChunk.java
++++ b/org/bukkit/craftbukkit/CraftChunk.java
 @@ -83,6 +83,12 @@ public class CraftChunk implements Chunk {
      }
  
@@ -36097,10 +36097,10 @@ index f3ab07e44e2e912ea66c6148cfdb2a4a528741b2..c2bffe3450ee9f768e00a23ec09df74d
      }
  
      @Override
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+diff --git a/org/bukkit/craftbukkit/CraftServer.java b/org/bukkit/craftbukkit/CraftServer.java
 index 5b64111bc8baca45ecc7bfa384e5f8a004163a0b..97b5d6ba2b19a7c730730c74175a29157aed1840 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+--- a/org/bukkit/craftbukkit/CraftServer.java
++++ b/org/bukkit/craftbukkit/CraftServer.java
 @@ -1448,7 +1448,7 @@ public final class CraftServer implements Server {
          // Paper - Put world into worldlist before initing the world; move up
  
@@ -36128,10 +36128,10 @@ index 5b64111bc8baca45ecc7bfa384e5f8a004163a0b..97b5d6ba2b19a7c730730c74175a2915
      }
  
      // Paper start - Adventure
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+diff --git a/org/bukkit/craftbukkit/CraftWorld.java b/org/bukkit/craftbukkit/CraftWorld.java
 index ca62105a0ff0aa69385cbf2018f8fe6a4bb69fd4..92d9f0ea8f7810ae20d3996f49aefa539b4bcb69 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+--- a/org/bukkit/craftbukkit/CraftWorld.java
++++ b/org/bukkit/craftbukkit/CraftWorld.java
 @@ -507,15 +507,17 @@ public class CraftWorld extends CraftRegionAccessor implements World {
          ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
          if (playerChunk == null) return false;
@@ -36226,10 +36226,10 @@ index ca62105a0ff0aa69385cbf2018f8fe6a4bb69fd4..92d9f0ea8f7810ae20d3996f49aefa53
      }
  
      // Paper start - implement pointers
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+diff --git a/org/bukkit/craftbukkit/entity/CraftPlayer.java b/org/bukkit/craftbukkit/entity/CraftPlayer.java
 index e9df37ff66700278bc94ea1e42135b92d97d03f7..6a647cab8b2e476987931486e290703b8726f2c7 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+--- a/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/org/bukkit/craftbukkit/entity/CraftPlayer.java
 @@ -3527,7 +3527,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
  
      @Override
@@ -36263,10 +36263,10 @@ index e9df37ff66700278bc94ea1e42135b92d97d03f7..6a647cab8b2e476987931486e290703b
      }
  
      // Paper start - entity effect API
-diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
+diff --git a/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
 index 39377ba0739f9660567b38475f101672f7b5e035..c025a4ff42257a4e84f0f9574b84f6987ef8ac11 100644
---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
+--- a/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
++++ b/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
 @@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
              return ichunkaccess1;
          };
@@ -36276,10 +36276,10 @@ index 39377ba0739f9660567b38475f101672f7b5e035..c025a4ff42257a4e84f0f9574b84f698
      }
  
      @Override
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
+diff --git a/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
 index 54c4434662d057a08800918641b95708cda61207..37458e8fd5d57acbf90a6bea4e66797cb07f69fa 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
+--- a/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
++++ b/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
 @@ -810,6 +810,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
      public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) {
          return this.handle.getChunkIfLoadedImmediately(x, z);
@@ -36294,10 +36294,10 @@ index 54c4434662d057a08800918641b95708cda61207..37458e8fd5d57acbf90a6bea4e66797c
      // Paper end
  }
  
-diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
+diff --git a/org/spigotmc/AsyncCatcher.java b/org/spigotmc/AsyncCatcher.java
 index ef2598760458833021ef1bee92137f42c9fe591f..1f23e775eba1c34e01145bd91b0ce26fed6ca9de 100644
---- a/src/main/java/org/spigotmc/AsyncCatcher.java
-+++ b/src/main/java/org/spigotmc/AsyncCatcher.java
+--- a/org/spigotmc/AsyncCatcher.java
++++ b/org/spigotmc/AsyncCatcher.java
 @@ -9,7 +9,7 @@ public class AsyncCatcher
  
      public static void catchOp(String reason)
@@ -36307,10 +36307,10 @@ index ef2598760458833021ef1bee92137f42c9fe591f..1f23e775eba1c34e01145bd91b0ce26f
          {
              MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
              throw new IllegalStateException( "Asynchronous " + reason + "!" );
-diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
+diff --git a/org/spigotmc/WatchdogThread.java b/org/spigotmc/WatchdogThread.java
 index ad282d34919716b75acd10426cd071da9d064a51..529df2a41dd93d6e1505053bd04032dbf0cdaa31 100644
---- a/src/main/java/org/spigotmc/WatchdogThread.java
-+++ b/src/main/java/org/spigotmc/WatchdogThread.java
+--- a/org/spigotmc/WatchdogThread.java
++++ b/org/spigotmc/WatchdogThread.java
 @@ -8,7 +8,7 @@ import java.util.logging.Logger;
  import net.minecraft.server.MinecraftServer;
  import org.bukkit.Bukkit;

From 6186079231d3c0cc03c484c7932b6eb9ed3015b1 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 16 Dec 2024 10:42:50 -0800
Subject: [PATCH 258/285] Migrate ChunkSystem class to PaperHooks

---
 .../0018-Moonrise-optimisation-patches.patch  | 634 ++++++++++++------
 .../features/0001-Add-PaperHooks.patch        | 345 +++++++++-
 .../0020-Rewrite-dataconverter-system.patch   |   4 +-
 .../server/level/ChunkHolder.java.patch       |  12 +-
 .../server/level/ChunkMap.java.patch          |  22 +-
 .../server/level/ServerLevel.java.patch       |   2 +-
 .../PersistentEntitySectionManager.java.patch |   2 +-
 .../moonrise/common/PlatformHooks.java        |   5 +-
 .../moonrise/common/util/ChunkSystem.java     | 288 --------
 .../common/util/ChunkSystemHooks.java         |  77 +++
 .../common/util/ThreadUnsafeRandom.java       |   2 +-
 .../org/bukkit/craftbukkit/CraftWorld.java    |   8 +-
 .../craftbukkit/entity/CraftPlayer.java       |   6 +-
 13 files changed, 897 insertions(+), 510 deletions(-)
 rename {paper-server/patches/features => feature-patches}/0018-Moonrise-optimisation-patches.patch (99%)
 delete mode 100644 paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
 create mode 100644 paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java

diff --git a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch b/feature-patches/0018-Moonrise-optimisation-patches.patch
similarity index 99%
rename from paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
rename to feature-patches/0018-Moonrise-optimisation-patches.patch
index dea5778bd2..b20c8eacca 100644
--- a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
+++ b/feature-patches/0018-Moonrise-optimisation-patches.patch
@@ -17,18 +17,47 @@ Currently includes:
 
 See https://github.com/Tuinity/Moonrise
 
+diff --git a/ca/spottedleaf/moonrise/common/PlatformHooks.java b/ca/spottedleaf/moonrise/common/PlatformHooks.java
+index 6c98d420ea84c10ef4f15d4deb3f04e610ed8548..9b879cbc037a17ffeb9a963111fd3f303a935eef 100644
+--- a/ca/spottedleaf/moonrise/common/PlatformHooks.java
++++ b/ca/spottedleaf/moonrise/common/PlatformHooks.java
+@@ -1,5 +1,6 @@
+ package ca.spottedleaf.moonrise.common;
+ 
++import ca.spottedleaf.moonrise.common.util.ChunkSystemHooks;
+ import com.mojang.datafixers.DSL;
+ import com.mojang.datafixers.DataFixer;
+ import net.minecraft.core.BlockPos;
+@@ -23,7 +24,7 @@ import java.util.List;
+ import java.util.ServiceLoader;
+ import java.util.function.Predicate;
+ 
+-public interface PlatformHooks {
++public interface PlatformHooks extends ChunkSystemHooks {
+     public static PlatformHooks get() {
+         return Holder.INSTANCE;
+     }
+@@ -63,8 +64,6 @@ public interface PlatformHooks {
+ 
+     public void entityMove(final Entity entity, final long oldSection, final long newSection);
+ 
+-    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
+-
+     public boolean configFixMC224294();
+ 
+     public boolean configAutoConfigSendDistance();
 diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d93e7d855d
+index 0000000000000000000000000000000000000000..1b8193587814225c2ef2c5d9e667436eb50ff6c5
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 @@ -0,0 +1,273 @@
 +package ca.spottedleaf.moonrise.common.misc;
 +
++import ca.spottedleaf.moonrise.common.PlatformHooks;
 +import ca.spottedleaf.moonrise.common.list.ReferenceList;
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
 +import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
-+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
 +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
@@ -146,8 +175,8 @@ index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d9
 +        players[NearbyMapType.GENERAL.ordinal()].update(chunk.x, chunk.z, GENERAL_AREA_VIEW_DISTANCE);
 +        players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
 +        players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
-+        players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
-+        players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getViewDistance(player));
++        players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, PlatformHooks.get().getTickViewDistance(player));
++        players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, PlatformHooks.get().getViewDistance(player));
 +        players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Moonrise - chunk tick iteration
 +    }
 +
@@ -296,50 +325,245 @@ index 0000000000000000000000000000000000000000..7e440b4a46b040365df7317035e577d9
 +        }
 +    }
 +}
-diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5b4b8abea 100644
---- a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-+++ b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-@@ -2,11 +2,17 @@ package ca.spottedleaf.moonrise.common.util;
- 
- import ca.spottedleaf.concurrentutil.util.Priority;
- import ca.spottedleaf.moonrise.common.PlatformHooks;
+diff --git a/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..89406dbda09eea03579ed724fda0df2d42e2e504
+--- /dev/null
++++ b/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java
+@@ -0,0 +1,190 @@
++package ca.spottedleaf.moonrise.common.util;
++
++import ca.spottedleaf.concurrentutil.util.Priority;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
 +import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
 +import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache;
 +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
- import com.mojang.logging.LogUtils;
- import net.minecraft.server.level.ChunkHolder;
- import net.minecraft.server.level.FullChunkStatus;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.FullChunkStatus;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
 +import net.minecraft.server.level.progress.ChunkProgressListener;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.level.chunk.ChunkAccess;
- import net.minecraft.world.level.chunk.LevelChunk;
-@@ -18,203 +24,46 @@ import java.util.function.Consumer;
- public final class ChunkSystem {
- 
-     private static final Logger LOGGER = LogUtils.getLogger();
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.status.ChunkStatus;
++import java.util.List;
++import java.util.function.Consumer;
++
++public abstract class BaseChunkSystemHooks implements ChunkSystemHooks {
++
++    @Override
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
++        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
++    }
++
++    @Override
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
++        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkTask(chunkX, chunkZ, run, priority);
++    }
++
++    @Override
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
++                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
++                                  final Consumer<ChunkAccess> onComplete) {
++        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete);
++    }
++
++    @Override
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
++                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
++        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++    }
++
++    @Override
++    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
++                                     final FullChunkStatus toStatus, final boolean addTicket,
++                                     final Priority priority, final Consumer<LevelChunk> onComplete) {
++        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++    }
++
++    @Override
++    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
++        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
++    }
++
++    @Override
++    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
++        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
++    }
++
++    @Override
++    public int getVisibleChunkHolderCount(final ServerLevel level) {
++        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
++    }
++
++    @Override
++    public int getUpdatingChunkHolderCount(final ServerLevel level) {
++        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
++    }
++
++    @Override
++    public boolean hasAnyChunkHolders(final ServerLevel level) {
++        return getUpdatingChunkHolderCount(level) != 0;
++    }
++
++    @Override
++    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
++        // Update progress listener for LevelLoadingScreen
++        final ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener;
++        if (progressListener != null) {
++            this.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> {
++                progressListener.onStatusChange(holder.getPos(), null);
++            });
++        }
++    }
++
++    @Override
++    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
++            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk);
++    }
++
++    @Override
++    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        chunk.loadCallback();
++    }
++
++    @Override
++    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        chunk.unloadCallback();
++    }
++
++    @Override
++    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
++            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null);
++    }
++
++    @Override
++    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
++            chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
++        }
++        ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
++        ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
++        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
++    }
++
++    @Override
++    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
++    }
++
++    @Override
++    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++    }
++
++    @Override
++    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
++        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove(
++            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++    }
++
++    @Override
++    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
++        return null;
++    }
++
++    @Override
++    public int getSendViewDistance(final ServerPlayer player) {
++        return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
++    }
++
++    @Override
++    public int getViewDistance(final ServerPlayer player) {
++        return RegionizedPlayerChunkLoader.getAPIViewDistance(player);
++    }
++
++    @Override
++    public int getTickViewDistance(final ServerPlayer player) {
++        return RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
++    }
++
++    @Override
++    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
++        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().addPlayer(player);
++    }
++
++    @Override
++    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
++        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().removePlayer(player);
++    }
++
++    @Override
++    public void updateMaps(final ServerLevel world, final ServerPlayer player) {
++        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
++    }
++}
+diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
+deleted file mode 100644
+index 58a99bc38e137431f10af36fa9e2d04fe61694aa..0000000000000000000000000000000000000000
+--- a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
++++ /dev/null
+@@ -1,288 +0,0 @@
+-package ca.spottedleaf.moonrise.common.util;
+-
+-import ca.spottedleaf.concurrentutil.util.Priority;
+-import ca.spottedleaf.moonrise.common.PlatformHooks;
+-import com.mojang.logging.LogUtils;
+-import net.minecraft.server.level.ChunkHolder;
+-import net.minecraft.server.level.FullChunkStatus;
+-import net.minecraft.server.level.ServerLevel;
+-import net.minecraft.server.level.ServerPlayer;
+-import net.minecraft.world.entity.Entity;
+-import net.minecraft.world.level.chunk.ChunkAccess;
+-import net.minecraft.world.level.chunk.LevelChunk;
+-import net.minecraft.world.level.chunk.status.ChunkStatus;
+-import org.slf4j.Logger;
+-import java.util.List;
+-import java.util.function.Consumer;
+-
+-public final class ChunkSystem {
+-
+-    private static final Logger LOGGER = LogUtils.getLogger();
 -    private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
 -
 -    private static int getDistance(final ChunkStatus status) {
 -        return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
 -    }
- 
-     public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
-         scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
-     }
- 
-     public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
+-
+-    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
+-        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
+-    }
+-
+-    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
 -        level.chunkSource.mainThreadProcessor.execute(run);
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkTask(chunkX, chunkZ, run, priority);
-     }
- 
-     public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-                                          final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
-                                          final Consumer<ChunkAccess> onComplete) {
+-    }
+-
+-    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
+-                                         final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
+-                                         final Consumer<ChunkAccess> onComplete) {
 -        if (gen) {
 -            scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
 -            return;
@@ -359,14 +583,13 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5
 -                }
 -            }
 -        });
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete);
-     }
- 
+-    }
+-
 -    static final net.minecraft.server.level.TicketType<Long> CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo);
 -
 -    private static long chunkLoadCounter = 0L;
-     public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-                                          final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
+-    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+-                                         final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
 -        if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
 -            scheduleChunkTask(level, chunkX, chunkZ, () -> {
 -                scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
@@ -422,12 +645,11 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5
 -        }, (final Runnable r) -> {
 -            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
 -        });
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-     }
- 
-     public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
-                                             final FullChunkStatus toStatus, final boolean addTicket,
-                                             final Priority priority, final Consumer<LevelChunk> onComplete) {
+-    }
+-
+-    public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+-                                            final FullChunkStatus toStatus, final boolean addTicket,
+-                                            final Priority priority, final Consumer<LevelChunk> onComplete) {
 -        // This method goes unused until the chunk system rewrite
 -        if (toStatus == FullChunkStatus.INACCESSIBLE) {
 -            throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
@@ -506,144 +728,176 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..1d288e73fd8605676c0da676e068afb5
 -        }, (final Runnable r) -> {
 -            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
 -        });
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-     }
- 
-     public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
+-    }
+-
+-    public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
 -        return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
-     }
- 
-     public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
+-    }
+-
+-    public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
 -        return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
-     }
- 
-     public static int getVisibleChunkHolderCount(final ServerLevel level) {
+-    }
+-
+-    public static int getVisibleChunkHolderCount(final ServerLevel level) {
 -        return level.chunkSource.chunkMap.visibleChunkMap.size();
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
-     }
- 
-     public static int getUpdatingChunkHolderCount(final ServerLevel level) {
+-    }
+-
+-    public static int getUpdatingChunkHolderCount(final ServerLevel level) {
 -        return level.chunkSource.chunkMap.updatingChunkMap.size();
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
-     }
- 
-     public static boolean hasAnyChunkHolders(final ServerLevel level) {
-@@ -233,55 +82,96 @@ public final class ChunkSystem {
-     }
- 
-     public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
-+        // Update progress listener for LevelLoadingScreen
-+        final ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener;
-+        if (progressListener != null) {
-+            ChunkSystem.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> {
-+                progressListener.onStatusChange(holder.getPos(), null);
-+            });
-+        }
-+    }
- 
-+    public static void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
-+                .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk);
-     }
- 
-     public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-    }
 -
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        chunk.loadCallback();
-     }
- 
-     public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        chunk.unloadCallback();
-+    }
- 
-+    public static void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
-+                .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null);
-     }
- 
-     public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-    public static boolean hasAnyChunkHolders(final ServerLevel level) {
+-        return getUpdatingChunkHolderCount(level) != 0;
+-    }
 -
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
-+            chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
-+        }
-+        ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
-+        ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
-+        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
-     }
- 
-     public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-    public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
+-        if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
+-            return false;
+-        }
+-        return true;
+-    }
 -
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
-     }
- 
-     public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-    public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
 -
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-     }
- 
-     public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-    }
 -
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove(
-+                ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-     }
- 
-     public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
+-    public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
+-    }
+-
+-    public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
 -        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
-+        return null;
-     }
- 
-     public static int getSendViewDistance(final ServerPlayer player) {
+-    }
+-
+-    public static int getSendViewDistance(final ServerPlayer player) {
 -        return getViewDistance(player);
-+        return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
-     }
- 
-     public static int getViewDistance(final ServerPlayer player) {
+-    }
+-
+-    public static int getViewDistance(final ServerPlayer player) {
 -        final ServerLevel level = player.serverLevel();
 -        if (level == null) {
 -            return org.bukkit.Bukkit.getViewDistance();
 -        }
 -        return level.chunkSource.chunkMap.serverViewDistance;
-+        return RegionizedPlayerChunkLoader.getAPIViewDistance(player);
-     }
- 
-     public static int getTickViewDistance(final ServerPlayer player) {
+-    }
+-
+-    public static int getTickViewDistance(final ServerPlayer player) {
 -        final ServerLevel level = player.serverLevel();
 -        if (level == null) {
 -            return org.bukkit.Bukkit.getSimulationDistance();
 -        }
 -        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
-+        return RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
-+    }
+-    }
+-
+-    private ChunkSystem() {}
+-}
+diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java b/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..427079ae47b6e0e1aa42013a8760fbefa76941f2
+--- /dev/null
++++ b/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
+@@ -0,0 +1,77 @@
++package ca.spottedleaf.moonrise.common.util;
 +
-+    public static void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().addPlayer(player);
-+    }
++import ca.spottedleaf.concurrentutil.util.Priority;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.FullChunkStatus;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.status.ChunkStatus;
++import java.util.List;
++import java.util.function.Consumer;
 +
-+    public static void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().removePlayer(player);
-+    }
++public interface ChunkSystemHooks {
 +
-+    public static void updateMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
-     }
- 
-     private ChunkSystem() {}
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run);
++
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority);
++
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
++                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
++                                  final Consumer<ChunkAccess> onComplete);
++
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
++                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete);
++
++    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
++                                     final FullChunkStatus toStatus, final boolean addTicket,
++                                     final Priority priority, final Consumer<LevelChunk> onComplete);
++
++    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level);
++
++    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level);
++
++    public int getVisibleChunkHolderCount(final ServerLevel level);
++
++    public int getUpdatingChunkHolderCount(final ServerLevel level);
++
++    public boolean hasAnyChunkHolders(final ServerLevel level);
++
++    public boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event);
++
++    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder);
++
++    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder);
++
++    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
++
++    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
++
++    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ);
++
++    public int getSendViewDistance(final ServerPlayer player);
++
++    public int getViewDistance(final ServerPlayer player);
++
++    public int getTickViewDistance(final ServerPlayer player);
++
++    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player);
++
++    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player);
++
++    public void updateMaps(final ServerLevel world, final ServerPlayer player);
++}
 diff --git a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
 index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508656ed7ac 100644
 --- a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
@@ -658,9 +912,18 @@ index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508
      private static final long MULTIPLIER = 25214903917L;
      private static final long ADDEND = 11L;
 diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..de22cfd2da4782072584d5140ce5567780d6feaa 100644
+index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..8c197c59eb35e02f163ec98b8aa0888e4ff40b1a 100644
 --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+@@ -27,7 +27,7 @@ import net.minecraft.world.phys.AABB;
+ import java.util.List;
+ import java.util.function.Predicate;
+ 
+-public final class PaperHooks implements PlatformHooks {
++public final class PaperHooks extends ca.spottedleaf.moonrise.common.util.BaseChunkSystemHooks implements PlatformHooks { // Paper - rewrite chunk system
+ 
+     @Override
+     public String getBrand() {
 @@ -267,7 +267,7 @@ public final class PaperHooks implements PlatformHooks {
  
      @Override
@@ -5101,17 +5364,16 @@ index 0000000000000000000000000000000000000000..2ff58cf753c60913ee73aae015182e9c
 +}
 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e069bf1bf5
+index 0000000000000000000000000000000000000000..26207443b1223119c03db478d7e816d9cdf8e618
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
-@@ -0,0 +1,116 @@
+@@ -0,0 +1,115 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
 +import ca.spottedleaf.moonrise.common.list.ReferenceList;
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
 +import ca.spottedleaf.moonrise.common.util.TickThread;
-+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
@@ -5218,7 +5480,7 @@ index 0000000000000000000000000000000000000000..58d9187adc188b693b6becc400f766e0
 +
 +    @Override
 +    protected boolean screenEntity(final Entity entity, final boolean fromDisk, final boolean event) {
-+        return ChunkSystem.screenEntity(this.serverWorld, entity, fromDisk, event);
++        return PlatformHooks.get().screenEntity(this.serverWorld, entity, fromDisk, event);
 +    }
 +}
 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/ChunkSystemPoiManager.java
@@ -6763,10 +7025,10 @@ index 0000000000000000000000000000000000000000..7eafc5b7cba23d8dec92ecc1050afe3f
 \ No newline at end of file
 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0fd989d97d
+index 0000000000000000000000000000000000000000..b5817aa8f537593f6d9fc6b612c82ccccb250ac7
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
-@@ -0,0 +1,1457 @@
+@@ -0,0 +1,1456 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
@@ -6776,7 +7038,6 @@ index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0f
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
 +import ca.spottedleaf.moonrise.common.util.TickThread;
 +import ca.spottedleaf.moonrise.common.util.WorldUtil;
-+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices;
@@ -7613,7 +7874,7 @@ index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0f
 +    private NewChunkHolder createChunkHolder(final long position) {
 +        final NewChunkHolder ret = new NewChunkHolder(this.world, CoordinateUtils.getChunkX(position), CoordinateUtils.getChunkZ(position), this.taskScheduler);
 +
-+        ChunkSystem.onChunkHolderCreate(this.world, ret.vanillaChunkHolder);
++        PlatformHooks.get().onChunkHolderCreate(this.world, ret.vanillaChunkHolder);
 +
 +        return ret;
 +    }
@@ -7821,7 +8082,7 @@ index 0000000000000000000000000000000000000000..3990834a41116682d6ae779a3bf24b0f
 +    private void removeChunkHolder(final NewChunkHolder holder) {
 +        holder.onUnload();
 +        this.autoSaveQueue.remove(holder);
-+        ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder);
++        PlatformHooks.get().onChunkHolderDelete(this.world, holder.vanillaChunkHolder);
 +        this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ));
 +    }
 +
@@ -9287,10 +9548,10 @@ index 0000000000000000000000000000000000000000..67532b85073b7978254a0b04caadfe82
 +}
 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48fbb5e1a4c
+index 0000000000000000000000000000000000000000..e4a5fa25ed368fc4662c30934da2963ef446d782
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java
-@@ -0,0 +1,1998 @@
+@@ -0,0 +1,1997 @@
 +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
 +
 +import ca.spottedleaf.concurrentutil.completable.CallbackCompletable;
@@ -9304,7 +9565,6 @@ index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48f
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
 +import ca.spottedleaf.moonrise.common.util.TickThread;
 +import ca.spottedleaf.moonrise.common.util.WorldUtil;
-+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
 +import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
 +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
@@ -10563,10 +10823,10 @@ index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48f
 +                    // state upgrade
 +                    if (!current.isOrAfter(FullChunkStatus.FULL) && pending.isOrAfter(FullChunkStatus.FULL)) {
 +                        this.updateCurrentState(FullChunkStatus.FULL);
-+                        ChunkSystem.onChunkPreBorder(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkPreBorder(chunk, this.vanillaChunkHolder);
 +                        this.scheduler.chunkHolderManager.ensureInAutosave(this);
 +                        this.changeEntityChunkStatus(FullChunkStatus.FULL);
-+                        ChunkSystem.onChunkBorder(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkBorder(chunk, this.vanillaChunkHolder);
 +                        this.onFullChunkLoadChange(true, changedFullStatus);
 +                        this.completeFullStatusConsumers(FullChunkStatus.FULL, chunk);
 +                    }
@@ -10574,34 +10834,34 @@ index 0000000000000000000000000000000000000000..eafa4e6d55cd0f9314ac0f2b96a7f48f
 +                    if (!current.isOrAfter(FullChunkStatus.BLOCK_TICKING) && pending.isOrAfter(FullChunkStatus.BLOCK_TICKING)) {
 +                        this.updateCurrentState(FullChunkStatus.BLOCK_TICKING);
 +                        this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING);
-+                        ChunkSystem.onChunkTicking(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkTicking(chunk, this.vanillaChunkHolder);
 +                        this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, chunk);
 +                    }
 +
 +                    if (!current.isOrAfter(FullChunkStatus.ENTITY_TICKING) && pending.isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
 +                        this.updateCurrentState(FullChunkStatus.ENTITY_TICKING);
 +                        this.changeEntityChunkStatus(FullChunkStatus.ENTITY_TICKING);
-+                        ChunkSystem.onChunkEntityTicking(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkEntityTicking(chunk, this.vanillaChunkHolder);
 +                        this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, chunk);
 +                    }
 +                } else {
 +                    if (current.isOrAfter(FullChunkStatus.ENTITY_TICKING) && !pending.isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
 +                        this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING);
-+                        ChunkSystem.onChunkNotEntityTicking(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkNotEntityTicking(chunk, this.vanillaChunkHolder);
 +                        this.updateCurrentState(FullChunkStatus.BLOCK_TICKING);
 +                    }
 +
 +                    if (current.isOrAfter(FullChunkStatus.BLOCK_TICKING) && !pending.isOrAfter(FullChunkStatus.BLOCK_TICKING)) {
 +                        this.changeEntityChunkStatus(FullChunkStatus.FULL);
-+                        ChunkSystem.onChunkNotTicking(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkNotTicking(chunk, this.vanillaChunkHolder);
 +                        this.updateCurrentState(FullChunkStatus.FULL);
 +                    }
 +
 +                    if (current.isOrAfter(FullChunkStatus.FULL) && !pending.isOrAfter(FullChunkStatus.FULL)) {
 +                        this.onFullChunkLoadChange(false, changedFullStatus);
 +                        this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE);
-+                        ChunkSystem.onChunkNotBorder(chunk, this.vanillaChunkHolder);
-+                        ChunkSystem.onChunkPostNotBorder(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkNotBorder(chunk, this.vanillaChunkHolder);
++                        PlatformHooks.get().onChunkPostNotBorder(chunk, this.vanillaChunkHolder);
 +                        this.updateCurrentState(FullChunkStatus.INACCESSIBLE);
 +                    }
 +                }
@@ -24117,7 +24377,7 @@ index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a604b8573 100644
+index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e11526dfd7c2f 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -108,7 +108,7 @@ import org.slf4j.Logger;
@@ -24886,7 +25146,7 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a
  
      public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
 -        return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
-+        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getSendViewDistance(player); // Paper - rewrite chunk system
++        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getSendViewDistance(player); // Paper - rewrite chunk system
      }
  
      private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) {
@@ -25074,7 +25334,7 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a
  
              player.setChunkTrackingView(ChunkTrackingView.EMPTY);
 -            this.updateChunkTracking(player);
-+            ca.spottedleaf.moonrise.common.util.ChunkSystem.addPlayerToDistanceMaps(this.level, player); // Paper - rewrite chunk system
++            ca.spottedleaf.moonrise.common.PlatformHooks.get().addPlayerToDistanceMaps(this.level, player); // Paper - rewrite chunk system
          } else {
              SectionPos sectionposition = player.getLastSectionPos();
  
@@ -25085,7 +25345,7 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a
              }
  
 -            this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY);
-+            ca.spottedleaf.moonrise.common.util.ChunkSystem.removePlayerFromDistanceMaps(this.level, player); // Paper - rewrite chunk system
++            ca.spottedleaf.moonrise.common.PlatformHooks.get().removePlayerFromDistanceMaps(this.level, player); // Paper - rewrite chunk system
          }
  
      }
@@ -25124,7 +25384,7 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..67cfc3236a39008cfcf3acffefafda1a
 +            // Paper - rewrite chunk system
          }
  
-+        ca.spottedleaf.moonrise.common.util.ChunkSystem.updateMaps(this.level, player); // Paper - rewrite chunk system
++        ca.spottedleaf.moonrise.common.PlatformHooks.get().updateMaps(this.level, player); // Paper - rewrite chunk system
      }
  
      private void updateChunkTracking(ServerPlayer player) {
diff --git a/paper-server/patches/features/0001-Add-PaperHooks.patch b/paper-server/patches/features/0001-Add-PaperHooks.patch
index db8dd7f311..5df4d535a5 100644
--- a/paper-server/patches/features/0001-Add-PaperHooks.patch
+++ b/paper-server/patches/features/0001-Add-PaperHooks.patch
@@ -6,13 +6,14 @@ Subject: [PATCH] Add PaperHooks
 
 diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..834c5ce238c7adb0164a6282582d709348ef96cc
+index 0000000000000000000000000000000000000000..2988c418b34d6f699a9c24406cfd6949465b64f0
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -0,0 +1,240 @@
+@@ -0,0 +1,241 @@
 +package ca.spottedleaf.moonrise.paper;
 +
 +import ca.spottedleaf.moonrise.common.PlatformHooks;
++import ca.spottedleaf.moonrise.paper.util.BaseChunkSystemHooks;
 +import com.mojang.datafixers.DSL;
 +import com.mojang.datafixers.DataFixer;
 +import com.mojang.serialization.Dynamic;
@@ -39,7 +40,7 @@ index 0000000000000000000000000000000000000000..834c5ce238c7adb0164a6282582d7093
 +import java.util.List;
 +import java.util.function.Predicate;
 +
-+public final class PaperHooks implements PlatformHooks {
++public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks {
 +
 +    @Override
 +    public String getBrand() {
@@ -250,3 +251,341 @@ index 0000000000000000000000000000000000000000..834c5ce238c7adb0164a6282582d7093
 +        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
 +    }
 +}
+diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..34b45bc11124efb22f0f3ae5b2ad8f445c719476
+--- /dev/null
++++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
+@@ -0,0 +1,332 @@
++package ca.spottedleaf.moonrise.paper.util;
++
++import ca.spottedleaf.concurrentutil.util.Priority;
++import com.mojang.logging.LogUtils;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.ChunkResult;
++import net.minecraft.server.level.FullChunkStatus;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.server.level.TicketType;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.status.ChunkPyramid;
++import net.minecraft.world.level.chunk.status.ChunkStatus;
++import net.minecraft.world.level.chunk.status.ChunkStep;
++import org.bukkit.Bukkit;
++import org.slf4j.Logger;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.concurrent.CompletableFuture;
++import java.util.function.Consumer;
++
++public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.common.util.ChunkSystemHooks {
++
++    private static final Logger LOGGER = LogUtils.getLogger();
++    private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
++    private static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
++
++    private long chunkLoadCounter = 0L;
++
++    private static int getDistance(final ChunkStatus status) {
++        return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
++    }
++
++    @Override
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
++        this.scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
++    }
++
++    @Override
++    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
++        level.chunkSource.mainThreadProcessor.execute(run);
++    }
++
++    @Override
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
++                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
++                                  final Consumer<ChunkAccess> onComplete) {
++        if (gen) {
++            this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++            return;
++        }
++        this.scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
++            if (chunk == null) {
++                if (onComplete != null) {
++                    onComplete.accept(null);
++                }
++            } else {
++                if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
++                    BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++                } else {
++                    if (onComplete != null) {
++                        onComplete.accept(null);
++                    }
++                }
++            }
++        });
++    }
++
++    @Override
++    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
++                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
++        if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
++            this.scheduleChunkTask(level, chunkX, chunkZ, () -> {
++                BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++            }, priority);
++            return;
++        }
++
++        final int minLevel = 33 + getDistance(toStatus);
++        final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null;
++        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
++
++        if (addTicket) {
++            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
++        }
++        level.chunkSource.runDistanceManagerUpdates();
++
++        final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
++            try {
++                if (onComplete != null) {
++                    onComplete.accept(chunk);
++                }
++            } catch (final Throwable thr) {
++                LOGGER.error("Exception handling chunk load callback", thr);
++                com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
++            } finally {
++                if (addTicket) {
++                    level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
++                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
++                }
++            }
++        };
++
++        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
++
++        if (holder == null || holder.getTicketLevel() > minLevel) {
++            loadCallback.accept(null);
++            return;
++        }
++
++        final CompletableFuture<ChunkResult<ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
++
++        if (loadFuture.isDone()) {
++            loadCallback.accept(loadFuture.join().orElse(null));
++            return;
++        }
++
++        loadFuture.whenCompleteAsync((final ChunkResult<ChunkAccess> result, final Throwable thr) -> {
++            if (thr != null) {
++                loadCallback.accept(null);
++                return;
++            }
++            loadCallback.accept(result.orElse(null));
++        }, (final Runnable r) -> {
++            BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
++        });
++    }
++
++    @Override
++    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
++                                     final FullChunkStatus toStatus, final boolean addTicket,
++                                     final Priority priority, final Consumer<LevelChunk> onComplete) {
++        // This method goes unused until the chunk system rewrite
++        if (toStatus == FullChunkStatus.INACCESSIBLE) {
++            throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
++        }
++
++        if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
++            this.scheduleChunkTask(level, chunkX, chunkZ, () -> {
++                BaseChunkSystemHooks.this.scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++            }, priority);
++            return;
++        }
++
++        final int minLevel = 33 - (toStatus.ordinal() - 1);
++        final int radius = toStatus.ordinal() - 1;
++        final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null;
++        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
++
++        if (addTicket) {
++            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
++        }
++        level.chunkSource.runDistanceManagerUpdates();
++
++        final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
++            try {
++                if (onComplete != null) {
++                    onComplete.accept(chunk);
++                }
++            } catch (final Throwable thr) {
++                LOGGER.error("Exception handling chunk load callback", thr);
++                com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
++            } finally {
++                if (addTicket) {
++                    level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
++                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
++                }
++            }
++        };
++
++        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
++
++        if (holder == null || holder.getTicketLevel() > minLevel) {
++            loadCallback.accept(null);
++            return;
++        }
++
++        final CompletableFuture<ChunkResult<LevelChunk>> tickingState;
++        switch (toStatus) {
++            case FULL: {
++                tickingState = holder.getFullChunkFuture();
++                break;
++            }
++            case BLOCK_TICKING: {
++                tickingState = holder.getTickingChunkFuture();
++                break;
++            }
++            case ENTITY_TICKING: {
++                tickingState = holder.getEntityTickingChunkFuture();
++                break;
++            }
++            default: {
++                throw new IllegalStateException("Cannot reach here");
++            }
++        }
++
++        if (tickingState.isDone()) {
++            loadCallback.accept(tickingState.join().orElse(null));
++            return;
++        }
++
++        tickingState.whenCompleteAsync((final ChunkResult<LevelChunk> result, final Throwable thr) -> {
++            if (thr != null) {
++                loadCallback.accept(null);
++                return;
++            }
++            loadCallback.accept(result.orElse(null));
++        }, (final Runnable r) -> {
++            BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
++        });
++    }
++
++    @Override
++    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
++        return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
++    }
++
++    @Override
++    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
++        return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
++    }
++
++    @Override
++    public int getVisibleChunkHolderCount(final ServerLevel level) {
++        return level.chunkSource.chunkMap.visibleChunkMap.size();
++    }
++
++    @Override
++    public int getUpdatingChunkHolderCount(final ServerLevel level) {
++        return level.chunkSource.chunkMap.updatingChunkMap.size();
++    }
++
++    @Override
++    public boolean hasAnyChunkHolders(final ServerLevel level) {
++        return this.getUpdatingChunkHolderCount(level) != 0;
++    }
++
++    @Override
++    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
++
++    }
++
++    @Override
++    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
++        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
++    }
++
++    @Override
++    public int getSendViewDistance(final ServerPlayer player) {
++        return this.getViewDistance(player);
++    }
++
++    @Override
++    public int getViewDistance(final ServerPlayer player) {
++        final ServerLevel level = player.serverLevel();
++        if (level == null) {
++            return Bukkit.getViewDistance();
++        }
++        return level.chunkSource.chunkMap.serverViewDistance;
++    }
++
++    @Override
++    public int getTickViewDistance(final ServerPlayer player) {
++        final ServerLevel level = player.serverLevel();
++        if (level == null) {
++            return Bukkit.getSimulationDistance();
++        }
++        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
++    }
++
++    @Override
++    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void updateMaps(final ServerLevel world, final ServerPlayer player) {
++
++    }
++}
diff --git a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
index 4e1356ce74..6168b4106d 100644
--- a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
@@ -30560,10 +30560,10 @@ index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98e
 +    }
 +}
 diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 834c5ce238c7adb0164a6282582d709348ef96cc..11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2 100644
+index 0e21efc60e7dd7d348fd024d713772069951ccd4..504a5f8626b42817f04088e2539a6941cd9c6d9d 100644
 --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -203,6 +203,43 @@ public final class PaperHooks implements PlatformHooks {
+@@ -204,6 +204,43 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
      @Override
      public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
                                    final int fromVersion, final int toVersion) {
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
index 8d1373ef67..b3f729b8b5 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch
@@ -100,7 +100,7 @@
 +                chunkResult.ifSuccess(chunk -> {
 +                    if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
 +                        ChunkHolder.this.isFullChunkReady = true;
-+                        ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this);
++                        ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkBorder(chunk, this);
 +                    }
 +                });
 +            });
@@ -111,7 +111,7 @@
          if (isOrAfter && !isOrAfter1) {
 +            // Paper start
 +            if (this.isFullChunkReady) {
-+                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
++                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 +            }
 +            // Paper end
              this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
@@ -126,7 +126,7 @@
 +                chunkResult.ifSuccess(chunk -> {
 +                    // note: Here is a very good place to add callbacks to logic waiting on this.
 +                    ChunkHolder.this.isTickingReady = true;
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this);
++                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkTicking(chunk, this);
 +                });
 +            });
 +            // Paper end
@@ -137,7 +137,7 @@
 -            this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
 +            // Paper start
 +            if (this.isTickingReady) {
-+                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
++                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 +            }
 +            // Paper end
 +            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
@@ -152,7 +152,7 @@
 +            this.entityTickingChunkFuture.thenAccept(chunkResult -> {
 +                chunkResult.ifSuccess(chunk -> {
 +                    ChunkHolder.this.isEntityTickingReady = true;
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this);
++                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkEntityTicking(chunk, this);
 +                });
 +            });
 +            // Paper end
@@ -163,7 +163,7 @@
 -            this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
 +            // Paper start
 +            if (this.isEntityTickingReady) {
-+                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
++                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
 +            }
 +            // Paper end
 +            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
index 6c090dc5e0..06c477be85 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
@@ -74,10 +74,10 @@
              );
          stringBuilder.append("Updating:").append(System.lineSeparator());
 -        this.updatingChunkMap.values().forEach(consumer);
-+        ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
++        ca.spottedleaf.moonrise.common.PlatformHooks.get().getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
          stringBuilder.append("Visible:").append(System.lineSeparator());
 -        this.visibleChunkMap.values().forEach(consumer);
-+        ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
++        ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level).forEach(consumer); // Paper
          CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading");
          CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading");
          crashReportCategory.setDetail("Details", details);
@@ -86,7 +86,7 @@
                  } else {
                      holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this);
 +                    // Paper start
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
++                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderCreate(this.level, holder);
 +                    // Paper end
                  }
  
@@ -97,7 +97,7 @@
          if (flush) {
 -            List<ChunkHolder> list = this.visibleChunkMap
 -                .values()
-+            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level) // Paper - moonrise
++            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level) // Paper - moonrise
 +                //.values() // Paper - moonrise
                  .stream()
                  .filter(ChunkHolder::wasAccessibleSinceLastSave)
@@ -107,7 +107,7 @@
              long millis = Util.getMillis();
  
 -            for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) {
-+            for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper
++            for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level)) { // Paper
                  this.saveChunkIfNeeded(chunkHolder, millis);
              }
          }
@@ -115,7 +115,7 @@
      public boolean hasWork() {
          return this.lightEngine.hasLightWork()
              || !this.pendingUnloads.isEmpty()
-+            || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) // Paper - moonrise
++            || ca.spottedleaf.moonrise.common.PlatformHooks.get().hasAnyChunkHolders(this.level) // Paper - moonrise
              || !this.updatingChunkMap.isEmpty()
              || this.poiManager.hasWork()
              || !this.toDrop.isEmpty()
@@ -127,7 +127,7 @@
 +                // Paper start
 +                boolean removed;
 +                if ((removed = this.pendingUnloads.remove(chunkPos, chunkHolder)) && latestChunk != null) {
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder);
++                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderDelete(this.level, chunkHolder);
 +                    // Paper end
                      if (latestChunk instanceof LevelChunk levelChunk) {
                          levelChunk.setLoaded(false);
@@ -138,7 +138,7 @@
                      this.nextChunkSaveTime.remove(latestChunk.getPos().toLong());
 -                }
 +                } else if (removed) { // Paper start
-+                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder);
++                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderDelete(this.level, chunkHolder);
 +                } // Paper end
              }
          }, this.unloadQueue::add).whenComplete((_void, error) -> {
@@ -148,7 +148,7 @@
  
      public int size() {
 -        return this.visibleChunkMap.size();
-+        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
++        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolderCount(this.level); // Paper
      }
  
      public net.minecraft.server.level.DistanceManager getDistanceManager() {
@@ -157,7 +157,7 @@
  
      protected Iterable<ChunkHolder> getChunks() {
 -        return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
-+        return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
++        return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level)); // Paper
      }
  
      void dumpChunks(Writer writer) throws IOException {
@@ -167,7 +167,7 @@
  
 -        for (Entry<ChunkHolder> entry : this.visibleChunkMap.long2ObjectEntrySet()) {
 -            long longKey = entry.getLongKey();
-+        for (ChunkHolder entry : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper - Moonrise
++        for (ChunkHolder entry : ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level)) { // Paper - Moonrise
 +            long longKey = entry.pos.toLong(); // Paper - Moonrise
              ChunkPos chunkPos = new ChunkPos(longKey);
 -            ChunkHolder chunkHolder = entry.getValue();
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 99d27344d3..b8d9ca742e 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -117,7 +117,7 @@
 +
 +        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
 +            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
-+                ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
++                ca.spottedleaf.moonrise.common.PlatformHooks.get().scheduleChunkLoad(
 +                    this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
 +                );
 +            }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
index 0d57d512ea..384e9f3c58 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
@@ -34,7 +34,7 @@
 +        // I don't want to know why this is a generic type.
 +        Entity entityCasted = (Entity)entity;
 +        boolean wasRemoved = entityCasted.isRemoved();
-+        boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, worldGenSpawned, true);
++        boolean screened = ca.spottedleaf.moonrise.common.PlatformHooks.get().screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, worldGenSpawned, true);
 +        if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
 +            // removed by callback
 +            return false;
diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
index 6c98d420ea..9b879cbc03 100644
--- a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
+++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
@@ -1,5 +1,6 @@
 package ca.spottedleaf.moonrise.common;
 
+import ca.spottedleaf.moonrise.common.util.ChunkSystemHooks;
 import com.mojang.datafixers.DSL;
 import com.mojang.datafixers.DataFixer;
 import net.minecraft.core.BlockPos;
@@ -23,7 +24,7 @@ import java.util.List;
 import java.util.ServiceLoader;
 import java.util.function.Predicate;
 
-public interface PlatformHooks {
+public interface PlatformHooks extends ChunkSystemHooks {
     public static PlatformHooks get() {
         return Holder.INSTANCE;
     }
@@ -63,8 +64,6 @@ public interface PlatformHooks {
 
     public void entityMove(final Entity entity, final long oldSection, final long newSection);
 
-    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
-
     public boolean configFixMC224294();
 
     public boolean configAutoConfigSendDistance();
diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
deleted file mode 100644
index 58a99bc38e..0000000000
--- a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
+++ /dev/null
@@ -1,288 +0,0 @@
-package ca.spottedleaf.moonrise.common.util;
-
-import ca.spottedleaf.concurrentutil.util.Priority;
-import ca.spottedleaf.moonrise.common.PlatformHooks;
-import com.mojang.logging.LogUtils;
-import net.minecraft.server.level.ChunkHolder;
-import net.minecraft.server.level.FullChunkStatus;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.level.chunk.ChunkAccess;
-import net.minecraft.world.level.chunk.LevelChunk;
-import net.minecraft.world.level.chunk.status.ChunkStatus;
-import org.slf4j.Logger;
-import java.util.List;
-import java.util.function.Consumer;
-
-public final class ChunkSystem {
-
-    private static final Logger LOGGER = LogUtils.getLogger();
-    private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
-
-    private static int getDistance(final ChunkStatus status) {
-        return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
-    }
-
-    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
-        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
-    }
-
-    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
-        level.chunkSource.mainThreadProcessor.execute(run);
-    }
-
-    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-                                         final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
-                                         final Consumer<ChunkAccess> onComplete) {
-        if (gen) {
-            scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-            return;
-        }
-        scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
-            if (chunk == null) {
-                if (onComplete != null) {
-                    onComplete.accept(null);
-                }
-            } else {
-                if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
-                    scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-                } else {
-                    if (onComplete != null) {
-                        onComplete.accept(null);
-                    }
-                }
-            }
-        });
-    }
-
-    static final net.minecraft.server.level.TicketType<Long> CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo);
-
-    private static long chunkLoadCounter = 0L;
-    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-                                         final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
-        if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
-            scheduleChunkTask(level, chunkX, chunkZ, () -> {
-                scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-            }, priority);
-            return;
-        }
-
-        final int minLevel = 33 + getDistance(toStatus);
-        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
-        final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
-
-        if (addTicket) {
-            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
-        }
-        level.chunkSource.runDistanceManagerUpdates();
-
-        final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
-            try {
-                if (onComplete != null) {
-                    onComplete.accept(chunk);
-                }
-            } catch (final Throwable thr) {
-                LOGGER.error("Exception handling chunk load callback", thr);
-                com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
-            } finally {
-                if (addTicket) {
-                    level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
-                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
-                }
-            }
-        };
-
-        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-
-        if (holder == null || holder.getTicketLevel() > minLevel) {
-            loadCallback.accept(null);
-            return;
-        }
-
-        final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
-
-        if (loadFuture.isDone()) {
-            loadCallback.accept(loadFuture.join().orElse(null));
-            return;
-        }
-
-        loadFuture.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess> result, final Throwable thr) -> {
-            if (thr != null) {
-                loadCallback.accept(null);
-                return;
-            }
-            loadCallback.accept(result.orElse(null));
-        }, (final Runnable r) -> {
-            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
-        });
-    }
-
-    public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
-                                            final FullChunkStatus toStatus, final boolean addTicket,
-                                            final Priority priority, final Consumer<LevelChunk> onComplete) {
-        // This method goes unused until the chunk system rewrite
-        if (toStatus == FullChunkStatus.INACCESSIBLE) {
-            throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
-        }
-
-        if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
-            scheduleChunkTask(level, chunkX, chunkZ, () -> {
-                scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-            }, priority);
-            return;
-        }
-
-        final int minLevel = 33 - (toStatus.ordinal() - 1);
-        final int radius = toStatus.ordinal() - 1;
-        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
-        final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
-
-        if (addTicket) {
-            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
-        }
-        level.chunkSource.runDistanceManagerUpdates();
-
-        final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
-            try {
-                if (onComplete != null) {
-                    onComplete.accept(chunk);
-                }
-            } catch (final Throwable thr) {
-                LOGGER.error("Exception handling chunk load callback", thr);
-                com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
-            } finally {
-                if (addTicket) {
-                    level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
-                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
-                }
-            }
-        };
-
-        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-
-        if (holder == null || holder.getTicketLevel() > minLevel) {
-            loadCallback.accept(null);
-            return;
-        }
-
-        final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk>> tickingState;
-        switch (toStatus) {
-            case FULL: {
-                tickingState = holder.getFullChunkFuture();
-                break;
-            }
-            case BLOCK_TICKING: {
-                tickingState = holder.getTickingChunkFuture();
-                break;
-            }
-            case ENTITY_TICKING: {
-                tickingState = holder.getEntityTickingChunkFuture();
-                break;
-            }
-            default: {
-                throw new IllegalStateException("Cannot reach here");
-            }
-        }
-
-        if (tickingState.isDone()) {
-            loadCallback.accept(tickingState.join().orElse(null));
-            return;
-        }
-
-        tickingState.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk> result, final Throwable thr) -> {
-            if (thr != null) {
-                loadCallback.accept(null);
-                return;
-            }
-            loadCallback.accept(result.orElse(null));
-        }, (final Runnable r) -> {
-            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
-        });
-    }
-
-    public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
-        return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
-    }
-
-    public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
-        return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
-    }
-
-    public static int getVisibleChunkHolderCount(final ServerLevel level) {
-        return level.chunkSource.chunkMap.visibleChunkMap.size();
-    }
-
-    public static int getUpdatingChunkHolderCount(final ServerLevel level) {
-        return level.chunkSource.chunkMap.updatingChunkMap.size();
-    }
-
-    public static boolean hasAnyChunkHolders(final ServerLevel level) {
-        return getUpdatingChunkHolderCount(level) != 0;
-    }
-
-    public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
-        if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
-            return false;
-        }
-        return true;
-    }
-
-    public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
-
-    }
-
-    public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
-        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
-    }
-
-    public static int getSendViewDistance(final ServerPlayer player) {
-        return getViewDistance(player);
-    }
-
-    public static int getViewDistance(final ServerPlayer player) {
-        final ServerLevel level = player.serverLevel();
-        if (level == null) {
-            return org.bukkit.Bukkit.getViewDistance();
-        }
-        return level.chunkSource.chunkMap.serverViewDistance;
-    }
-
-    public static int getTickViewDistance(final ServerPlayer player) {
-        final ServerLevel level = player.serverLevel();
-        if (level == null) {
-            return org.bukkit.Bukkit.getSimulationDistance();
-        }
-        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
-    }
-
-    private ChunkSystem() {}
-}
diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
new file mode 100644
index 0000000000..427079ae47
--- /dev/null
+++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
@@ -0,0 +1,77 @@
+package ca.spottedleaf.moonrise.common.util;
+
+import ca.spottedleaf.concurrentutil.util.Priority;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.FullChunkStatus;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.status.ChunkStatus;
+import java.util.List;
+import java.util.function.Consumer;
+
+public interface ChunkSystemHooks {
+
+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run);
+
+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority);
+
+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
+                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
+                                  final Consumer<ChunkAccess> onComplete);
+
+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete);
+
+    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+                                     final FullChunkStatus toStatus, final boolean addTicket,
+                                     final Priority priority, final Consumer<LevelChunk> onComplete);
+
+    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level);
+
+    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level);
+
+    public int getVisibleChunkHolderCount(final ServerLevel level);
+
+    public int getUpdatingChunkHolderCount(final ServerLevel level);
+
+    public boolean hasAnyChunkHolders(final ServerLevel level);
+
+    public boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event);
+
+    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder);
+
+    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder);
+
+    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
+
+    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
+
+    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ);
+
+    public int getSendViewDistance(final ServerPlayer player);
+
+    public int getViewDistance(final ServerPlayer player);
+
+    public int getTickViewDistance(final ServerPlayer player);
+
+    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player);
+
+    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player);
+
+    public void updateMaps(final ServerLevel world, final ServerPlayer player);
+}
diff --git a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
index 12eb3add09..5239993a68 100644
--- a/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
+++ b/paper-server/src/main/java/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
@@ -9,7 +9,7 @@ import net.minecraft.world.level.levelgen.PositionalRandomFactory;
 /**
  * Avoid costly CAS of superclass
  */
-public final class ThreadUnsafeRandom implements BitRandomSource {
+public class ThreadUnsafeRandom implements BitRandomSource { // Paper - replace random
 
     private static final long MULTIPLIER = 25214903917L;
     private static final long ADDEND = 11L;
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 61eac5fbbe..9649f41a95 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -216,7 +216,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
     public int getTileEntityCount() {
         // We don't use the full world tile entity list, so we must iterate chunks
         int size = 0;
-        for (ChunkHolder playerchunk : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.world)) {
+        for (ChunkHolder playerchunk : ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.world)) {
             net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk();
             if (chunk == null) {
                 continue;
@@ -405,7 +405,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
             return chunk instanceof ImposterProtoChunk || chunk instanceof net.minecraft.world.level.chunk.LevelChunk;
         }
         final java.util.concurrent.CompletableFuture<ChunkAccess> future = new java.util.concurrent.CompletableFuture<>();
-        ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
+        ca.spottedleaf.moonrise.common.PlatformHooks.get().scheduleChunkLoad(
             this.world, x, z, false, ChunkStatus.EMPTY, true, ca.spottedleaf.concurrentutil.util.Priority.NORMAL, future::complete
         );
         world.getChunkSource().mainThreadProcessor.managedBlock(future::isDone);
@@ -420,7 +420,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public Chunk[] getLoadedChunks() {
-        List<ChunkHolder> chunks = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.world); // Paper
+        List<ChunkHolder> chunks = ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.world); // Paper
         return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new);
     }
 
@@ -2447,7 +2447,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
     @Override
     public void getChunkAtAsync(int x, int z, boolean gen, boolean urgent, @NotNull Consumer<? super Chunk> cb) {
         warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper
-        ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
+        ca.spottedleaf.moonrise.common.PlatformHooks.get().scheduleChunkLoad(
             this.getHandle(), x, z, gen, ChunkStatus.FULL, true,
             urgent ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL,
             (ChunkAccess chunk) -> {
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 1bdad8088d..039e17ad5d 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -3522,7 +3522,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public int getViewDistance() {
-        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getViewDistance(this.getHandle());
+        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getViewDistance(this.getHandle());
     }
 
     @Override
@@ -3532,7 +3532,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public int getSimulationDistance() {
-        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getTickViewDistance(this.getHandle());
+        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getTickViewDistance(this.getHandle());
     }
 
     @Override
@@ -3542,7 +3542,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public int getSendViewDistance() {
-        return ca.spottedleaf.moonrise.common.util.ChunkSystem.getSendViewDistance(this.getHandle());
+        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getSendViewDistance(this.getHandle());
     }
 
     @Override

From 45c905b928fca277a4fba763b47427b3e08e9eef Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 20 Dec 2024 09:19:19 -0800
Subject: [PATCH 259/285] Apply Moonrise patch minus CB diff

---
 .../0018-Moonrise-optimisation-patches.patch  | 6358 +++++++----------
 1 file changed, 2765 insertions(+), 3593 deletions(-)
 rename {feature-patches => paper-server/patches/features}/0018-Moonrise-optimisation-patches.patch (88%)

diff --git a/feature-patches/0018-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
similarity index 88%
rename from feature-patches/0018-Moonrise-optimisation-patches.patch
rename to paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
index b20c8eacca..16ce461d49 100644
--- a/feature-patches/0018-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
@@ -17,35 +17,6 @@ Currently includes:
 
 See https://github.com/Tuinity/Moonrise
 
-diff --git a/ca/spottedleaf/moonrise/common/PlatformHooks.java b/ca/spottedleaf/moonrise/common/PlatformHooks.java
-index 6c98d420ea84c10ef4f15d4deb3f04e610ed8548..9b879cbc037a17ffeb9a963111fd3f303a935eef 100644
---- a/ca/spottedleaf/moonrise/common/PlatformHooks.java
-+++ b/ca/spottedleaf/moonrise/common/PlatformHooks.java
-@@ -1,5 +1,6 @@
- package ca.spottedleaf.moonrise.common;
- 
-+import ca.spottedleaf.moonrise.common.util.ChunkSystemHooks;
- import com.mojang.datafixers.DSL;
- import com.mojang.datafixers.DataFixer;
- import net.minecraft.core.BlockPos;
-@@ -23,7 +24,7 @@ import java.util.List;
- import java.util.ServiceLoader;
- import java.util.function.Predicate;
- 
--public interface PlatformHooks {
-+public interface PlatformHooks extends ChunkSystemHooks {
-     public static PlatformHooks get() {
-         return Holder.INSTANCE;
-     }
-@@ -63,8 +64,6 @@ public interface PlatformHooks {
- 
-     public void entityMove(final Entity entity, final long oldSection, final long newSection);
- 
--    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
--
-     public boolean configFixMC224294();
- 
-     public boolean configAutoConfigSendDistance();
 diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..1b8193587814225c2ef2c5d9e667436eb50ff6c5
@@ -325,257 +296,65 @@ index 0000000000000000000000000000000000000000..1b8193587814225c2ef2c5d9e667436e
 +        }
 +    }
 +}
-diff --git a/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..89406dbda09eea03579ed724fda0df2d42e2e504
---- /dev/null
-+++ b/ca/spottedleaf/moonrise/common/util/BaseChunkSystemHooks.java
-@@ -0,0 +1,190 @@
-+package ca.spottedleaf.moonrise.common.util;
-+
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
-+import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
-+import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
-+import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache;
-+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.FullChunkStatus;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.progress.ChunkProgressListener;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.chunk.status.ChunkStatus;
-+import java.util.List;
-+import java.util.function.Consumer;
-+
-+public abstract class BaseChunkSystemHooks implements ChunkSystemHooks {
-+
-+    @Override
-+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
-+        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
-+    }
-+
-+    @Override
-+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkTask(chunkX, chunkZ, run, priority);
-+    }
-+
-+    @Override
-+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-+                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
-+                                  final Consumer<ChunkAccess> onComplete) {
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete);
-+    }
-+
-+    @Override
-+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-+                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-+    }
-+
-+    @Override
-+    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
-+                                     final FullChunkStatus toStatus, final boolean addTicket,
-+                                     final Priority priority, final Consumer<LevelChunk> onComplete) {
-+        ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
-+    }
-+
-+    @Override
-+    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
-+    }
-+
-+    @Override
-+    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
-+    }
-+
-+    @Override
-+    public int getVisibleChunkHolderCount(final ServerLevel level) {
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
-+    }
-+
-+    @Override
-+    public int getUpdatingChunkHolderCount(final ServerLevel level) {
-+        return ((ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
-+    }
-+
-+    @Override
-+    public boolean hasAnyChunkHolders(final ServerLevel level) {
-+        return getUpdatingChunkHolderCount(level) != 0;
-+    }
-+
-+    @Override
-+    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
-+
-+    }
-+
-+    @Override
-+    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
-+        // Update progress listener for LevelLoadingScreen
-+        final ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener;
-+        if (progressListener != null) {
-+            this.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> {
-+                progressListener.onStatusChange(holder.getPos(), null);
-+            });
-+        }
-+    }
-+
-+    @Override
-+    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
-+            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk);
-+    }
-+
-+    @Override
-+    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        chunk.loadCallback();
-+    }
-+
-+    @Override
-+    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        chunk.unloadCallback();
-+    }
-+
-+    @Override
-+    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
-+            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null);
-+    }
-+
-+    @Override
-+    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
-+            chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
-+        }
-+        ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
-+        ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
-+        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
-+    }
-+
-+    @Override
-+    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+        ((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
-+    }
-+
-+    @Override
-+    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+    }
-+
-+    @Override
-+    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
-+        ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove(
-+            ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
-+        );
-+    }
-+
-+    @Override
-+    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
-+        return null;
-+    }
-+
-+    @Override
-+    public int getSendViewDistance(final ServerPlayer player) {
-+        return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
-+    }
-+
-+    @Override
-+    public int getViewDistance(final ServerPlayer player) {
-+        return RegionizedPlayerChunkLoader.getAPIViewDistance(player);
-+    }
-+
-+    @Override
-+    public int getTickViewDistance(final ServerPlayer player) {
-+        return RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
-+    }
-+
-+    @Override
-+    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().addPlayer(player);
-+    }
-+
-+    @Override
-+    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().removePlayer(player);
-+    }
-+
-+    @Override
-+    public void updateMaps(final ServerLevel world, final ServerPlayer player) {
-+        ((ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
-+    }
-+}
-diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-deleted file mode 100644
-index 58a99bc38e137431f10af36fa9e2d04fe61694aa..0000000000000000000000000000000000000000
---- a/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-+++ /dev/null
-@@ -1,288 +0,0 @@
--package ca.spottedleaf.moonrise.common.util;
--
--import ca.spottedleaf.concurrentutil.util.Priority;
--import ca.spottedleaf.moonrise.common.PlatformHooks;
--import com.mojang.logging.LogUtils;
--import net.minecraft.server.level.ChunkHolder;
--import net.minecraft.server.level.FullChunkStatus;
--import net.minecraft.server.level.ServerLevel;
--import net.minecraft.server.level.ServerPlayer;
--import net.minecraft.world.entity.Entity;
--import net.minecraft.world.level.chunk.ChunkAccess;
--import net.minecraft.world.level.chunk.LevelChunk;
--import net.minecraft.world.level.chunk.status.ChunkStatus;
--import org.slf4j.Logger;
--import java.util.List;
--import java.util.function.Consumer;
--
--public final class ChunkSystem {
--
+diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+index c2dfbce23af5741b7f78ddd6df9bcbce69915ae9..5955a56a63e91edafbac07ac1f0c640a4f7cbb26 100644
+--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+@@ -268,7 +268,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
+ 
+     @Override
+     public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
+-        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
++        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities(), chunk.getPos()); // Paper - rewrite chunk system - add ChunkPos param
+     }
+ 
+     @Override
+diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
+index 34b45bc11124efb22f0f3ae5b2ad8f445c719476..62a9e62711a46283931d22b0e72b2b1903d973a1 100644
+--- a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
++++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
+@@ -23,218 +23,59 @@ import java.util.function.Consumer;
+ 
+ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.common.util.ChunkSystemHooks {
+ 
 -    private static final Logger LOGGER = LogUtils.getLogger();
--    private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
+-    private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
+-    private static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
+-
+-    private long chunkLoadCounter = 0L;
 -
 -    private static int getDistance(final ChunkStatus status) {
 -        return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
 -    }
 -
--    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
--        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
--    }
--
--    public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
+     @Override
+     public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
+-        this.scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
++        scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
+     }
+ 
+     @Override
+     public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
 -        level.chunkSource.mainThreadProcessor.execute(run);
--    }
--
--    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
--                                         final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
--                                         final Consumer<ChunkAccess> onComplete) {
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkTask(chunkX, chunkZ, run, priority);
+     }
+ 
+     @Override
+     public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
+                                   final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
+                                   final Consumer<ChunkAccess> onComplete) {
 -        if (gen) {
--            scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+-            this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
 -            return;
 -        }
--        scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
+-        this.scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
 -            if (chunk == null) {
 -                if (onComplete != null) {
 -                    onComplete.accept(null);
 -                }
 -            } else {
 -                if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
--                    scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+-                    BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
 -                } else {
 -                    if (onComplete != null) {
 -                        onComplete.accept(null);
@@ -583,23 +362,22 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..00000000000000000000000000000000
 -                }
 -            }
 -        });
--    }
--
--    static final net.minecraft.server.level.TicketType<Long> CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo);
--
--    private static long chunkLoadCounter = 0L;
--    public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
--                                         final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
--        if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
--            scheduleChunkTask(level, chunkX, chunkZ, () -> {
--                scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete);
+     }
+ 
+     @Override
+     public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+                                   final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
+-        if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
+-            this.scheduleChunkTask(level, chunkX, chunkZ, () -> {
+-                BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
 -            }, priority);
 -            return;
 -        }
 -
 -        final int minLevel = 33 + getDistance(toStatus);
--        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
--        final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
+-        final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null;
+-        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
 -
 -        if (addTicket) {
 -            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
@@ -622,50 +400,52 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..00000000000000000000000000000000
 -            }
 -        };
 -
--        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+-        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
 -
 -        if (holder == null || holder.getTicketLevel() > minLevel) {
 -            loadCallback.accept(null);
 -            return;
 -        }
 -
--        final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
+-        final CompletableFuture<ChunkResult<ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
 -
 -        if (loadFuture.isDone()) {
 -            loadCallback.accept(loadFuture.join().orElse(null));
 -            return;
 -        }
 -
--        loadFuture.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess> result, final Throwable thr) -> {
+-        loadFuture.whenCompleteAsync((final ChunkResult<ChunkAccess> result, final Throwable thr) -> {
 -            if (thr != null) {
 -                loadCallback.accept(null);
 -                return;
 -            }
 -            loadCallback.accept(result.orElse(null));
 -        }, (final Runnable r) -> {
--            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
+-            BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
 -        });
--    }
--
--    public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
--                                            final FullChunkStatus toStatus, final boolean addTicket,
--                                            final Priority priority, final Consumer<LevelChunk> onComplete) {
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+     }
+ 
+     @Override
+     public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+                                      final FullChunkStatus toStatus, final boolean addTicket,
+                                      final Priority priority, final Consumer<LevelChunk> onComplete) {
 -        // This method goes unused until the chunk system rewrite
 -        if (toStatus == FullChunkStatus.INACCESSIBLE) {
 -            throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
 -        }
 -
--        if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
--            scheduleChunkTask(level, chunkX, chunkZ, () -> {
--                scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+-        if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
+-            this.scheduleChunkTask(level, chunkX, chunkZ, () -> {
+-                BaseChunkSystemHooks.this.scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
 -            }, priority);
 -            return;
 -        }
 -
 -        final int minLevel = 33 - (toStatus.ordinal() - 1);
 -        final int radius = toStatus.ordinal() - 1;
--        final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
--        final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
+-        final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null;
+-        final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
 -
 -        if (addTicket) {
 -            level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
@@ -682,20 +462,20 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..00000000000000000000000000000000
 -                com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
 -            } finally {
 -                if (addTicket) {
--                    level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
+-                    level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
 -                    level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
 -                }
 -            }
 -        };
 -
--        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+-        final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
 -
 -        if (holder == null || holder.getTicketLevel() > minLevel) {
 -            loadCallback.accept(null);
 -            return;
 -        }
 -
--        final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk>> tickingState;
+-        final CompletableFuture<ChunkResult<LevelChunk>> tickingState;
 -        switch (toStatus) {
 -            case FULL: {
 -                tickingState = holder.getFullChunkFuture();
@@ -719,220 +499,184 @@ index 58a99bc38e137431f10af36fa9e2d04fe61694aa..00000000000000000000000000000000
 -            return;
 -        }
 -
--        tickingState.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk> result, final Throwable thr) -> {
+-        tickingState.whenCompleteAsync((final ChunkResult<LevelChunk> result, final Throwable thr) -> {
 -            if (thr != null) {
 -                loadCallback.accept(null);
 -                return;
 -            }
 -            loadCallback.accept(result.orElse(null));
 -        }, (final Runnable r) -> {
--            scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
+-            BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
 -        });
--    }
--
--    public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
--        return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
--    }
--
--    public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
--        return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
--    }
--
--    public static int getVisibleChunkHolderCount(final ServerLevel level) {
--        return level.chunkSource.chunkMap.visibleChunkMap.size();
--    }
--
--    public static int getUpdatingChunkHolderCount(final ServerLevel level) {
--        return level.chunkSource.chunkMap.updatingChunkMap.size();
--    }
--
--    public static boolean hasAnyChunkHolders(final ServerLevel level) {
--        return getUpdatingChunkHolderCount(level) != 0;
--    }
--
--    public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
--        if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
--            return false;
--        }
--        return true;
--    }
--
--    public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
--
--    }
--
--    public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
--        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
--    }
--
--    public static int getSendViewDistance(final ServerPlayer player) {
--        return getViewDistance(player);
--    }
--
--    public static int getViewDistance(final ServerPlayer player) {
--        final ServerLevel level = player.serverLevel();
--        if (level == null) {
--            return org.bukkit.Bukkit.getViewDistance();
--        }
--        return level.chunkSource.chunkMap.serverViewDistance;
--    }
--
--    public static int getTickViewDistance(final ServerPlayer player) {
--        final ServerLevel level = player.serverLevel();
--        if (level == null) {
--            return org.bukkit.Bukkit.getSimulationDistance();
--        }
--        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
--    }
--
--    private ChunkSystem() {}
--}
-diff --git a/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java b/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..427079ae47b6e0e1aa42013a8760fbefa76941f2
---- /dev/null
-+++ b/ca/spottedleaf/moonrise/common/util/ChunkSystemHooks.java
-@@ -0,0 +1,77 @@
-+package ca.spottedleaf.moonrise.common.util;
-+
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.FullChunkStatus;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.chunk.status.ChunkStatus;
-+import java.util.List;
-+import java.util.function.Consumer;
-+
-+public interface ChunkSystemHooks {
-+
-+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run);
-+
-+    public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority);
-+
-+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-+                                  final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
-+                                  final Consumer<ChunkAccess> onComplete);
-+
-+    public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-+                                  final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete);
-+
-+    public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
-+                                     final FullChunkStatus toStatus, final boolean addTicket,
-+                                     final Priority priority, final Consumer<LevelChunk> onComplete);
-+
-+    public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level);
-+
-+    public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level);
-+
-+    public int getVisibleChunkHolderCount(final ServerLevel level);
-+
-+    public int getUpdatingChunkHolderCount(final ServerLevel level);
-+
-+    public boolean hasAnyChunkHolders(final ServerLevel level);
-+
-+    public boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event);
-+
-+    public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder);
-+
-+    public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder);
-+
-+    public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder);
-+
-+    public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ);
-+
-+    public int getSendViewDistance(final ServerPlayer player);
-+
-+    public int getViewDistance(final ServerPlayer player);
-+
-+    public int getTickViewDistance(final ServerPlayer player);
-+
-+    public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player);
-+
-+    public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player);
-+
-+    public void updateMaps(final ServerLevel world, final ServerPlayer player);
-+}
-diff --git a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java b/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-index 12eb3add0931a4d77acdf6e875c42dda9c313dc3..5239993a681d6113eec99fa627b85508656ed7ac 100644
---- a/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-+++ b/ca/spottedleaf/moonrise/common/util/ThreadUnsafeRandom.java
-@@ -9,7 +9,7 @@ import net.minecraft.world.level.levelgen.PositionalRandomFactory;
- /**
-  * Avoid costly CAS of superclass
-  */
--public final class ThreadUnsafeRandom implements BitRandomSource {
-+public class ThreadUnsafeRandom implements BitRandomSource { // Paper - replace random
- 
-     private static final long MULTIPLIER = 25214903917L;
-     private static final long ADDEND = 11L;
-diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 11cfe9cc29666ce3a6a40281069fb9eb4fa0ded2..8c197c59eb35e02f163ec98b8aa0888e4ff40b1a 100644
---- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
-+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -27,7 +27,7 @@ import net.minecraft.world.phys.AABB;
- import java.util.List;
- import java.util.function.Predicate;
- 
--public final class PaperHooks implements PlatformHooks {
-+public final class PaperHooks extends ca.spottedleaf.moonrise.common.util.BaseChunkSystemHooks implements PlatformHooks { // Paper - rewrite chunk system
- 
-     @Override
-     public String getBrand() {
-@@ -267,7 +267,7 @@ public final class PaperHooks implements PlatformHooks {
- 
-     @Override
-     public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
--        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
-+        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities(), chunk.getPos()); // Paper - rewrite chunk system - add ChunkPos param
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
      }
  
      @Override
+     public List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
+-        return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
+     }
+ 
+     @Override
+     public List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
+-        return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.getOldChunkHolders();
+     }
+ 
+     @Override
+     public int getVisibleChunkHolderCount(final ServerLevel level) {
+-        return level.chunkSource.chunkMap.visibleChunkMap.size();
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
+     }
+ 
+     @Override
+     public int getUpdatingChunkHolderCount(final ServerLevel level) {
+-        return level.chunkSource.chunkMap.updatingChunkMap.size();
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)level).moonrise$getChunkTaskScheduler().chunkHolderManager.size();
+     }
+ 
+     @Override
+     public boolean hasAnyChunkHolders(final ServerLevel level) {
+-        return this.getUpdatingChunkHolderCount(level) != 0;
++        return getUpdatingChunkHolderCount(level) != 0;
+     }
+ 
+     @Override
+@@ -244,89 +85,110 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co
+ 
+     @Override
+     public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
+-
++        // Update progress listener for LevelLoadingScreen
++        final net.minecraft.server.level.progress.ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener;
++        if (progressListener != null) {
++            this.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> {
++                progressListener.onStatusChange(holder.getPos(), null);
++            });
++        }
+     }
+ 
+     @Override
+     public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
++            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk);
+     }
+ 
+     @Override
+     public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        chunk.loadCallback();
+     }
+ 
+     @Override
+     public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        chunk.unloadCallback();
+     }
+ 
+     @Override
+     public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource())
++            .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null);
+     }
+ 
+     @Override
+     public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        if (!((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
++            chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
++        }
++        ((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
++        ((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
++        ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
+     }
+ 
+     @Override
+     public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
++        ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
+     }
+ 
+     @Override
+     public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
+     }
+ 
+     @Override
+     public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove(
++            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
++        );
+     }
+ 
+     @Override
+     public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
+-        return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
++        return null;
+     }
+ 
+     @Override
+     public int getSendViewDistance(final ServerPlayer player) {
+-        return this.getViewDistance(player);
++        return ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
+     }
+ 
+     @Override
+     public int getViewDistance(final ServerPlayer player) {
+-        final ServerLevel level = player.serverLevel();
+-        if (level == null) {
+-            return Bukkit.getViewDistance();
+-        }
+-        return level.chunkSource.chunkMap.serverViewDistance;
++        return ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.getAPIViewDistance(player);
+     }
+ 
+     @Override
+     public int getTickViewDistance(final ServerPlayer player) {
+-        final ServerLevel level = player.serverLevel();
+-        if (level == null) {
+-            return Bukkit.getSimulationDistance();
+-        }
+-        return level.chunkSource.chunkMap.distanceManager.simulationDistance;
++        return ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
+     }
+ 
+     @Override
+     public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().addPlayer(player);
+     }
+ 
+     @Override
+     public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().removePlayer(player);
+     }
+ 
+     @Override
+     public void updateMaps(final ServerLevel world, final ServerPlayer player) {
+-
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
+     }
+ }
 diff --git a/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4a30e800b
@@ -23024,7 +22768,7 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +    private SaveUtil() {}
 +}
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index a6779295bff446ee79e7c9d41e405447becc2966..efc7f4071655201c59c912e9c84e35a8da66e34c 100644
+index 764daee7cd619c56314bcea9a4c35702afcb262d..e54355728183c594643f2e28ba2e92b1502882ac 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -1,6 +1,8 @@
@@ -23036,7 +22780,7 @@ index a6779295bff446ee79e7c9d41e405447becc2966..efc7f4071655201c59c912e9c84e35a8
  import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
  import it.unimi.dsi.fastutil.longs.LongSet;
  import it.unimi.dsi.fastutil.longs.LongSets;
-@@ -29,9 +31,12 @@ import org.bukkit.World;
+@@ -30,9 +32,12 @@ import org.bukkit.World;
  public final class FeatureHooks {
  
      public static void initChunkTaskScheduler(final boolean useParallelGen) {
@@ -23471,7 +23215,7 @@ index 0000000000000000000000000000000000000000..8424cf9d4617b4732d44cc460d25b044
 +
 +}
 diff --git a/net/minecraft/core/Direction.java b/net/minecraft/core/Direction.java
-index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f7507519a39b 100644
+index 3d3eec1db91cb47395f40c4f47aa77164ad42175..216f97207dac88cc1dc3df59c6ee8a62c7614b4a 100644
 --- a/net/minecraft/core/Direction.java
 +++ b/net/minecraft/core/Direction.java
 @@ -28,7 +28,7 @@ import org.joml.Quaternionf;
@@ -23529,7 +23273,7 @@ index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f750
 +    // Paper end - optimise collisions
  
      private Direction(
-         final int id,
+         final int data3d,
 @@ -147,14 +187,13 @@ public enum Direction implements StringRepresentable {
      }
  
@@ -23580,7 +23324,7 @@ index 690e1d2394e68356c56a39ac083cc53ee0388d71..928f38fd6beb00753c92ae9f4678f750
 +    // Paper end - optimise collisions
  }
 diff --git a/net/minecraft/core/MappedRegistry.java b/net/minecraft/core/MappedRegistry.java
-index 063630c1ffcce099139c59d598fc5a210e21f640..a61153c5d99bdc26f37a10f33baf839e943e17e1 100644
+index 47b1fafd91b39e73c4e9134b0b8048000fba108a..76994c1491221c06cca5405ba239e6ff642b19ed 100644
 --- a/net/minecraft/core/MappedRegistry.java
 +++ b/net/minecraft/core/MappedRegistry.java
 @@ -50,6 +50,19 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
@@ -23600,53 +23344,44 @@ index 063630c1ffcce099139c59d598fc5a210e21f640..a61153c5d99bdc26f37a10f33baf839e
 +    }
 +    // Paper end - fluid method optimisations
 +
-     public MappedRegistry(ResourceKey<? extends Registry<T>> key, Lifecycle lifecycle) {
-         this(key, lifecycle, false);
+     public MappedRegistry(ResourceKey<? extends Registry<T>> key, Lifecycle registryLifecycle) {
+         this(key, registryLifecycle, false);
      }
 @@ -114,6 +127,7 @@ public class MappedRegistry<T> implements WritableRegistry<T> {
-             this.toId.put(value, i);
-             this.registrationInfos.put(key, info);
-             this.registryLifecycle = this.registryLifecycle.add(info.lifecycle());
+             this.toId.put(value, size);
+             this.registrationInfos.put(key, registrationInfo);
+             this.registryLifecycle = this.registryLifecycle.add(registrationInfo.lifecycle());
 +            this.injectFluidRegister(key, value); // Paper - fluid method optimisations
              return reference;
          }
      }
 diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index 731bdabd53fd4a3d17494f26781223097a5d6e16..42d46c7a7437bea5335a23cbee5708ac57131474 100644
+index 4437283a5d157eede121b98be0112c1067eded5e..e627618f2368258d7cb9cd35908d0f42a9c504f5 100644
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
-@@ -322,6 +322,7 @@ public class Main {
- 
-             convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
+@@ -320,6 +320,7 @@ public class Main {
+             WorldData worldData = worldStem.worldData();
+             levelStorageAccess.saveDataTag(frozen, worldData);
              */
 +            Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async
-             final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
-                 DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius);
- 
+             final DedicatedServer dedicatedServer = MinecraftServer.spin(
+                 thread1 -> {
+                     DedicatedServer dedicatedServer1 = new DedicatedServer(
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd923cd0b1e 100644
+index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3dd10c767e 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -204,7 +204,7 @@ import org.bukkit.event.server.ServerLoadEvent;
- // CraftBukkit end
- 
+@@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
  
 -public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, ChunkIOErrorReporter, CommandSource {
 +public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, ChunkIOErrorReporter, CommandSource, ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer { // Paper - rewrite chunk system
- 
      private static MinecraftServer SERVER; // Paper
      public static final Logger LOGGER = LogUtils.getLogger();
-@@ -333,7 +333,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
-         ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-         AtomicReference<S> atomicreference = new AtomicReference();
--        Thread thread = new Thread(() -> {
-+        Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> { // Paper - rewrite chunk system
-             ((MinecraftServer) atomicreference.get()).runServer();
-         }, "Server thread");
- 
-@@ -352,6 +352,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-         return s0;
+     public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
+@@ -316,6 +316,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         return minecraftServer;
      }
  
 +    // Paper start - rewrite chunk system
@@ -23720,73 +23455,71 @@ index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd9
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
-         super("Server");
-         SERVER = this; // Paper - better singleton
-@@ -672,7 +743,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+     public MinecraftServer(
+         // CraftBukkit start
+         joptsimple.OptionSet options,
+@@ -626,7 +697,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          this.forceDifficulty();
-         for (ServerLevel worldserver : this.getAllLevels()) {
-             this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
--            worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
+         for (ServerLevel serverLevel : this.getAllLevels()) {
+             this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
+-            serverLevel.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
 +            // Paper - rewrite chunk system
-             this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
+             this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
          }
  
-@@ -888,6 +959,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -825,6 +896,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public abstract boolean shouldRconBroadcast();
  
-     public boolean saveAllChunks(boolean suppressLogs, boolean flush, boolean force) {
+     public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
 +        // Paper start - add close param
-+        return this.saveAllChunks(suppressLogs, flush, force, false);
++        return this.saveAllChunks(suppressLog, flush, forced, false);
 +    }
-+    public boolean saveAllChunks(boolean suppressLogs, boolean flush, boolean force, boolean close) {
++    public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced, boolean close) {
 +        // Paper end - add close param
-         boolean flag3 = false;
+         boolean flag = false;
  
-         for (Iterator iterator = this.getAllLevels().iterator(); iterator.hasNext(); flag3 = true) {
-@@ -897,7 +973,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-                 MinecraftServer.LOGGER.info("Saving chunks for level '{}'/{}", worldserver, worldserver.dimension().location());
+         for (ServerLevel serverLevel : this.getAllLevels()) {
+@@ -832,7 +908,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+                 LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
              }
  
--            worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force);
-+            worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force, close); // Paper - add close param
+-            serverLevel.save(null, flush, serverLevel.noSave && !forced);
++            serverLevel.save(null, flush, serverLevel.noSave && !forced, close); // Paper - add close param
+             flag = true;
          }
  
-         // CraftBukkit start - moved to WorldServer.save
-@@ -998,7 +1074,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -923,7 +999,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              }
          }
  
--        while (this.levels.values().stream().anyMatch((worldserver1) -> {
-+        while (false && this.levels.values().stream().anyMatch((worldserver1) -> { // Paper - rewrite chunk system
-             return worldserver1.getChunkSource().chunkMap.hasWork();
-         })) {
+-        while (this.levels.values().stream().anyMatch(level -> level.getChunkSource().chunkMap.hasWork())) {
++        while (false && this.levels.values().stream().anyMatch(level -> level.getChunkSource().chunkMap.hasWork())) { // Paper - rewrite chunk system
              this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
-@@ -1015,19 +1091,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ 
+             for (ServerLevel serverLevelx : this.getAllLevels()) {
+@@ -934,17 +1010,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.waitUntilNextTick();
          }
  
 -        this.saveAllChunks(false, true, false);
--        iterator = this.getAllLevels().iterator();
 -
--        while (iterator.hasNext()) {
--            worldserver = (ServerLevel) iterator.next();
--            if (worldserver != null) {
+-        for (ServerLevel serverLevelx : this.getAllLevels()) {
+-            if (serverLevelx != null) {
 -                try {
--                    worldserver.close();
--                } catch (IOException ioexception) {
--                    MinecraftServer.LOGGER.error("Exception closing the level", ioexception);
+-                    serverLevelx.close();
+-                } catch (IOException var5) {
+-                    LOGGER.error("Exception closing the level", (Throwable)var5);
 -                }
 -            }
 -        }
-+        this.saveAllChunks(false, true, true, true); // Paper - rewrite chunk system
++        this.saveAllChunks(false, true, false, true); // Paper - rewrite chunk system
  
          this.isSaving = false;
          this.resources.close();
-@@ -1047,6 +1111,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -963,6 +1029,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+             this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
          // Spigot end
- 
 +        // Paper start - rewrite chunk system
 +        LOGGER.info("Waiting for I/O tasks to complete...");
 +        ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.flush((MinecraftServer)(Object)this);
@@ -23798,21 +23531,21 @@ index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd9
      }
  
      public String getLocalIp() {
-@@ -1228,6 +1300,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-                         this.tickServer(flag ? () -> {
-                             return false;
-                         } : this::haveTime);
-+                        // Paper start - rewrite chunk system
-+                        final Throwable crash = this.chunkSystemCrash;
-+                        if (crash != null) {
-+                            this.chunkSystemCrash = null;
-+                            throw new RuntimeException("Chunk system crash propagated to tick()", crash);
-+                        }
-+                        // Paper end - rewrite chunk system
-                         this.tickFrame.end();
-                         gameprofilerfiller.popPush("nextTickWait");
-                         this.mayHaveDelayedTasks = true;
-@@ -1432,6 +1511,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1133,6 +1207,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+                     profilerFiller.push("tick");
+                     this.tickFrame.start();
+                     this.tickServer(flag ? () -> false : this::haveTime);
++                    // Paper start - rewrite chunk system
++                    final Throwable crash = this.chunkSystemCrash;
++                    if (crash != null) {
++                        this.chunkSystemCrash = null;
++                        throw new RuntimeException("Chunk system crash propagated to tick()", crash);
++                    }
++                    // Paper end - rewrite chunk system
+                     this.tickFrame.end();
+                     profilerFiller.popPush("nextTickWait");
+                     this.mayHaveDelayedTasks = true;
+@@ -1305,6 +1386,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      private boolean pollTaskInternal() {
          if (super.pollTask()) {
@@ -23820,8 +23553,8 @@ index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd9
              return true;
          } else {
              boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
-@@ -2713,6 +2793,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
+@@ -2425,6 +2507,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         }
      }
  
 +    // Paper start - rewrite chunk system
@@ -23830,23 +23563,22 @@ index 807d05097f7313361eadb600187421d25e294413..5e7ba47247fc9b6bc8da86d8f67c6cd9
 +        return ca.spottedleaf.moonrise.common.util.TickThread.isTickThread();
 +    }
 +    // Paper end - rewrite chunk system
-+
+ 
      // CraftBukkit start
      public boolean isDebugging() {
-         return false;
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 2f47d95943c00020a24ea3ff1a49e64e114de675..0dd9ed7465d222505d5368781654ec4954f6e5c3 100644
+index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..ab356d1afb352f1f134d25dfa17ce5476a2039cd 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -458,7 +458,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-         return world.dimension() == net.minecraft.world.level.Level.NETHER ? this.getProperties().allowNether : true;
+@@ -432,7 +432,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+         return level.dimension() != Level.NETHER || this.getProperties().allowNether;
      }
  
 +    private static final java.util.concurrent.atomic.AtomicInteger ASYNC_DEBUG_CHUNKS_COUNT = new java.util.concurrent.atomic.AtomicInteger(); // Paper - rewrite chunk system
 +
-     public void handleConsoleInput(String command, CommandSourceStack commandSource) {
+     public void handleConsoleInput(String msg, CommandSourceStack source) {
 +        // Paper start - rewrite chunk system
-+        if (command.equalsIgnoreCase("paper debug chunks --async")) {
++        if (msg.equalsIgnoreCase("paper debug chunks --async")) {
 +            LOGGER.info("Scheduling async debug chunks");
 +            Runnable run = () -> {
 +                LOGGER.info("Async debug chunks executing");
@@ -23869,42 +23601,41 @@ index 2f47d95943c00020a24ea3ff1a49e64e114de675..0dd9ed7465d222505d5368781654ec49
 +            return;
 +        }
 +        // Paper end - rewrite chunk system
-         this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue
+         this.serverCommandQueue.add(new ConsoleInput(msg, source)); // Paper - Perf: use proper queue
      }
  
 diff --git a/net/minecraft/server/level/ChunkHolder.java b/net/minecraft/server/level/ChunkHolder.java
-index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f977e09e7e0 100644
+index cc63e49b7d1b4ba6e8df87aff4cf71036d3de5c5..a37cc96ec26c92a60b0f0ca43d48705cd2bb072e 100644
 --- a/net/minecraft/server/level/ChunkHolder.java
 +++ b/net/minecraft/server/level/ChunkHolder.java
-@@ -32,46 +32,125 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
- import net.minecraft.server.MinecraftServer;
- // CraftBukkit end
+@@ -29,27 +29,112 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
+ import net.minecraft.world.level.chunk.status.ChunkStatus;
+ import net.minecraft.world.level.lighting.LevelLightEngine;
  
 -public class ChunkHolder extends GenerationChunkHolder {
 +public class ChunkHolder extends GenerationChunkHolder implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder { // Paper - rewrite chunk system
- 
      public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
-     private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+     private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK);
      private final LevelHeightAccessor levelHeightAccessor;
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
--    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
+-    private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
 -    public int oldTicketLevel;
 -    private int ticketLevel;
 -    private int queueLevel;
 +    // Paper - rewrite chunk system
      private boolean hasChangedSections;
      private final ShortSet[] changedBlocksPerSection;
-     private final BitSet blockChangedLightSectionFilter;
-     private final BitSet skyChangedLightSectionFilter;
+     private final BitSet blockChangedLightSectionFilter = new BitSet();
+     private final BitSet skyChangedLightSectionFilter = new BitSet();
      private final LevelLightEngine lightEngine;
 -    private final ChunkHolder.LevelChangeListener onLevelChange;
 +    // Paper - rewrite chunk system
      public final ChunkHolder.PlayerProvider playerProvider;
 -    private boolean wasAccessibleSinceLastSave;
--    private CompletableFuture<?> pendingFullStateConfirmation;
--    private CompletableFuture<?> sendSync;
--    private CompletableFuture<?> saveSync;
+-    private CompletableFuture<?> pendingFullStateConfirmation = CompletableFuture.completedFuture(null);
+-    private CompletableFuture<?> sendSync = CompletableFuture.completedFuture(null);
+-    private CompletableFuture<?> saveSync = CompletableFuture.completedFuture(null);
 +    // Paper - rewrite chunk system
 +
 +    // Paper start - rewrite chunk system
@@ -24000,31 +23731,23 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 +    }
 +    // Paper end - rewrite chunk system
  
-     public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
+     public ChunkHolder(
+         ChunkPos pos,
+@@ -62,11 +147,9 @@ public class ChunkHolder extends GenerationChunkHolder {
          super(pos);
--        this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
--        this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
--        this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+         this.levelHeightAccessor = levelHeightAccessor;
+         this.lightEngine = lightEngine;
+-        this.onLevelChange = onLevelChange;
 +        // Paper - rewrite chunk system
-         this.blockChangedLightSectionFilter = new BitSet();
-         this.skyChangedLightSectionFilter = new BitSet();
--        this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
--        this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
--        this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
-+        // Paper - rewrite chunk system
-         this.levelHeightAccessor = world;
-         this.lightEngine = lightingProvider;
--        this.onLevelChange = levelUpdateListener;
-+        // Paper - rewrite chunk system
-         this.playerProvider = playersWatchingChunkProvider;
+         this.playerProvider = playerProvider;
 -        this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1;
 -        this.ticketLevel = this.oldTicketLevel;
 -        this.queueLevel = this.oldTicketLevel;
 +        // Paper - rewrite chunk system
-         this.setTicketLevel(level);
-         this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
+         this.setTicketLevel(ticketLevel);
+         this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()];
      }
-@@ -79,7 +158,7 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -74,7 +157,7 @@ public class ChunkHolder extends GenerationChunkHolder {
      // CraftBukkit start
      public LevelChunk getFullChunkNow() {
          // Note: We use the oldTicketLevel for isLoaded checks.
@@ -24033,7 +23756,7 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
          return this.getFullChunkNowUnchecked();
      }
  
-@@ -89,64 +168,65 @@ public class ChunkHolder extends GenerationChunkHolder {
+@@ -84,58 +167,63 @@ public class ChunkHolder extends GenerationChunkHolder {
      // CraftBukkit end
  
      public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
@@ -24052,8 +23775,8 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
      }
  
      @Nullable
-     public final LevelChunk getTickingChunk() { // Paper - final for inline
--        return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error
+     public LevelChunk getTickingChunk() {
+-        return this.getTickingChunkFuture().getNow(UNLOADED_LEVEL_CHUNK).orElse(null);
 +        // Paper start - rewrite chunk system
 +        if (this.newChunkHolder.isTickingReady()) {
 +            if (this.newChunkHolder.getCurrentChunk() instanceof LevelChunk levelChunk) {
@@ -24081,16 +23804,13 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public void addSendDependency(CompletableFuture<?> postProcessingFuture) {
+     public void addSendDependency(CompletableFuture<?> dependency) {
 -        if (this.sendSync.isDone()) {
--            this.sendSync = postProcessingFuture;
+-            this.sendSync = dependency;
 -        } else {
--            this.sendSync = this.sendSync.thenCombine(postProcessingFuture, (object, object1) -> {
--                return null;
--            });
+-            this.sendSync = this.sendSync.thenCombine((CompletionStage<? extends Object>)dependency, (object, object1) -> null);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
- 
      }
  
      public CompletableFuture<?> getSaveSyncFuture() {
@@ -24104,52 +23824,49 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
      }
  
      @Override
-     protected void addSaveDependency(CompletableFuture<?> savingFuture) {
+     protected void addSaveDependency(CompletableFuture<?> dependency) {
 -        if (this.saveSync.isDone()) {
--            this.saveSync = savingFuture;
+-            this.saveSync = dependency;
 -        } else {
--            this.saveSync = this.saveSync.thenCombine(savingFuture, (object, object1) -> {
--                return null;
--            });
+-            this.saveSync = this.saveSync.thenCombine((CompletionStage<? extends Object>)dependency, (object, object1) -> null);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
- 
      }
  
      public boolean blockChanged(BlockPos pos) {
--        LevelChunk chunk = this.getTickingChunk();
-+        LevelChunk chunk = this.playersSentChunkTo.size() == 0 ? null : this.getChunkToSend(); // Paper - rewrite chunk system
- 
-         if (chunk == null) {
-             return false;
-@@ -172,7 +252,7 @@ public class ChunkHolder extends GenerationChunkHolder {
+-        LevelChunk tickingChunk = this.getTickingChunk();
++        LevelChunk tickingChunk = this.playersSentChunkTo.size() == 0 ? null : this.getChunkToSend(); // Paper - rewrite chunk system
+         if (tickingChunk == null) {
              return false;
          } else {
-             ichunkaccess.markUnsaved();
--            LevelChunk chunk = this.getTickingChunk();
-+            LevelChunk chunk = this.playersSentChunkTo.size() == 0 ? null : this.getChunkToSend(); // Paper - rewrite chunk system
- 
-             if (chunk == null) {
+@@ -158,7 +246,7 @@ public class ChunkHolder extends GenerationChunkHolder {
+             return false;
+         } else {
+             chunkIfPresent.markUnsaved();
+-            LevelChunk tickingChunk = this.getTickingChunk();
++            LevelChunk tickingChunk = this.playersSentChunkTo.size() == 0 ? null : this.getChunkToSend(); // Paper - rewrite chunk system
+             if (tickingChunk == null) {
                  return false;
-@@ -207,7 +287,7 @@ public class ChunkHolder extends GenerationChunkHolder {
-             List list;
- 
+             } else {
+@@ -188,7 +276,7 @@ public class ChunkHolder extends GenerationChunkHolder {
+         if (this.hasChangesToBroadcast()) {
+             Level level = chunk.getLevel();
              if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
--                list = this.playerProvider.getPlayers(this.pos, true);
-+                list = this.moonrise$getPlayers(true); // Paper - rewrite chunk system
-                 if (!list.isEmpty()) {
-                     ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter);
- 
-@@ -219,7 +299,7 @@ public class ChunkHolder extends GenerationChunkHolder {
+-                List<ServerPlayer> players = this.playerProvider.getPlayers(this.pos, true);
++                List<ServerPlayer> players = this.moonrise$getPlayers(true); // Paper - rewrite chunk system
+                 if (!players.isEmpty()) {
+                     ClientboundLightUpdatePacket clientboundLightUpdatePacket = new ClientboundLightUpdatePacket(
+                         chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter
+@@ -201,7 +289,7 @@ public class ChunkHolder extends GenerationChunkHolder {
              }
  
              if (this.hasChangedSections) {
--                list = this.playerProvider.getPlayers(this.pos, false);
-+                list = this.moonrise$getPlayers(false); // Paper - rewrite chunk system
+-                List<ServerPlayer> players = this.playerProvider.getPlayers(this.pos, false);
++                List<ServerPlayer> players = this.moonrise$getPlayers(false); // Paper - rewrite chunk system
  
-                 for (int i = 0; i < this.changedBlocksPerSection.length; ++i) {
-                     ShortSet shortset = this.changedBlocksPerSection[i];
-@@ -285,201 +365,48 @@ public class ChunkHolder extends GenerationChunkHolder {
+                 for (int i = 0; i < this.changedBlocksPerSection.length; i++) {
+                     ShortSet set = this.changedBlocksPerSection[i];
+@@ -256,193 +344,50 @@ public class ChunkHolder extends GenerationChunkHolder {
  
      @Override
      public int getTicketLevel() {
@@ -24163,8 +23880,8 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void setQueueLevel(int level) {
--        this.queueLevel = level;
+     private void setQueueLevel(int queueLevel) {
+-        this.queueLevel = queueLevel;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
@@ -24173,41 +23890,36 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 +        // Paper - rewrite chunk system
      }
  
-     private void scheduleFullChunkPromotion(ChunkMap chunkLoadingManager, CompletableFuture<ChunkResult<LevelChunk>> chunkFuture, Executor executor, FullChunkStatus target) {
+     private void scheduleFullChunkPromotion(
+         ChunkMap chunkMap, CompletableFuture<ChunkResult<LevelChunk>> future, Executor executor, FullChunkStatus fullChunkStatus
+     ) {
 -        this.pendingFullStateConfirmation.cancel(false);
--        CompletableFuture<Void> completablefuture1 = new CompletableFuture();
--
--        completablefuture1.thenRunAsync(() -> {
--            chunkLoadingManager.onFullChunkStatusChange(this.pos, target);
--        }, executor);
--        this.pendingFullStateConfirmation = completablefuture1;
--        chunkFuture.thenAccept((chunkresult) -> {
--            chunkresult.ifSuccess((chunk) -> {
--                completablefuture1.complete(null); // CraftBukkit - decompile error
--            });
--        });
+-        CompletableFuture<Void> completableFuture = new CompletableFuture<>();
+-        completableFuture.thenRunAsync(() -> chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus), executor);
+-        this.pendingFullStateConfirmation = completableFuture;
+-        future.thenAccept(chunkResult -> chunkResult.ifSuccess(levelChunk -> completableFuture.complete(null)));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void demoteFullChunk(ChunkMap chunkLoadingManager, FullChunkStatus target) {
+     private void demoteFullChunk(ChunkMap chunkMap, FullChunkStatus fullChunkStatus) {
 -        this.pendingFullStateConfirmation.cancel(false);
--        chunkLoadingManager.onFullChunkStatusChange(this.pos, target);
+-        chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      // CraftBukkit start
      // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
      // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks
-     protected void callEventIfUnloading(ChunkMap playerchunkmap) {
+     protected void callEventIfUnloading(ChunkMap chunkMap) {
 -        FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
 -        FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel);
 -        boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
 -        boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL);
 -        if (oldIsFull && !newIsFull) {
 -            this.getFullChunkFuture().thenAccept((either) -> {
--                LevelChunk chunk = (LevelChunk) either.orElse(null);
+-                LevelChunk chunk = either.orElse(null);
 -                if (chunk != null) {
--                    playerchunkmap.callbackExecutor.execute(() -> {
+-                    chunkMap.callbackExecutor.execute(() -> {
 -                        // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
 -                        // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
 -                        // These actions may however happen deferred, so we manually set the needsSaving flag already here.
@@ -24217,34 +23929,33 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 -                }
 -            }).exceptionally((throwable) -> {
 -                // ensure exceptions are printed, by default this is not the case
--                MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
+-                net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
 -                return null;
 -            });
 -
 -            // Run callback right away if the future was already done
--            playerchunkmap.callbackExecutor.run();
+-            chunkMap.callbackExecutor.run();
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
      // CraftBukkit end
  
-     protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) {
--        FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
--        FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
--        boolean flag = fullchunkstatus.isOrAfter(FullChunkStatus.FULL);
--        boolean flag1 = fullchunkstatus1.isOrAfter(FullChunkStatus.FULL);
--
--        this.wasAccessibleSinceLastSave |= flag1;
--        if (!flag && flag1) {
+     protected void updateFutures(ChunkMap chunkMap, Executor executor) {
+-        FullChunkStatus fullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+-        FullChunkStatus fullChunkStatus1 = ChunkLevel.fullStatus(this.ticketLevel);
+-        boolean isOrAfter = fullChunkStatus.isOrAfter(FullChunkStatus.FULL);
+-        boolean isOrAfter1 = fullChunkStatus1.isOrAfter(FullChunkStatus.FULL);
+-        this.wasAccessibleSinceLastSave |= isOrAfter1;
+-        if (!isOrAfter && isOrAfter1) {
 -            int expectCreateCount = ++this.fullChunkCreateCount; // Paper
--            this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this);
--            this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL);
+-            this.fullChunkFuture = chunkMap.prepareAccessibleChunk(this);
+-            this.scheduleFullChunkPromotion(chunkMap, this.fullChunkFuture, executor, FullChunkStatus.FULL);
 -            // Paper start - cache ticking ready status
 -            this.fullChunkFuture.thenAccept(chunkResult -> {
 -                chunkResult.ifSuccess(chunk -> {
 -                    if (ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
 -                        ChunkHolder.this.isFullChunkReady = true;
--                        ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkBorder(chunk, this);
+-                        ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkBorder(chunk, this);
 -                    }
 -                });
 -            });
@@ -24252,99 +23963,97 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
 -            this.addSaveDependency(this.fullChunkFuture);
 -        }
 -
--        if (flag && !flag1) {
+-        if (isOrAfter && !isOrAfter1) {
 -            // Paper start
 -            if (this.isFullChunkReady) {
--                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+-                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 -            }
 -            // Paper end
--            this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
--            this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+-            this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
+-            this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 -        }
 -
--        boolean flag2 = fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
--        boolean flag3 = fullchunkstatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING);
--
--        if (!flag2 && flag3) {
--            this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this);
--            this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
+-        boolean isOrAfter2 = fullChunkStatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
+-        boolean isOrAfter3 = fullChunkStatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING);
+-        if (!isOrAfter2 && isOrAfter3) {
+-            this.tickingChunkFuture = chunkMap.prepareTickingChunk(this);
+-            this.scheduleFullChunkPromotion(chunkMap, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING);
 -            // Paper start - cache ticking ready status
 -            this.tickingChunkFuture.thenAccept(chunkResult -> {
 -                chunkResult.ifSuccess(chunk -> {
 -                    // note: Here is a very good place to add callbacks to logic waiting on this.
 -                    ChunkHolder.this.isTickingReady = true;
--                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkTicking(chunk, this);
+-                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkTicking(chunk, this);
 -                });
 -            });
 -            // Paper end
 -            this.addSaveDependency(this.tickingChunkFuture);
 -        }
 -
--        if (flag2 && !flag3) {
+-        if (isOrAfter2 && !isOrAfter3) {
 -            // Paper start
 -            if (this.isTickingReady) {
--                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
+-                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper
 -            }
 -            // Paper end
 -            this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
--            this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+-            this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 -        }
 -
--        boolean flag4 = fullchunkstatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
--        boolean flag5 = fullchunkstatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING);
--
--        if (!flag4 && flag5) {
--            if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) {
--                throw (IllegalStateException) Util.pauseInIde(new IllegalStateException());
+-        boolean isOrAfter4 = fullChunkStatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
+-        boolean isOrAfter5 = fullChunkStatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING);
+-        if (!isOrAfter4 && isOrAfter5) {
+-            if (this.entityTickingChunkFuture != UNLOADED_LEVEL_CHUNK_FUTURE) {
+-                throw (IllegalStateException)Util.pauseInIde(new IllegalStateException());
 -            }
 -
--            this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this);
--            this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
+-            this.entityTickingChunkFuture = chunkMap.prepareEntityTickingChunk(this);
+-            this.scheduleFullChunkPromotion(chunkMap, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING);
 -            // Paper start - cache ticking ready status
 -            this.entityTickingChunkFuture.thenAccept(chunkResult -> {
 -                chunkResult.ifSuccess(chunk -> {
 -                    ChunkHolder.this.isEntityTickingReady = true;
--                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkEntityTicking(chunk, this);
+-                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkEntityTicking(chunk, this);
 -                });
 -            });
 -            // Paper end
 -            this.addSaveDependency(this.entityTickingChunkFuture);
 -        }
 -
--        if (flag4 && !flag5) {
+-        if (isOrAfter4 && !isOrAfter5) {
 -            // Paper start
 -            if (this.isEntityTickingReady) {
--                ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
+-                ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this);
 -            }
 -            // Paper end
 -            this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
--            this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+-            this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
 -        }
 -
--        if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) {
--            this.demoteFullChunk(chunkLoadingManager, fullchunkstatus1);
+-        if (!fullChunkStatus1.isOrAfter(fullChunkStatus)) {
+-            this.demoteFullChunk(chunkMap, fullChunkStatus1);
 -        }
 -
 -        this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
 -        this.oldTicketLevel = this.ticketLevel;
 -        // CraftBukkit start
 -        // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
--        if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
+-        if (!fullChunkStatus.isOrAfter(FullChunkStatus.FULL) && fullChunkStatus1.isOrAfter(FullChunkStatus.FULL)) {
 -            this.getFullChunkFuture().thenAccept((either) -> {
 -                LevelChunk chunk = (LevelChunk) either.orElse(null);
 -                if (chunk != null) {
--                    chunkLoadingManager.callbackExecutor.execute(() -> {
+-                    chunkMap.callbackExecutor.execute(() -> {
 -                        chunk.loadCallback();
 -                    });
 -                }
 -            }).exceptionally((throwable) -> {
 -                // ensure exceptions are printed, by default this is not the case
--                MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
+-                net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
 -                return null;
 -            });
 -
 -            // Run callback right away if the future was already done
--            chunkLoadingManager.callbackExecutor.run();
+-            chunkMap.callbackExecutor.run();
 -        }
 -        // CraftBukkit end
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
@@ -24362,7 +24071,7 @@ index b9ab241b930edc63a39dbbcf14cd0b5edacb9ea9..8dd9375f2ad2c65a773a3195aeff1f97
  
      @FunctionalInterface
 diff --git a/net/minecraft/server/level/ChunkLevel.java b/net/minecraft/server/level/ChunkLevel.java
-index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb9652bb66 100644
+index e823b8aac00158892538083bc877ccf99895909a..7d871318065f19540748363809de82652613e733 100644
 --- a/net/minecraft/server/level/ChunkLevel.java
 +++ b/net/minecraft/server/level/ChunkLevel.java
 @@ -7,8 +7,8 @@ import net.minecraft.world.level.chunk.status.ChunkStep;
@@ -24377,50 +24086,50 @@ index 11b30b6daa1d049634350e34502c701e9800add4..fae17a075d7efaf24d916877dd5968eb
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e11526dfd7c2f 100644
+index 8c1eea5339b650bd1527b5b3aa010c12d70a6de1..86aaf32de5d80ee222cbe7eff0f6c119a032e04f 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
-@@ -108,7 +108,7 @@ import org.slf4j.Logger;
- import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
- // CraftBukkit end
+@@ -96,7 +96,7 @@ import net.minecraft.world.level.storage.LevelStorageSource;
+ import org.apache.commons.lang3.mutable.MutableBoolean;
+ import org.slf4j.Logger;
  
 -public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
 +public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemChunkMap { // Paper - rewrite chunk system
- 
      private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
-     private static final CompletableFuture<ChunkResult<List<ChunkAccess>>> UNLOADED_CHUNK_LIST_FUTURE = CompletableFuture.completedFuture(ChunkMap.UNLOADED_CHUNK_LIST_RESULT);
-@@ -123,10 +123,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+     private static final CompletableFuture<ChunkResult<List<ChunkAccess>>> UNLOADED_CHUNK_LIST_FUTURE = CompletableFuture.completedFuture(
+         UNLOADED_CHUNK_LIST_RESULT
+@@ -112,10 +112,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      public static final int MIN_VIEW_DISTANCE = 2;
      public static final int MAX_VIEW_DISTANCE = 32;
      public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
--    public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap();
--    public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
--    private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads;
--    private final List<ChunkGenerationTask> pendingGenerationTasks;
+-    public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap<>();
+-    public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap = this.updatingChunkMap.clone();
+-    private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads = new Long2ObjectLinkedOpenHashMap<>();
+-    private final List<ChunkGenerationTask> pendingGenerationTasks = new ArrayList<>();
 +    // Paper - rewrite chunk system
      public final ServerLevel level;
      private final ThreadedLevelLightEngine lightEngine;
      private final BlockableEventLoop<Runnable> mainThreadExecutor;
-@@ -136,22 +133,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -125,22 +122,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      private final PoiManager poiManager;
-     public final LongSet toDrop;
+     public final LongSet toDrop = new LongOpenHashSet();
      private boolean modified;
 -    private final ChunkTaskDispatcher worldgenTaskDispatcher;
 -    private final ChunkTaskDispatcher lightTaskDispatcher;
 +    // Paper - rewrite chunk system
      public final ChunkProgressListener progressListener;
      private final ChunkStatusUpdateListener chunkStatusListener;
-     public final ChunkMap.ChunkDistanceManager distanceManager;
--    private final AtomicInteger tickingGenerated;
-+    public final AtomicInteger tickingGenerated; // Paper - public
+     public final ChunkMap.DistanceManager distanceManager;
+-    private final AtomicInteger tickingGenerated = new AtomicInteger();
++    public final AtomicInteger tickingGenerated = new AtomicInteger();  // Paper - public
      private final String storageName;
-     private final PlayerMap playerMap;
-     public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
-     private final Long2ByteMap chunkTypeCache;
--    private final Long2LongMap nextChunkSaveTime;
--    private final LongSet chunksToEagerlySave;
--    private final Queue<Runnable> unloadQueue;
--    private final AtomicInteger activeChunkWrites;
+     private final PlayerMap playerMap = new PlayerMap();
+     public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>();
+     private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap();
+-    private final Long2LongMap nextChunkSaveTime = new Long2LongOpenHashMap();
+-    private final LongSet chunksToEagerlySave = new LongLinkedOpenHashSet();
+-    private final Queue<Runnable> unloadQueue = Queues.newConcurrentLinkedQueue();
+-    private final AtomicInteger activeChunkWrites = new AtomicInteger();
 +    // Paper - rewrite chunk system
      public int serverViewDistance;
 -    private final WorldGenContext worldGenContext;
@@ -24428,7 +24137,7 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
  
      // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
      public final CallbackExecutor callbackExecutor = new CallbackExecutor();
-@@ -176,24 +169,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -165,9 +158,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
      // Paper start
      public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
@@ -24444,62 +24153,50 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +    }
 +    // Paper end - rewrite chunk system
  
-     public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
-         super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
--        this.visibleChunkMap = this.updatingChunkMap.clone();
--        this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
--        this.pendingGenerationTasks = new ArrayList();
+     public ChunkMap(
+         ServerLevel level,
+@@ -213,10 +213,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         this.progressListener = progressListener;
+         this.chunkStatusListener = chunkStatusListener;
+         ConsecutiveExecutor consecutiveExecutor1 = new ConsecutiveExecutor(dispatcher, "light");
+-        this.worldgenTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor, dispatcher);
+-        this.lightTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor1, dispatcher);
 +        // Paper - rewrite chunk system
-         this.toDrop = new LongOpenHashSet();
-         this.tickingGenerated = new AtomicInteger();
-         this.playerMap = new PlayerMap();
-         this.entityMap = new Int2ObjectOpenHashMap();
-         this.chunkTypeCache = new Long2ByteOpenHashMap();
--        this.nextChunkSaveTime = new Long2LongOpenHashMap();
--        this.chunksToEagerlySave = new LongLinkedOpenHashSet();
--        this.unloadQueue = Queues.newConcurrentLinkedQueue();
--        this.activeChunkWrites = new AtomicInteger();
-+        // Paper - rewrite chunk system
-         Path path = session.getDimensionPath(world.dimension());
- 
-         this.storageName = path.getFileName().toString();
-@@ -221,18 +216,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-         this.chunkStatusListener = chunkStatusChangeListener;
-         ConsecutiveExecutor consecutiveexecutor1 = new ConsecutiveExecutor(executor, "light");
- 
--        this.worldgenTaskDispatcher = new ChunkTaskDispatcher(consecutiveexecutor, executor);
--        this.lightTaskDispatcher = new ChunkTaskDispatcher(consecutiveexecutor1, executor);
--        this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), consecutiveexecutor1, this.lightTaskDispatcher);
-+        this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), consecutiveexecutor1, null); // Paper - rewrite chunk system
-         this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
-         this.overworldDataStorage = persistentStateManagerFactory;
-         this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world.getServer(), world);
+         this.lightEngine = new ThreadedLevelLightEngine(
+-            lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor1, this.lightTaskDispatcher
++            lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor1, null // Paper - rewrite chunk system
+         );
+         this.distanceManager = new ChunkMap.DistanceManager(dispatcher, mainThreadExecutor);
+         this.overworldDataStorage = overworldDataStorage;
+@@ -230,11 +229,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+             level
+         );
          this.setServerViewDistance(viewDistance);
--        this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, mainThreadExecutor, this::setChunkUnsaved);
-+        this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system
+-        this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, mainThreadExecutor, this::setChunkUnsaved);
++        this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system
      }
  
-     private void setChunkUnsaved(ChunkPos pos) {
--        this.chunksToEagerlySave.add(pos.toLong());
+     private void setChunkUnsaved(ChunkPos chunkPos) {
+-        this.chunksToEagerlySave.add(chunkPos.toLong());
 +        // Paper - rewrite chunk system
      }
  
      // Paper start
-@@ -263,23 +256,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -264,23 +263,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) {
--        return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ));
-+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, chunkX, chunkZ); // Paper - rewrite chunk system
+     boolean isChunkTracked(ServerPlayer player, int x, int z) {
+-        return player.getChunkTrackingView().contains(x, z) && !player.connection.chunkSender.isPending(ChunkPos.asLong(x, z));
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, x, z); // Paper - rewrite chunk system
      }
  
-     private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) {
--        if (!this.isChunkTracked(player, chunkX, chunkZ)) {
+     private boolean isChunkOnTrackedBorder(ServerPlayer player, int x, int z) {
+-        if (!this.isChunkTracked(player, x, z)) {
 -            return false;
 -        } else {
--            for (int k = -1; k <= 1; ++k) {
--                for (int l = -1; l <= 1; ++l) {
--                    if ((k != 0 || l != 0) && !this.isChunkTracked(player, chunkX + k, chunkZ + l)) {
+-            for (int i = -1; i <= 1; i++) {
+-                for (int i1 = -1; i1 <= 1; i1++) {
+-                    if ((i != 0 || i1 != 0) && !this.isChunkTracked(player, x + i, z + i1)) {
 -                        return true;
 -                    }
 -                }
@@ -24507,15 +24204,15 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 -
 -            return false;
 -        }
-+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, chunkX, chunkZ, true); // Paper - rewrite chunk system
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().isChunkSent(player, x, z, true); // Paper - rewrite chunk system
      }
  
      protected ThreadedLevelLightEngine getLightEngine() {
-@@ -288,20 +269,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -289,21 +276,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
      @Nullable
-     protected ChunkHolder getUpdatingChunkIfPresent(long pos) {
--        return (ChunkHolder) this.updatingChunkMap.get(pos);
+     protected ChunkHolder getUpdatingChunkIfPresent(long chunkPos) {
+-        return this.updatingChunkMap.get(chunkPos);
 +        // Paper start - rewrite chunk system
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
 +        return holder == null ? null : holder.vanillaChunkHolder;
@@ -24523,73 +24220,65 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
      }
  
      @Nullable
-     public ChunkHolder getVisibleChunkIfPresent(long pos) {
--        return (ChunkHolder) this.visibleChunkMap.get(pos);
+     public ChunkHolder getVisibleChunkIfPresent(long chunkPos) {
+-        return this.visibleChunkMap.get(chunkPos);
 +        // Paper start - rewrite chunk system
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
 +        return holder == null ? null : holder.vanillaChunkHolder;
 +        // Paper end - rewrite chunk system
      }
  
-     protected IntSupplier getChunkQueueLevel(long pos) {
+     protected IntSupplier getChunkQueueLevel(long chunkPos) {
 -        return () -> {
--            ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
--
--            return playerchunk == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(playerchunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1);
+-            ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(chunkPos);
+-            return visibleChunkIfPresent == null
+-                ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1
+-                : Math.min(visibleChunkIfPresent.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1);
 -        };
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public String getChunkDebugData(ChunkPos chunkPos) {
-@@ -330,56 +313,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+     public String getChunkDebugData(ChunkPos pos) {
+@@ -329,47 +317,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
--        if (margin == 0) {
--            ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(0);
--
--            return centerChunk.scheduleChunkGenerationTask(chunkstatus, this).thenApply((chunkresult) -> {
--                return chunkresult.map(List::of);
--            });
+     private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction<ChunkStatus> statusGetter) {
+-        if (range == 0) {
+-            ChunkStatus chunkStatus = statusGetter.apply(0);
+-            return chunkHolder.scheduleChunkGenerationTask(chunkStatus, this).thenApply(chunkResult -> chunkResult.map(List::of));
 -        } else {
--            int j = Mth.square(margin * 2 + 1);
--            List<CompletableFuture<ChunkResult<ChunkAccess>>> list = new ArrayList(j);
--            ChunkPos chunkcoordintpair = centerChunk.getPos();
+-            int squared = Mth.square(range * 2 + 1);
+-            List<CompletableFuture<ChunkResult<ChunkAccess>>> list = new ArrayList<>(squared);
+-            ChunkPos pos = chunkHolder.getPos();
 -
--            for (int k = -margin; k <= margin; ++k) {
--                for (int l = -margin; l <= margin; ++l) {
--                    int i1 = Math.max(Math.abs(l), Math.abs(k));
--                    long j1 = ChunkPos.asLong(chunkcoordintpair.x + l, chunkcoordintpair.z + k);
--                    ChunkHolder playerchunk1 = this.getUpdatingChunkIfPresent(j1);
--
--                    if (playerchunk1 == null) {
--                        return ChunkMap.UNLOADED_CHUNK_LIST_FUTURE;
+-            for (int i = -range; i <= range; i++) {
+-                for (int i1 = -range; i1 <= range; i1++) {
+-                    int max = Math.max(Math.abs(i1), Math.abs(i));
+-                    long packedChunkPos = ChunkPos.asLong(pos.x + i1, pos.z + i);
+-                    ChunkHolder updatingChunkIfPresent = this.getUpdatingChunkIfPresent(packedChunkPos);
+-                    if (updatingChunkIfPresent == null) {
+-                        return UNLOADED_CHUNK_LIST_FUTURE;
 -                    }
 -
--                    ChunkStatus chunkstatus1 = (ChunkStatus) distanceToStatus.apply(i1);
--
--                    list.add(playerchunk1.scheduleChunkGenerationTask(chunkstatus1, this));
+-                    ChunkStatus chunkStatus1 = statusGetter.apply(max);
+-                    list.add(updatingChunkIfPresent.scheduleChunkGenerationTask(chunkStatus1, this));
 -                }
 -            }
 -
--            return Util.sequence(list).thenApply((list1) -> {
--                List<ChunkAccess> list2 = new ArrayList(list1.size());
--                Iterator iterator = list1.iterator();
+-            return Util.sequence(list).thenApply(list1 -> {
+-                List<ChunkAccess> list2 = new ArrayList<>(list1.size());
 -
--                while (iterator.hasNext()) {
--                    ChunkResult<ChunkAccess> chunkresult = (ChunkResult) iterator.next();
--
--                    if (chunkresult == null) {
+-                for (ChunkResult<ChunkAccess> chunkResult : list1) {
+-                    if (chunkResult == null) {
 -                        throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
 -                    }
 -
--                    ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
--
--                    if (ichunkaccess == null) {
--                        return ChunkMap.UNLOADED_CHUNK_LIST_RESULT;
+-                    ChunkAccess chunkAccess = chunkResult.orElse(null);
+-                    if (chunkAccess == null) {
+-                        return UNLOADED_CHUNK_LIST_RESULT;
 -                    }
 -
--                    list2.add(ichunkaccess);
+-                    list2.add(chunkAccess);
 -                }
 -
 -                return ChunkResult.of(list2);
@@ -24599,49 +24288,44 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
      }
  
      public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
-@@ -409,104 +343,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -401,95 +349,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder holder) {
--        return this.getChunkRangeFuture(holder, 2, (i) -> {
--            return ChunkStatus.FULL;
--        }).thenApply((chunkresult) -> {
--            return chunkresult.map((list) -> {
--                return (LevelChunk) list.get(list.size() / 2);
--            });
--        });
+     public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder chunk) {
+-        return this.getChunkRangeFuture(chunk, 2, i -> ChunkStatus.FULL)
+-            .thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2)));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Nullable
-     ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) {
--        if (!ChunkLevel.isLoaded(k) && !ChunkLevel.isLoaded(level)) {
+     ChunkHolder updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel) {
+-        if (!ChunkLevel.isLoaded(oldLevel) && !ChunkLevel.isLoaded(newLevel)) {
 -            return holder;
 -        } else {
 -            if (holder != null) {
--                holder.setTicketLevel(level);
+-                holder.setTicketLevel(newLevel);
 -            }
 -
 -            if (holder != null) {
--                if (!ChunkLevel.isLoaded(level)) {
--                    this.toDrop.add(pos);
+-                if (!ChunkLevel.isLoaded(newLevel)) {
+-                    this.toDrop.add(chunkPos);
 -                } else {
--                    this.toDrop.remove(pos);
+-                    this.toDrop.remove(chunkPos);
 -                }
 -            }
 -
--            if (ChunkLevel.isLoaded(level) && holder == null) {
--                holder = (ChunkHolder) this.pendingUnloads.remove(pos);
+-            if (ChunkLevel.isLoaded(newLevel) && holder == null) {
+-                holder = this.pendingUnloads.remove(chunkPos);
 -                if (holder != null) {
--                    holder.setTicketLevel(level);
+-                    holder.setTicketLevel(newLevel);
 -                } else {
--                    holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this);
+-                    holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this);
 -                    // Paper start
--                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
+-                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderCreate(this.level, holder);
 -                    // Paper end
 -                }
 -
--                this.updatingChunkMap.put(pos, holder);
+-                this.updatingChunkMap.put(chunkPos, holder);
 -                this.modified = true;
 -            }
 -
@@ -24650,9 +24334,9 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void onLevelChange(ChunkPos pos, IntSupplier levelGetter, int targetLevel, IntConsumer levelSetter) {
--        this.worldgenTaskDispatcher.onLevelChange(pos, levelGetter, targetLevel, levelSetter);
--        this.lightTaskDispatcher.onLevelChange(pos, levelGetter, targetLevel, levelSetter);
+     private void onLevelChange(ChunkPos chunkPos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter) {
+-        this.worldgenTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter);
+-        this.lightTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
@@ -24666,43 +24350,39 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 -            super.close();
 -        }
 +        throw new UnsupportedOperationException("Use ServerChunkCache#close"); // Paper - rewrite chunk system
- 
      }
  
      protected void saveAllChunks(boolean flush) {
 -        if (flush) {
--            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
--            MutableBoolean mutableboolean = new MutableBoolean();
+-            List<ChunkHolder> list = ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level) // Paper - moonrise
+-                //.values() // Paper - moonrise
+-                .stream()
+-                .filter(ChunkHolder::wasAccessibleSinceLastSave)
+-                .peek(ChunkHolder::refreshAccessibility)
+-                .toList();
+-            MutableBoolean mutableBoolean = new MutableBoolean();
 -
 -            do {
--                mutableboolean.setFalse();
--                list.stream().map((playerchunk) -> {
--                    BlockableEventLoop iasynctaskhandler = this.mainThreadExecutor;
--
--                    Objects.requireNonNull(playerchunk);
--                    iasynctaskhandler.managedBlock(playerchunk::isReadyForSaving);
--                    return playerchunk.getLatestChunk();
--                }).filter((ichunkaccess) -> {
--                    return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk;
--                }).filter(this::save).forEach((ichunkaccess) -> {
--                    mutableboolean.setTrue();
--                });
--            } while (mutableboolean.isTrue());
+-                mutableBoolean.setFalse();
+-                list.stream()
+-                    .map(chunk -> {
+-                        this.mainThreadExecutor.managedBlock(chunk::isReadyForSaving);
+-                        return chunk.getLatestChunk();
+-                    })
+-                    .filter(chunk -> chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk)
+-                    .filter(this::save)
+-                    .forEach(chunk -> mutableBoolean.setTrue());
+-            } while (mutableBoolean.isTrue());
 -
 -            this.poiManager.flushAll();
--            this.processUnloads(() -> {
--                return true;
--            });
+-            this.processUnloads(() -> true);
 -            this.flushWorker();
 -        } else {
 -            this.nextChunkSaveTime.clear();
--            long i = Util.getMillis();
--            Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
+-            long millis = Util.getMillis();
 -
--            while (objectiterator.hasNext()) {
--                ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
--
--                this.saveChunkIfNeeded(playerchunk, i);
+-            for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.PlatformHooks.get().getVisibleChunkHolders(this.level)) { // Paper
+-                this.saveChunkIfNeeded(chunkHolder, millis);
 -            }
 -        }
 +        // Paper start - rewrite chunk system
@@ -24710,112 +24390,104 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +            flush, false, false
 +        );
 +        // Paper end - rewrite chunk system
- 
      }
  
-@@ -524,143 +384,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+     protected void tick(BooleanSupplier hasMoreTime) {
+@@ -505,130 +387,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public boolean hasWork() {
--        return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
+-        return this.lightEngine.hasLightWork()
+-            || !this.pendingUnloads.isEmpty()
+-            || ca.spottedleaf.moonrise.common.PlatformHooks.get().hasAnyChunkHolders(this.level) // Paper - moonrise
+-            || !this.updatingChunkMap.isEmpty()
+-            || this.poiManager.hasWork()
+-            || !this.toDrop.isEmpty()
+-            || !this.unloadQueue.isEmpty()
+-            || this.worldgenTaskDispatcher.hasWork()
+-            || this.lightTaskDispatcher.hasWork()
+-            || this.distanceManager.hasTickets();
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void processUnloads(BooleanSupplier shouldKeepTicking) {
--        for (LongIterator longiterator = this.toDrop.iterator(); longiterator.hasNext(); longiterator.remove()) {
--            long i = longiterator.nextLong();
--            ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.get(i);
--
--            if (playerchunk != null) {
--                this.updatingChunkMap.remove(i);
--                this.pendingUnloads.put(i, playerchunk);
+     private void processUnloads(BooleanSupplier hasMoreTime) {
+-        for (LongIterator longIterator = this.toDrop.iterator(); longIterator.hasNext(); longIterator.remove()) {
+-            long l = longIterator.nextLong();
+-            ChunkHolder chunkHolder = this.updatingChunkMap.get(l);
+-            if (chunkHolder != null) {
+-                this.updatingChunkMap.remove(l);
+-                this.pendingUnloads.put(l, chunkHolder);
 -                this.modified = true;
--                this.scheduleUnload(i, playerchunk);
+-                this.scheduleUnload(l, chunkHolder);
 -            }
 -        }
 -
--        int j = Math.max(0, this.unloadQueue.size() - 2000);
+-        int max = Math.max(0, this.unloadQueue.size() - 2000);
 -
 -        Runnable runnable;
--
--        while ((j > 0 || shouldKeepTicking.getAsBoolean()) && (runnable = (Runnable) this.unloadQueue.poll()) != null) {
--            --j;
+-        while ((max > 0 || hasMoreTime.getAsBoolean()) && (runnable = this.unloadQueue.poll()) != null) {
+-            max--;
 -            runnable.run();
 -        }
 -
--        this.saveChunksEagerly(shouldKeepTicking);
+-        this.saveChunksEagerly(hasMoreTime);
 +        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.processUnloads(); // Paper - rewrite chunk system
 +        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.autoSave(); // Paper - rewrite chunk system
      }
  
-     private void saveChunksEagerly(BooleanSupplier shouldKeepTicking) {
--        long i = Util.getMillis();
--        int j = 0;
--        LongIterator longiterator = this.chunksToEagerlySave.iterator();
+     private void saveChunksEagerly(BooleanSupplier hasMoreTime) {
+-        long millis = Util.getMillis();
+-        int i = 0;
+-        LongIterator longIterator = this.chunksToEagerlySave.iterator();
 -
--        while (j < 20 && this.activeChunkWrites.get() < 128 && shouldKeepTicking.getAsBoolean() && longiterator.hasNext()) {
--            long k = longiterator.nextLong();
--            ChunkHolder playerchunk = (ChunkHolder) this.visibleChunkMap.get(k);
--            ChunkAccess ichunkaccess = playerchunk != null ? playerchunk.getLatestChunk() : null;
--
--            if (ichunkaccess != null && ichunkaccess.isUnsaved()) {
--                if (this.saveChunkIfNeeded(playerchunk, i)) {
--                    ++j;
--                    longiterator.remove();
--                }
--            } else {
--                longiterator.remove();
+-        while (i < 20 && this.activeChunkWrites.get() < 128 && hasMoreTime.getAsBoolean() && longIterator.hasNext()) {
+-            long l = longIterator.nextLong();
+-            ChunkHolder chunkHolder = this.visibleChunkMap.get(l);
+-            ChunkAccess chunkAccess = chunkHolder != null ? chunkHolder.getLatestChunk() : null;
+-            if (chunkAccess == null || !chunkAccess.isUnsaved()) {
+-                longIterator.remove();
+-            } else if (this.saveChunkIfNeeded(chunkHolder, millis)) {
+-                i++;
+-                longIterator.remove();
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
- 
      }
  
-     private void scheduleUnload(long pos, ChunkHolder chunk) {
--        CompletableFuture<?> completablefuture = chunk.getSaveSyncFuture();
--        Runnable runnable = () -> {
--            CompletableFuture<?> completablefuture1 = chunk.getSaveSyncFuture();
--
--            if (completablefuture1 != completablefuture) {
--                this.scheduleUnload(pos, chunk);
+     private void scheduleUnload(long chunkPos, ChunkHolder chunkHolder) {
+-        CompletableFuture<?> saveSyncFuture = chunkHolder.getSaveSyncFuture();
+-        saveSyncFuture.thenRunAsync(() -> {
+-            CompletableFuture<?> saveSyncFuture1 = chunkHolder.getSaveSyncFuture();
+-            if (saveSyncFuture1 != saveSyncFuture) {
+-                this.scheduleUnload(chunkPos, chunkHolder);
 -            } else {
--                ChunkAccess ichunkaccess = chunk.getLatestChunk();
+-                ChunkAccess latestChunk = chunkHolder.getLatestChunk();
 -                // Paper start
 -                boolean removed;
--                if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) {
--                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+-                if ((removed = this.pendingUnloads.remove(chunkPos, chunkHolder)) && latestChunk != null) {
+-                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderDelete(this.level, chunkHolder);
 -                    // Paper end
--                    LevelChunk chunk1;
--
--                    if (ichunkaccess instanceof LevelChunk) {
--                        chunk1 = (LevelChunk) ichunkaccess;
--                        chunk1.setLoaded(false);
+-                    if (latestChunk instanceof LevelChunk levelChunk) {
+-                        levelChunk.setLoaded(false);
 -                    }
 -
--                    this.save(ichunkaccess);
--                    if (ichunkaccess instanceof LevelChunk) {
--                        chunk1 = (LevelChunk) ichunkaccess;
--                        this.level.unload(chunk1);
+-                    this.save(latestChunk);
+-                    if (latestChunk instanceof LevelChunk levelChunk) {
+-                        this.level.unload(levelChunk);
 -                    }
 -
--                    this.lightEngine.updateChunkStatus(ichunkaccess.getPos());
+-                    this.lightEngine.updateChunkStatus(latestChunk.getPos());
 -                    this.lightEngine.tryScheduleUpdate();
--                    this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
--                    this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong());
+-                    this.progressListener.onStatusChange(latestChunk.getPos(), null);
+-                    this.nextChunkSaveTime.remove(latestChunk.getPos().toLong());
 -                } else if (removed) { // Paper start
--                    ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+-                    ca.spottedleaf.moonrise.common.PlatformHooks.get().onChunkHolderDelete(this.level, chunkHolder);
 -                } // Paper end
--
 -            }
--        };
--        Queue queue = this.unloadQueue;
--
--        Objects.requireNonNull(this.unloadQueue);
--        completablefuture.thenRunAsync(runnable, queue::add).whenComplete((ovoid, throwable) -> {
--            if (throwable != null) {
--                ChunkMap.LOGGER.error("Failed to save chunk {}", chunk.getPos(), throwable);
+-        }, this.unloadQueue::add).whenComplete((_void, error) -> {
+-            if (error != null) {
+-                LOGGER.error("Failed to save chunk {}", chunkHolder.getPos(), error);
 -            }
--
 -        });
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
@@ -24831,120 +24503,99 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private CompletableFuture<ChunkAccess> scheduleChunkLoad(ChunkPos pos) {
--        CompletableFuture<Optional<SerializableChunkData>> completablefuture = this.readChunk(pos).thenApplyAsync((optional) -> {
--            return optional.map((nbttagcompound) -> {
--                SerializableChunkData serializablechunkdata = SerializableChunkData.parse(this.level, this.level.registryAccess(), nbttagcompound);
--
--                if (serializablechunkdata == null) {
--                    ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos);
--                }
--
--                return serializablechunkdata;
--            });
--        }, Util.backgroundExecutor().forName("parseChunk"));
--        CompletableFuture<?> completablefuture1 = this.poiManager.prefetch(pos);
--
--        return completablefuture.thenCombine(completablefuture1, (optional, object) -> {
--            return optional;
--        }).thenApplyAsync((optional) -> {
--            Profiler.get().incrementCounter("chunkLoad");
--            if (optional.isPresent()) {
--                ProtoChunk protochunk = ((SerializableChunkData) optional.get()).read(this.level, this.poiManager, this.storageInfo(), pos);
--
--                this.markPosition(pos, protochunk.getPersistedStatus().getChunkType());
--                return protochunk;
--            } else {
--                return this.createEmptyChunk(pos);
+     private CompletableFuture<ChunkAccess> scheduleChunkLoad(ChunkPos chunkPos) {
+-        CompletableFuture<Optional<SerializableChunkData>> completableFuture = this.readChunk(chunkPos).thenApplyAsync(optional -> optional.map(tag -> {
+-            SerializableChunkData serializableChunkData = SerializableChunkData.parse(this.level, this.level.registryAccess(), tag);
+-            if (serializableChunkData == null) {
+-                LOGGER.error("Chunk file at {} is missing level data, skipping", chunkPos);
 -            }
--        }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> {
--            return this.handleChunkLoadFailure(throwable, pos);
--        }, this.mainThreadExecutor);
-+        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
-     }
- 
-     private ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos chunkPos) {
-@@ -716,139 +462,43 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- 
-     @Override
-     public GenerationChunkHolder acquireGeneration(long pos) {
--        ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.get(pos);
 -
--        playerchunk.increaseGenerationRefCount();
--        return playerchunk;
+-            return serializableChunkData;
+-        }), Util.backgroundExecutor().forName("parseChunk"));
+-        CompletableFuture<?> completableFuture1 = this.poiManager.prefetch(chunkPos);
+-        return completableFuture.<Object, Optional<SerializableChunkData>>thenCombine(
+-                (CompletionStage<? extends Object>)completableFuture1, (optional, object) -> optional
+-            )
+-            .thenApplyAsync(optional -> {
+-                Profiler.get().incrementCounter("chunkLoad");
+-                if (optional.isPresent()) {
+-                    ChunkAccess chunkAccess = optional.get().read(this.level, this.poiManager, this.storageInfo(), chunkPos);
+-                    this.markPosition(chunkPos, chunkAccess.getPersistedStatus().getChunkType());
+-                    return chunkAccess;
+-                } else {
+-                    return this.createEmptyChunk(chunkPos);
+-                }
+-            }, this.mainThreadExecutor)
+-            .exceptionallyAsync(throwable -> this.handleChunkLoadFailure(throwable, chunkPos), this.mainThreadExecutor);
++        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
+     }
+ 
+     private ChunkAccess handleChunkLoadFailure(Throwable exception, ChunkPos chunkPos) {
+@@ -666,108 +446,43 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ 
+     @Override
+     public GenerationChunkHolder acquireGeneration(long chunkPos) {
+-        ChunkHolder chunkHolder = this.updatingChunkMap.get(chunkPos);
+-        chunkHolder.increaseGenerationRefCount();
+-        return chunkHolder;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Override
-     public void releaseGeneration(GenerationChunkHolder chunkHolder) {
--        chunkHolder.decreaseGenerationRefCount();
+     public void releaseGeneration(GenerationChunkHolder chunk) {
+-        chunk.decreaseGenerationRefCount();
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Override
-     public CompletableFuture<ChunkAccess> applyStep(GenerationChunkHolder chunkHolder, ChunkStep step, StaticCache2D<GenerationChunkHolder> chunks) {
--        ChunkPos chunkcoordintpair = chunkHolder.getPos();
--
+     public CompletableFuture<ChunkAccess> applyStep(GenerationChunkHolder chunk, ChunkStep step, StaticCache2D<GenerationChunkHolder> cache) {
+-        ChunkPos pos = chunk.getPos();
 -        if (step.targetStatus() == ChunkStatus.EMPTY) {
--            return this.scheduleChunkLoad(chunkcoordintpair);
+-            return this.scheduleChunkLoad(pos);
 -        } else {
 -            try {
--                GenerationChunkHolder generationchunkholder1 = (GenerationChunkHolder) chunks.get(chunkcoordintpair.x, chunkcoordintpair.z);
--                ChunkAccess ichunkaccess = generationchunkholder1.getChunkIfPresentUnchecked(step.targetStatus().getParent());
--
--                if (ichunkaccess == null) {
+-                GenerationChunkHolder generationChunkHolder = cache.get(pos.x, pos.z);
+-                ChunkAccess chunkIfPresentUnchecked = generationChunkHolder.getChunkIfPresentUnchecked(step.targetStatus().getParent());
+-                if (chunkIfPresentUnchecked == null) {
 -                    throw new IllegalStateException("Parent chunk missing");
 -                } else {
--                    CompletableFuture<ChunkAccess> completablefuture = step.apply(this.worldGenContext, chunks, ichunkaccess);
--
--                    this.progressListener.onStatusChange(chunkcoordintpair, step.targetStatus());
--                    return completablefuture;
+-                    CompletableFuture<ChunkAccess> completableFuture = step.apply(this.worldGenContext, cache, chunkIfPresentUnchecked);
+-                    this.progressListener.onStatusChange(pos, step.targetStatus());
+-                    return completableFuture;
 -                }
--            } catch (Exception exception) {
--                exception.getStackTrace();
--                CrashReport crashreport = CrashReport.forThrowable(exception, "Exception generating new chunk");
--                CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk to be generated");
--
--                crashreportsystemdetails.setDetail("Status being generated", () -> {
--                    return step.targetStatus().getName();
--                });
--                crashreportsystemdetails.setDetail("Location", (Object) String.format(Locale.ROOT, "%d,%d", chunkcoordintpair.x, chunkcoordintpair.z));
--                crashreportsystemdetails.setDetail("Position hash", (Object) ChunkPos.asLong(chunkcoordintpair.x, chunkcoordintpair.z));
--                crashreportsystemdetails.setDetail("Generator", (Object) this.generator());
+-            } catch (Exception var8) {
+-                var8.getStackTrace();
+-                CrashReport crashReport = CrashReport.forThrowable(var8, "Exception generating new chunk");
+-                CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk to be generated");
+-                crashReportCategory.setDetail("Status being generated", () -> step.targetStatus().getName());
+-                crashReportCategory.setDetail("Location", String.format(Locale.ROOT, "%d,%d", pos.x, pos.z));
+-                crashReportCategory.setDetail("Position hash", ChunkPos.asLong(pos.x, pos.z));
+-                crashReportCategory.setDetail("Generator", this.generator());
 -                this.mainThreadExecutor.execute(() -> {
--                    throw new ReportedException(crashreport);
+-                    throw new ReportedException(crashReport);
 -                });
--                throw new ReportedException(crashreport);
+-                throw new ReportedException(crashReport);
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Override
-     public ChunkGenerationTask scheduleGenerationTask(ChunkStatus requestedStatus, ChunkPos pos) {
--        ChunkGenerationTask chunkgenerationtask = ChunkGenerationTask.create(this, requestedStatus, pos);
--
--        this.pendingGenerationTasks.add(chunkgenerationtask);
--        return chunkgenerationtask;
+     public ChunkGenerationTask scheduleGenerationTask(ChunkStatus targetStatus, ChunkPos pos) {
+-        ChunkGenerationTask chunkGenerationTask = ChunkGenerationTask.create(this, targetStatus, pos);
+-        this.pendingGenerationTasks.add(chunkGenerationTask);
+-        return chunkGenerationTask;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void runGenerationTask(ChunkGenerationTask loader) {
--        GenerationChunkHolder generationchunkholder = loader.getCenter();
--        ChunkTaskDispatcher chunktaskdispatcher = this.worldgenTaskDispatcher;
--        Runnable runnable = () -> {
--            CompletableFuture<?> completablefuture = loader.runUntilWait();
--
--            if (completablefuture != null) {
--                completablefuture.thenRun(() -> {
--                    this.runGenerationTask(loader);
--                });
+     private void runGenerationTask(ChunkGenerationTask task) {
+-        GenerationChunkHolder center = task.getCenter();
+-        this.worldgenTaskDispatcher.submit(() -> {
+-            CompletableFuture<?> completableFuture = task.runUntilWait();
+-            if (completableFuture != null) {
+-                completableFuture.thenRun(() -> this.runGenerationTask(task));
 -            }
--        };
--        long i = generationchunkholder.getPos().toLong();
--
--        Objects.requireNonNull(generationchunkholder);
--        chunktaskdispatcher.submit(runnable, i, generationchunkholder::getQueueLevel);
+-        }, center.getPos().toLong(), center::getQueueLevel);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
@@ -24956,46 +24607,34 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
      }
  
      public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(ChunkHolder holder) {
--        CompletableFuture<ChunkResult<List<ChunkAccess>>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> {
--            return ChunkStatus.FULL;
--        });
--        CompletableFuture<ChunkResult<LevelChunk>> completablefuture1 = completablefuture.thenApplyAsync((chunkresult) -> {
--            return chunkresult.map((list) -> {
--                LevelChunk chunk = (LevelChunk) list.get(list.size() / 2);
+-        CompletableFuture<ChunkResult<List<ChunkAccess>>> chunkRangeFuture = this.getChunkRangeFuture(holder, 1, i -> ChunkStatus.FULL);
+-        CompletableFuture<ChunkResult<LevelChunk>> completableFuture = chunkRangeFuture.thenApplyAsync(chunk -> chunk.map(list -> {
+-            LevelChunk levelChunk = (LevelChunk)list.get(list.size() / 2);
+-            levelChunk.postProcessGeneration(this.level);
+-            this.level.startTickingChunk(levelChunk);
+-            CompletableFuture<?> sendSyncFuture = holder.getSendSyncFuture();
+-            if (sendSyncFuture.isDone()) {
+-                this.onChunkReadyToSend(holder, levelChunk);
+-            } else {
+-                sendSyncFuture.thenAcceptAsync(object -> this.onChunkReadyToSend(holder, levelChunk), this.mainThreadExecutor);
+-            }
 -
--                chunk.postProcessGeneration(this.level);
--                this.level.startTickingChunk(chunk);
--                CompletableFuture<?> completablefuture2 = holder.getSendSyncFuture();
--
--                if (completablefuture2.isDone()) {
--                    this.onChunkReadyToSend(holder, chunk);
--                } else {
--                    completablefuture2.thenAcceptAsync((object) -> {
--                        this.onChunkReadyToSend(holder, chunk);
--                    }, this.mainThreadExecutor);
--                }
--
--                return chunk;
--            });
--        }, this.mainThreadExecutor);
--
--        completablefuture1.handle((chunkresult, throwable) -> {
+-            return levelChunk;
+-        }), this.mainThreadExecutor);
+-        completableFuture.handle((chunk, exception) -> {
 -            this.tickingGenerated.getAndIncrement();
 -            return null;
 -        });
--        return completablefuture1;
+-        return completableFuture;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) {
--        ChunkPos chunkcoordintpair = chunk.getPos();
--        Iterator iterator = this.playerMap.getAllPlayers().iterator();
+-        ChunkPos pos = chunk.getPos();
 -
--        while (iterator.hasNext()) {
--            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
--
--            if (entityplayer.getChunkTrackingView().contains(chunkcoordintpair)) {
--                ChunkMap.markChunkPendingToSend(entityplayer, chunk);
+-        for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-            if (serverPlayer.getChunkTrackingView().contains(pos)) {
+-                markChunkPendingToSend(serverPlayer, chunk);
 -            }
 -        }
 -
@@ -25003,39 +24642,33 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(ChunkHolder holder) {
--        return this.getChunkRangeFuture(holder, 1, ChunkLevel::getStatusAroundFullChunk).thenApply((chunkresult) -> {
--            return chunkresult.map((list) -> {
--                return (LevelChunk) list.get(list.size() / 2);
--            });
--        });
+     public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(ChunkHolder chunk) {
+-        return this.getChunkRangeFuture(chunk, 1, ChunkLevel::getStatusAroundFullChunk)
+-            .thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2)));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      public int getTickingGenerated() {
-@@ -856,144 +506,80 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -775,125 +490,78 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     private boolean saveChunkIfNeeded(ChunkHolder chunkHolder, long currentTime) {
--        if (chunkHolder.wasAccessibleSinceLastSave() && chunkHolder.isReadyForSaving()) {
--            ChunkAccess ichunkaccess = chunkHolder.getLatestChunk();
--
--            if (!(ichunkaccess instanceof ImposterProtoChunk) && !(ichunkaccess instanceof LevelChunk)) {
+     private boolean saveChunkIfNeeded(ChunkHolder chunk, long gametime) {
+-        if (chunk.wasAccessibleSinceLastSave() && chunk.isReadyForSaving()) {
+-            ChunkAccess latestChunk = chunk.getLatestChunk();
+-            if (!(latestChunk instanceof ImposterProtoChunk) && !(latestChunk instanceof LevelChunk)) {
 -                return false;
--            } else if (!ichunkaccess.isUnsaved()) {
+-            } else if (!latestChunk.isUnsaved()) {
 -                return false;
 -            } else {
--                long j = ichunkaccess.getPos().toLong();
--                long k = this.nextChunkSaveTime.getOrDefault(j, -1L);
--
--                if (currentTime < k) {
+-                long packedChunkPos = latestChunk.getPos().toLong();
+-                long orDefault = this.nextChunkSaveTime.getOrDefault(packedChunkPos, -1L);
+-                if (gametime < orDefault) {
 -                    return false;
 -                } else {
--                    boolean flag = this.save(ichunkaccess);
--
--                    chunkHolder.refreshAccessibility();
+-                    boolean flag = this.save(latestChunk);
+-                    chunk.refreshAccessibility();
 -                    if (flag) {
--                        this.nextChunkSaveTime.put(j, currentTime + 10000L);
+-                        this.nextChunkSaveTime.put(packedChunkPos, gametime + 10000L);
 -                    }
 -
 -                    return flag;
@@ -25052,111 +24685,97 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 -        if (!chunk.tryMarkSaved()) {
 -            return false;
 -        } else {
--            ChunkPos chunkcoordintpair = chunk.getPos();
+-            ChunkPos pos = chunk.getPos();
 -
 -            try {
--                ChunkStatus chunkstatus = chunk.getPersistedStatus();
--
--                if (chunkstatus.getChunkType() != ChunkType.LEVELCHUNK) {
--                    if (this.isExistingChunkFull(chunkcoordintpair)) {
+-                ChunkStatus persistedStatus = chunk.getPersistedStatus();
+-                if (persistedStatus.getChunkType() != ChunkType.LEVELCHUNK) {
+-                    if (this.isExistingChunkFull(pos)) {
 -                        return false;
 -                    }
 -
--                    if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
+-                    if (persistedStatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
 -                        return false;
 -                    }
 -                }
 -
 -                Profiler.get().incrementCounter("chunkSave");
 -                this.activeChunkWrites.incrementAndGet();
--                SerializableChunkData serializablechunkdata = SerializableChunkData.copyOf(this.level, chunk);
--
--                Objects.requireNonNull(serializablechunkdata);
--                CompletableFuture<CompoundTag> completablefuture = CompletableFuture.supplyAsync(serializablechunkdata::write, Util.backgroundExecutor());
--
--                Objects.requireNonNull(completablefuture);
--                this.write(chunkcoordintpair, completablefuture::join).handle((ovoid, throwable) -> {
--                    if (throwable != null) {
--                        this.level.getServer().reportChunkSaveFailure(throwable, this.storageInfo(), chunkcoordintpair);
+-                SerializableChunkData serializableChunkData = SerializableChunkData.copyOf(this.level, chunk);
+-                CompletableFuture<CompoundTag> completableFuture = CompletableFuture.supplyAsync(serializableChunkData::write, Util.backgroundExecutor());
+-                this.write(pos, completableFuture::join).handle((_void, exception1) -> {
+-                    if (exception1 != null) {
+-                        this.level.getServer().reportChunkSaveFailure(exception1, this.storageInfo(), pos);
 -                    }
 -
 -                    this.activeChunkWrites.decrementAndGet();
 -                    return null;
 -                });
--                this.markPosition(chunkcoordintpair, chunkstatus.getChunkType());
+-                this.markPosition(pos, persistedStatus.getChunkType());
 -                return true;
--            } catch (Exception exception) {
--                this.level.getServer().reportChunkSaveFailure(exception, this.storageInfo(), chunkcoordintpair);
+-            } catch (Exception var6) {
+-                this.level.getServer().reportChunkSaveFailure(var6, this.storageInfo(), pos);
 -                return false;
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private boolean isExistingChunkFull(ChunkPos pos) {
--        byte b0 = this.chunkTypeCache.get(pos.toLong());
--
--        if (b0 != 0) {
--            return b0 == 1;
+     private boolean isExistingChunkFull(ChunkPos chunkPos) {
+-        byte b = this.chunkTypeCache.get(chunkPos.toLong());
+-        if (b != 0) {
+-            return b == 1;
 -        } else {
--            CompoundTag nbttagcompound;
--
+-            CompoundTag compoundTag;
 -            try {
--                nbttagcompound = (CompoundTag) ((Optional) this.readChunk(pos).join()).orElse((Object) null);
--                if (nbttagcompound == null) {
--                    this.markPositionReplaceable(pos);
+-                compoundTag = this.readChunk(chunkPos).join().orElse(null);
+-                if (compoundTag == null) {
+-                    this.markPositionReplaceable(chunkPos);
 -                    return false;
 -                }
--            } catch (Exception exception) {
--                ChunkMap.LOGGER.error("Failed to read chunk {}", pos, exception);
--                this.markPositionReplaceable(pos);
+-            } catch (Exception var5) {
+-                LOGGER.error("Failed to read chunk {}", chunkPos, var5);
+-                this.markPositionReplaceable(chunkPos);
 -                return false;
 -            }
 -
--            ChunkType chunktype = SerializableChunkData.getChunkTypeFromTag(nbttagcompound);
--
--            return this.markPosition(pos, chunktype) == 1;
+-            ChunkType chunkTypeFromTag = SerializableChunkData.getChunkTypeFromTag(compoundTag);
+-            return this.markPosition(chunkPos, chunkTypeFromTag) == 1;
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public void setServerViewDistance(int watchDistance) { // Paper - public
--        int j = Mth.clamp(watchDistance, 2, 32);
--
--        if (j != this.serverViewDistance) {
--            this.serverViewDistance = j;
+     public void setServerViewDistance(int viewDistance) { // Paper - publi
+-        int i = Mth.clamp(viewDistance, 2, 32);
+-        if (i != this.serverViewDistance) {
+-            this.serverViewDistance = i;
 -            this.distanceManager.updatePlayerTickets(this.serverViewDistance);
--            Iterator iterator = this.playerMap.getAllPlayers().iterator();
 -
--            while (iterator.hasNext()) {
--                ServerPlayer entityplayer = (ServerPlayer) iterator.next();
--
--                this.updateChunkTracking(entityplayer);
+-            for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-                this.updateChunkTracking(serverPlayer);
 -            }
 +        // Paper start - rewrite chunk system
-+        final int clamped = Mth.clamp(watchDistance, 2, ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE);
++        final int clamped = Mth.clamp(viewDistance, 2, ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE);
 +        if (clamped == this.serverViewDistance) {
 +            return;
          }
- 
++
 +        this.serverViewDistance = clamped;
 +        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().setLoadDistance(this.serverViewDistance + 1);
 +        // Paper end - rewrite chunk system
      }
  
-     public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
+     int getPlayerViewDistance(ServerPlayer player) {
 -        return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
 +        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getSendViewDistance(player); // Paper - rewrite chunk system
      }
  
-     private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) {
--        LevelChunk chunk = this.getChunkToSend(pos.toLong());
--
--        if (chunk != null) {
--            ChunkMap.markChunkPendingToSend(player, chunk);
+     private void markChunkPendingToSend(ServerPlayer player, ChunkPos chunkPos) {
+-        LevelChunk chunkToSend = this.getChunkToSend(chunkPos.toLong());
+-        if (chunkToSend != null) {
+-            markChunkPendingToSend(player, chunkToSend);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
- 
      }
  
      private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) {
@@ -25164,8 +24783,8 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private static void dropChunk(ServerPlayer player, ChunkPos pos) {
--        player.connection.chunkSender.dropChunk(player, pos);
+     private static void dropChunk(ServerPlayer player, ChunkPos chunkPos) {
+-        player.connection.chunkSender.dropChunk(player, chunkPos);
 +        // Paper - rewrite chunk system
 +    }
 +
@@ -25195,116 +24814,98 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +            ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionFileType.CHUNK_DATA
 +        );
 +        return null;
-     }
- 
++    }
++
 +    @Override
 +    public void flushWorker() {
 +        ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.flush(this.level);
-+    }
+     }
 +    // Paper end - rewrite chunk system
-+
+ 
      @Nullable
-     public LevelChunk getChunkToSend(long pos) {
-         ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
-@@ -1059,7 +645,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+     public LevelChunk getChunkToSend(long chunkPos) {
+@@ -981,7 +649,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      // CraftBukkit start
--    private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
-+    public CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) { // Paper - public
-         return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
-         // CraftBukkit end
+-    private CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) {
++    public CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) { // Paper - public
+         return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level);
+     // CraftBukkit end
      }
-@@ -1069,7 +655,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -991,7 +659,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
  
-         while (longiterator.hasNext()) {
-             long i = longiterator.nextLong();
--            ChunkHolder playerchunk = (ChunkHolder) this.visibleChunkMap.get(i);
-+            ChunkHolder playerchunk = (ChunkHolder) this.getVisibleChunkIfPresent(i); // Paper - rewrite chunk system
- 
-             if (playerchunk != null && this.anyPlayerCloseEnoughForSpawningInternal(playerchunk.getPos())) {
-                 callback.accept(playerchunk);
-@@ -1084,7 +670,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         while (spawnCandidateChunks.hasNext()) {
+             long l = spawnCandidateChunks.nextLong();
+-            ChunkHolder chunkHolder = this.visibleChunkMap.get(l);
++            ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(l); // Paper - rewrite chunk system
+             if (chunkHolder != null && this.anyPlayerCloseEnoughForSpawningInternal(chunkHolder.getPos())) {
+                 action.accept(chunkHolder);
+             }
+@@ -1004,7 +672,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
--        return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange);
-+        return this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange); // Paper - chunk tick iteration optimisation
+     boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos, boolean reducedRange) {
+-        return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, reducedRange);
++        return this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, reducedRange); // Paper - chunk tick iteration optimisation
          // Spigot end
      }
  
-@@ -1096,16 +682,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-     private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
+@@ -1016,7 +684,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+     private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) {
          double blockRange; // Paper - use from event
          // Spigot end
--        Iterator iterator = this.playerMap.getAllPlayers().iterator();
--
--        ServerPlayer entityplayer;
+-        for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
 +        // Paper start - chunk tick iteration optimisation
 +        final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
-+            chunkcoordintpair, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE
++            chunkPos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE
 +        );
 +        if (players == null) {
 +            return false;
 +        }
- 
--        do {
--            if (!iterator.hasNext()) {
--                return false;
--            }
++
 +        final ServerPlayer[] raw = players.getRawDataUnchecked();
 +        final int len = players.size();
- 
--            entityplayer = (ServerPlayer) iterator.next();
++
 +        Objects.checkFromIndexSize(0, len, raw.length);
 +        for (int i = 0; i < len; ++i) {
-+            final ServerPlayer entityplayer = raw[i];
++            final ServerPlayer serverPlayer = raw[i];
              // Paper start - PlayerNaturallySpawnCreaturesEvent
              com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
              blockRange = 16384.0D;
-@@ -1115,33 +705,47 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-                 blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
-             }
-             // Paper end - PlayerNaturallySpawnCreaturesEvent
--        } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
-+            if (this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)) {
-+                return true;
-+            }
-+        }
+@@ -1032,26 +713,41 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         }
  
--        return true;
-+        return false;
+         return false;
 +        // Paper end - chunk tick iteration optimisation
      }
  
-     public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
--        long i = pos.toLong();
+     public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos chunkPos) {
+-        long packedChunkPos = chunkPos.toLong();
+-        if (!this.distanceManager.hasPlayersNearby(packedChunkPos)) {
+-            return List.of();
+-        } else {
+-            Builder<ServerPlayer> builder = ImmutableList.builder();
 +        // Paper start - chunk tick iteration optimisation
 +        final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
-+            pos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE
++            chunkPos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE
 +        );
 +        if (players == null) {
 +            return new ArrayList<>();
 +        }
- 
--        if (!this.distanceManager.hasPlayersNearby(i)) {
--            return List.of();
--        } else {
--            Builder<ServerPlayer> builder = ImmutableList.builder();
--            Iterator iterator = this.playerMap.getAllPlayers().iterator();
++
 +        List<ServerPlayer> ret = null;
- 
--            while (iterator.hasNext()) {
--                ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
 +        final ServerPlayer[] raw = players.getRawDataUnchecked();
 +        final int len = players.size();
  
--                if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
--                    builder.add(entityplayer);
+-            for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-                if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, 16384.0D)) { // Spigot
+-                    builder.add(serverPlayer);
 +        Objects.checkFromIndexSize(0, len, raw.length);
 +        for (int i = 0; i < len; ++i) {
 +            final ServerPlayer player = raw[i];
-+            if (this.playerIsCloseEnoughForSpawning(player, pos, 16384.0D)) { // Spigot
++            if (this.playerIsCloseEnoughForSpawning(player, chunkPos, 16384.0D)) { // Spigot
 +                if (ret == null) {
 +                    ret = new ArrayList<>(len - i);
 +                    ret.add(player);
@@ -25320,15 +24921,15 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        // Paper end - chunk tick iteration optimisation
      }
  
--    private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
-+    public boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot // Paper - chunk tick iteration optimisation - public
-         if (entityplayer.isSpectator()) {
+-    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos, double range) { // Spigot
++    public boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos, double range) { // Spigot // Paper - chunk tick iteration optimisation - public
+         if (player.isSpectator()) {
              return false;
          } else {
-@@ -1164,19 +768,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1072,18 +768,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
              this.updatePlayerPos(player);
-             if (!flag1) {
-                 this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player);
+             if (!flag) {
+                 this.distanceManager.addPlayer(SectionPos.of(player), player);
 +                ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$addPlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation
              }
  
@@ -25336,121 +24937,95 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 -            this.updateChunkTracking(player);
 +            ca.spottedleaf.moonrise.common.PlatformHooks.get().addPlayerToDistanceMaps(this.level, player); // Paper - rewrite chunk system
          } else {
-             SectionPos sectionposition = player.getLastSectionPos();
- 
+             SectionPos lastSectionPos = player.getLastSectionPos();
              this.playerMap.removePlayer(player);
-             if (!flag2) {
-                 this.distanceManager.removePlayer(sectionposition, player);
+             if (!flag1) {
+                 this.distanceManager.removePlayer(lastSectionPos, player);
 +                ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$removePlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation
              }
  
 -            this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY);
 +            ca.spottedleaf.moonrise.common.PlatformHooks.get().removePlayerFromDistanceMaps(this.level, player); // Paper - rewrite chunk system
          }
- 
      }
-@@ -1188,17 +794,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ 
+@@ -1093,13 +791,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
      public void move(ServerPlayer player) {
--        ObjectIterator objectiterator = this.entityMap.values().iterator();
--
--        while (objectiterator.hasNext()) {
--            ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
--
--            if (playerchunkmap_entitytracker.entity == player) {
--                playerchunkmap_entitytracker.updatePlayers(this.level.players());
+-        for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
+-            if (trackedEntity.entity == player) {
+-                trackedEntity.updatePlayers(this.level.players());
 -            } else {
--                playerchunkmap_entitytracker.updatePlayer(player);
+-                trackedEntity.updatePlayer(player);
 -            }
 -        }
 +        // Paper - optimise entity tracker
  
-         SectionPos sectionposition = player.getLastSectionPos();
-         SectionPos sectionposition1 = SectionPos.of((EntityAccess) player);
-@@ -1208,6 +804,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- 
+         SectionPos lastSectionPos = player.getLastSectionPos();
+         SectionPos sectionPos = SectionPos.of(player);
+@@ -1108,6 +800,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong();
          if (flag2 || flag != flag1) {
              this.updatePlayerPos(player);
 +            ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, sectionposition, sectionposition1, flag, flag1); // Paper - chunk tick iteration optimisation
              if (!flag) {
-                 this.distanceManager.removePlayer(sectionposition, player);
+                 this.distanceManager.removePlayer(lastSectionPos, player);
              }
-@@ -1224,70 +821,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1124,49 +817,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                  this.playerMap.unIgnorePlayer(player);
              }
  
 -            this.updateChunkTracking(player);
 +            // Paper - rewrite chunk system
          }
- 
 +        ca.spottedleaf.moonrise.common.PlatformHooks.get().updateMaps(this.level, player); // Paper - rewrite chunk system
      }
  
      private void updateChunkTracking(ServerPlayer player) {
--        ChunkPos chunkcoordintpair = player.chunkPosition();
--        int i = this.getPlayerViewDistance(player);
--        ChunkTrackingView chunktrackingview = player.getChunkTrackingView();
--
--        if (chunktrackingview instanceof ChunkTrackingView.Positioned chunktrackingview_a) {
--            if (chunktrackingview_a.center().equals(chunkcoordintpair) && chunktrackingview_a.viewDistance() == i) {
--                return;
--            }
+-        ChunkPos chunkPos = player.chunkPosition();
+-        int playerViewDistance = this.getPlayerViewDistance(player);
+-        if (!(
+-            player.getChunkTrackingView() instanceof ChunkTrackingView.Positioned positioned
+-                && positioned.center().equals(chunkPos)
+-                && positioned.viewDistance() == playerViewDistance
+-        )) {
+-            this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, playerViewDistance));
 -        }
--
--        this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkcoordintpair, i));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkFilter) {
+     private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkTrackingView) {
 -        if (player.level() == this.level) {
--            ChunkTrackingView chunktrackingview1 = player.getChunkTrackingView();
--
--            if (chunkFilter instanceof ChunkTrackingView.Positioned) {
--                label15:
--                {
--                    ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunkFilter;
--
--                    if (chunktrackingview1 instanceof ChunkTrackingView.Positioned) {
--                        ChunkTrackingView.Positioned chunktrackingview_a1 = (ChunkTrackingView.Positioned) chunktrackingview1;
--
--                        if (chunktrackingview_a1.center().equals(chunktrackingview_a.center())) {
--                            break label15;
--                        }
--                    }
--
--                    player.connection.send(new ClientboundSetChunkCacheCenterPacket(chunktrackingview_a.center().x, chunktrackingview_a.center().z));
--                }
+-            ChunkTrackingView chunkTrackingView1 = player.getChunkTrackingView();
+-            if (chunkTrackingView instanceof ChunkTrackingView.Positioned positioned
+-                && !(chunkTrackingView1 instanceof ChunkTrackingView.Positioned positioned1 && positioned1.center().equals(positioned.center()))) {
+-                player.connection.send(new ClientboundSetChunkCacheCenterPacket(positioned.center().x, positioned.center().z));
 -            }
 -
--            ChunkTrackingView.difference(chunktrackingview1, chunkFilter, (chunkcoordintpair) -> {
--                this.markChunkPendingToSend(player, chunkcoordintpair);
--            }, (chunkcoordintpair) -> {
--                ChunkMap.dropChunk(player, chunkcoordintpair);
--            });
--            player.setChunkTrackingView(chunkFilter);
+-            ChunkTrackingView.difference(
+-                chunkTrackingView1, chunkTrackingView, chunkPos -> this.markChunkPendingToSend(player, chunkPos), chunkPos -> dropChunk(player, chunkPos)
+-            );
+-            player.setChunkTrackingView(chunkTrackingView);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Override
-     public List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
--        Set<ServerPlayer> set = this.playerMap.getAllPlayers();
+     public List<ServerPlayer> getPlayers(ChunkPos pos, boolean boundaryOnly) {
+-        Set<ServerPlayer> allPlayers = this.playerMap.getAllPlayers();
 -        Builder<ServerPlayer> builder = ImmutableList.builder();
--        Iterator iterator = set.iterator();
 -
--        while (iterator.hasNext()) {
--            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
--
--            if (onlyOnWatchDistanceEdge && this.isChunkOnTrackedBorder(entityplayer, chunkPos.x, chunkPos.z) || !onlyOnWatchDistanceEdge && this.isChunkTracked(entityplayer, chunkPos.x, chunkPos.z)) {
--                builder.add(entityplayer);
+-        for (ServerPlayer serverPlayer : allPlayers) {
+-            if (boundaryOnly && this.isChunkOnTrackedBorder(serverPlayer, pos.x, pos.z) || !boundaryOnly && this.isChunkTracked(serverPlayer, pos.x, pos.z)) {
+-                builder.add(serverPlayer);
 -            }
 +        // Paper start - rewrite chunk system
-+        final ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong());
++        final ChunkHolder holder = this.getVisibleChunkIfPresent(pos.toLong());
 +        if (holder == null) {
 +            return new ArrayList<>();
 +        } else {
-+            return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)holder).moonrise$getPlayers(onlyOnWatchDistanceEdge);
++            return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)holder).moonrise$getPlayers(boundaryOnly);
          }
 -
 -        return builder.build();
@@ -25458,34 +25033,30 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
      }
  
      public void addEntity(Entity entity) {
-@@ -1314,6 +871,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-                     ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas());
- 
-                     this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
+@@ -1190,6 +863,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+                 } else {
+                     ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, updateInterval, type.trackDeltas());
+                     this.entityMap.put(entity.getId(), trackedEntity);
 +                    // Paper start - optimise entity tracker
 +                    if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) {
 +                        throw new IllegalStateException("Entity is already tracked");
 +                    }
 +                    ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(playerchunkmap_entitytracker);
 +                    // Paper end - optimise entity tracker
-                     playerchunkmap_entitytracker.updatePlayers(this.level.players());
-                     if (entity instanceof ServerPlayer) {
-                         ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -1354,16 +917,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-             playerchunkmap_entitytracker1.broadcastRemoved();
+                     trackedEntity.updatePlayers(this.level.players());
+                     if (entity instanceof ServerPlayer serverPlayer) {
+                         this.updatePlayerStatus(serverPlayer, true);
+@@ -1219,12 +898,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         if (trackedEntity1 != null) {
+             trackedEntity1.broadcastRemoved();
          }
- 
 +        ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker
      }
  
--    protected void tick() {
--        Iterator iterator = this.playerMap.getAllPlayers().iterator();
 +    // Paper start - optimise entity tracker
 +    private void newTrackerTick() {
 +        final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();;
- 
--        while (iterator.hasNext()) {
--            ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
 +        final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
 +        final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
 +        for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
@@ -25502,9 +25073,10 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        }
 +    }
 +    // Paper end - optimise entity tracker
- 
--            this.updateChunkTracking(entityplayer);
-+    protected void tick() {
++
+     protected void tick() {
+-        for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
+-            this.updateChunkTracking(serverPlayer);
 +        // Paper start - optimise entity tracker
 +        if (true) {
 +            this.newTrackerTick();
@@ -25515,28 +25087,24 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
  
          List<ServerPlayer> list = Lists.newArrayList();
          List<ServerPlayer> list1 = this.level.players();
-@@ -1466,27 +1051,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1302,23 +1007,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
      }
  
-     public void waitForLightBeforeSending(ChunkPos centerPos, int radius) {
--        int j = radius + 1;
--
--        ChunkPos.rangeClosed(centerPos, j).forEach((chunkcoordintpair1) -> {
--            ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkcoordintpair1.toLong());
--
--            if (playerchunk != null) {
--                playerchunk.addSendDependency(this.lightEngine.waitForPendingTasks(chunkcoordintpair1.x, chunkcoordintpair1.z));
+     public void waitForLightBeforeSending(ChunkPos chunkPos, int range) {
+-        int i = range + 1;
+-        ChunkPos.rangeClosed(chunkPos, i).forEach(pos -> {
+-            ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(pos.toLong());
+-            if (visibleChunkIfPresent != null) {
+-                visibleChunkIfPresent.addSendDependency(this.lightEngine.waitForPendingTasks(pos.x, pos.z));
 -            }
--
 -        });
 +        // Paper - rewrite chunk system
      }
  
--    public class ChunkDistanceManager extends DistanceManager { // Paper - public
-+    public class ChunkDistanceManager extends DistanceManager implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager { // Paper - public // Paper - rewrite chunk system
- 
-         protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
-             super(workerExecutor, mainThreadExecutor);
+-    public class DistanceManager extends net.minecraft.server.level.DistanceManager { // Paper - public
++    public class DistanceManager extends net.minecraft.server.level.DistanceManager implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager { // Paper - public // Paper - rewrite chunk system
+         protected DistanceManager(final Executor dispatcher, final Executor mainThreadExecutor) {
+             super(dispatcher, mainThreadExecutor);
          }
  
 +        // Paper start - rewrite chunk system
@@ -25547,22 +25115,21 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        // Paper end - rewrite chunk system
 +
          @Override
-         protected boolean isChunkToRemove(long pos) {
--            return ChunkMap.this.toDrop.contains(pos);
+         protected boolean isChunkToRemove(long chunkPos) {
+-            return ChunkMap.this.toDrop.contains(chunkPos);
 +            throw new UnsupportedOperationException(); // Paper - rewrite chunk system
          }
  
          @Nullable
-@@ -1502,7 +1085,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1334,13 +1040,96 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
      }
  
 -    public class TrackedEntity {
 +    public class TrackedEntity implements ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity { // Paper - optimise entity tracker
- 
          public final ServerEntity serverEntity;
          final Entity entity;
-@@ -1510,6 +1093,89 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         private final int range;
          SectionPos lastSectionPos;
          public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
  
@@ -25649,30 +25216,28 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +        }
 +        // Paper end - optimise entity tracker
 +
-         public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
-             this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
+         public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) {
+             this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this.seenBy); // CraftBukkit
              this.entity = entity;
-@@ -1612,20 +1278,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1431,17 +1220,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
  
          private int getEffectiveRange() {
 -            int i = this.range;
--            Iterator iterator = this.entity.getIndirectPassengers().iterator();
 +            // Paper start - optimise entity tracker
 +            final Entity entity = this.entity;
 +            int range = this.range;
  
--            while (iterator.hasNext()) {
--                Entity entity = (Entity) iterator.next();
--                int j = entity.getType().clientTrackingRange() * 16;
--                j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
+-            for (Entity entity : this.entity.getIndirectPassengers()) {
+-                int i1 = entity.getType().clientTrackingRange() * 16;
+-                i1 = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i1); // Paper
+-                if (i1 > i) {
+-                    i = i1;
+-                }
 +            if (entity.getPassengers() == ImmutableList.<Entity>of()) {
 +                return this.scaledRange(range);
 +            }
- 
--                if (j > i) {
--                    i = j;
--                }
++
 +            // note: we change to List
 +            final List<Entity> passengers = (List<Entity>)entity.getIndirectPassengers();
 +            for (int i = 0, len = passengers.size(); i < len; ++i) {
@@ -25686,28 +25251,27 @@ index e9b585387f6cbc454e7b16feb36a256e733c5488..204965b3dfa2ac9f6709e61b847e1152
 +            // Paper end - optimise entity tracker
          }
  
-         public void updatePlayers(List<ServerPlayer> players) {
+         public void updatePlayers(List<ServerPlayer> playersList) {
 diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java
-index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57fbca45b2 100644
+index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9873d2411 100644
 --- a/net/minecraft/server/level/DistanceManager.java
 +++ b/net/minecraft/server/level/DistanceManager.java
-@@ -34,58 +34,57 @@ import net.minecraft.world.level.ChunkPos;
+@@ -34,56 +34,56 @@ import net.minecraft.world.level.ChunkPos;
  import net.minecraft.world.level.chunk.LevelChunk;
  import org.slf4j.Logger;
  
 -public abstract class DistanceManager {
 +public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager { // Paper - rewrite chunk system // Paper - chunk tick iteration optimisation
- 
      static final Logger LOGGER = LogUtils.getLogger();
      static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
      private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
-     final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
--    public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
+     final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap<>();
+-    public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap<>();
 -    private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
 -    private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
 -    private final TickingTracker tickingTicketsTracker = new TickingTracker();
 -    private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32);
--    final Set<ChunkHolder> chunksToUpdateFutures = new ReferenceOpenHashSet();
+-    final Set<ChunkHolder> chunksToUpdateFutures = new ReferenceOpenHashSet<>();
 -    final ThrottlingChunkTaskDispatcher ticketDispatcher;
 -    final LongSet ticketsToRelease = new LongOpenHashSet();
 -    final Executor mainThreadExecutor;
@@ -25718,25 +25282,30 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 -    public int simulationDistance = 10;
 +    // Paper - rewrite chunk system
  
-     protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor) {
-         TaskScheduler<Runnable> taskscheduler = TaskScheduler.wrapExecutor("player ticket throttler", mainThreadExecutor);
- 
--        this.ticketDispatcher = new ThrottlingChunkTaskDispatcher(taskscheduler, workerExecutor, 4);
+     protected DistanceManager(Executor dispatcher, Executor mainThreadExecutor) {
+         TaskScheduler<Runnable> taskScheduler = TaskScheduler.wrapExecutor("player ticket throttler", mainThreadExecutor);
+-        this.ticketDispatcher = new ThrottlingChunkTaskDispatcher(taskScheduler, dispatcher, 4);
 -        this.mainThreadExecutor = mainThreadExecutor;
 +        // Paper - rewrite chunk system
      }
  
 -    protected void purgeStaleTickets() {
--        ++this.ticketTickCounter;
--        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
+-        this.ticketTickCounter++;
+-        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
 -
--        while (objectiterator.hasNext()) {
--            Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
--            Iterator<Ticket<?>> iterator = ((SortedArraySet) entry.getValue()).iterator();
+-        while (objectIterator.hasNext()) {
+-            Entry<SortedArraySet<Ticket<?>>> entry = objectIterator.next();
+-            Iterator<Ticket<?>> iterator = entry.getValue().iterator();
 -            boolean flag = false;
 -
 -            while (iterator.hasNext()) {
--                Ticket<?> ticket = (Ticket) iterator.next();
+-                Ticket<?> ticket = iterator.next();
+-                if (ticket.timedOut(this.ticketTickCounter)) {
+-                    iterator.remove();
+-                    flag = true;
+-                    this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
+-                }
+-            }
 +    // Paper start - rewrite chunk system
 +    @Override
 +    public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager moonrise$getChunkHolderManager() {
@@ -25746,28 +25315,22 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +    // Paper start - chunk tick iteration optimisation
 +    private final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<ServerPlayer> spawnChunkTracker = new ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap<>();
  
--                if (ticket.timedOut(this.ticketTickCounter)) {
--                    iterator.remove();
--                    flag = true;
--                    this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
--                }
+-            if (flag) {
+-                this.ticketTracker.update(entry.getLongKey(), getTicketLevelAt(entry.getValue()), false);
 -            }
 +    @Override
 +    public final void moonrise$addPlayer(final ServerPlayer player, final SectionPos pos) {
 +        this.spawnChunkTracker.add(player, pos.x(), pos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
 +    }
  
--            if (flag) {
--                this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
+-            if (entry.getValue().isEmpty()) {
+-                objectIterator.remove();
 -            }
 +    @Override
 +    public final void moonrise$removePlayer(final ServerPlayer player, final SectionPos pos) {
 +        this.spawnChunkTracker.remove(player);
 +    }
- 
--            if (((SortedArraySet) entry.getValue()).isEmpty()) {
--                objectiterator.remove();
--            }
++
 +    @Override
 +    public final void moonrise$updatePlayer(final ServerPlayer player,
 +                                            final SectionPos oldPos, final SectionPos newPos,
@@ -25777,80 +25340,60 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +        } else {
 +            this.spawnChunkTracker.addOrUpdate(player, newPos.x(), newPos.z(), ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE);
          }
-+    }
+     }
 +    // Paper end - chunk tick iteration optimisation
 +
 +    protected void purgeStaleTickets() {
 +        this.moonrise$getChunkHolderManager().tick(); // Paper - rewrite chunk system
++    }
  
-     }
+     private static int getTicketLevelAt(SortedArraySet<Ticket<?>> tickets) {
+         return !tickets.isEmpty() ? tickets.first().getTicketLevel() : ChunkLevel.MAX_LEVEL + 1;
+@@ -98,81 +98,15 @@ public abstract class DistanceManager {
+     protected abstract ChunkHolder updateChunkScheduling(long chunkPos, int i, @Nullable ChunkHolder newLevel, int holder);
  
-@@ -102,105 +101,15 @@ public abstract class DistanceManager {
-     protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
- 
-     public boolean runAllUpdates(ChunkMap chunkLoadingManager) {
+     public boolean runAllUpdates(ChunkMap chunkMap) {
 -        this.naturalSpawnChunkCounter.runAllUpdates();
 -        this.tickingTicketsTracker.runAllUpdates();
 -        this.playerTicketManager.runAllUpdates();
 -        int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
 -        boolean flag = i != 0;
--
 -        if (flag) {
--            ;
 -        }
 -
 -        if (!this.chunksToUpdateFutures.isEmpty()) {
--            Iterator iterator = this.chunksToUpdateFutures.iterator();
--
--            ChunkHolder playerchunk;
--
 -            // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
--            while (iterator.hasNext()) {
--                playerchunk = (ChunkHolder) iterator.next();
--                playerchunk.callEventIfUnloading(chunkLoadingManager);
+-            for (final ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
+-                chunkHolder.callEventIfUnloading(chunkMap);
+-            }
+-            // CraftBukkit end - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus
+-
+-            for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
+-                chunkHolder.updateHighestAllowedStatus(chunkMap);
 -            }
 -
--            iterator = this.chunksToUpdateFutures.iterator();
--            // CraftBukkit end
--
--            while (iterator.hasNext()) {
--                playerchunk = (ChunkHolder) iterator.next();
--                playerchunk.updateHighestAllowedStatus(chunkLoadingManager);
--            }
--
--            iterator = this.chunksToUpdateFutures.iterator();
--
--            while (iterator.hasNext()) {
--                playerchunk = (ChunkHolder) iterator.next();
--                playerchunk.updateFutures(chunkLoadingManager, this.mainThreadExecutor);
+-            for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) {
+-                chunkHolder.updateFutures(chunkMap, this.mainThreadExecutor);
 -            }
 -
 -            this.chunksToUpdateFutures.clear();
 -            return true;
 -        } else {
 -            if (!this.ticketsToRelease.isEmpty()) {
--                LongIterator longiterator = this.ticketsToRelease.iterator();
+-                LongIterator longIterator = this.ticketsToRelease.iterator();
 -
--                while (longiterator.hasNext()) {
--                    long j = longiterator.nextLong();
--
--                    if (this.getTickets(j).stream().anyMatch((ticket) -> {
--                        return ticket.getType() == TicketType.PLAYER;
--                    })) {
--                        ChunkHolder playerchunk1 = chunkLoadingManager.getUpdatingChunkIfPresent(j);
--
--                        if (playerchunk1 == null) {
+-                while (longIterator.hasNext()) {
+-                    long l = longIterator.nextLong();
+-                    if (this.getTickets(l).stream().anyMatch(ticket -> ticket.getType() == TicketType.PLAYER)) {
+-                        ChunkHolder updatingChunkIfPresent = chunkMap.getUpdatingChunkIfPresent(l);
+-                        if (updatingChunkIfPresent == null) {
 -                            throw new IllegalStateException();
 -                        }
 -
--                        CompletableFuture<ChunkResult<LevelChunk>> completablefuture = playerchunk1.getEntityTickingChunkFuture();
--
--                        completablefuture.thenAccept((chunkresult) -> {
--                            this.mainThreadExecutor.execute(() -> {
--                                this.ticketDispatcher.release(j, () -> {
--                                }, false);
--                            });
--                        });
+-                        CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = updatingChunkIfPresent.getEntityTickingChunkFuture();
+-                        entityTickingChunkFuture.thenAccept(
+-                            chunkResult -> this.mainThreadExecutor.execute(() -> this.ticketDispatcher.release(l, () -> {}, false))
+-                        );
 -                    }
 -                }
 -
@@ -25862,115 +25405,105 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +        return this.moonrise$getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system
      }
  
-     boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
--        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
--        int j = DistanceManager.getTicketLevelAt(arraysetsorted);
--        Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
--
+     boolean addTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+-        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
+-        int ticketLevelAt = getTicketLevelAt(tickets);
+-        Ticket<?> ticket1 = tickets.addOrGet(ticket);
 -        ticket1.setCreatedTick(this.ticketTickCounter);
--        if (ticket.getTicketLevel() < j) {
--            this.ticketTracker.update(i, ticket.getTicketLevel(), true);
+-        if (ticket.getTicketLevel() < ticketLevelAt) {
+-            this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
 -        }
--
 -        return ticket == ticket1; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
++        return this.moonrise$getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
      }
  
-     boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
--        SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
--
+     boolean removeTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+-        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
 -        boolean removed = false; // CraftBukkit
--        if (arraysetsorted.remove(ticket)) {
+-        if (tickets.remove(ticket)) {
 -            removed = true; // CraftBukkit
 -        }
 -
--        if (arraysetsorted.isEmpty()) {
--            this.tickets.remove(i);
+-        if (tickets.isEmpty()) {
+-            this.tickets.remove(chunkPos);
 -        }
 -
--        this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
+-        this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
 -        return removed; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
++        return this.moonrise$getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
      }
  
-     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T argument) {
-@@ -219,13 +128,7 @@ public abstract class DistanceManager {
+     public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
+@@ -189,12 +123,7 @@ public abstract class DistanceManager {
+         this.addRegionTicketAtDistance(type, pos, distance, value);
      }
- 
-     public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
+     public <T> boolean addRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
 -        // CraftBukkit end
--        Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
--        long j = chunkcoordintpair.toLong();
--
--        boolean added = this.addTicket(j, ticket); // CraftBukkit
--        this.tickingTicketsTracker.addTicket(j, ticket);
--        return added; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().addTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system
+-        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+-        long packedChunkPos = pos.toLong();
+-        final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit
+-        this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
+-        return addded; // CraftBukkit
++        return this.moonrise$getChunkHolderManager().addTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
      }
  
-     public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int radius, T argument) {
-@@ -234,32 +137,21 @@ public abstract class DistanceManager {
+     public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
+@@ -202,37 +131,29 @@ public abstract class DistanceManager {
+         removeRegionTicketAtDistance(type, pos, distance, value);
      }
- 
-     public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
+     public <T> boolean removeRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
 -        // CraftBukkit end
--        Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
--        long j = chunkcoordintpair.toLong();
--
--        boolean removed = this.removeTicket(j, ticket); // CraftBukkit
--        this.tickingTicketsTracker.removeTicket(j, ticket);
+-        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+-        long packedChunkPos = pos.toLong();
+-        final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit
+-        this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
 -        return removed; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().removeTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system
++        return this.moonrise$getChunkHolderManager().removeTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
      }
  
-     private SortedArraySet<Ticket<?>> getTickets(long position) {
--        return (SortedArraySet) this.tickets.computeIfAbsent(position, (j) -> {
--            return SortedArraySet.create(4);
--        });
+     private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
+-        return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     protected void updateChunkForced(ChunkPos pos, boolean forced) {
+     protected void updateChunkForced(ChunkPos pos, boolean add) {
 -        Ticket<ChunkPos> ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos);
--        long i = pos.toLong();
--
+-        long packedChunkPos = pos.toLong();
 +        // Paper start - rewrite chunk system
-         if (forced) {
--            this.addTicket(i, ticket);
--            this.tickingTicketsTracker.addTicket(i, ticket);
+         if (add) {
+-            this.addTicket(packedChunkPos, ticket);
+-            this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
 +            this.moonrise$getChunkHolderManager().addTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
          } else {
--            this.removeTicket(i, ticket);
--            this.tickingTicketsTracker.removeTicket(i, ticket);
+-            this.removeTicket(packedChunkPos, ticket);
+-            this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
 +            this.moonrise$getChunkHolderManager().removeTicketAtLevel(TicketType.FORCED, pos, ChunkMap.FORCED_TICKET_LEVEL, pos);
          }
 +        // Paper end - rewrite chunk system
- 
      }
  
-@@ -270,9 +162,8 @@ public abstract class DistanceManager {
-         ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> {
-             return new ObjectOpenHashSet();
-         })).add(player);
--        this.naturalSpawnChunkCounter.update(i, 0, true);
--        this.playerTicketManager.update(i, 0, true);
--        this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
+     public void addPlayer(SectionPos sectionPos, ServerPlayer player) {
+         ChunkPos chunkPos = sectionPos.chunk();
+         long packedChunkPos = chunkPos.toLong();
+         this.playersPerChunk.computeIfAbsent(packedChunkPos, l -> new ObjectOpenHashSet<>()).add(player);
+-        this.naturalSpawnChunkCounter.update(packedChunkPos, 0, true);
+-        this.playerTicketManager.update(packedChunkPos, 0, true);
+-        this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkPos, this.getPlayerTicketLevel(), chunkPos);
 +        // Paper - chunk tick iteration optimisation
 +        // Paper - rewrite chunk system
      }
  
-     public void removePlayer(SectionPos pos, ServerPlayer player) {
-@@ -284,160 +175,93 @@ public abstract class DistanceManager {
-         if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
-         if (objectset == null || objectset.isEmpty()) { // Paper
-             this.playersPerChunk.remove(i);
--            this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
--            this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
--            this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
+     public void removePlayer(SectionPos sectionPos, ServerPlayer player) {
+@@ -246,136 +167,90 @@ public abstract class DistanceManager {
+         if (set == null || set.isEmpty()) {
+             // Paper end - some state corruption happens here, don't crash, clean up gracefully
+             this.playersPerChunk.remove(packedChunkPos);
+-            this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
+-            this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);
+-            this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkPos, this.getPlayerTicketLevel(), chunkPos);
 +            // Paper - chunk tick iteration optimisation
 +            // Paper - rewrite chunk system
          }
- 
      }
  
      private int getPlayerTicketLevel() {
@@ -25994,11 +25527,10 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +        // Paper end - rewrite chunk system
      }
  
-     protected String getTicketDebugString(long pos) {
--        SortedArraySet<Ticket<?>> arraysetsorted = (SortedArraySet) this.tickets.get(pos);
--
--        return arraysetsorted != null && !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).toString() : "no_ticket";
-+        return this.moonrise$getChunkHolderManager().getTicketDebugString(pos); // Paper - rewrite chunk system
+     protected String getTicketDebugString(long chunkPos) {
+-        SortedArraySet<Ticket<?>> set = this.tickets.get(chunkPos);
+-        return set != null && !set.isEmpty() ? set.first().toString() : "no_ticket";
++        return this.moonrise$getChunkHolderManager().getTicketDebugString(chunkPos); // Paper - rewrite chunk system
      }
  
      protected void updatePlayerTickets(int viewDistance) {
@@ -26014,7 +25546,7 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +        // Paper start - rewrite chunk system
 +        // note: vanilla does not clamp to 0, but we do simply because we need a min of 0
 +        final int clamped = net.minecraft.util.Mth.clamp(simulationDistance, 0, ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE);
- 
++
 +        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(clamped);
 +        // Paper end - rewrite chunk system
      }
@@ -26042,40 +25574,21 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 +        return "No DistanceManager stats available"; // Paper - rewrite chunk system
      }
  
-     private void dumpTickets(String path) {
--        try {
--            FileOutputStream fileoutputstream = new FileOutputStream(new File(path));
+     private void dumpTickets(String filename) {
+-        try (FileOutputStream fileOutputStream = new FileOutputStream(new File(filename))) {
+-            for (Entry<SortedArraySet<Ticket<?>>> entry : this.tickets.long2ObjectEntrySet()) {
+-                ChunkPos chunkPos = new ChunkPos(entry.getLongKey());
 -
--            try {
--                ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().iterator();
--
--                while (objectiterator.hasNext()) {
--                    Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
--                    ChunkPos chunkcoordintpair = new ChunkPos(entry.getLongKey());
--                    Iterator iterator = ((SortedArraySet) entry.getValue()).iterator();
--
--                    while (iterator.hasNext()) {
--                        Ticket<?> ticket = (Ticket) iterator.next();
--
--                        fileoutputstream.write((chunkcoordintpair.x + "\t" + chunkcoordintpair.z + "\t" + String.valueOf(ticket.getType()) + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8));
--                    }
+-                for (Ticket<?> ticket : entry.getValue()) {
+-                    fileOutputStream.write(
+-                        (chunkPos.x + "\t" + chunkPos.z + "\t" + ticket.getType() + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8)
+-                    );
 -                }
--            } catch (Throwable throwable) {
--                try {
--                    fileoutputstream.close();
--                } catch (Throwable throwable1) {
--                    throwable.addSuppressed(throwable1);
--                }
--
--                throw throwable;
 -            }
--
--            fileoutputstream.close();
--        } catch (IOException ioexception) {
--            DistanceManager.LOGGER.error("Failed to dump tickets to {}", path, ioexception);
+-        } catch (IOException var10) {
+-            LOGGER.error("Failed to dump tickets to {}", filename, var10);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
- 
      }
  
      @VisibleForTesting
@@ -26090,18 +25603,17 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
      }
  
      public void removeTicketsOnClosing() {
--        ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
--        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
+-        ImmutableSet<TicketType<?>> set = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
+-        ObjectIterator<Entry<SortedArraySet<Ticket<?>>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator();
 -
--        while (objectiterator.hasNext()) {
--            Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
--            Iterator<Ticket<?>> iterator = ((SortedArraySet) entry.getValue()).iterator();
+-        while (objectIterator.hasNext()) {
+-            Entry<SortedArraySet<Ticket<?>>> entry = objectIterator.next();
+-            Iterator<Ticket<?>> iterator = entry.getValue().iterator();
 -            boolean flag = false;
 -
 -            while (iterator.hasNext()) {
--                Ticket<?> ticket = (Ticket) iterator.next();
--
--                if (!immutableset.contains(ticket.getType())) {
+-                Ticket<?> ticket = iterator.next();
+-                if (!set.contains(ticket.getType())) {
 -                    iterator.remove();
 -                    flag = true;
 -                    this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
@@ -26109,15 +25621,14 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
 -            }
 -
 -            if (flag) {
--                this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
+-                this.ticketTracker.update(entry.getLongKey(), getTicketLevelAt(entry.getValue()), false);
 -            }
 -
--            if (((SortedArraySet) entry.getValue()).isEmpty()) {
--                objectiterator.remove();
+-            if (entry.getValue().isEmpty()) {
+-                objectIterator.remove();
 -            }
 -        }
 +        // Paper - rewrite chunk system
- 
      }
  
      public boolean hasTickets() {
@@ -26146,36 +25657,36 @@ index f7c2c03749d6be25bf33afd61e1da120770b3432..746f61661e22d22f2acbbe54a5933e57
      }
      // CraftBukkit end
  
-+    /*  // Paper - rewrite chunk system
-     private class ChunkTicketTracker extends ChunkTracker {
- 
++/*  // Paper - rewrite chunk system
+     class ChunkTicketTracker extends ChunkTracker {
          private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
-@@ -483,7 +307,7 @@ public abstract class DistanceManager {
-         public int runDistanceUpdates(int distance) {
-             return this.runUpdates(distance);
+ 
+@@ -420,7 +295,7 @@ public abstract class DistanceManager {
+         public int runDistanceUpdates(int toUpdateCount) {
+             return this.runUpdates(toUpdateCount);
          }
 -    }
 +    }*/  // Paper - rewrite chunk system
  
-     private class FixedPlayerDistanceChunkTracker extends ChunkTracker {
- 
-@@ -563,6 +387,7 @@ public abstract class DistanceManager {
+     class FixedPlayerDistanceChunkTracker extends ChunkTracker {
+         protected final Long2ByteMap chunks = new Long2ByteOpenHashMap();
+@@ -479,6 +354,7 @@ public abstract class DistanceManager {
          }
      }
  
-+    /*  // Paper - rewrite chunk system
-     private class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker {
- 
-         private int viewDistance = 0;
-@@ -657,5 +482,5 @@ public abstract class DistanceManager {
-         private boolean haveTicketFor(int distance) {
-             return distance <= this.viewDistance;
++/*  // Paper - rewrite chunk system
+     class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker {
+         private int viewDistance;
+         private final Long2IntMap queueLevels = Long2IntMaps.synchronize(new Long2IntOpenHashMap());
+@@ -555,5 +431,5 @@ public abstract class DistanceManager {
+         private boolean haveTicketFor(int level) {
+             return level <= this.viewDistance;
          }
 -    }
 +    }*/  // Paper - rewrite chunk system
  }
 diff --git a/net/minecraft/server/level/GenerationChunkHolder.java b/net/minecraft/server/level/GenerationChunkHolder.java
-index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a5fd7489a 100644
+index cb66209c64b855dedf2e4e114a7716da13bc4587..da1366fdc4889d6a3befd43d81a19a816ed4cbe9 100644
 --- a/net/minecraft/server/level/GenerationChunkHolder.java
 +++ b/net/minecraft/server/level/GenerationChunkHolder.java
 @@ -27,13 +27,7 @@ public abstract class GenerationChunkHolder {
@@ -26196,52 +25707,52 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 @@ -43,243 +37,96 @@ public abstract class GenerationChunkHolder {
      }
  
-     public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGenerationTask(ChunkStatus requestedStatus, ChunkMap chunkLoadingManager) {
--        if (this.isStatusDisallowed(requestedStatus)) {
+     public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGenerationTask(ChunkStatus targetStatus, ChunkMap chunkMap) {
+-        if (this.isStatusDisallowed(targetStatus)) {
 -            return UNLOADED_CHUNK_FUTURE;
 -        } else {
--            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.getOrCreateFuture(requestedStatus);
--            if (completableFuture.isDone()) {
--                return completableFuture;
+-            CompletableFuture<ChunkResult<ChunkAccess>> future = this.getOrCreateFuture(targetStatus);
+-            if (future.isDone()) {
+-                return future;
 -            } else {
 -                ChunkGenerationTask chunkGenerationTask = this.task.get();
--                if (chunkGenerationTask == null || requestedStatus.isAfter(chunkGenerationTask.targetStatus)) {
--                    this.rescheduleChunkTask(chunkLoadingManager, requestedStatus);
+-                if (chunkGenerationTask == null || targetStatus.isAfter(chunkGenerationTask.targetStatus)) {
+-                    this.rescheduleChunkTask(chunkMap, targetStatus);
 -                }
 -
--                return completableFuture;
+-                return future;
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     CompletableFuture<ChunkResult<ChunkAccess>> applyStep(ChunkStep step, GeneratingChunkMap chunkLoadingManager, StaticCache2D<GenerationChunkHolder> chunks) {
+     CompletableFuture<ChunkResult<ChunkAccess>> applyStep(ChunkStep step, GeneratingChunkMap chunkMap, StaticCache2D<GenerationChunkHolder> cache) {
 -        if (this.isStatusDisallowed(step.targetStatus())) {
 -            return UNLOADED_CHUNK_FUTURE;
 -        } else {
--            return this.acquireStatusBump(step.targetStatus()) ? chunkLoadingManager.applyStep(this, step, chunks).handle((chunk, throwable) -> {
+-            return this.acquireStatusBump(step.targetStatus()) ? chunkMap.applyStep(this, step, cache).handle((chunkAccess, throwable) -> {
 -                if (throwable != null) {
 -                    CrashReport crashReport = CrashReport.forThrowable(throwable, "Exception chunk generation/loading");
 -                    MinecraftServer.setFatalException(new ReportedException(crashReport));
 -                } else {
--                    this.completeFuture(step.targetStatus(), chunk);
+-                    this.completeFuture(step.targetStatus(), chunkAccess);
 -                }
 -
--                return ChunkResult.of(chunk);
+-                return ChunkResult.of(chunkAccess);
 -            }) : this.getOrCreateFuture(step.targetStatus());
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     protected void updateHighestAllowedStatus(ChunkMap chunkLoadingManager) {
+     protected void updateHighestAllowedStatus(ChunkMap chunkMap) {
 -        ChunkStatus chunkStatus = this.highestAllowedStatus;
--        ChunkStatus chunkStatus2 = ChunkLevel.generationStatus(this.getTicketLevel());
--        this.highestAllowedStatus = chunkStatus2;
--        boolean bl = chunkStatus != null && (chunkStatus2 == null || chunkStatus2.isBefore(chunkStatus));
--        if (bl) {
--            this.failAndClearPendingFuturesBetween(chunkStatus2, chunkStatus);
+-        ChunkStatus chunkStatus1 = ChunkLevel.generationStatus(this.getTicketLevel());
+-        this.highestAllowedStatus = chunkStatus1;
+-        boolean flag = chunkStatus != null && (chunkStatus1 == null || chunkStatus1.isBefore(chunkStatus));
+-        if (flag) {
+-            this.failAndClearPendingFuturesBetween(chunkStatus1, chunkStatus);
 -            if (this.task.get() != null) {
--                this.rescheduleChunkTask(chunkLoadingManager, this.findHighestStatusWithPendingFuture(chunkStatus2));
+-                this.rescheduleChunkTask(chunkMap, this.findHighestStatusWithPendingFuture(chunkStatus1));
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
@@ -26251,57 +25762,57 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 -        CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = CompletableFuture.completedFuture(ChunkResult.of(chunk));
 -
 -        for (int i = 0; i < this.futures.length() - 1; i++) {
--            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture2 = this.futures.get(i);
--            Objects.requireNonNull(completableFuture2);
--            ChunkAccess chunkAccess = completableFuture2.getNow(NOT_DONE_YET).orElse(null);
+-            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture1 = this.futures.get(i);
+-            Objects.requireNonNull(completableFuture1);
+-            ChunkAccess chunkAccess = completableFuture1.getNow(NOT_DONE_YET).orElse(null);
 -            if (!(chunkAccess instanceof ProtoChunk)) {
 -                throw new IllegalStateException("Trying to replace a ProtoChunk, but found " + chunkAccess);
 -            }
 -
--            if (!this.futures.compareAndSet(i, completableFuture2, completableFuture)) {
+-            if (!this.futures.compareAndSet(i, completableFuture1, completableFuture)) {
 -                throw new IllegalStateException("Future changed by other thread while trying to replace it");
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     void removeTask(ChunkGenerationTask loader) {
--        this.task.compareAndSet(loader, null);
+     void removeTask(ChunkGenerationTask task) {
+-        this.task.compareAndSet(task, null);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void rescheduleChunkTask(ChunkMap chunkLoadingManager, @Nullable ChunkStatus requestedStatus) {
+     private void rescheduleChunkTask(ChunkMap chunkMap, @Nullable ChunkStatus targetStatus) {
 -        ChunkGenerationTask chunkGenerationTask;
--        if (requestedStatus != null) {
--            chunkGenerationTask = chunkLoadingManager.scheduleGenerationTask(requestedStatus, this.getPos());
+-        if (targetStatus != null) {
+-            chunkGenerationTask = chunkMap.scheduleGenerationTask(targetStatus, this.getPos());
 -        } else {
 -            chunkGenerationTask = null;
 -        }
 -
--        ChunkGenerationTask chunkGenerationTask3 = this.task.getAndSet(chunkGenerationTask);
--        if (chunkGenerationTask3 != null) {
--            chunkGenerationTask3.markForCancellation();
+-        ChunkGenerationTask chunkGenerationTask1 = this.task.getAndSet(chunkGenerationTask);
+-        if (chunkGenerationTask1 != null) {
+-            chunkGenerationTask1.markForCancellation();
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private CompletableFuture<ChunkResult<ChunkAccess>> getOrCreateFuture(ChunkStatus status) {
--        if (this.isStatusDisallowed(status)) {
+     private CompletableFuture<ChunkResult<ChunkAccess>> getOrCreateFuture(ChunkStatus targetStatus) {
+-        if (this.isStatusDisallowed(targetStatus)) {
 -            return UNLOADED_CHUNK_FUTURE;
 -        } else {
--            int i = status.getIndex();
--            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(i);
+-            int index = targetStatus.getIndex();
+-            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(index);
 -
 -            while (completableFuture == null) {
--                CompletableFuture<ChunkResult<ChunkAccess>> completableFuture2 = new CompletableFuture<>();
--                completableFuture = this.futures.compareAndExchange(i, null, completableFuture2);
+-                CompletableFuture<ChunkResult<ChunkAccess>> completableFuture1 = new CompletableFuture<>();
+-                completableFuture = this.futures.compareAndExchange(index, null, completableFuture1);
 -                if (completableFuture == null) {
--                    if (this.isStatusDisallowed(status)) {
--                        this.failAndClearPendingFuture(i, completableFuture2);
+-                    if (this.isStatusDisallowed(targetStatus)) {
+-                        this.failAndClearPendingFuture(index, completableFuture1);
 -                        return UNLOADED_CHUNK_FUTURE;
 -                    }
 -
--                    return completableFuture2;
+-                    return completableFuture1;
 -                }
 -            }
 -
@@ -26310,34 +25821,34 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void failAndClearPendingFuturesBetween(@Nullable ChunkStatus from, ChunkStatus to) {
--        int i = from == null ? 0 : from.getIndex() + 1;
--        int j = to.getIndex();
+     private void failAndClearPendingFuturesBetween(@Nullable ChunkStatus highestAllowableStatus, ChunkStatus currentStatus) {
+-        int i = highestAllowableStatus == null ? 0 : highestAllowableStatus.getIndex() + 1;
+-        int index = currentStatus.getIndex();
 -
--        for (int k = i; k <= j; k++) {
--            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(k);
+-        for (int i1 = i; i1 <= index; i1++) {
+-            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(i1);
 -            if (completableFuture != null) {
--                this.failAndClearPendingFuture(k, completableFuture);
+-                this.failAndClearPendingFuture(i1, completableFuture);
 -            }
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void failAndClearPendingFuture(int statusIndex, CompletableFuture<ChunkResult<ChunkAccess>> previousFuture) {
--        if (previousFuture.complete(UNLOADED_CHUNK) && !this.futures.compareAndSet(statusIndex, previousFuture, null)) {
+     private void failAndClearPendingFuture(int status, CompletableFuture<ChunkResult<ChunkAccess>> future) {
+-        if (future.complete(UNLOADED_CHUNK) && !this.futures.compareAndSet(status, future, null)) {
 -            throw new IllegalStateException("Nothing else should replace the future here");
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void completeFuture(ChunkStatus status, ChunkAccess chunk) {
--        ChunkResult<ChunkAccess> chunkResult = ChunkResult.of(chunk);
--        int i = status.getIndex();
+     private void completeFuture(ChunkStatus targetStatus, ChunkAccess chunkAccess) {
+-        ChunkResult<ChunkAccess> chunkResult = ChunkResult.of(chunkAccess);
+-        int index = targetStatus.getIndex();
 -
 -        while (true) {
--            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(i);
+-            CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(index);
 -            if (completableFuture == null) {
--                if (this.futures.compareAndSet(i, null, CompletableFuture.completedFuture(chunkResult))) {
+-                if (this.futures.compareAndSet(index, null, CompletableFuture.completedFuture(chunkResult))) {
 -                    return;
 -                }
 -            } else {
@@ -26356,14 +25867,14 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
      }
  
      @Nullable
-     private ChunkStatus findHighestStatusWithPendingFuture(@Nullable ChunkStatus checkUpperBound) {
--        if (checkUpperBound == null) {
+     private ChunkStatus findHighestStatusWithPendingFuture(@Nullable ChunkStatus generationStatus) {
+-        if (generationStatus == null) {
 -            return null;
 -        } else {
--            ChunkStatus chunkStatus = checkUpperBound;
+-            ChunkStatus chunkStatus = generationStatus;
 -
--            for (ChunkStatus chunkStatus2 = this.startedWork.get();
--                chunkStatus2 == null || chunkStatus.isAfter(chunkStatus2);
+-            for (ChunkStatus chunkStatus1 = this.startedWork.get();
+-                chunkStatus1 == null || chunkStatus.isAfter(chunkStatus1);
 -                chunkStatus = chunkStatus.getParent()
 -            ) {
 -                if (this.futures.get(chunkStatus.getIndex()) != null) {
@@ -26380,15 +25891,15 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private boolean acquireStatusBump(ChunkStatus nextStatus) {
--        ChunkStatus chunkStatus = nextStatus == ChunkStatus.EMPTY ? null : nextStatus.getParent();
--        ChunkStatus chunkStatus2 = this.startedWork.compareAndExchange(chunkStatus, nextStatus);
--        if (chunkStatus2 == chunkStatus) {
+     private boolean acquireStatusBump(ChunkStatus status) {
+-        ChunkStatus chunkStatus = status == ChunkStatus.EMPTY ? null : status.getParent();
+-        ChunkStatus chunkStatus1 = this.startedWork.compareAndExchange(chunkStatus, status);
+-        if (chunkStatus1 == chunkStatus) {
 -            return true;
--        } else if (chunkStatus2 != null && !nextStatus.isAfter(chunkStatus2)) {
+-        } else if (chunkStatus1 != null && !status.isAfter(chunkStatus1)) {
 -            return false;
 -        } else {
--            throw new IllegalStateException("Unexpected last startedWork status: " + chunkStatus2 + " while trying to start: " + nextStatus);
+-            throw new IllegalStateException("Unexpected last startedWork status: " + chunkStatus1 + " while trying to start: " + status);
 -        }
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
@@ -26399,7 +25910,7 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     protected abstract void addSaveDependency(CompletableFuture<?> savingFuture);
+     protected abstract void addSaveDependency(CompletableFuture<?> saveDependency);
  
      public void increaseGenerationRefCount() {
 -        if (this.generationRefCount.getAndIncrement() == 0) {
@@ -26423,19 +25934,19 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
      }
  
      @Nullable
-     public ChunkAccess getChunkIfPresentUnchecked(ChunkStatus requestedStatus) {
--        CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(requestedStatus.getIndex());
+     public ChunkAccess getChunkIfPresentUnchecked(ChunkStatus status) {
+-        CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.futures.get(status.getIndex());
 -        return completableFuture == null ? null : completableFuture.getNow(NOT_DONE_YET).orElse(null);
 +        // Paper start - rewrite chunk system
-+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)(Object)this).moonrise$getRealChunkHolder().getChunkIfPresentUnchecked(requestedStatus);
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)(Object)this).moonrise$getRealChunkHolder().getChunkIfPresentUnchecked(status);
 +        // Paper end - rewrite chunk system
      }
  
      @Nullable
-     public ChunkAccess getChunkIfPresent(ChunkStatus requestedStatus) {
--        return this.isStatusDisallowed(requestedStatus) ? null : this.getChunkIfPresentUnchecked(requestedStatus);
+     public ChunkAccess getChunkIfPresent(ChunkStatus status) {
+-        return this.isStatusDisallowed(status) ? null : this.getChunkIfPresentUnchecked(status);
 +        // Paper start - rewrite chunk system
-+        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)(Object)this).moonrise$getRealChunkHolder().getChunkIfPresent(requestedStatus);
++        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)(Object)this).moonrise$getRealChunkHolder().getChunkIfPresent(status);
 +        // Paper end - rewrite chunk system
      }
  
@@ -26445,8 +25956,8 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
 -        if (chunkStatus == null) {
 -            return null;
 -        } else {
--            ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(chunkStatus);
--            return chunkAccess != null ? chunkAccess : this.getChunkIfPresentUnchecked(chunkStatus.getParent());
+-            ChunkAccess chunkIfPresentUnchecked = this.getChunkIfPresentUnchecked(chunkStatus);
+-            return chunkIfPresentUnchecked != null ? chunkIfPresentUnchecked : this.getChunkIfPresentUnchecked(chunkStatus.getParent());
 -        }
 +        // Paper start - rewrite chunk system
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder.ChunkCompletion lastCompletion = ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)(Object)this).moonrise$getRealChunkHolder().getLastChunkCompletion();
@@ -26494,8 +26005,8 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
      public ChunkStatus getLatestStatus() {
 -        for (int i = CHUNK_STATUSES.size() - 1; i >= 0; i--) {
 -            ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
--            ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(chunkStatus);
--            if (chunkAccess != null) {
+-            ChunkAccess chunkIfPresentUnchecked = this.getChunkIfPresentUnchecked(chunkStatus);
+-            if (chunkIfPresentUnchecked != null) {
 -                return chunkStatus;
 -            }
 -        }
@@ -26508,7 +26019,7 @@ index 65206fdfa5b94eaca139e433b4865c16b16641f3..bf4463bcb5dc439ac5a3fa08dd60845a
      }
  }
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa7130c20f 100644
+index 796b5f8541b0cf84482ab2b5a60adde544d43593..7ce641cc34ef95e248e62ebf0b7fdfb8b2924256 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -26517,10 +26028,10 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
  
 -public class ServerChunkCache extends ChunkSource {
 +public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache { // Paper - rewrite chunk system
- 
      private static final Logger LOGGER = LogUtils.getLogger();
      private final DistanceManager distanceManager;
-@@ -81,6 +81,107 @@ public class ServerChunkCache extends ChunkSource {
+     private final ServerLevel level;
+@@ -80,6 +80,107 @@ public class ServerChunkCache extends ChunkSource {
      }
      long chunkFutureAwaitCounter;
      // Paper end
@@ -26626,9 +26137,9 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 +    // Paper end - chunk tick iteration optimisations
 +
  
-     public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
-         this.level = world;
-@@ -112,13 +213,7 @@ public class ServerChunkCache extends ChunkSource {
+     public ServerChunkCache(
+         ServerLevel level,
+@@ -138,13 +239,7 @@ public class ServerChunkCache extends ChunkSource {
      }
      // CraftBukkit end
      // Paper start
@@ -26643,61 +26154,54 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
  
      @Nullable
      public ChunkAccess getChunkAtImmediately(int x, int z) {
-@@ -189,59 +284,42 @@ public class ServerChunkCache extends ChunkSource {
+@@ -215,51 +310,42 @@ public class ServerChunkCache extends ChunkSource {
      @Nullable
      @Override
-     public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) {
+     public ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
 -        if (Thread.currentThread() != this.mainThread) {
--            return (ChunkAccess) CompletableFuture.supplyAsync(() -> {
--                return this.getChunk(x, z, leastStatus, create);
--            }, this.mainThreadProcessor).join();
+-            return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(x, z, chunkStatus, requireChunk), this.mainThreadProcessor).join();
 -        } else {
 -            // Paper start - Perf: Optimise getChunkAt calls for loaded chunks
--            LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z);
+-            LevelChunk ifLoaded = this.getChunkAtIfCachedImmediately(x, z);
 -            if (ifLoaded != null) {
 -                return ifLoaded;
 -            }
 -            // Paper end - Perf: Optimise getChunkAt calls for loaded chunks
--            ProfilerFiller gameprofilerfiller = Profiler.get();
-+        // Paper start - rewrite chunk system
-+        if (leastStatus == ChunkStatus.FULL) {
-+            final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z));
- 
--            gameprofilerfiller.incrementCounter("getChunk");
--            long k = ChunkPos.asLong(x, z);
+-            ProfilerFiller profilerFiller = Profiler.get();
+-            profilerFiller.incrementCounter("getChunk");
+-            long packedChunkPos = ChunkPos.asLong(x, z);
 -
--            for (int l = 0; l < 4; ++l) {
--                if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) {
--                    ChunkAccess ichunkaccess = this.lastChunk[l];
--
--                    if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
--                        return ichunkaccess;
+-            for (int i = 0; i < 4; i++) {
+-                if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) {
+-                    ChunkAccess chunkAccess = this.lastChunk[i];
+-                    if (chunkAccess != null) {  // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
+-                        return chunkAccess;
 -                    }
 -                }
+-            }
++        // Paper start - rewrite chunk system
++        if (chunkStatus == ChunkStatus.FULL) {
++            final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z));
+ 
+-            profilerFiller.incrementCounter("getChunkCacheMiss");
+-            CompletableFuture<ChunkResult<ChunkAccess>> chunkFutureMainThread = this.getChunkFutureMainThread(x, z, chunkStatus, requireChunk);
+-            this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone);
+-            // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
+-            ChunkResult<ChunkAccess> chunkResult = chunkFutureMainThread.join();
+-            ChunkAccess chunkAccess1 = chunkResult.orElse(null);
+-            if (chunkAccess1 == null && requireChunk) {
+-                throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkResult.getError()));
+-            } else {
+-                this.storeInCache(packedChunkPos, chunkAccess1, chunkStatus);
+-                return chunkAccess1;
 +            if (ret != null) {
 +                return ret;
              }
- 
--            gameprofilerfiller.incrementCounter("getChunkCacheMiss");
--            CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create);
--            ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
--
--            Objects.requireNonNull(completablefuture);
--            chunkproviderserver_b.managedBlock(completablefuture::isDone);
--            // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
--            ChunkResult<ChunkAccess> chunkresult = (ChunkResult) completablefuture.join();
--            ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
--
--            if (ichunkaccess1 == null && create) {
--                throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError()));
--            } else {
--                this.storeInCache(k, ichunkaccess1, leastStatus);
--                return ichunkaccess1;
--            }
-+            return create ? this.getChunkFallback(x, z, leastStatus, create) : null;
++
++            return requireChunk ? this.getChunkFallback(x, z, chunkStatus, requireChunk) : null;
          }
 +
-+        return this.getChunkFallback(x, z, leastStatus, create);
++        return this.getChunkFallback(x, z, chunkStatus, requireChunk);
 +        // Paper end - rewrite chunk system
      }
  
@@ -26707,7 +26211,7 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 -        if (Thread.currentThread() != this.mainThread) {
 -            return null;
 -        } else {
--            return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
+-            return this.getChunkAtIfCachedImmediately(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks
 +        // Paper start - rewrite chunk system
 +        final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
 +        if (!ca.spottedleaf.moonrise.common.PlatformHooks.get().hasCurrentlyLoadingChunk()) {
@@ -26716,64 +26220,64 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 +
 +        if (ret != null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) {
 +            return ret;
-+        }
+         }
 +
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler()
 +            .chunkHolderManager.getChunkHolder(chunkX, chunkZ);
 +        if (holder == null) {
 +            return ret;
-         }
++        }
 +
 +        return ca.spottedleaf.moonrise.common.PlatformHooks.get().getCurrentlyLoadingChunk(holder.vanillaChunkHolder);
 +        // Paper end - rewrite chunk system
      }
  
      private void clearCache() {
-@@ -272,56 +350,59 @@ public class ServerChunkCache extends ChunkSource {
+@@ -285,54 +371,59 @@ public class ServerChunkCache extends ChunkSource {
      }
  
-     private CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
--        ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ);
--        long k = chunkcoordintpair.toLong();
--        int l = ChunkLevel.byStatus(leastStatus);
--        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
--
+     private CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
+-        ChunkPos chunkPos = new ChunkPos(x, z);
+-        long packedChunkPos = chunkPos.toLong();
+-        int i = ChunkLevel.byStatus(chunkStatus);
+-        ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
 -        // CraftBukkit start - don't add new ticket for currently unloading chunk
 -        boolean currentlyUnloading = false;
--        if (playerchunk != null) {
--            FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
--            FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
+-        if (visibleChunkIfPresent != null) {
+-            FullChunkStatus oldChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.oldTicketLevel);
+-            FullChunkStatus currentChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.getTicketLevel());
 -            currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
 -        }
--        if (create && !currentlyUnloading) {
--            // CraftBukkit end
--            this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
--            if (this.chunkAbsent(playerchunk, l)) {
--                ProfilerFiller gameprofilerfiller = Profiler.get();
--
--                gameprofilerfiller.push("chunkLoad");
+-        if (requireChunk && !currentlyUnloading) {
+-        // CraftBukkit end
+-            this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos);
+-            if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+-                ProfilerFiller profilerFiller = Profiler.get();
+-                profilerFiller.push("chunkLoad");
 -                this.runDistanceManagerUpdates();
--                playerchunk = this.getVisibleChunkIfPresent(k);
--                gameprofilerfiller.pop();
--                if (this.chunkAbsent(playerchunk, l)) {
--                    throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
+-                visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
+-                profilerFiller.pop();
+-                if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+-                    throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
 -                }
 -            }
 +        // Paper start - rewrite chunk system
-+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main");
++        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, x, z, "Scheduling chunk load off-main");
 +
-+        final int minLevel = ChunkLevel.byStatus(leastStatus);
-+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
++        final int minLevel = ChunkLevel.byStatus(chunkStatus);
++        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(x, z);
 +
-+        final boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL));
++        final boolean needsFullScheduling = chunkStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL));
 +
-+        if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) {
++        if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !requireChunk) {
 +            return ChunkHolder.UNLOADED_CHUNK_FUTURE;
          }
  
--        return this.chunkAbsent(playerchunk, l) ? GenerationChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.scheduleChunkGenerationTask(leastStatus, this.chunkMap);
+-        return this.chunkAbsent(visibleChunkIfPresent, i)
+-            ? GenerationChunkHolder.UNLOADED_CHUNK_FUTURE
+-            : visibleChunkIfPresent.scheduleChunkGenerationTask(chunkStatus, this.chunkMap);
 -    }
-+        final ChunkAccess ifPresent = chunkHolder == null ? null : chunkHolder.getChunkIfPresent(leastStatus);
++        final ChunkAccess ifPresent = chunkHolder == null ? null : chunkHolder.getChunkIfPresent(chunkStatus);
 +        if (needsFullScheduling || ifPresent == null) {
 +            // schedule
 +            final CompletableFuture<ChunkResult<ChunkAccess>> ret = new CompletableFuture<>();
@@ -26785,10 +26289,10 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 +                }
 +            };
  
--    private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
--        return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks
+-    private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) {
+-        return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks
 +            ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad(
-+                chunkX, chunkZ, leastStatus, true,
++                x, z, chunkStatus, true,
 +                ca.spottedleaf.concurrentutil.util.Priority.HIGHER,
 +                complete
 +            );
@@ -26803,20 +26307,18 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
  
      @Override
      public boolean hasChunk(int x, int z) {
--        ChunkHolder playerchunk = this.getVisibleChunkIfPresent((new ChunkPos(x, z)).toLong());
--        int k = ChunkLevel.byStatus(ChunkStatus.FULL);
--
--        return !this.chunkAbsent(playerchunk, k);
+-        ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(new ChunkPos(x, z).toLong());
+-        int i = ChunkLevel.byStatus(ChunkStatus.FULL);
+-        return !this.chunkAbsent(visibleChunkIfPresent, i);
 +        return this.getChunkNow(x, z) != null; // Paper - rewrite chunk system
      }
  
      @Nullable
      @Override
      public LightChunk getChunkForLighting(int chunkX, int chunkZ) {
--        long k = ChunkPos.asLong(chunkX, chunkZ);
--        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
--
--        return playerchunk == null ? null : playerchunk.getChunkIfPresentUnchecked(ChunkStatus.INITIALIZE_LIGHT.getParent());
+-        long packedChunkPos = ChunkPos.asLong(chunkX, chunkZ);
+-        ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
+-        return visibleChunkIfPresent == null ? null : visibleChunkIfPresent.getChunkIfPresentUnchecked(ChunkStatus.INITIALIZE_LIGHT.getParent());
 +        // Paper start - rewrite chunk system
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
 +        if (newChunkHolder == null) {
@@ -26827,13 +26329,12 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
      }
  
      @Override
-@@ -334,30 +415,18 @@ public class ServerChunkCache extends ChunkSource {
+@@ -345,28 +436,18 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      public boolean runDistanceManagerUpdates() { // Paper - public
 -        boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
 -        boolean flag1 = this.chunkMap.promoteChunkMap();
--
 -        this.chunkMap.runGenerationTasks();
 -        if (!flag && !flag1) {
 -            return false;
@@ -26844,16 +26345,15 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 +        return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(); // Paper - rewrite chunk system
      }
  
-     public boolean isPositionTicking(long pos) {
--        if (!this.level.shouldTickBlocksAt(pos)) {
+     public boolean isPositionTicking(long chunkPos) {
+-        if (!this.level.shouldTickBlocksAt(chunkPos)) {
 -            return false;
 -        } else {
--            ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
--
--            return playerchunk == null ? false : ((ChunkResult) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).isSuccess();
+-            ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(chunkPos);
+-            return visibleChunkIfPresent != null && visibleChunkIfPresent.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).isSuccess();
 -        }
 +        // Paper start - rewrite chunk system
-+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
++        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
 +        return newChunkHolder != null && newChunkHolder.isTickingReady();
 +        // Paper end - rewrite chunk system
      }
@@ -26864,7 +26364,7 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
          this.chunkMap.saveAllChunks(flush);
      }
  
-@@ -368,17 +437,15 @@ public class ServerChunkCache extends ChunkSource {
+@@ -377,17 +458,15 @@ public class ServerChunkCache extends ChunkSource {
      }
  
      public void close(boolean save) throws IOException {
@@ -26885,50 +26385,50 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
          ProfilerFiller gameprofilerfiller = Profiler.get();
  
          gameprofilerfiller.push("purge");
-@@ -403,6 +470,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -411,6 +490,7 @@ public class ServerChunkCache extends ChunkSource {
          this.runDistanceManagerUpdates();
-         gameprofilerfiller.popPush("chunks");
+         profilerFiller.popPush("chunks");
          if (tickChunks) {
 +            ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getPlayerChunkLoader().tick(); // Paper - rewrite chunk system
              this.tickChunks();
              this.chunkMap.tick();
          }
-@@ -429,7 +497,10 @@ public class ServerChunkCache extends ChunkSource {
-                     gameprofilerfiller.push("filteringTickingChunks");
+@@ -435,7 +515,10 @@ public class ServerChunkCache extends ChunkSource {
+                     profilerFiller.push("filteringTickingChunks");
                      this.collectTickingChunks(list);
-                     gameprofilerfiller.popPush("shuffleChunks");
+                     profilerFiller.popPush("shuffleChunks");
 -                    Util.shuffle(list, this.level.random);
 +                    // Paper start - chunk tick iteration optimisation
 +                    this.shuffleRandom.setSeed(this.level.random.nextLong());
 +                    Util.shuffle(list, this.shuffleRandom);
 +                    // Paper end - chunk tick iteration optimisation
-                     this.tickChunks(gameprofilerfiller, j, list);
-                     gameprofilerfiller.pop();
+                     this.tickChunks(profilerFiller, l, list);
+                     profilerFiller.pop();
                  } finally {
-@@ -448,7 +519,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -452,7 +535,7 @@ public class ServerChunkCache extends ChunkSource {
+         profiler.push("broadcast");
  
-         while (iterator.hasNext()) {
-             ChunkHolder playerchunk = (ChunkHolder) iterator.next();
--            LevelChunk chunk = playerchunk.getTickingChunk();
-+            LevelChunk chunk = playerchunk.getChunkToSend(); // Paper - rewrite chunk system
- 
-             if (chunk != null) {
-                 playerchunk.broadcastChanges(chunk);
-@@ -460,14 +531,26 @@ public class ServerChunkCache extends ChunkSource {
+         for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) {
+-            LevelChunk tickingChunk = chunkHolder.getTickingChunk();
++            LevelChunk tickingChunk = chunkHolder.getChunkToSend(); // Paper - rewrite chunk system
+             if (tickingChunk != null) {
+                 chunkHolder.broadcastChanges(tickingChunk);
+             }
+@@ -463,12 +546,26 @@ public class ServerChunkCache extends ChunkSource {
      }
  
-     private void collectTickingChunks(List<LevelChunk> chunks) {
--        this.chunkMap.forEachSpawnCandidateChunk((playerchunk) -> {
--            LevelChunk chunk = playerchunk.getTickingChunk();
+     private void collectTickingChunks(List<LevelChunk> output) {
+-        this.chunkMap.forEachSpawnCandidateChunk(chunk -> {
+-            LevelChunk tickingChunk = chunk.getTickingChunk();
+-            if (tickingChunk != null && this.level.isNaturalSpawningAllowed(chunk.getPos())) {
+-                output.add(tickingChunk);
 +        // Paper start - chunk tick iteration optimisation
 +        final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> tickingChunks =
 +            ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
 +
 +        final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
 +        final int size = tickingChunks.size();
- 
--            if (chunk != null && this.level.isNaturalSpawningAllowed(playerchunk.getPos())) {
--                chunks.add(chunk);
++
 +        final ChunkMap chunkMap = this.chunkMap;
 +
 +        for (int i = 0; i < size; ++i) {
@@ -26938,43 +26438,42 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
 +            if (!this.isChunkNearPlayer(chunkMap, levelChunk.getPos(), levelChunk)) {
 +                continue;
              }
- 
 -        });
-+            chunks.add(levelChunk);
++
++            output.add(levelChunk);
 +        }
 +        // Paper end - chunk tick iteration optimisation
      }
  
-     private void tickChunks(ProfilerFiller profiler, long timeDelta, List<LevelChunk> chunks) {
-@@ -508,7 +591,7 @@ public class ServerChunkCache extends ChunkSource {
-                 NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1);
+     private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
+@@ -504,7 +601,7 @@ public class ServerChunkCache extends ChunkSource {
+                 NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, filteredSpawningCategories);
              }
  
--            if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
+-            if (this.level.shouldTickBlocksAt(pos.toLong())) {
 +            if (true) { // Paper - rewrite chunk system
-                 this.level.tickChunk(chunk, k);
+                 this.level.tickChunk(levelChunk, _int);
              }
          }
-@@ -521,11 +604,13 @@ public class ServerChunkCache extends ChunkSource {
+@@ -516,10 +613,13 @@ public class ServerChunkCache extends ChunkSource {
      }
  
-     private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {
--        ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
--
--        if (playerchunk != null) {
--            ((ChunkResult) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).ifSuccess(chunkConsumer);
+     private void getFullChunk(long chunkPos, Consumer<LevelChunk> fullChunkGetter) {
+-        ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(chunkPos);
+-        if (visibleChunkIfPresent != null) {
+-            visibleChunkIfPresent.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).ifSuccess(fullChunkGetter);
 +        // Paper start - rewrite chunk system
 +        // note: bypass currentlyLoaded from getChunkNow
-+        final LevelChunk fullChunk = this.fullChunks.get(pos);
++        final LevelChunk fullChunk = this.fullChunks.get(chunkPos);
 +        if (fullChunk != null) {
-+            chunkConsumer.accept(fullChunk);
++            fullChunkGetter.accept(fullChunk);
          }
 +        // Paper end - rewrite chunk system
- 
      }
  
-@@ -619,6 +704,12 @@ public class ServerChunkCache extends ChunkSource {
-         this.chunkMap.setServerViewDistance(watchDistance);
+     @Override
+@@ -607,6 +707,12 @@ public class ServerChunkCache extends ChunkSource {
+         this.chunkMap.setServerViewDistance(viewDistance);
      }
  
 +    // Paper start - rewrite chunk system
@@ -26986,11 +26485,20 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
      public void setSimulationDistance(int simulationDistance) {
          this.distanceManager.updateSimulationDistance(simulationDistance);
      }
-@@ -710,21 +801,19 @@ public class ServerChunkCache extends ChunkSource {
+@@ -654,7 +760,7 @@ public class ServerChunkCache extends ChunkSource {
+         }
+     }
+ 
+-    record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
++    public record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) { // Paper - public
+     }
+ 
+     public final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
+@@ -695,18 +801,14 @@ public class ServerChunkCache extends ChunkSource {
+ 
          @Override
-         // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
          public boolean pollTask() {
--        try {
+-            try { // CraftBukkit - process pending Chunk loadCallback() and unloadCallback() after each run task
 -            if (ServerChunkCache.this.runDistanceManagerUpdates()) {
 +            // Paper start - rewrite chunk system
 +            final ServerChunkCache serverChunkCache = ServerChunkCache.this;
@@ -26999,26 +26507,22 @@ index 6a2af3cd3aebe525a5ff41a801929547d59b8fec..d7382fc1498a33db909c343d8d07c5aa
              } else {
 -                ServerChunkCache.this.lightEngine.tryScheduleUpdate();
 -                return super.pollTask();
+-            }
+-            // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+-            } finally {
+-                ServerChunkCache.this.chunkMap.callbackExecutor.run();
 +                return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask();
              }
--        } finally {
--            ServerChunkCache.this.chunkMap.callbackExecutor.run();
--        }
+-            // CraftBukkit end - process pending Chunk loadCallback() and unloadCallback() after each run task
 +            // Paper end - rewrite chunk system
-         // CraftBukkit end
          }
      }
- 
--    private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
-+    public static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) { // Paper - rewrite chunk system - public
- 
-     }
  }
 diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
-index d5bc702f2676b1b7a32c8f3a4a349fc2710ee825..301e8d6599d200cb0f1328f0e386af2f9a619939 100644
+index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..d38ddfd7afba189a489701e62b5fa6db8df87a9d 100644
 --- a/net/minecraft/server/level/ServerEntity.java
 +++ b/net/minecraft/server/level/ServerEntity.java
-@@ -101,6 +101,11 @@ public class ServerEntity {
+@@ -91,6 +91,11 @@ public class ServerEntity {
      }
  
      public void sendChanges() {
@@ -27027,24 +26531,24 @@ index d5bc702f2676b1b7a32c8f3a4a349fc2710ee825..301e8d6599d200cb0f1328f0e386af2f
 +            this.teleportDelay = 9999;
 +        }
 +        // Paper end - optimise collisions
-         List<Entity> list = this.entity.getPassengers();
- 
-         if (!list.equals(this.lastPassengers)) {
+         List<Entity> passengers = this.entity.getPassengers();
+         if (!passengers.equals(this.lastPassengers)) {
+             this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905d8325618 100644
+index 08ee3583a07b6e4e877e530d422e386597bce6de..3927f5603d18f1c42eb29d916f287b6ef3f39612 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -186,7 +186,7 @@ import org.bukkit.event.weather.LightningStrikeEvent;
- import org.bukkit.event.world.TimeSkipEvent;
- // CraftBukkit end
+@@ -170,7 +170,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
+ import net.minecraft.world.ticks.LevelTicks;
+ import org.slf4j.Logger;
  
 -public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel {
-+public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel {  // Paper - rewrite chunk system // Paper - chunk tick iteration
- 
++public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration
      public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0);
      public static final IntProvider RAIN_DELAY = UniformInt.of(12000, 180000);
-@@ -202,7 +202,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-     public final PrimaryLevelData serverLevelData; // CraftBukkit - type
+     public static final IntProvider RAIN_DURATION = UniformInt.of(12000, 24000);
+@@ -185,7 +185,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+     public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type
      private int lastSpawnChunkRadius;
      final EntityTickList entityTickList = new EntityTickList();
 -    public final PersistentEntitySectionManager<Entity> entityManager;
@@ -27052,7 +26556,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
      private final GameEventDispatcher gameEventDispatcher;
      public boolean noSave;
      private final SleepStatus sleepStatus;
-@@ -273,12 +273,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -256,12 +256,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority,
                                               java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
@@ -27066,7 +26570,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
          int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3;
          int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3;
  
-@@ -297,32 +292,159 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -280,32 +275,159 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ,
                                   ca.spottedleaf.concurrentutil.util.Priority priority,
                                   java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
@@ -27114,9 +26618,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder.ChunkCompletion lastCompletion = newChunkHolder.getLastChunkCompletion();
 +        return lastCompletion == null ? null : lastCompletion.chunk();
 +    }
- 
--        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
--        int[] loadedChunks = new int[1];
++
 +    @Override
 +    public final ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus leastStatus) {
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
@@ -27135,14 +26637,12 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +    public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) {
 +        return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status);
 +    }
- 
--        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
++
 +    @Override
 +    public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler moonrise$getChunkTaskScheduler() {
 +        return this.chunkTaskScheduler;
 +    }
- 
--        java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
++
 +    @Override
 +    public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController  moonrise$getChunkDataController() {
 +        return this.chunkDataController;
@@ -27207,14 +26707,18 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +                                               final java.util.function.Consumer<java.util.List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = this.moonrise$getChunkTaskScheduler();
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager chunkHolderManager = chunkTaskScheduler.chunkHolderManager;
-+
+ 
+-        int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
+-        int[] loadedChunks = new int[1];
 +        final int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1);
 +        final java.util.concurrent.atomic.AtomicInteger loadedChunks = new java.util.concurrent.atomic.AtomicInteger();
 +        final Long holderIdentifier = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getNextChunkLoadId();
 +        final int ticketLevel = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getTicketLevel(chunkStatus);
-+
+ 
+-        Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++);
 +        final List<ChunkAccess> ret = new ArrayList<>(requiredChunks);
-+
+ 
+-        java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
 +        final java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (final ChunkAccess chunk) -> {
              if (chunk != null) {
 -                int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel());
@@ -27242,14 +26746,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
                      }
                  }
              }
-@@ -330,22 +452,137 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
- 
-         for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
-             for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
--                ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad(
--                    this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer
--                );
-+                chunkTaskScheduler.scheduleChunkLoad(cx, cz, chunkStatus, true, priority, consumer);
+@@ -319,16 +441,133 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              }
          }
      }
@@ -27263,8 +26760,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 -        return player != null && player.level() == this ? player : null;
 +    public final ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder() {
 +        return this.viewDistanceHolder;
-     }
--    // Paper end - optimise getPlayerByUUID
++    }
 +
 +    @Override
 +    public final long moonrise$getLastMidTickFailure() {
@@ -27279,7 +26775,8 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +    @Override
 +    public final ca.spottedleaf.moonrise.common.misc.NearbyPlayers moonrise$getNearbyPlayers() {
 +        return this.nearbyPlayers;
-+    }
+     }
+-    // Paper end - optimise getPlayerByUUID
 +
 +    @Override
 +    public final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> moonrise$getLoadedChunks() {
@@ -27388,38 +26885,49 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +    }
 +    // Paper end - chunk tick iteration
  
-     // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
-     public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
-@@ -379,14 +616,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         DataFixer datafixer = minecraftserver.getFixerUpper();
-         EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver);
- 
--        this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage);
+     public ServerLevel(
+         MinecraftServer server,
+@@ -376,18 +615,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         // CraftBukkit end
+         boolean flag = server.forceSynchronousWrites();
+         DataFixer fixerUpper = server.getFixerUpper();
+-        EntityPersistentStorage<Entity> entityPersistentStorage = new EntityStorage(
+-            new SimpleRegionStorage(
+-                new RegionStorageInfo(levelStorageAccess.getLevelId(), dimension, "entities"),
+-                levelStorageAccess.getDimensionPath(dimension).resolve("entities"),
+-                fixerUpper,
+-                flag,
+-                DataFixTypes.ENTITY_CHUNK
+-            ),
+-            this,
+-            server
+-        );
+-        this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entityPersistentStorage);
 +        // Paper - rewrite chunk system
-         StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
-         int j = this.spigotConfig.viewDistance; // Spigot
-         int k = this.spigotConfig.simulationDistance; // Spigot
--        PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
-+        // Paper - rewrite chunk system
- 
--        Objects.requireNonNull(this.entityManager);
--        this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> {
-+        this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system
-             return minecraftserver.overworld().getDataStorage();
-         });
+         this.chunkSource = new ServerChunkCache(
+             this,
+             levelStorageAccess,
+@@ -399,7 +627,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+             this.spigotConfig.simulationDistance, // Spigot
+             flag,
+             progressListener,
+-            this.entityManager::updateChunkStatus,
++            null, // Paper - rewrite chunk system
+             () -> server.overworld().getDataStorage()
+         );
          this.chunkSource.getGeneratorState().ensureStructuresGenerated();
-@@ -414,6 +650,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
-             return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
-         });
+@@ -437,6 +665,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         this.randomSequences = Objects.requireNonNullElseGet(
+             randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(seed), "random_sequences")
+         );
 +        // Paper start - rewrite chunk system
 +        this.moonrise$setEntityLookup(new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup((ServerLevel)(Object)this, ((ServerLevel)(Object)this).new EntityCallbacks()));
 +        this.chunkTaskScheduler = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler((ServerLevel)(Object)this);
 +        this.entityDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController(
 +            new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController.EntityRegionFileStorage(
-+                new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"),
-+                convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"),
-+                minecraftserver.forceSynchronousWrites()
++                new RegionStorageInfo(levelStorageAccess.getLevelId(), dimension, "entities"),
++                levelStorageAccess.getDimensionPath(dimension).resolve("entities"),
++                server.forceSynchronousWrites()
 +            ),
 +            this.chunkTaskScheduler
 +        );
@@ -27429,22 +26937,23 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
          this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
      }
  
-@@ -536,7 +786,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-                         gameprofilerfiller.push("checkDespawn");
-                         entity.checkDespawn();
-                         gameprofilerfiller.pop();
--                        if (entity instanceof ServerPlayer || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
-+                        if (true) { // Paper - rewrite chunk system
-                             Entity entity1 = entity.getVehicle();
- 
-                             if (entity1 != null) {
-@@ -559,13 +809,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -560,8 +802,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+                                 profilerFiller.push("checkDespawn");
+                                 entity.checkDespawn();
+                                 profilerFiller.pop();
+-                                if (entity instanceof ServerPlayer
+-                                    || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
++                                if (true) { // Paper - rewrite chunk system
+                                     Entity vehicle = entity.getVehicle();
+                                     if (vehicle != null) {
+                                         if (!vehicle.isRemoved() && vehicle.hasPassenger(entity)) {
+@@ -584,13 +825,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          }
  
-         gameprofilerfiller.push("entityManagement");
+         profilerFiller.push("entityManagement");
 -        this.entityManager.tick();
 +        // Paper - rewrite chunk system
-         gameprofilerfiller.pop();
+         profilerFiller.pop();
      }
  
      @Override
@@ -27457,8 +26966,8 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
      }
  
      protected void tickTime() {
-@@ -605,7 +858,60 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         });
+@@ -621,14 +865,67 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
      }
  
 +    // Paper start - optimise random ticking
@@ -27515,128 +27024,125 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +
      public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
 +        final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking
-         ChunkPos chunkcoordintpair = chunk.getPos();
-         boolean flag = this.isRaining();
-         int j = chunkcoordintpair.getMinBlockX();
-@@ -613,7 +919,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-         gameprofilerfiller.push("thunder");
--        if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
-+        if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking
-             BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
- 
-             if (this.isRainingAt(blockposition)) {
-@@ -645,7 +951,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         ChunkPos pos = chunk.getPos();
+         boolean isRaining = this.isRaining();
+         int minBlockX = pos.getMinBlockX();
+         int minBlockZ = pos.getMinBlockZ();
+         ProfilerFiller profilerFiller = Profiler.get();
+         profilerFiller.push("thunder");
+-        if (!this.paperConfig().environment.disableThunder && isRaining && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
++        if (!this.paperConfig().environment.disableThunder && isRaining && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking
+             BlockPos blockPos = this.findLightningTargetAround(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
+             if (this.isRainingAt(blockPos)) {
+                 DifficultyInstance currentDifficultyAt = this.getCurrentDifficultyAt(blockPos);
+@@ -658,7 +955,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
          if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
-         for (int l = 0; l < randomTickSpeed; ++l) {
+         for (int i = 0; i < randomTickSpeed; i++) {
 -            if (this.random.nextInt(48) == 0) {
-+            if (simpleRandom.nextInt(48) == 0) { // Paper - optimise random ticking
-                 this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15));
++            if (simpleRandom.nextInt(48) == 0) {  // Paper - optimise random ticking
+                 this.tickPrecipitation(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
              }
          }
-@@ -653,35 +959,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -666,33 +963,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
-         gameprofilerfiller.popPush("tickBlocks");
+         profilerFiller.popPush("tickBlocks");
          if (randomTickSpeed > 0) {
--            LevelChunkSection[] achunksection = chunk.getSections();
+-            LevelChunkSection[] sections = chunk.getSections();
 -
--            for (int i1 = 0; i1 < achunksection.length; ++i1) {
--                LevelChunkSection chunksection = achunksection[i1];
+-            for (int i1 = 0; i1 < sections.length; i1++) {
+-                LevelChunkSection levelChunkSection = sections[i1];
+-                if (levelChunkSection.isRandomlyTicking()) {
+-                    int sectionYFromSectionIndex = chunk.getSectionYFromSectionIndex(i1);
+-                    int blockPosCoord = SectionPos.sectionToBlockCoord(sectionYFromSectionIndex);
 -
--                if (chunksection.isRandomlyTicking()) {
--                    int j1 = chunk.getSectionYFromSectionIndex(i1);
--                    int k1 = SectionPos.sectionToBlockCoord(j1);
--
--                    for (int l1 = 0; l1 < randomTickSpeed; ++l1) {
--                        BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15);
--
--                        gameprofilerfiller.push("randomTick");
--                        BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k);
--
--                        if (iblockdata.isRandomlyTicking()) {
--                            iblockdata.randomTick(this, blockposition1, this.random);
+-                    for (int i2 = 0; i2 < randomTickSpeed; i2++) {
+-                        BlockPos blockRandomPos = this.getBlockRandomPos(minBlockX, blockPosCoord, minBlockZ, 15);
+-                        profilerFiller.push("randomTick");
+-                        BlockState blockState = levelChunkSection.getBlockState(
+-                            blockRandomPos.getX() - minBlockX, blockRandomPos.getY() - blockPosCoord, blockRandomPos.getZ() - minBlockZ
+-                        );
+-                        if (blockState.isRandomlyTicking()) {
+-                            blockState.randomTick(this, blockRandomPos, this.random);
 -                        }
 -
--                        FluidState fluid = iblockdata.getFluidState();
--
--                        if (fluid.isRandomlyTicking()) {
--                            fluid.randomTick(this, blockposition1, this.random);
+-                        FluidState fluidState = blockState.getFluidState();
+-                        if (fluidState.isRandomlyTicking()) {
+-                            fluidState.randomTick(this, blockRandomPos, this.random);
 -                        }
 -
--                        gameprofilerfiller.pop();
+-                        profilerFiller.pop();
 -                    }
 -                }
 -            }
 +            this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking
          }
  
-         gameprofilerfiller.pop();
-@@ -954,6 +1232,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         if (fluid1.is(fluid)) {
-             fluid1.tick(this, pos, iblockdata);
+         profilerFiller.pop();
+@@ -946,6 +1217,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         if (fluidState.is(fluid)) {
+             fluidState.tick(this, pos, blockState);
          }
 +        // Paper start - rewrite chunk system
 +        if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
 +            ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
 +        }
 +        // Paper end - rewrite chunk system
- 
++
      }
  
-@@ -963,6 +1246,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         if (iblockdata.is(block)) {
-             iblockdata.tick(this, pos, this.random);
+     private void tickBlock(BlockPos pos, Block block) {
+@@ -953,6 +1230,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         if (blockState.is(block)) {
+             blockState.tick(this, pos, this.random);
          }
 +        // Paper start - rewrite chunk system
 +        if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
 +            ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
 +        }
 +        // Paper end - rewrite chunk system
- 
++
      }
  
-@@ -1041,6 +1329,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+     public void tickNonPassenger(Entity entity) {
+@@ -1007,6 +1290,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      }
  
-     public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) {
+     public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
 +        // Paper start - add close param
-+        this.save(progressListener, flush, savingDisabled, false);
++        this.save(progress, flush, skipSave, false);
 +    }
-+    public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled, boolean close) {
++    public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave, boolean close) {
 +        // Paper end - add close param
-         ServerChunkCache chunkproviderserver = this.getChunkSource();
- 
-         if (!savingDisabled) {
-@@ -1054,14 +1347,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-                 progressListener.progressStage(Component.translatable("menu.savingChunks"));
+         ServerChunkCache chunkSource = this.getChunkSource();
+         if (!skipSave) {
+             org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit
+@@ -1019,13 +1307,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+                 progress.progressStage(Component.translatable("menu.savingChunks"));
              }
  
--            chunkproviderserver.save(flush);
+-            chunkSource.save(flush);
 -            if (flush) {
 -                this.entityManager.saveAll();
 -            } else {
 -                this.entityManager.autoSave();
--            }
-+            if (!close) { chunkproviderserver.save(flush); } // Paper - add close param
++            if (!close) { chunkSource.save(flush); } // Paper - add close param
 +            // Paper - rewrite chunk system
- 
-         }
++        }
 +        // Paper start - add close param
 +        if (close) {
 +            try {
-+                chunkproviderserver.close(!savingDisabled);
++                chunkSource.close(!skipSave);
 +            } catch (IOException never) {
 +                throw new RuntimeException(never);
-+            }
-+        }
+             }
+         }
 +        // Paper end - add close param
  
          // CraftBukkit start - moved from MinecraftServer.saveChunks
          ServerLevel worldserver1 = this;
-@@ -1201,7 +1499,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-             this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED);
+@@ -1156,7 +1449,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+             this.removePlayerImmediately((ServerPlayer)entity, Entity.RemovalReason.DISCARDED);
          }
  
 -        this.entityManager.addNewEntity(player);
@@ -27644,7 +27150,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
      }
  
      // CraftBukkit start
-@@ -1232,7 +1530,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1187,7 +1480,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              }
              // CraftBukkit end
  
@@ -27653,56 +27159,52 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
          }
      }
  
-@@ -1243,11 +1541,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1198,7 +1491,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
          // CraftBukkit end
--        Stream<UUID> stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error
--        PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
--
--        Objects.requireNonNull(this.entityManager);
--        if (stream.anyMatch(persistententitysectionmanager::isLoaded)) {
+-        if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) {
 +        if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.moonrise$getEntityLookup()::hasEntity)) { // Paper - rewrite chunk system
              return false;
          } else {
              this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
-@@ -1924,7 +2218,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1933,7 +2226,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                  }
              }
  
--            bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats()));
-+            bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.moonrise$getEntityLookup().getDebugInfo())); // Paper - rewrite chunk system
-             bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
-             bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
-             bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
-@@ -1973,7 +2267,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1);
+-            bufferedWriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats()));
++            bufferedWriter.write(String.format(Locale.ROOT, "entities: %s\n", this.moonrise$getEntityLookup().getDebugInfo()));  // Paper - rewrite chunk system
+             bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
+             bufferedWriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
+             bufferedWriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
+@@ -1951,13 +2244,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         Path path1 = path.resolve("chunks.csv");
  
-         try {
--            playerchunkmap.dumpChunks(bufferedwriter2);
-+            //playerchunkmap.dumpChunks(bufferedwriter2); // Paper - rewrite chunk system
-         } catch (Throwable throwable4) {
-             if (bufferedwriter2 != null) {
-                 try {
-@@ -1994,7 +2288,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2);
+         try (Writer bufferedWriter2 = Files.newBufferedWriter(path1)) {
+-            chunkMap.dumpChunks(bufferedWriter2);
++            //chunkMap.dumpChunks(bufferedWriter2); // Paper - rewrite chunk system
+         }
  
-         try {
--            this.entityManager.dumpSections(bufferedwriter3);
-+            //this.entityManager.dumpSections(bufferedwriter3); // Paper - rewrite chunk system
-         } catch (Throwable throwable6) {
-             if (bufferedwriter3 != null) {
-                 try {
-@@ -2136,7 +2430,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         Path path2 = path.resolve("entity_chunks.csv");
  
-     @VisibleForTesting
-     public String getWatchdogStats() {
--        return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityManager.gatherStats(), ServerLevel.getTypeCount(this.entityManager.getEntityGetter().getAll(), (entity) -> {
-+        return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.moonrise$getEntityLookup().getDebugInfo(), ServerLevel.getTypeCount(this.moonrise$getEntityLookup().getAll(), (entity) -> { // Paper - rewrite chunk system
-             return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
-         }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats());
-     }
-@@ -2166,15 +2460,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         try (Writer bufferedWriter3 = Files.newBufferedWriter(path2)) {
+-            this.entityManager.dumpSections(bufferedWriter3);
++            //this.entityManager.dumpSections(bufferedWriter3); // Paper - rewrite chunk system
+         }
+ 
+         Path path3 = path.resolve("entities.csv");
+@@ -2066,8 +2359,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+             Locale.ROOT,
+             "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s",
+             this.players.size(),
+-            this.entityManager.gatherStats(),
+-            getTypeCount(this.entityManager.getEntityGetter().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()),
++            this.moonrise$getEntityLookup().getDebugInfo(), // Paper - rewrite chunk system
++            getTypeCount(this.moonrise$getEntityLookup().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()), // Paper - rewrite chunk system
+             this.blockEntityTickers.size(),
+             getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
+             this.getBlockTicks().count(),
+@@ -2099,15 +2392,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public LevelEntityGetter<Entity> getEntities() {
          org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -27731,7 +27233,7 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
      }
  
      public void startTickingChunk(LevelChunk chunk) {
-@@ -2194,34 +2498,47 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2125,32 +2428,45 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public void close() throws IOException {
          super.close();
@@ -27741,10 +27243,8 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
  
      @Override
      public String gatherChunkSourceStats() {
-         String s = this.chunkSource.gatherStats();
- 
--        return "Chunks[S] W: " + s + " E: " + this.entityManager.gatherStats();
-+        return "Chunks[S] W: " + s + " E: " + this.moonrise$getEntityLookup().getDebugInfo(); // Paper - rewrite chunk system
+-        return "Chunks[S] W: " + this.chunkSource.gatherStats() + " E: " + this.entityManager.gatherStats();
++        return "Chunks[S] W: " + this.chunkSource.gatherStats() + " E: " + this.moonrise$getEntityLookup().getDebugInfo(); // Paper - rewrite chunk system
      }
  
      public boolean areEntitiesLoaded(long chunkPos) {
@@ -27777,8 +27277,8 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
 +        // Paper end - rewrite chunk system
      }
  
-     public boolean isNaturalSpawningAllowed(ChunkPos pos) {
--        return this.entityManager.canPositionTick(pos);
+     public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) {
+-        return this.entityManager.canPositionTick(chunkPos);
 +        // Paper start - rewrite chunk system
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos));
 +        return chunkHolder != null && chunkHolder.isEntityTickingReady();
@@ -27786,29 +27286,29 @@ index 6c71ef3c7430623900a7021f853d2bb514273e4d..cf692267c6376ed8484478dc90f4f905
      }
  
      @Override
-@@ -2277,7 +2594,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report);
- 
-         crashreportsystemdetails.setDetail("Loaded entity count", () -> {
--            return String.valueOf(this.entityManager.count());
-+            return String.valueOf(this.moonrise$getEntityLookup().getEntityCount()); // Paper - rewrite chunk system
-         });
-         return crashreportsystemdetails;
+@@ -2204,7 +2520,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+     @Override
+     public CrashReportCategory fillReportDetails(CrashReport report) {
+         CrashReportCategory crashReportCategory = super.fillReportDetails(report);
+-        crashReportCategory.setDetail("Loaded entity count", () -> String.valueOf(this.entityManager.count()));
++        crashReportCategory.setDetail("Loaded entity count", () -> String.valueOf(this.moonrise$getEntityLookup().getEntityCount())); // Paper - rewrite chunk system
+         return crashReportCategory;
      }
+ 
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index f6e3073e1f1ff99f6917d84974a18e3e756fa9ea..ba873bcc183f9b3f64ba39be08cb88a95ff52b0e 100644
+index 940509d1f31aedf20b8f5b9192c34ad004875728..b455e0e0fea949bee1953324ae530c19405c5d3b 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -217,7 +217,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
- import org.bukkit.inventory.MainHand;
- // CraftBukkit end
- 
--public class ServerPlayer extends net.minecraft.world.entity.player.Player {
-+public class ServerPlayer extends net.minecraft.world.entity.player.Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
+@@ -178,7 +178,7 @@ import net.minecraft.world.scores.Team;
+ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
+ import org.slf4j.Logger;
  
+-public class ServerPlayer extends Player {
++public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
-@@ -322,6 +322,36 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+     private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
+@@ -387,6 +387,36 @@ public class ServerPlayer extends Player {
      public @Nullable String clientBrandName = null; // Paper - Brand support
      public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
  
@@ -27842,11 +27342,11 @@ index f6e3073e1f1ff99f6917d84974a18e3e756fa9ea..ba873bcc183f9b3f64ba39be08cb88a9
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
-         super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
-         this.chatVisibility = ChatVisiblity.FULL;
+     public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
+         super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
+         this.textFilter = server.createTextFilterForPlayer(this);
 diff --git a/net/minecraft/server/level/ThreadedLevelLightEngine.java b/net/minecraft/server/level/ThreadedLevelLightEngine.java
-index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157bddf99a7 100644
+index 11a264ef2f43c2b00741397c9c9ea5393afad6ab..5c9ac44a3b4bc8e047feaf61a94eb163761498a2 100644
 --- a/net/minecraft/server/level/ThreadedLevelLightEngine.java
 +++ b/net/minecraft/server/level/ThreadedLevelLightEngine.java
 @@ -22,23 +22,134 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
@@ -27981,17 +27481,17 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
 +    // Paper end - rewrite chunk system
  
      public ThreadedLevelLightEngine(
-         LightChunkGetter chunkProvider, ChunkMap chunkLoadingManager, boolean hasBlockLight, ConsecutiveExecutor processor, ChunkTaskDispatcher executor
+         LightChunkGetter lightChunkGetter, ChunkMap chunkMap, boolean skyLight, ConsecutiveExecutor consecutiveExecutor, ChunkTaskDispatcher taskDispatcher
      ) {
-         super(chunkProvider, true, hasBlockLight);
-         this.chunkMap = chunkLoadingManager;
--        this.taskDispatcher = executor;
--        this.consecutiveExecutor = processor;
-+        // Paper - rewrite chunk sytem
+         super(lightChunkGetter, true, skyLight);
+         this.chunkMap = chunkMap;
+-        this.taskDispatcher = taskDispatcher;
+-        this.consecutiveExecutor = consecutiveExecutor;
++        // Paper - rewrite chunk system
      }
  
      @Override
-@@ -52,164 +163,73 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
+@@ -52,163 +163,73 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
  
      @Override
      public void checkBlock(BlockPos pos) {
@@ -28010,35 +27510,35 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
 +        // Paper end - rewrite chunk system
      }
  
-     protected void updateChunkStatus(ChunkPos pos) {
--        this.addTask(pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
--            super.retainData(pos, false);
--            super.setLightEnabled(pos, false);
+     protected void updateChunkStatus(ChunkPos chunkPos) {
+-        this.addTask(chunkPos.x, chunkPos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
+-            super.retainData(chunkPos, false);
+-            super.setLightEnabled(chunkPos, false);
 -
--            for (int i = this.getMinLightSection(); i < this.getMaxLightSection(); i++) {
--                super.queueSectionData(LightLayer.BLOCK, SectionPos.of(pos, i), null);
--                super.queueSectionData(LightLayer.SKY, SectionPos.of(pos, i), null);
+-            for (int lightSection = this.getMinLightSection(); lightSection < this.getMaxLightSection(); lightSection++) {
+-                super.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, lightSection), null);
+-                super.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, lightSection), null);
 -            }
 -
--            for (int j = this.levelHeightAccessor.getMinSectionY(); j <= this.levelHeightAccessor.getMaxSectionY(); j++) {
--                super.updateSectionStatus(SectionPos.of(pos, j), true);
+-            for (int lightSection = this.levelHeightAccessor.getMinSectionY(); lightSection <= this.levelHeightAccessor.getMaxSectionY(); lightSection++) {
+-                super.updateSectionStatus(SectionPos.of(chunkPos, lightSection), true);
 -            }
--        }, () -> "updateChunkStatus " + pos + " true"));
+-        }, () -> "updateChunkStatus " + chunkPos + " true"));
 +        // Paper - rewrite chunk system
      }
  
      @Override
-     public void updateSectionStatus(SectionPos pos, boolean notReady) {
+     public void updateSectionStatus(SectionPos pos, boolean isEmpty) {
 -        this.addTask(
 -            pos.x(),
 -            pos.z(),
 -            () -> 0,
 -            ThreadedLevelLightEngine.TaskType.PRE_UPDATE,
--            Util.name(() -> super.updateSectionStatus(pos, notReady), () -> "updateSectionStatus " + pos + " " + notReady)
+-            Util.name(() -> super.updateSectionStatus(pos, isEmpty), () -> "updateSectionStatus " + pos + " " + isEmpty)
 -        );
 +        // Paper start - rewrite chunk system
 +        this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> {
-+            return ThreadedLevelLightEngine.this.starlight$getLightEngine().sectionChange(pos, notReady);
++            return ThreadedLevelLightEngine.this.starlight$getLightEngine().sectionChange(pos, isEmpty);
 +        });
 +        // Paper end - rewrite chunk system
      }
@@ -28055,84 +27555,84 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
      }
  
      @Override
-     public void setLightEnabled(ChunkPos pos, boolean retainData) {
+     public void setLightEnabled(ChunkPos chunkPos, boolean lightEnabled) {
 -        this.addTask(
--            pos.x,
--            pos.z,
+-            chunkPos.x,
+-            chunkPos.z,
 -            ThreadedLevelLightEngine.TaskType.PRE_UPDATE,
--            Util.name(() -> super.setLightEnabled(pos, retainData), () -> "enableLight " + pos + " " + retainData)
+-            Util.name(() -> super.setLightEnabled(chunkPos, lightEnabled), () -> "enableLight " + chunkPos + " " + lightEnabled)
 -        );
 +        // Paper start - rewrite chunk system
      }
  
      @Override
-     public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) {
+     public void queueSectionData(LightLayer lightLayer, SectionPos sectionPos, @Nullable DataLayer dataLayer) {
 -        this.addTask(
--            pos.x(),
--            pos.z(),
+-            sectionPos.x(),
+-            sectionPos.z(),
 -            () -> 0,
 -            ThreadedLevelLightEngine.TaskType.PRE_UPDATE,
--            Util.name(() -> super.queueSectionData(lightType, pos, nibbles), () -> "queueData " + pos)
+-            Util.name(() -> super.queueSectionData(lightLayer, sectionPos, dataLayer), () -> "queueData " + sectionPos)
 -        );
 +        // Paper start - rewrite chunk system
      }
  
-     private void addTask(int x, int z, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
--        this.addTask(x, z, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(x, z)), stage, task);
+     private void addTask(int chunkX, int chunkZ, ThreadedLevelLightEngine.TaskType type, Runnable task) {
+-        this.addTask(chunkX, chunkZ, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(chunkX, chunkZ)), type, task);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
+     private void addTask(int chunkX, int chunkZ, IntSupplier queueLevelSupplier, ThreadedLevelLightEngine.TaskType type, Runnable task) {
 -        this.taskDispatcher.submit(() -> {
--            this.lightTasks.add(Pair.of(stage, task));
+-            this.lightTasks.add(Pair.of(type, task));
 -            if (this.lightTasks.size() >= 1000) {
 -                this.runUpdate();
 -            }
--        }, ChunkPos.asLong(x, z), completedLevelSupplier);
+-        }, ChunkPos.asLong(chunkX, chunkZ), queueLevelSupplier);
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      @Override
-     public void retainData(ChunkPos pos, boolean retainData) {
+     public void retainData(ChunkPos pos, boolean retain) {
 -        this.addTask(
--            pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> super.retainData(pos, retainData), () -> "retainData " + pos)
+-            pos.x, pos.z, () -> 0, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> super.retainData(pos, retain), () -> "retainData " + pos)
 -        );
 +        // Paper start - rewrite chunk system
      }
  
-     public CompletableFuture<ChunkAccess> initializeLight(ChunkAccess chunk, boolean bl) {
--        ChunkPos chunkPos = chunk.getPos();
--        this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
--            LevelChunkSection[] levelChunkSections = chunk.getSections();
+     public CompletableFuture<ChunkAccess> initializeLight(ChunkAccess chunk, boolean lightEnabled) {
+-        ChunkPos pos = chunk.getPos();
+-        this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
+-            LevelChunkSection[] sections = chunk.getSections();
 -
 -            for (int i = 0; i < chunk.getSectionsCount(); i++) {
--                LevelChunkSection levelChunkSection = levelChunkSections[i];
+-                LevelChunkSection levelChunkSection = sections[i];
 -                if (!levelChunkSection.hasOnlyAir()) {
--                    int j = this.levelHeightAccessor.getSectionYFromSectionIndex(i);
--                    super.updateSectionStatus(SectionPos.of(chunkPos, j), false);
+-                    int sectionYFromSectionIndex = this.levelHeightAccessor.getSectionYFromSectionIndex(i);
+-                    super.updateSectionStatus(SectionPos.of(pos, sectionYFromSectionIndex), false);
 -                }
 -            }
--        }, () -> "initializeLight: " + chunkPos));
+-        }, () -> "initializeLight: " + pos));
 -        return CompletableFuture.supplyAsync(() -> {
--            super.setLightEnabled(chunkPos, bl);
--            super.retainData(chunkPos, false);
+-            super.setLightEnabled(pos, lightEnabled);
+-            super.retainData(pos, false);
 -            return chunk;
--        }, task -> this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
+-        }, task -> this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
 +        return CompletableFuture.completedFuture(chunk); // Paper start - rewrite chunk system
      }
  
-     public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean excludeBlocks) {
--        ChunkPos chunkPos = chunk.getPos();
+     public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean isLighted) {
+-        ChunkPos pos = chunk.getPos();
 -        chunk.setLightCorrect(false);
--        this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
--            if (!excludeBlocks) {
--                super.propagateLightSources(chunkPos);
+-        this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
+-            if (!isLighted) {
+-                super.propagateLightSources(pos);
 -            }
--        }, () -> "lightChunk " + chunkPos + " " + excludeBlocks));
+-        }, () -> "lightChunk " + pos + " " + isLighted));
 -        return CompletableFuture.supplyAsync(() -> {
 -            chunk.setLightCorrect(true);
 -            return chunk;
--        }, task -> this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
+-        }, task -> this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
@@ -28147,24 +27647,24 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
      }
  
      private void runUpdate() {
--        int i = Math.min(this.lightTasks.size(), 1000);
+-        int min = Math.min(this.lightTasks.size(), 1000);
 -        ObjectListIterator<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> objectListIterator = this.lightTasks.iterator();
 -
--        int j;
--        for (j = 0; objectListIterator.hasNext() && j < i; j++) {
+-        int i;
+-        for (i = 0; objectListIterator.hasNext() && i < min; i++) {
 -            Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair = objectListIterator.next();
 -            if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) {
 -                pair.getSecond().run();
 -            }
 -        }
 -
--        objectListIterator.back(j);
+-        objectListIterator.back(i);
 -        super.runLightUpdates();
 -
--        for (int var5 = 0; objectListIterator.hasNext() && var5 < i; var5++) {
--            Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair2 = objectListIterator.next();
--            if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) {
--                pair2.getSecond().run();
+-        for (int var5 = 0; objectListIterator.hasNext() && var5 < min; var5++) {
+-            Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair = objectListIterator.next();
+-            if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) {
+-                pair.getSecond().run();
 -            }
 -
 -            objectListIterator.remove();
@@ -28173,14 +27673,13 @@ index 39d34f3728ae8d845d1bffc09f3ab8b64eb4d48b..3e82adf061bd0ec0100ca4d16ec9b157
      }
  
      public CompletableFuture<?> waitForPendingTasks(int x, int z) {
--        return CompletableFuture.runAsync(() -> {
--        }, callback -> this.addTask(x, z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, callback));
+-        return CompletableFuture.runAsync(() -> {}, task -> this.addTask(x, z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
      static enum TaskType {
 diff --git a/net/minecraft/server/level/Ticket.java b/net/minecraft/server/level/Ticket.java
-index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466f5512bcd 100644
+index e574f217386d677400b4c093d50261045df06d5c..ed8a3d5bd25909ee4648b1ec2ee66878198a1d8a 100644
 --- a/net/minecraft/server/level/Ticket.java
 +++ b/net/minecraft/server/level/Ticket.java
 @@ -2,13 +2,25 @@ package net.minecraft.server.level;
@@ -28196,7 +27695,7 @@ index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466
 +    // Paper start - rewrite chunk system
 +    private long removeDelay;
  
--    protected Ticket(TicketType<T> type, int level, T argument) {
+-    protected Ticket(TicketType<T> type, int ticketLevel, T key) {
 +    @Override
 +    public final long moonrise$getRemoveDelay() {
 +        return this.removeDelay;
@@ -28206,12 +27705,12 @@ index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466
 +    public final void moonrise$setRemoveDelay(final long removeDelay) {
 +        this.removeDelay = removeDelay;
 +    }
-+    // Paper end - rewerite chunk system
++    // Paper end - rewrite chunk system
 +
-+    public Ticket(TicketType<T> type, int level, T argument) { // Paper - public
++    public Ticket(TicketType<T> type, int ticketLevel, T key) { // Paper - public
          this.type = type;
-         this.ticketLevel = level;
-         this.key = argument;
+         this.ticketLevel = ticketLevel;
+         this.key = key;
 @@ -41,7 +53,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
  
      @Override
@@ -28224,22 +27723,22 @@ index eba83b085435150e5954fd5d41dda9ce1d0601ad..daf543b51d8875b374688957ae4bc466
 @@ -53,11 +65,10 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
      }
  
-     protected void setCreatedTick(long tickCreated) {
--        this.createdTick = tickCreated;
+     protected void setCreatedTick(long timestamp) {
+-        this.createdTick = timestamp;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     protected boolean timedOut(long currentTick) {
--        long l = this.type.timeout();
--        return l != 0L && currentTick - this.createdTick > l;
+     protected boolean timedOut(long currentTime) {
+-        long timeout = this.type.timeout();
+-        return timeout != 0L && currentTime - this.createdTick > timeout;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  }
 diff --git a/net/minecraft/server/level/WorldGenRegion.java b/net/minecraft/server/level/WorldGenRegion.java
-index b7d29389a357f142237cecd75f8ca91cf1eb6b5b..e4b0dc3121101d54394a0c3a413dabf8103b2ea6 100644
+index 4eb040006f5d41b47e5ac9df5d9f19c4315d6343..7fa41dea184b01891f45d8e404bc1cba19cf1bcf 100644
 --- a/net/minecraft/server/level/WorldGenRegion.java
 +++ b/net/minecraft/server/level/WorldGenRegion.java
-@@ -85,6 +85,36 @@ public class WorldGenRegion implements WorldGenLevel {
+@@ -78,6 +78,36 @@ public class WorldGenRegion implements WorldGenLevel {
      private final AtomicLong subTickCount = new AtomicLong();
      private static final ResourceLocation WORLDGEN_REGION_RANDOM = ResourceLocation.withDefaultNamespace("worldgen_region_random");
  
@@ -28273,33 +27772,33 @@ index b7d29389a357f142237cecd75f8ca91cf1eb6b5b..e4b0dc3121101d54394a0c3a413dabf8
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public WorldGenRegion(ServerLevel world, StaticCache2D<GenerationChunkHolder> chunks, ChunkStep generationStep, ChunkAccess centerPos) {
-         this.generatingStep = generationStep;
-         this.cache = chunks;
+     public WorldGenRegion(ServerLevel level, StaticCache2D<GenerationChunkHolder> cache, ChunkStep generatingStep, ChunkAccess center) {
+         this.generatingStep = generatingStep;
+         this.cache = cache;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index c68040a59fa8aa9b8b9f1e0b4fdded565ea592d9..7913c41aac1f9dd53a2b49da2a17fd894bcb6b3a 100644
+index d227714de0fe13544779fae6cf0e9ff6af5469c7..4722230e74e0778ebdb2cfd383764b34004e9568 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -1426,7 +1426,7 @@ public abstract class PlayerList {
+@@ -1318,7 +1318,7 @@ public abstract class PlayerList {
  
      public void setViewDistance(int viewDistance) {
          this.viewDistance = viewDistance;
 -        this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
 +        //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - rewrite chunk system
-         Iterator iterator = this.server.getAllLevels().iterator();
  
-         while (iterator.hasNext()) {
-@@ -1441,7 +1441,7 @@ public abstract class PlayerList {
+         for (ServerLevel serverLevel : this.server.getAllLevels()) {
+             if (serverLevel != null) {
+@@ -1329,7 +1329,7 @@ public abstract class PlayerList {
  
      public void setSimulationDistance(int simulationDistance) {
          this.simulationDistance = simulationDistance;
 -        this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));
-+        //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - rewrite chunk system
-         Iterator iterator = this.server.getAllLevels().iterator();
++        //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));  // Paper - rewrite chunk system
  
-         while (iterator.hasNext()) {
+         for (ServerLevel serverLevel : this.server.getAllLevels()) {
+             if (serverLevel != null) {
 diff --git a/net/minecraft/util/BitStorage.java b/net/minecraft/util/BitStorage.java
-index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907cf68b15c 100644
+index 32fe9b22e1d3a422dd80c64d61156dbc7241ba20..02502d50f0255f5bbcc0ecb965abb48cc1a112da 100644
 --- a/net/minecraft/util/BitStorage.java
 +++ b/net/minecraft/util/BitStorage.java
 @@ -2,7 +2,7 @@ package net.minecraft.util;
@@ -28312,7 +27811,7 @@ index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907
  
      void set(int index, int value);
 @@ -20,4 +20,22 @@ public interface BitStorage {
-     void unpack(int[] out);
+     void unpack(int[] array);
  
      BitStorage copy();
 +
@@ -28335,7 +27834,7 @@ index 68648c5a5e3ff079f832092af0f2f801c42d1ede..e4e153cb8899e70273aa150b8ea26907
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17b2fc8db5 100644
+index 4a7c83c56dfbff59af71c3cd2fa4205c9a22bdc7..f28fbf81a417a678726d3f77b3999054676d522e 100644
 --- a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
 +++ b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
 @@ -7,7 +7,7 @@ import java.util.Iterator;
@@ -28376,9 +27875,9 @@ index 61dee55417bc802e25b9ba2f271d32d8c12844a9..a8a260a3caaa8e5004069b833ecc8b17
 +        // Paper end - optimise palette reads
      }
  
-     public void addMapping(K value, int id) {
+     public void addMapping(K object, int intKey) {
 diff --git a/net/minecraft/util/SimpleBitStorage.java b/net/minecraft/util/SimpleBitStorage.java
-index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a6428b20c38 100644
+index 6fb3a3f167d8cbaa78135af0c180b592661e2c1d..e6306a68c8652d4c5d22d5ecb1416f5f931f76ee 100644
 --- a/net/minecraft/util/SimpleBitStorage.java
 +++ b/net/minecraft/util/SimpleBitStorage.java
 @@ -208,6 +208,20 @@ public class SimpleBitStorage implements BitStorage {
@@ -28399,12 +27898,12 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
 +    private final int mulBits;
 +    // Paper end - optimise bitstorage read/write operations
 +
-     public SimpleBitStorage(int elementBits, int size, int[] data) {
-         this(elementBits, size);
+     public SimpleBitStorage(int bits, int size, int[] data) {
+         this(bits, size);
          int i = 0;
 @@ -261,6 +275,13 @@ public class SimpleBitStorage implements BitStorage {
          } else {
-             this.data = new long[j];
+             this.data = new long[i1];
          }
 +        // Paper start - optimise bitstorage read/write operations
 +        this.magic = BETTER_MAGIC[this.bits];
@@ -28416,16 +27915,16 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
      }
  
      private int cellIndex(int index) {
-@@ -273,31 +294,54 @@ public class SimpleBitStorage implements BitStorage {
+@@ -269,28 +290,51 @@ public class SimpleBitStorage implements BitStorage {
+ 
+     @Override
      public final int getAndSet(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
-         //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
-         //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage
 -        int i = this.cellIndex(index);
 -        long l = this.data[i];
--        int j = (index - i * this.valuesPerLong) * this.bits;
--        int k = (int)(l >> j & this.mask);
--        this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j;
--        return k;
+-        int i1 = (index - i * this.valuesPerLong) * this.bits;
+-        int i2 = (int)(l >> i1 & this.mask);
+-        this.data[i] = l & ~(this.mask << i1) | (value & this.mask) << i1;
+-        return i2;
 +        // Paper start - optimise bitstorage read/write operations
 +        final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int
 +        final int divQ = full >>> 20;
@@ -28446,12 +27945,10 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
  
      @Override
      public final void set(int index, int value) { // Paper - Perf: Optimize SimpleBitStorage
-         //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
-         //Validate.inclusiveBetween(0L, this.mask, (long)value); // Paper - Perf: Optimize SimpleBitStorage
 -        int i = this.cellIndex(index);
 -        long l = this.data[i];
--        int j = (index - i * this.valuesPerLong) * this.bits;
--        this.data[i] = l & ~(this.mask << j) | ((long)value & this.mask) << j;
+-        int i1 = (index - i * this.valuesPerLong) * this.bits;
+-        this.data[i] = l & ~(this.mask << i1) | (value & this.mask) << i1;
 +        // Paper start - optimise bitstorage read/write operations
 +        final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int
 +        final int divQ = full >>> 20;
@@ -28470,11 +27967,10 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
  
      @Override
      public final int get(int index) { // Paper - Perf: Optimize SimpleBitStorage
-         //Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)index); // Paper - Perf: Optimize SimpleBitStorage
 -        int i = this.cellIndex(index);
 -        long l = this.data[i];
--        int j = (index - i * this.valuesPerLong) * this.bits;
--        return (int)(l >> j & this.mask);
+-        int i1 = (index - i * this.valuesPerLong) * this.bits;
+-        return (int)(l >> i1 & this.mask);
 +        // Paper start - optimise bitstorage read/write operations
 +        final int full = this.magic * index; // 20 bits of magic + 12 bits of index = barely int
 +        final int divQ = full >>> 20;
@@ -28485,7 +27981,7 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
      }
  
      @Override
-@@ -362,6 +406,67 @@ public class SimpleBitStorage implements BitStorage {
+@@ -355,6 +399,67 @@ public class SimpleBitStorage implements BitStorage {
          return new SimpleBitStorage(this.bits, this.size, (long[])this.data.clone());
      }
  
@@ -28554,7 +28050,7 @@ index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..d99ec470b4653beab630999a5b2c1a64
          InitializationException(String message) {
              super(message);
 diff --git a/net/minecraft/util/SortedArraySet.java b/net/minecraft/util/SortedArraySet.java
-index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf857043e1 100644
+index 2c6b35b86eed9002016b8228c3195f8033d219ca..339b19e88567be382e550ed54477fabd58d51faa 100644
 --- a/net/minecraft/util/SortedArraySet.java
 +++ b/net/minecraft/util/SortedArraySet.java
 @@ -8,12 +8,89 @@ import java.util.Iterator;
@@ -28649,7 +28145,7 @@ index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf
          this.comparator = comparator;
          if (initialCapacity < 0) {
 diff --git a/net/minecraft/util/ZeroBitStorage.java b/net/minecraft/util/ZeroBitStorage.java
-index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7eb80334e 100644
+index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b89651948ed8 100644
 --- a/net/minecraft/util/ZeroBitStorage.java
 +++ b/net/minecraft/util/ZeroBitStorage.java
 @@ -62,4 +62,22 @@ public class ZeroBitStorage implements BitStorage {
@@ -28676,19 +28172,19 @@ index 50040c497a819cd1229042ab3cb057d34a32cacc..1f9c436a632e4f110be61cf76fcfc3b7
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3b5d9b6ce 100644
+index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1d122428b 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -176,7 +176,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
- import org.bukkit.plugin.PluginManager;
- // CraftBukkit end
+@@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
+ import net.minecraft.world.scores.Team;
+ import org.slf4j.Logger;
  
 -public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder {
 +public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity {  // Paper - rewrite chunk system // Paper - optimise entity tracker
  
      // CraftBukkit start
      private static final int CURRENT_LEVEL = 2;
-@@ -187,7 +187,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -146,7 +146,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      // Paper start - Share random for entities to make them more random
      public static RandomSource SHARED_RANDOM = new RandomRandomSource();
@@ -28707,7 +28203,7 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
          private boolean locked = false;
  
          @Override
-@@ -200,61 +210,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -159,61 +169,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              }
          }
  
@@ -28770,7 +28266,7 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
      }
      // Paper end - Share random for entities to make them more random
      public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
-@@ -462,6 +418,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -425,6 +381,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return this.dimensions.makeBoundingBox(x, y, z);
      }
      // Paper end
@@ -28925,26 +28421,24 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +    }
 +    // Paper end - optimise entity tracker
  
-     public Entity(EntityType<?> type, Level world) {
-         this.id = Entity.ENTITY_COUNTER.incrementAndGet();
-@@ -1387,41 +1493,76 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     public Entity(EntityType<?> entityType, Level level) {
+         this.type = entityType;
+@@ -1286,34 +1392,76 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
-     private Vec3 collide(Vec3 movement) {
--        AABB axisalignedbb = this.getBoundingBox();
--        List<VoxelShape> list = this.level().getEntityCollisions(this, axisalignedbb.expandTowards(movement));
--        Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level(), list);
--        boolean flag = movement.x != vec3d1.x;
--        boolean flag1 = movement.y != vec3d1.y;
--        boolean flag2 = movement.z != vec3d1.z;
--        boolean flag3 = flag1 && movement.y < 0.0D;
--
+     private Vec3 collide(Vec3 vec) {
+-        AABB boundingBox = this.getBoundingBox();
+-        List<VoxelShape> entityCollisions = this.level().getEntityCollisions(this, boundingBox.expandTowards(vec));
+-        Vec3 vec3 = vec.lengthSqr() == 0.0 ? vec : collideBoundingBox(this, vec, boundingBox, this.level(), entityCollisions);
+-        boolean flag = vec.x != vec3.x;
+-        boolean flag1 = vec.y != vec3.y;
+-        boolean flag2 = vec.z != vec3.z;
+-        boolean flag3 = flag1 && vec.y < 0.0;
 -        if (this.maxUpStep() > 0.0F && (flag3 || this.onGround()) && (flag || flag2)) {
--            AABB axisalignedbb1 = flag3 ? axisalignedbb.move(0.0D, vec3d1.y, 0.0D) : axisalignedbb;
--            AABB axisalignedbb2 = axisalignedbb1.expandTowards(movement.x, (double) this.maxUpStep(), movement.z);
--
+-            AABB aabb = flag3 ? boundingBox.move(0.0, vec3.y, 0.0) : boundingBox;
+-            AABB aabb1 = aabb.expandTowards(vec.x, this.maxUpStep(), vec.z);
 -            if (!flag3) {
--                axisalignedbb2 = axisalignedbb2.expandTowards(0.0D, -9.999999747378752E-6D, 0.0D);
+-                aabb1 = aabb1.expandTowards(0.0, -1.0E-5F, 0.0);
 -            }
 +        // Paper start - optimise collisions
 +        final boolean xZero = movement.x == 0.0;
@@ -28953,17 +28447,21 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +        if (xZero & yZero & zZero) {
 +            return movement;
 +        }
-+
+ 
+-            List<VoxelShape> list = collectColliders(this, this.level, entityCollisions, aabb1);
+-            float f = (float)vec3.y;
+-            float[] floats = collectCandidateStepUpHeights(aabb, list, this.maxUpStep(), f);
 +        final AABB currentBox = this.getBoundingBox();
-+
+ 
+-            for (float f1 : floats) {
+-                Vec3 vec31 = collideWithShapes(new Vec3(vec.x, f1, vec.z), aabb, list);
+-                if (vec31.horizontalDistanceSqr() > vec3.horizontalDistanceSqr()) {
+-                    double d = boundingBox.minY - aabb.minY;
+-                    return vec31.add(0.0, -d, 0.0);
+-                }
 +        final List<VoxelShape> potentialCollisionsVoxel = new ArrayList<>();
 +        final List<AABB> potentialCollisionsBB = new ArrayList<>();
- 
--            List<VoxelShape> list1 = Entity.collectColliders(this, this.level, list, axisalignedbb2);
--            float f = (float) vec3d1.y;
--            float[] afloat = Entity.collectCandidateStepUpHeights(axisalignedbb1, list1, this.maxUpStep(), f);
--            float[] afloat1 = afloat;
--            int i = afloat.length;
++
 +        final AABB initialCollisionBox;
 +        if (xZero & zZero) {
 +            // note: xZero & zZero -> collision on x/z == 0 -> no step height calculation
@@ -28973,26 +28471,19 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +        } else {
 +            initialCollisionBox = currentBox.expandTowards(movement);
 +        }
- 
--            for (int j = 0; j < i; ++j) {
--                float f1 = afloat1[j];
--                Vec3 vec3d2 = Entity.collideWithShapes(new Vec3(movement.x, (double) f1, movement.z), axisalignedbb1, list1);
++
 +        final List<AABB> entityAABBs = new ArrayList<>();
 +        ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions(
 +            this.level, (Entity)(Object)this, initialCollisionBox, entityAABBs, 0, null
 +        );
- 
--                if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) {
--                    double d0 = axisalignedbb.minY - axisalignedbb1.minY;
++
 +        ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder(
 +            this.level, (Entity)(Object)this, initialCollisionBox, potentialCollisionsVoxel, potentialCollisionsBB,
 +            ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, null
 +        );
 +        potentialCollisionsBB.addAll(entityAABBs);
 +        final Vec3 collided = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currentBox, potentialCollisionsVoxel, potentialCollisionsBB);
- 
--                    return vec3d2.add(0.0D, -d0, 0.0D);
--                }
++
 +        final boolean collidedX = collided.x != movement.x;
 +        final boolean collidedY = collided.y != movement.y;
 +        final boolean collidedZ = collided.z != movement.z;
@@ -29026,13 +28517,13 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
              }
          }
  
--        return vec3d1;
+-        return vec3;
 +        return collided;
 +        // Paper end - optimise collisions
      }
  
-     private static float[] collectCandidateStepUpHeights(AABB collisionBox, List<VoxelShape> collisions, float f, float stepHeight) {
-@@ -2821,18 +2962,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+     private static float[] collectCandidateStepUpHeights(AABB box, List<VoxelShape> colliders, float deltaY, float maxUpStep) {
+@@ -2622,23 +2770,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public boolean isInWall() {
@@ -29041,20 +28532,25 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
              return false;
 -        } else {
 -            float f = this.dimensions.width() * 0.8F;
--            AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
+-            AABB aabb = AABB.ofSize(this.getEyePosition(), f, 1.0E-6, f);
+-            return BlockPos.betweenClosedStream(aabb)
+-                .anyMatch(
+-                    pos -> {
+-                        BlockState blockState = this.level().getBlockState(pos);
+-                        return !blockState.isAir()
+-                            && blockState.isSuffocating(this.level(), pos)
+-                            && Shapes.joinIsNotEmpty(
+-                                blockState.getCollisionShape(this.level(), pos).move(pos.getX(), pos.getY(), pos.getZ()), Shapes.create(aabb), BooleanOp.AND
+-                            );
 +        }
- 
--            return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> {
--                BlockState iblockdata = this.level().getBlockState(blockposition);
++
 +        final double reducedWith = (double)(this.dimensions.width() * 0.8F);
 +        final AABB boundingBox = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
 +        final Level world = this.level;
- 
--                return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND);
--            });
++
 +        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) {
 +            return false;
-         }
++        }
 +
 +        final int minBlockX = Mth.floor(boundingBox.minX);
 +        final int minBlockY = Mth.floor(boundingBox.minY);
@@ -29084,7 +28580,8 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +                    final int sectionIdx = currChunkY - minSection;
 +                    if (sectionIdx < 0 || sectionIdx >= sections.length) {
 +                        continue;
-+                    }
+                     }
+-                );
 +                    final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx];
 +                    if (section.hasOnlyAir()) {
 +                        // empty
@@ -29143,14 +28640,14 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +                    }
 +                }
 +            }
-+        }
+         }
 +
 +        return false;
 +        // Paper end - optimise collisions
      }
  
      public InteractionResult interact(Player player, InteractionHand hand) {
-@@ -4310,14 +4543,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4063,15 +4298,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public Iterable<Entity> getIndirectPassengers() {
@@ -29167,55 +28664,53 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +            return ret;
          }
 -        return indirectPassengers.build();
+-        // Paper end - Optimize indirect passenger iteration
 +
 +        collectIndirectPassengers(ret, this.passengers);
 +
 +        return ret;
 +        // Paper end - optimise entity tracker
      }
-     private Iterable<Entity> getIndirectPassengers_old() {
-         // Paper end - Optimize indirect passenger iteration
-@@ -4475,82 +4711,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-         return Mth.lerp(delta, this.yRotO, this.yRot);
+ 
+     public int countPlayerPassengers() {
+@@ -4209,77 +4446,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+         return Mth.lerp(partialTick, this.yRotO, this.yRot);
      }
  
--    public boolean updateFluidHeightAndDoFluidPushing(TagKey<Fluid> tag, double speed) {
+-    public boolean updateFluidHeightAndDoFluidPushing(TagKey<Fluid> fluidTag, double motionScale) {
 +    // Paper start - optimise collisions
 +    public boolean updateFluidHeightAndDoFluidPushing(final TagKey<Fluid> fluid, final double flowScale) {
          if (this.touchingUnloadedChunk()) {
              return false;
 -        } else {
--            AABB axisalignedbb = this.getBoundingBox().deflate(0.001D);
--            int i = Mth.floor(axisalignedbb.minX);
--            int j = Mth.ceil(axisalignedbb.maxX);
--            int k = Mth.floor(axisalignedbb.minY);
--            int l = Mth.ceil(axisalignedbb.maxY);
--            int i1 = Mth.floor(axisalignedbb.minZ);
--            int j1 = Mth.ceil(axisalignedbb.maxZ);
--            double d1 = 0.0D;
--            boolean flag = this.isPushedByFluid();
--            boolean flag1 = false;
--            Vec3 vec3d = Vec3.ZERO;
--            int k1 = 0;
--            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+-            AABB aabb = this.getBoundingBox().deflate(0.001);
+-            int floor = Mth.floor(aabb.minX);
+-            int ceil = Mth.ceil(aabb.maxX);
+-            int floor1 = Mth.floor(aabb.minY);
+-            int ceil1 = Mth.ceil(aabb.maxY);
+-            int floor2 = Mth.floor(aabb.minZ);
+-            int ceil2 = Mth.ceil(aabb.maxZ);
+-            double d = 0.0;
+-            boolean isPushedByFluid = this.isPushedByFluid();
+-            boolean flag = false;
+-            Vec3 vec3 = Vec3.ZERO;
+-            int i = 0;
+-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
 -
--            for (int l1 = i; l1 < j; ++l1) {
--                for (int i2 = k; i2 < l; ++i2) {
--                    for (int j2 = i1; j2 < j1; ++j2) {
--                        blockposition_mutableblockposition.set(l1, i2, j2);
--                        FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
--
--                        if (fluid.is(tag)) {
--                            double d2 = (double) ((float) i2 + fluid.getHeight(this.level(), blockposition_mutableblockposition));
--
--                            if (d2 >= axisalignedbb.minY) {
--                                flag1 = true;
--                                d1 = Math.max(d2 - axisalignedbb.minY, d1);
--                                if (flag) {
--                                    Vec3 vec3d1 = fluid.getFlow(this.level(), blockposition_mutableblockposition);
--
--                                    if (d1 < 0.4D) {
--                                        vec3d1 = vec3d1.scale(d1);
+-            for (int i1 = floor; i1 < ceil; i1++) {
+-                for (int i2 = floor1; i2 < ceil1; i2++) {
+-                    for (int i3 = floor2; i3 < ceil2; i3++) {
+-                        mutableBlockPos.set(i1, i2, i3);
+-                        FluidState fluidState = this.level().getFluidState(mutableBlockPos);
+-                        if (fluidState.is(fluidTag)) {
+-                            double d1 = i2 + fluidState.getHeight(this.level(), mutableBlockPos);
+-                            if (d1 >= aabb.minY) {
+-                                flag = true;
+-                                d = Math.max(d1 - aabb.minY, d);
+-                                if (isPushedByFluid) {
+-                                    Vec3 flow = fluidState.getFlow(this.level(), mutableBlockPos);
+-                                    if (d < 0.4) {
+-                                        flow = flow.scale(d);
 -                                    }
 +        }
 +
@@ -29277,8 +28772,8 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +                    final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
 +                    final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
  
--                                    vec3d = vec3d.add(vec3d1);
--                                    ++k1;
+-                                    vec3 = vec3.add(flow);
+-                                    i++;
 +                    for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
 +                        for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
 +                            for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
@@ -29288,8 +28783,8 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
 +                                    continue;
                                  }
 -                                // CraftBukkit start - store last lava contact location
--                                if (tag == FluidTags.LAVA) {
--                                    this.lastLavaContact = blockposition_mutableblockposition.immutable();
+-                                if (fluidTag == FluidTags.LAVA) {
+-                                    this.lastLavaContact = mutableBlockPos.immutable();
 +
 +                                mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4));
 +
@@ -29324,53 +28819,52 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
              }
 +        }
  
--            if (vec3d.length() > 0.0D) {
--                if (k1 > 0) {
--                    vec3d = vec3d.scale(1.0D / (double) k1);
+-            if (vec3.length() > 0.0) {
+-                if (i > 0) {
+-                    vec3 = vec3.scale(1.0 / i);
 -                }
 +        this.fluidHeight.put(fluid, maxHeightDiff);
  
 -                if (!(this instanceof Player)) {
--                    vec3d = vec3d.normalize();
+-                    vec3 = vec3.normalize();
 -                }
 +        if (pushVector.lengthSqr() == 0.0) {
 +            return inFluid;
 +        }
  
--                Vec3 vec3d2 = this.getDeltaMovement();
+-                Vec3 deltaMovement = this.getDeltaMovement();
+-                vec3 = vec3.scale(motionScale);
+-                double d2 = 0.003;
+-                if (Math.abs(deltaMovement.x) < 0.003 && Math.abs(deltaMovement.z) < 0.003 && vec3.length() < 0.0045000000000000005) {
+-                    vec3 = vec3.normalize().scale(0.0045000000000000005);
+-                }
 +        // note: totalPushes != 0 as pushVector != 0
 +        pushVector = pushVector.scale(1.0 / totalPushes);
 +        final Vec3 currMovement = this.getDeltaMovement();
  
--                vec3d = vec3d.scale(speed);
--                double d3 = 0.003D;
+-                this.setDeltaMovement(this.getDeltaMovement().add(vec3));
+-            }
 +        if (!((Entity)(Object)this instanceof Player)) {
 +            pushVector = pushVector.normalize();
 +        }
  
--                if (Math.abs(vec3d2.x) < 0.003D && Math.abs(vec3d2.z) < 0.003D && vec3d.length() < 0.0045000000000000005D) {
--                    vec3d = vec3d.normalize().scale(0.0045000000000000005D);
--                }
+-            this.fluidHeight.put(fluidTag, d);
+-            return flag;
 +        pushVector = pushVector.scale(flowScale);
 +        if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) {
 +            pushVector = pushVector.normalize().scale(0.0045000000000000005);
-+        }
- 
--                this.setDeltaMovement(this.getDeltaMovement().add(vec3d));
--            }
+         }
++
 +        this.setDeltaMovement(currMovement.add(pushVector));
- 
--            this.fluidHeight.put(tag, d1);
--            return flag1;
--        }
++
 +        // note: inFluid = true here as pushVector != 0
 +        return true;
      }
 +    // Paper end - optimise collisions
  
      public boolean touchingUnloadedChunk() {
-         AABB axisalignedbb = this.getBoundingBox().inflate(1.0D);
-@@ -4702,6 +4992,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+         AABB aabb = this.getBoundingBox().inflate(1.0);
+@@ -4430,6 +4726,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          this.setPosRaw(x, y, z, false);
      }
      public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29386,39 +28880,39 @@ index 766031d1482b0f49b196326b820d5ce9ae1c7c06..1f54752a4ea0788e73279cd99c7c35e3
          if (!checkPosition(this, x, y, z)) {
              return;
          }
-@@ -4831,6 +5130,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4558,6 +4863,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      @Override
-     public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
+     public final void setRemoved(Entity.RemovalReason removalReason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
 +        // Paper start - rewrite chunk system
 +        if (!((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this.level).moonrise$getEntityLookup().canRemoveEntity((Entity)(Object)this)) {
 +            LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable());
 +            return;
 +        }
 +        // Paper end - rewrite chunk system
-         CraftEventFactory.callEntityRemoveEvent(this, cause);
+         org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause);
          // CraftBukkit end
          final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
-@@ -4842,7 +5147,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4569,7 +4880,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              this.stopRiding();
          }
  
 -        this.getPassengers().forEach(Entity::stopRiding);
 +        if (this.removalReason != Entity.RemovalReason.UNLOADED_TO_CHUNK) { this.getPassengers().forEach(Entity::stopRiding); } // Paper - rewrite chunk system
-         this.levelCallback.onRemove(entity_removalreason);
-         this.onRemoval(entity_removalreason);
+         this.levelCallback.onRemove(removalReason);
+         this.onRemoval(removalReason);
          // Paper start - Folia schedulers
-@@ -4874,7 +5179,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- 
-     @Override
+@@ -4603,7 +4914,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      public boolean shouldBeSaved() {
--        return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasExactlyOnePlayerPassenger());
-+        return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system
+         return (this.removalReason == null || this.removalReason.shouldSave())
+             && !this.isPassenger()
+-            && (!this.isVehicle() || !this.hasExactlyOnePlayerPassenger());
++            && (!this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system
      }
  
      @Override
 diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462ca1b78cd 100644
+index 7d590dd06cc69c0925d22708425520c38e3cda25..c8590e0175dacd6d7efdb1830bd60c92325083f9 100644
 --- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 +++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 @@ -38,12 +38,137 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
@@ -29558,10 +29052,10 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462
 +    // Paper end - rewrite chunk system
 +
      public PoiManager(
-         RegionStorageInfo storageKey,
-         Path directory,
+         RegionStorageInfo info,
+         Path folder,
 @@ -64,6 +189,7 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
-             world
+             levelHeightAccessor
          );
          this.distanceTracker = new PoiManager.DistanceTracker();
 +        this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system
@@ -29571,30 +29065,29 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462
 @@ -197,8 +323,10 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
      }
  
-     public int sectionsToVillage(SectionPos pos) {
+     public int sectionsToVillage(SectionPos sectionPos) {
 -        this.distanceTracker.runAllUpdates();
--        return this.distanceTracker.getLevel(pos.asLong());
+-        return this.distanceTracker.getLevel(sectionPos.asLong());
 +        // Paper start - rewrite chunk system
 +        this.villageDistanceTracker.propagateUpdates();
-+        return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(pos)));
++        return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(sectionPos)));
 +        // Paper end - rewrite chunk system
      }
  
-     boolean isVillageCenter(long pos) {
+     boolean isVillageCenter(long chunkPos) {
 @@ -212,19 +340,26 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
  
      @Override
-     public void tick(BooleanSupplier shouldKeepTicking) {
--        super.tick(shouldKeepTicking);
+     public void tick(BooleanSupplier aheadOfTime) {
+-        super.tick(aheadOfTime);
 -        this.distanceTracker.runAllUpdates();
 +        this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system
      }
  
      @Override
--    protected void setDirty(long pos) {
--        super.setDirty(pos);
--        this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
-+    public void setDirty(long pos) { // Paper - public
+     protected void setDirty(long sectionPos) {
+-        super.setDirty(sectionPos);
+-        this.distanceTracker.update(sectionPos, this.distanceTracker.getLevelFromSource(sectionPos), false);
 +        // Paper start - rewrite chunk system
 +        final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionX(pos);
 +        final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionZ(pos);
@@ -29608,23 +29101,23 @@ index 96bc0ba60195e5e666d47b3a0b943b733986d96a..5930a430983061afddf20e3208ff2462
      }
  
      @Override
-     protected void onSectionLoad(long pos) {
--        this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
-+        this.updateDistanceTracking(pos); // Paper - rewrite chunk system
+     protected void onSectionLoad(long sectionKey) {
+-        this.distanceTracker.update(sectionKey, this.distanceTracker.getLevelFromSource(sectionKey), false);
++        this.updateDistanceTracking(sectionKey); // Paper - rewrite chunk system
      }
  
-     public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) {
+     public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection levelChunkSection) {
 @@ -263,7 +398,7 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> {
              .map(sectionPos -> Pair.of(sectionPos, this.getOrLoad(sectionPos.asLong())))
              .filter(pair -> !pair.getSecond().map(PoiSection::isValid).orElse(false))
              .map(pair -> pair.getFirst().chunk())
 -            .filter(chunkPos -> this.loadedChunks.add(chunkPos.toLong()))
 +            // Paper - rewrite chunk system
-             .forEach(chunkPos -> world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY));
+             .forEach(chunkPos -> levelReader.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY));
      }
  
 diff --git a/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57f892a768 100644
+index 324cc0686f0f5b1371b2bbea5b8c8fdb1f363006..39cd1e3d8192d7077d6b7864d33933097cc6b986 100644
 --- a/net/minecraft/world/entity/ai/village/poi/PoiSection.java
 +++ b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
 @@ -23,13 +23,27 @@ import net.minecraft.core.SectionPos;
@@ -29653,27 +29146,27 @@ index b9e0bc8f1e948614d986335de1f3d2df199eea81..712cbfc100e8aaf612d1d651dae64f57
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public PoiSection(Runnable updateListener) {
-         this(updateListener, true, ImmutableList.of());
+     public PoiSection(Runnable setDirty) {
+         this(setDirty, true, ImmutableList.of());
      }
 diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java
-index 63f02cdc67d9e88cc6998d0ae9d139c83e85b447..70b8023c3badc745f342d5b0ab54699e3923826a 100644
+index 613675c86787dd1147e140f1ef4d17b09ab9a74b..5cc19a67c7a29f1bda8cdd6921201f47fe6bf91f 100644
 --- a/net/minecraft/world/entity/decoration/ArmorStand.java
 +++ b/net/minecraft/world/entity/decoration/ArmorStand.java
-@@ -364,7 +364,7 @@ public class ArmorStand extends LivingEntity {
+@@ -316,7 +316,7 @@ public class ArmorStand extends LivingEntity {
      @Override
      protected void pushEntities() {
          if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
--        List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS);
-+        List<AbstractMinecart> list = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS); // Paper - optimise collisions
-         Iterator iterator = list.iterator();
- 
-         while (iterator.hasNext()) {
+-        for (Entity entity : this.level().getEntities(this, this.getBoundingBox(), RIDABLE_MINECARTS)) {
++        for (Entity entity : this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS)) { // Paper - optimise collisions
+             if (this.distanceToSqr(entity) <= 0.2) {
+                 entity.push(this);
+             }
 diff --git a/net/minecraft/world/level/ClipContext.java b/net/minecraft/world/level/ClipContext.java
-index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda6757b4477c 100644
+index 9f34fc4278860dd7bcfa1fd79b15e588b0cc3973..a7ebd624652cb6f0edc735bf6b9760e7b443594f 100644
 --- a/net/minecraft/world/level/ClipContext.java
 +++ b/net/minecraft/world/level/ClipContext.java
-@@ -18,7 +18,7 @@ public class ClipContext {
+@@ -17,7 +17,7 @@ public class ClipContext {
      private final Vec3 from;
      private final Vec3 to;
      private final ClipContext.Block block;
@@ -29681,9 +29174,9 @@ index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda67
 +    public final ClipContext.Fluid fluid; // Paper - optimise collisions - public
      private final CollisionContext collisionContext;
  
-     public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) {
+     public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, Entity entity) {
 diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java
-index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b150013e940ee6 100644
+index 300f3ed58109219d97846082941b860585f66fed..e81195df621159da67136f020fa7a6d39d1ee5ed 100644
 --- a/net/minecraft/world/level/EntityGetter.java
 +++ b/net/minecraft/world/level/EntityGetter.java
 @@ -15,7 +15,7 @@ import net.minecraft.world.phys.shapes.BooleanOp;
@@ -29692,22 +29185,22 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001
  
 -public interface EntityGetter {
 +public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system
-     List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate);
+     List<Entity> getEntities(@Nullable Entity entity, AABB area, Predicate<? super Entity> predicate);
  
-     <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate);
+     <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> entityTypeTest, AABB bounds, Predicate<? super T> predicate);
 @@ -30,21 +30,44 @@ public interface EntityGetter {
-         return this.getEntities(except, box, EntitySelector.NO_SPECTATORS);
+         return this.getEntities(entity, area, EntitySelector.NO_SPECTATORS);
      }
  
--    default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) {
+-    default boolean isUnobstructed(@Nullable Entity entity, VoxelShape shape) {
 -        if (shape.isEmpty()) {
 -            return true;
 -        } else {
--            for (Entity entity : this.getEntities(except, shape.bounds())) {
--                if (!entity.isRemoved()
--                    && entity.blocksBuilding
--                    && (except == null || !entity.isPassengerOfSameVehicle(except))
--                    && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) {
+-            for (Entity entity1 : this.getEntities(entity, shape.bounds())) {
+-                if (!entity1.isRemoved()
+-                    && entity1.blocksBuilding
+-                    && (entity == null || !entity1.isPassengerOfSameVehicle(entity))
+-                    && Shapes.joinIsNotEmpty(shape, Shapes.create(entity1.getBoundingBox()), BooleanOp.AND)) {
 -                    return false;
 +    // Paper start - rewrite chunk system
 +    @Override
@@ -29750,16 +29243,16 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001
 +        // Paper end - optimise collisions
      }
  
-     default <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB box) {
+     default <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB area) {
 @@ -52,23 +75,41 @@ public interface EntityGetter {
      }
  
-     default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
--        if (box.getSize() < 1.0E-7) {
+     default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB collisionBox) {
+-        if (collisionBox.getSize() < 1.0E-7) {
 -            return List.of();
 +        // Paper start - optimise collisions
 +        // first behavior change is to correctly check for empty AABB
-+        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) {
++        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(collisionBox)) {
 +            // reduce indirection by always returning type with same class
 +            return new java.util.ArrayList<>();
 +        }
@@ -29767,23 +29260,23 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001
 +        // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with.
 +        // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
 +        // specifically with boat collisions.
-+        box = box.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON);
++        collisionBox = collisionBox.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON);
 +
 +        final List<Entity> entities;
-+        if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) {
-+            entities = this.getEntities(entity, box, null);
++        if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$isHardColliding()) {
++            entities = this.getEntities(entity, collisionBox, null);
          } else {
 -            Predicate<Entity> predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith);
--            List<Entity> list = this.getEntities(entity, box.inflate(1.0E-7), predicate);
--            if (list.isEmpty()) {
+-            List<Entity> entities = this.getEntities(entity, collisionBox.inflate(1.0E-7), predicate);
+-            if (entities.isEmpty()) {
 -                return List.of();
 -            } else {
--                Builder<VoxelShape> builder = ImmutableList.builderWithExpectedSize(list.size());
+-                Builder<VoxelShape> builder = ImmutableList.builderWithExpectedSize(entities.size());
 -
--                for (Entity entity2 : list) {
--                    builder.add(Shapes.create(entity2.getBoundingBox()));
+-                for (Entity entity1 : entities) {
+-                    builder.add(Shapes.create(entity1.getBoundingBox()));
 -                }
-+            entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)this).moonrise$getHardCollidingEntities(entity, box, null);
++            entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter) this).moonrise$getHardCollidingEntities(entity, collisionBox, null);
 +        }
  
 -                return builder.build();
@@ -29807,10 +29300,10 @@ index e185a33b5b1f8e8e0a0e666b24ba3e9186a8a7ff..5d7a6e4b73f032db356e7ec369b15001
  
      // Paper start - Affects Spawning API
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b827401434bcfff1 100644
+index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9befe6a2040 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -84,6 +84,7 @@ import net.minecraft.world.level.storage.LevelData;
+@@ -79,6 +79,7 @@ import net.minecraft.world.level.storage.LevelData;
  import net.minecraft.world.level.storage.WritableLevelData;
  import net.minecraft.world.phys.AABB;
  import net.minecraft.world.phys.Vec3;
@@ -29818,25 +29311,25 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
  import net.minecraft.world.scores.Scoreboard;
  
  // CraftBukkit start
-@@ -105,7 +106,7 @@ import org.bukkit.entity.SpawnCategory;
+@@ -102,7 +103,7 @@ import org.bukkit.entity.SpawnCategory;
  import org.bukkit.event.block.BlockPhysicsEvent;
  // CraftBukkit end
  
 -public abstract class Level implements LevelAccessor, AutoCloseable {
 +public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system // Paper - optimise collisions
- 
      public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
      public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
-@@ -131,7 +132,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+     public static final ResourceKey<Level> NETHER = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("the_nether"));
+@@ -127,7 +128,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public float rainLevel;
      protected float oThunderLevel;
      public float thunderLevel;
 -    public final RandomSource random = RandomSource.create();
 +    public final RandomSource random = new ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Paper - replace random
-     /** @deprecated */
      @Deprecated
      private final RandomSource threadSafeRandom = RandomSource.createThreadSafe();
-@@ -207,7 +208,639 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+     private final Holder<DimensionType> dimensionTypeRegistration;
+@@ -202,6 +203,629 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
  
      public abstract ResourceKey<LevelStem> getTypeKey();
  
@@ -30463,9 +29956,15 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
 +    }
 +    // Paper end - optimise random ticking
 +
-     protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config & Anti-Xray
+     protected Level(
+         WritableLevelData levelData,
+         ResourceKey<Level> dimension,
+@@ -218,6 +842,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+         io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
+         java.util.concurrent.Executor executor // Paper - Anti-Xray
+     ) {
 +        // Paper start - getblock optimisations - cache world height/sections
-+        final DimensionType dimType = holder.value();
++        final DimensionType dimType = dimensionTypeRegistration.value();
 +        this.minY = dimType.minY();
 +        this.height = dimType.height();
 +        this.maxY = this.minY + this.height - 1;
@@ -30473,18 +29972,18 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
 +        this.maxSectionY = this.maxY >> 4;
 +        this.sectionsCount = this.maxSectionY - this.minSectionY + 1;
 +        // Paper end - getblock optimisations - cache world height/sections
-         this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+         this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot
          this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
          this.generator = gen;
-@@ -288,6 +921,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -298,6 +931,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
          this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
-         this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
+         this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
 +        this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
      }
  
      // Paper start - Cancel hit for vanished players
-@@ -557,7 +1191,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -567,7 +1201,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                  this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
              }
  
@@ -30493,19 +29992,19 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
                  this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
              }
  
-@@ -820,6 +1454,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -836,6 +1470,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
-         boolean flag = this.tickRateManager().runsNormally();
+         boolean runsNormally = this.tickRateManager().runsNormally();
  
 +        int tickedEntities = 0; // Paper - rewrite chunk system
 +
          int tilesThisCycle = 0;
          var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
          toRemove.add(null); // Paper - Fix MC-117075
-@@ -835,6 +1471,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -850,6 +1486,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                  // Spigot end
-             } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
-                 tickingblockentity.tick();
+             } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
+                 tickingBlockEntity.tick();
 +                // Paper start - rewrite chunk system
 +                if ((++tickedEntities & 7) == 0) {
 +                    ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
@@ -30514,16 +30013,18 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
              }
          }
          this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
-@@ -855,12 +1496,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -870,6 +1511,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
              entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
              // Paper end - Prevent block entity and entity crashes
          }
 +        this.moonrise$midTickTasks(); // Paper - rewrite chunk system
      }
+ 
      // Paper start - Option to prevent armor stands from doing entity lookups
-     @Override
+@@ -877,7 +1519,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public boolean noCollision(@Nullable Entity entity, AABB box) {
-         if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false;
+         if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups)
+             return false;
 -        return LevelAccessor.super.noCollision(entity, box);
 +        // Paper start - optimise collisions
 +        final int flags = entity == null ? (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY) : ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY;
@@ -30536,59 +30037,62 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
      }
      // Paper end - Option to prevent armor stands from doing entity lookups
  
-@@ -912,7 +1561,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1015,7 +1664,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+         if (this.isOutsideBuildHeight(pos)) {
+             return null;
+         } else {
+-            return !this.isClientSide && Thread.currentThread() != this.thread
++            return !this.isClientSide && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThread() // Paper - rewrite chunk system
+                 ? null
+                 : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE);
          }
-         // Paper end - Perf: Optimize capturedTileEntities lookup
-         // CraftBukkit end
--        return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE));
-+        return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system
-     }
- 
-     public void setBlockEntity(BlockEntity blockEntity) {
-@@ -1004,23 +1653,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1108,22 +1757,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+     public List<Entity> getEntities(@Nullable Entity entity, AABB boundingBox, Predicate<? super Entity> predicate) {
          Profiler.get().incrementCounter("getEntities");
          List<Entity> list = Lists.newArrayList();
- 
--        this.getEntities().get(box, (entity1) -> {
--            if (entity1 != except && predicate.test(entity1)) {
+-        this.getEntities().get(boundingBox, entity1 -> {
+-            if (entity1 != entity && predicate.test(entity1)) {
 -                list.add(entity1);
 -            }
--
 -        });
--        Iterator iterator = this.dragonParts().iterator();
+ 
+-        for (EnderDragonPart enderDragonPart : this.dragonParts()) {
+-            if (enderDragonPart != entity
+-                && enderDragonPart.parentMob != entity
+-                && predicate.test(enderDragonPart)
+-                && boundingBox.intersects(enderDragonPart.getBoundingBox())) {
+-                list.add(enderDragonPart);
+-            }
+-        }
 +        // Paper start - rewrite chunk system
 +        final List<Entity> ret = new java.util.ArrayList<>();
  
--        while (iterator.hasNext()) {
--            EnderDragonPart entitycomplexpart = (EnderDragonPart) iterator.next();
-+        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(except, box, ret, predicate);
- 
--            if (entitycomplexpart != except && entitycomplexpart.parentMob != except && predicate.test(entitycomplexpart) && box.intersects(entitycomplexpart.getBoundingBox())) {
--                list.add(entitycomplexpart);
--            }
--        }
-+        ca.spottedleaf.moonrise.common.PlatformHooks.get().addToGetEntities((Level)(Object)this, except, box, predicate, ret);
- 
 -        return list;
++        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entity, boundingBox, ret, predicate);
++
++        ca.spottedleaf.moonrise.common.PlatformHooks.get().addToGetEntities((Level)(Object)this, entity, boundingBox, predicate, ret);
++
 +        return ret;
 +        // Paper end - rewrite chunk system
      }
  
      @Override
-@@ -1035,36 +1676,94 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-         this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE);
+@@ -1137,33 +1780,94 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+         this.getEntities(entityTypeTest, bounds, predicate, output, Integer.MAX_VALUE);
      }
  
--    public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate, List<? super T> result, int limit) {
+-    public <T extends Entity> void getEntities(
+-        EntityTypeTest<Entity, T> entityTypeTest, AABB bounds, Predicate<? super T> predicate, List<? super T> output, int maxResults
+-    ) {
 +    // Paper start - rewrite chunk system
 +    public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
 +                                               final AABB boundingBox, final Predicate<? super T> predicate,
 +                                               final List<? super T> into, final int maxCount) {
          Profiler.get().incrementCounter("getEntities");
--        this.getEntities().get(filter, box, (entity) -> {
+-        this.getEntities().get(entityTypeTest, bounds, entity -> {
 -            if (predicate.test(entity)) {
--                result.add(entity);
--                if (result.size() >= limit) {
+-                output.add(entity);
+-                if (output.size() >= maxResults) {
 -                    return AbortableIterationConsumer.Continuation.ABORT;
 -                }
 +
@@ -30604,9 +30108,15 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
              }
 +        }
  
--            if (entity instanceof EnderDragon entityenderdragon) {
--                EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities();
--                int j = aentitycomplexpart.length;
+-            if (entity instanceof EnderDragon enderDragon) {
+-                for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
+-                    T entity1 = entityTypeTest.tryCast(enderDragonPart);
+-                    if (entity1 != null && predicate.test(entity1)) {
+-                        output.add(entity1);
+-                        if (output.size() >= maxResults) {
+-                            return AbortableIterationConsumer.Continuation.ABORT;
+-                        }
+-                    }
 +        if (entityTypeTest == null) {
 +            if (maxCount != Integer.MAX_VALUE) {
 +                ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities((Entity)null, boundingBox, (List)into, (Predicate)predicate, maxCount);
@@ -30618,18 +30128,9 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
 +                return;
 +            }
 +        }
- 
--                for (int k = 0; k < j; ++k) {
--                    EnderDragonPart entitycomplexpart = aentitycomplexpart[k];
--                    T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error
++
 +        final Class<? extends Entity> base = entityTypeTest.getBaseClass();
- 
--                    if (t0 != null && predicate.test(t0)) {
--                        result.add(t0);
--                        if (result.size() >= limit) {
--                            return AbortableIterationConsumer.Continuation.ABORT;
--                        }
--                    }
++
 +        final Predicate<? super T> modifiedPredicate;
 +        if (predicate == null) {
 +            modifiedPredicate = (final T obj) -> {
@@ -30692,7 +30193,7 @@ index 2a078293332efe4369f314ab021dfa16f63f7f3f..f477c5817f022ce7c4ad25e9b8274014
      @Nullable
      public abstract Entity getEntity(int id);
 diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java
-index 5eb8982678110fabb82a93c5ec67c666b7fde017..ade435de0af4ee3566fa4a490df53cddd2f6531c 100644
+index 2709803b9266ff4a2034d83321cd0ba4e30fc0aa..26c8c1e5598daf3550aef05b12218c47bda6618b 100644
 --- a/net/minecraft/world/level/LevelReader.java
 +++ b/net/minecraft/world/level/LevelReader.java
 @@ -22,7 +22,18 @@ import net.minecraft.world.level.dimension.DimensionType;
@@ -30713,10 +30214,10 @@ index 5eb8982678110fabb82a93c5ec67c666b7fde017..ade435de0af4ee3566fa4a490df53cdd
 +    // Paper end - rewrite chunk system
 +
      @Nullable
-     ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
+     ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk);
  
 diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f81daa428 100644
+index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e997a5d9d1 100644
 --- a/net/minecraft/world/level/ServerExplosion.java
 +++ b/net/minecraft/world/level/ServerExplosion.java
 @@ -64,6 +64,249 @@ public class ServerExplosion implements Explosion {
@@ -30967,61 +30468,60 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
 +    }
 +    // Paper end - collisions optimisations
  
-     public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) {
-         this.level = world;
-@@ -127,65 +370,101 @@ public class ServerExplosion implements Explosion {
+     public ServerExplosion(
+         ServerLevel level,
+@@ -135,63 +378,102 @@ public class ServerExplosion implements Explosion {
      }
  
      private List<BlockPos> calculateExplodedPositions() {
--        Set<BlockPos> set = new HashSet();
--        boolean flag = true;
+-        Set<BlockPos> set = new HashSet<>();
+-        int i = 16;
 -
--        for (int i = 0; i < 16; ++i) {
--            for (int j = 0; j < 16; ++j) {
--                for (int k = 0; k < 16; ++k) {
--                    if (i == 0 || i == 15 || j == 0 || j == 15 || k == 0 || k == 15) {
--                        double d0 = (double) ((float) i / 15.0F * 2.0F - 1.0F);
--                        double d1 = (double) ((float) j / 15.0F * 2.0F - 1.0F);
--                        double d2 = (double) ((float) k / 15.0F * 2.0F - 1.0F);
--                        double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
--
--                        d0 /= d3;
--                        d1 /= d3;
--                        d2 /= d3;
+-        for (int i1 = 0; i1 < 16; i1++) {
+-            for (int i2 = 0; i2 < 16; i2++) {
+-                for (int i3 = 0; i3 < 16; i3++) {
+-                    if (i1 == 0 || i1 == 15 || i2 == 0 || i2 == 15 || i3 == 0 || i3 == 15) {
+-                        double d = i1 / 15.0F * 2.0F - 1.0F;
+-                        double d1 = i2 / 15.0F * 2.0F - 1.0F;
+-                        double d2 = i3 / 15.0F * 2.0F - 1.0F;
+-                        double squareRoot = Math.sqrt(d * d + d1 * d1 + d2 * d2);
+-                        d /= squareRoot;
+-                        d1 /= squareRoot;
+-                        d2 /= squareRoot;
 -                        float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
--                        double d4 = this.center.x;
--                        double d5 = this.center.y;
--                        double d6 = this.center.z;
+-                        double d3 = this.center.x;
+-                        double d4 = this.center.y;
+-                        double d5 = this.center.z;
 -
 -                        for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
--                            BlockPos blockposition = BlockPos.containing(d4, d5, d6);
--                            BlockState iblockdata = this.level.getBlockState(blockposition);
--                            if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
--                            FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
--
--                            if (!this.level.isInWorldBounds(blockposition)) {
+-                            BlockPos blockPos = BlockPos.containing(d3, d4, d5);
+-                            BlockState blockState = this.level.getBlockState(blockPos);
+-                            if (!blockState.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
+-                            FluidState fluidState = blockState.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
+-                            if (!this.level.isInWorldBounds(blockPos)) {
 -                                break;
 -                            }
 +        // Paper start - collision optimisations
 +        final ObjectArrayList<BlockPos> ret = new ObjectArrayList<>();
  
--                            Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid);
+-                            Optional<Float> blockExplosionResistance = this.damageCalculator
+-                                .getBlockExplosionResistance(this, this.level, blockPos, blockState, fluidState);
+-                            if (blockExplosionResistance.isPresent()) {
+-                                f -= (blockExplosionResistance.get() + 0.3F) * 0.3F;
+-                            }
 +        final Vec3 center = this.center;
  
--                            if (optional.isPresent()) {
--                                f -= ((Float) optional.get() + 0.3F) * 0.3F;
--                            }
-+        final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache = this.directMappedBlockCache;
- 
--                            if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
--                                set.add(blockposition);
+-                            if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, f)) {
+-                                set.add(blockPos);
 -                                // Paper start - prevent headless pistons from forming
--                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
--                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition);
+-                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.getBlock() == Blocks.MOVING_PISTON) {
+-                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockPos);
 -                                    if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
--                                        net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
--                                        set.add(blockposition.relative(direction.getOpposite()));
+-                                        net.minecraft.core.Direction direction = blockState.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
+-                                        set.add(blockPos.relative(direction.getOpposite()));
 -                                    }
++        final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache = this.directMappedBlockCache;
++
 +        // use initial cache value that is most likely to be used: the source position
 +        final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache initialCache;
 +        {
@@ -31097,10 +30597,10 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
                                  }
 -                                // Paper end - prevent headless pistons from forming
                              }
--
--                            d4 += d0 * 0.30000001192092896D;
--                            d5 += d1 * 0.30000001192092896D;
--                            d6 += d2 * 0.30000001192092896D;
+ 
+-                            d3 += d * 0.3F;
+-                            d4 += d1 * 0.3F;
+-                            d5 += d2 * 0.3F;
 +                            // Paper end - prevent headless pistons from forming
                          }
                      }
@@ -31114,13 +30614,13 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
 +            } while (power > 0.0f);
          }
  
--        return new ObjectArrayList(set);
+-        return new ObjectArrayList<>(set);
 +        return ret;
 +        // Paper end - collision optimisations
      }
  
      private void hurtEntities() {
-@@ -391,6 +670,14 @@ public class ServerExplosion implements Explosion {
+@@ -372,6 +654,14 @@ public class ServerExplosion implements Explosion {
              return;
          }
          // CraftBukkit end
@@ -31132,10 +30632,10 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
 +        this.directMappedBlockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
 +        this.mutablePos = new BlockPos.MutableBlockPos();
 +        // Paper end - collision optimisations
-         this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, this.center);
+         this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
          List<BlockPos> list = this.calculateExplodedPositions();
- 
-@@ -406,6 +693,13 @@ public class ServerExplosion implements Explosion {
+         this.hurtEntities();
+@@ -385,6 +675,13 @@ public class ServerExplosion implements Explosion {
          if (this.fire) {
              this.createFire(list);
          }
@@ -31146,10 +30646,10 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
 +        this.directMappedBlockCache = null;
 +        this.mutablePos = null;
 +        // Paper end - collision optimisations
- 
      }
  
-@@ -499,12 +793,12 @@ public class ServerExplosion implements Explosion {
+     private static void addOrAppendStack(List<ServerExplosion.StackCollector> stackCollectors, ItemStack stack, BlockPos pos) {
+@@ -475,12 +772,12 @@ public class ServerExplosion implements Explosion {
      // Paper start - Optimize explosions
      private float getBlockDensity(Vec3 vec3d, Entity entity) {
          if (!this.level.paperConfig().environment.optimizeExplosions) {
@@ -31165,72 +30665,72 @@ index b8ffe547ad29645b65c3df8bd6ccb7c20985711d..685ccfb73bf7125585ef90b6a0f51b2f
          }
  
 diff --git a/net/minecraft/world/level/biome/Biome.java b/net/minecraft/world/level/biome/Biome.java
-index 9f86b69d8c93a63e0b408ea52519f1fc2e798226..78afd8e51e03cd53c12b64db8a817da457f81bef 100644
+index ea521e1d636b8ffdeb22882dfc8875b2ddbd8da1..7b666bbeefe296e7fdbadcc72dbf9e602f73e925 100644
 --- a/net/minecraft/world/level/biome/Biome.java
 +++ b/net/minecraft/world/level/biome/Biome.java
-@@ -113,20 +113,7 @@ public final class Biome {
+@@ -117,20 +117,7 @@ public final class Biome {
  
      @Deprecated
-     public float getTemperature(BlockPos blockPos, int seaLevel) {
--        long l = blockPos.asLong();
--        Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap = this.temperatureCache.get();
--        float f = long2FloatLinkedOpenHashMap.get(l);
+     public float getTemperature(BlockPos pos, int seaLevel) {
+-        long packedBlockPos = pos.asLong();
+-        Long2FloatLinkedOpenHashMap map = this.temperatureCache.get();
+-        float f = map.get(packedBlockPos);
 -        if (!Float.isNaN(f)) {
 -            return f;
 -        } else {
--            float g = this.getHeightAdjustedTemperature(blockPos, seaLevel);
--            if (long2FloatLinkedOpenHashMap.size() == 1024) {
--                long2FloatLinkedOpenHashMap.removeFirstFloat();
+-            float heightAdjustedTemperature = this.getHeightAdjustedTemperature(pos, seaLevel);
+-            if (map.size() == 1024) {
+-                map.removeFirstFloat();
 -            }
 -
--            long2FloatLinkedOpenHashMap.put(l, g);
--            return g;
+-            map.put(packedBlockPos, heightAdjustedTemperature);
+-            return heightAdjustedTemperature;
 -        }
-+        return this.getHeightAdjustedTemperature(blockPos, seaLevel); // Paper - optimise random ticking
++        return this.getHeightAdjustedTemperature(pos, seaLevel); // Paper - optimise random ticking
      }
  
-     public boolean shouldFreeze(LevelReader world, BlockPos blockPos) {
+     public boolean shouldFreeze(LevelReader level, BlockPos pos) {
 diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java
-index 01352cc83b25eb0e30b7e0ff521fc7c1b3d5155b..90f8360f547ce709fd13ee34f8e67d8bfa94b498 100644
+index 8d98cba3830dc5dfb5cae9a6f5fedfffee0d2cd8..73962e79a0f3d892e3155443a1b84508b0f4042e 100644
 --- a/net/minecraft/world/level/biome/BiomeManager.java
 +++ b/net/minecraft/world/level/biome/BiomeManager.java
 @@ -98,8 +98,7 @@ public class BiomeManager {
      }
  
-     private static double getFiddle(long l) {
--        double d = (double)Math.floorMod(l >> 24, 1024) / 1024.0;
+     private static double getFiddle(long seed) {
+-        double d = Math.floorMod(seed >> 24, 1024) / 1024.0;
 -        return (d - 0.5) * 0.9;
-+        return (double)(((l >> 24) & (1024 - 1)) - (1024/2)) * (0.9 / 1024.0); // Paper - avoid floorMod, fp division, and fp subtraction
++        return (double)(((seed >> 24) & (1024 - 1)) - (1024/2)) * (0.9 / 1024.0); // Paper - avoid floorMod, fp division, and fp subtraction
      }
  
      public interface NoiseBiomeSource {
 diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java
-index 1aa69f4a7005242925124c74b8229e6fa7362717..c0b1f903962b25d8ff6c2b4fcd2be0e45de09b35 100644
+index 3eedc6c2a1325a0fd1baa14e246aeda27de4117d..34177f27680612055526553c849777067882ad74 100644
 --- a/net/minecraft/world/level/block/Block.java
 +++ b/net/minecraft/world/level/block/Block.java
-@@ -271,7 +271,7 @@ public class Block extends BlockBehaviour implements ItemLike {
+@@ -259,7 +259,7 @@ public class Block extends BlockBehaviour implements ItemLike {
      }
  
      public static boolean isShapeFullBlock(VoxelShape shape) {
--        return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape);
+-        return SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape);
 +        return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); // Paper - optimise collisions
      }
  
-     public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {}
+     public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
 diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
-index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe4fe74166 100644
+index e0c3d8923f56c059b5d6111ab2ff01be3566f6d9..73b913da595e7ad2de8f363f342de98f6c647b71 100644
 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java
 +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
-@@ -797,7 +797,7 @@ public abstract class BlockBehaviour implements FeatureElement {
-         boolean test(BlockState state, BlockGetter world, BlockPos pos);
+@@ -417,7 +417,7 @@ public abstract class BlockBehaviour implements FeatureElement {
+         return this.properties.destroyTime;
      }
  
 -    public abstract static class BlockStateBase extends StateHolder<Block, BlockState> {
 +    public abstract static class BlockStateBase extends StateHolder<Block, BlockState> implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState, ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState { // Paper - rewrite chunk system // Paper - optimise collisions
- 
          private static final Direction[] DIRECTIONS = Direction.values();
-         private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = (VoxelShape[]) Util.make(new VoxelShape[BlockBehaviour.BlockStateBase.DIRECTIONS.length], (avoxelshape) -> {
-@@ -841,6 +841,76 @@ public abstract class BlockBehaviour implements FeatureElement {
+         private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = Util.make(new VoxelShape[DIRECTIONS.length], shape -> Arrays.fill(shape, Shapes.empty()));
+         private static final VoxelShape[] FULL_BLOCK_OCCLUSION_SHAPES = Util.make(
+@@ -456,6 +456,76 @@ public abstract class BlockBehaviour implements FeatureElement {
          private boolean propagatesSkylightDown;
          private int lightBlock;
  
@@ -31304,13 +30804,13 @@ index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe
 +        }
 +        // Paper end - optimise collisions
 +
-         protected BlockStateBase(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
-             super(block, propertyMap, codec);
-             this.fluidState = Fluids.EMPTY.defaultFluidState();
-@@ -925,6 +995,41 @@ public abstract class BlockBehaviour implements FeatureElement {
+         protected BlockStateBase(Block owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<BlockState> propertiesCodec) {
+             super(owner, values, propertiesCodec);
+             BlockBehaviour.Properties properties = owner.properties;
+@@ -534,6 +604,41 @@ public abstract class BlockBehaviour implements FeatureElement {
  
-             this.propagatesSkylightDown = ((Block) this.owner).propagatesSkylightDown(this.asState());
-             this.lightBlock = ((Block) this.owner).getLightBlock(this.asState());
+             this.propagatesSkylightDown = this.owner.propagatesSkylightDown(this.asState());
+             this.lightBlock = this.owner.getLightBlock(this.asState());
 +            // Paper start - rewrite chunk system
 +            this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
 +            // Paper end - rewrite chunk system
@@ -31350,7 +30850,7 @@ index b1101156b281d800f18b25208018722bbecded9f..8c0f332a1a0918f60226d969918ae7fe
  
          public Block getBlock() {
 diff --git a/net/minecraft/world/level/block/state/StateHolder.java b/net/minecraft/world/level/block/state/StateHolder.java
-index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d769478de477bd 100644
+index 2f2dbf02a9732a7e640a6c730d4fc1443e723933..098518383d2c07491e047749ce3a834e98b85b1d 100644
 --- a/net/minecraft/world/level/block/state/StateHolder.java
 +++ b/net/minecraft/world/level/block/state/StateHolder.java
 @@ -15,7 +15,7 @@ import java.util.stream.Collectors;
@@ -31381,10 +30881,10 @@ index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d76947
 +    }
 +    // Paper end - optimise blockstate property access
 +
-     protected StateHolder(O owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<S> codec) {
+     protected StateHolder(O owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<S> propertiesCodec) {
          this.owner = owner;
-         this.values = propertyMap;
-         this.propertiesCodec = codec;
+         this.values = values;
+         this.propertiesCodec = propertiesCodec;
 +        // Paper start - optimise blockstate property access
 +        this.optimisedTable = new ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable<>(this.values.keySet());
 +        this.tableIndex = this.optimisedTable.getIndex((StateHolder<O, S>)(Object)this);
@@ -31460,45 +30960,49 @@ index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d76947
 +        // Paper end - optimise blockstate property access
      }
  
-     private <T extends Comparable<T>, V extends T> S setValueInternal(Property<T> property, V newValue, Comparable<?> oldValue) {
-@@ -125,18 +148,27 @@ public abstract class StateHolder<O, S> {
+     private <T extends Comparable<T>, V extends T> S setValueInternal(Property<T> property, V value, Comparable<?> comparable) {
+@@ -125,21 +148,27 @@ public abstract class StateHolder<O, S> {
      }
  
-     public void populateNeighbours(Map<Map<Property<?>, Comparable<?>>, S> states) {
+     public void populateNeighbours(Map<Map<Property<?>, Comparable<?>>, S> possibleStateMap) {
 -        if (this.neighbours != null) {
 -            throw new IllegalStateException();
 -        } else {
 -            Map<Property<?>, S[]> map = new Reference2ObjectArrayMap<>(this.values.size());
+-
+-            for (Entry<Property<?>, Comparable<?>> entry : this.values.entrySet()) {
+-                Property<?> property = entry.getKey();
+-                map.put(
+-                    property,
+-                    (S[]) property.getPossibleValues().stream().map(comparable -> possibleStateMap.get(this.makeNeighbourValues(property, comparable))).toArray()
+-                );
+-            }
 +        // Paper start - optimise blockstate property access
-+        final Map<Map<Property<?>, Comparable<?>>, S> map = states;
++        final Map<Map<Property<?>, Comparable<?>>, S> map = possibleStateMap;
 +        if (this.optimisedTable.isLoaded()) {
 +            return;
 +        }
 +        this.optimisedTable.loadInTable(map);
  
--            for (Entry<Property<?>, Comparable<?>> entry : this.values.entrySet()) {
--                Property<?> property = entry.getKey();
--                map.put(property, property.getPossibleValues().stream().map(value -> states.get(this.makeNeighbourValues(property, value))).toArray());
--            }
+-            this.neighbours = map;
 +        // de-duplicate the tables
 +        for (final Map.Entry<Map<Property<?>, Comparable<?>>, S> entry : map.entrySet()) {
 +            final S value = entry.getValue();
 +            ((StateHolder<O, S>)value).optimisedTable = this.optimisedTable;
-+        }
- 
--            this.neighbours = map;
+         }
++
 +        // remove values arrays
 +        for (final Map.Entry<Map<Property<?>, Comparable<?>>, S> entry : map.entrySet()) {
 +            final S value = entry.getValue();
 +            ((StateHolder<O, S>)value).values = null;
-         }
++        }
 +
 +        return;
 +        // Paper end  optimise blockstate property access
      }
  
      private Map<Property<?>, Comparable<?>> makeNeighbourValues(Property<?> property, Comparable<?> value) {
-@@ -146,7 +178,11 @@ public abstract class StateHolder<O, S> {
+@@ -149,7 +178,11 @@ public abstract class StateHolder<O, S> {
      }
  
      public Map<Property<?>, Comparable<?>> getValues() {
@@ -31510,9 +31014,9 @@ index 422b364764e0df16ca250b4939d7b226e69c0840..815ee11aa5ed3448ff255e9c36d76947
 +        // Paper end - optimise blockstate property access
      }
  
-     protected static <O, S extends StateHolder<O, S>> Codec<S> codec(Codec<O> codec, Function<O, S> ownerToStateFunction) {
+     protected static <O, S extends StateHolder<O, S>> Codec<S> codec(Codec<O> propertyMap, Function<O, S> holderFunction) {
 diff --git a/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/net/minecraft/world/level/block/state/properties/BooleanProperty.java
-index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196713b14a7 100644
+index 40c83ff614169be8ab988f3ab476eca93acee28d..654f14e0fd1920ec94300649719c2460918899e2 100644
 --- a/net/minecraft/world/level/block/state/properties/BooleanProperty.java
 +++ b/net/minecraft/world/level/block/state/properties/BooleanProperty.java
 @@ -3,13 +3,23 @@ package net.minecraft.world.level.block.state.properties;
@@ -31541,7 +31045,7 @@ index ea76aa490358e9e1d13350ba0ea246ec2c423894..98058505d36baf74008da08339afc196
  
      @Override
 diff --git a/net/minecraft/world/level/block/state/properties/EnumProperty.java b/net/minecraft/world/level/block/state/properties/EnumProperty.java
-index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5640127a7 100644
+index c56728863a084a5e1f6e6d9489d00bb0c83af168..1d785b7bb046ef291342efa3ede6cdeb460f12fb 100644
 --- a/net/minecraft/world/level/block/state/properties/EnumProperty.java
 +++ b/net/minecraft/world/level/block/state/properties/EnumProperty.java
 @@ -10,11 +10,39 @@ import java.util.function.Predicate;
@@ -31582,8 +31086,8 @@ index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5
 +    }
 +    // Paper end - optimise blockstate property access
 +
-     private EnumProperty(String name, Class<T> type, List<T> values) {
-         super(name, type);
+     private EnumProperty(String name, Class<T> clazz, List<T> values) {
+         super(name, clazz);
          if (values.isEmpty()) {
 @@ -37,6 +65,7 @@ public final class EnumProperty<T extends Enum<T> & StringRepresentable> extends
  
@@ -31594,7 +31098,7 @@ index 85a197232be9377c0313ec00e8f935551e2c60e0..30b2fce9e47ffcc3de1542b1d0f073f5
  
      @Override
 diff --git a/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
-index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0efe1506d 100644
+index 28a15908420cb239c317d58f7e3a1df3c6278b33..b7543eb5a8f87bc7bd275ed9d46a68072c1e57b5 100644
 --- a/net/minecraft/world/level/block/state/properties/IntegerProperty.java
 +++ b/net/minecraft/world/level/block/state/properties/IntegerProperty.java
 @@ -5,11 +5,33 @@ import java.util.List;
@@ -31641,7 +31145,7 @@ index 55a87592a99105dbf57b26fb6ccba695295fce24..986365acc9983331a7982ea2e1eac2b0
  
      @Override
 diff --git a/net/minecraft/world/level/block/state/properties/Property.java b/net/minecraft/world/level/block/state/properties/Property.java
-index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276f0386e4d 100644
+index 92350434746f06bbf4a161c6bc42602de7b45220..1c24f38d21da1be9740512981f219924c5d3cf76 100644
 --- a/net/minecraft/world/level/block/state/properties/Property.java
 +++ b/net/minecraft/world/level/block/state/properties/Property.java
 @@ -10,7 +10,7 @@ import java.util.stream.Stream;
@@ -31685,15 +31189,15 @@ index fcf04c5c58ff35d38c5bf0df562ae2f8dc98a0ee..0b116160924300a9d62ad5948bfaf276
 +    public abstract int moonrise$getIdFor(final T value);
 +    // Paper end - optimise blockstate property access
 +
-     protected Property(String name, Class<T> type) {
-         this.clazz = type;
+     protected Property(String name, Class<T> clazz) {
+         this.clazz = clazz;
          this.name = name;
 +        this.id = ID_GENERATOR.getAndIncrement(); // Paper - optimise blockstate property access
      }
  
      public Property.Value<T> value(T value) {
 diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
-index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95c9ac2dbb 100644
+index bc688ad1097ef4159dfc5f96d963a9fa63262e20..6e0210f32c679e3a33256cb05dc282f2e8b7d856 100644
 --- a/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/net/minecraft/world/level/chunk/ChunkAccess.java
 @@ -57,7 +57,7 @@ import net.minecraft.world.ticks.SavedTick;
@@ -31702,10 +31206,10 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
  
 -public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, LightChunk, StructureAccess {
 +public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, LightChunk, StructureAccess, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk { // Paper - rewrite chunk system
- 
      public static final int NO_FILLED_SECTION = -1;
      private static final Logger LOGGER = LogUtils.getLogger();
-@@ -77,7 +77,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+     private static final LongSet EMPTY_REFERENCE_SET = new LongOpenHashSet();
+@@ -75,7 +75,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      @Nullable
      protected BlendingData blendingData;
      public final Map<Heightmap.Types, Heightmap> heightmaps = Maps.newEnumMap(Heightmap.Types.class);
@@ -31714,9 +31218,9 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
      private final Map<Structure, StructureStart> structureStarts = Maps.newHashMap();
      private final Map<Structure, LongSet> structuresRefences = Maps.newHashMap();
      protected final Map<BlockPos, CompoundTag> pendingBlockEntities = Maps.newHashMap();
-@@ -90,6 +90,57 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
-     public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
+@@ -88,6 +88,57 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      // CraftBukkit end
+     public final Registry<Biome> biomeRegistry; // CraftBukkit
  
 +    // Paper start - rewrite chunk system
 +    private volatile ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles;
@@ -31769,22 +31273,22 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
 +    private final int maxSection;
 +    // Paper end - get block chunk optimisation
 +
-     public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
-         this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
-         this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
-@@ -99,7 +150,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+     public ChunkAccess(
+         ChunkPos chunkPos,
+         UpgradeData upgradeData,
+@@ -105,7 +156,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
          this.inhabitedTime = inhabitedTime;
-         this.postProcessing = new ShortList[heightLimitView.getSectionsCount()];
+         this.postProcessing = new ShortList[levelHeightAccessor.getSectionsCount()];
          this.blendingData = blendingData;
--        this.skyLightSources = new ChunkSkyLightSources(heightLimitView);
+-        this.skyLightSources = new ChunkSkyLightSources(levelHeightAccessor);
 +        // Paper - rewrite chunk system
-         if (sectionArray != null) {
-             if (this.sections.length == sectionArray.length) {
-                 System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length);
-@@ -111,6 +162,16 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+         if (sections != null) {
+             if (this.sections.length == sections.length) {
+                 System.arraycopy(sections, 0, this.sections, 0, this.sections.length);
+@@ -116,6 +167,16 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+ 
          this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method
-         // CraftBukkit start
-         this.biomeRegistry = biomeRegistry;
+         this.biomeRegistry = biomeRegistry; // Craftbukkit
 +        // Paper start - rewrite chunk system
 +        if (!((Object)this instanceof ImposterProtoChunk)) {
 +            this.starlight$setBlockNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(heightLimitView));
@@ -31796,30 +31300,26 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
 +        this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(levelHeightAccessor);
 +        // Paper end - get block chunk optimisation
      }
-     public final Registry<Biome> biomeRegistry;
-     // CraftBukkit end
-@@ -457,22 +518,22 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+ 
+     private void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) { // Paper - Anti-Xray - make it a non-static method
+@@ -442,18 +503,22 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
  
      @Override
-     public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
+     public Holder<Biome> getNoiseBiome(int x, int y, int z) {
 -        try {
--            int l = QuartPos.fromBlock(this.getMinY());
--            int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
--            int j1 = Mth.clamp(biomeY, l, i1);
--            int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
--
--            return this.sections[k1].getNoiseBiome(biomeX & 3, j1 & 3, biomeZ & 3);
--        } catch (Throwable throwable) {
--            CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting biome");
--            CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being got");
--
--            crashreportsystemdetails.setDetail("Location", () -> {
--                return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ);
--            });
--            throw new ReportedException(crashreport);
+-            int quartPosMinY = QuartPos.fromBlock(this.getMinY());
+-            int i = quartPosMinY + QuartPos.fromBlock(this.getHeight()) - 1;
+-            int i1 = Mth.clamp(y, quartPosMinY, i);
+-            int sectionIndex = this.getSectionIndex(QuartPos.toBlock(i1));
+-            return this.sections[sectionIndex].getNoiseBiome(x & 3, i1 & 3, z & 3);
+-        } catch (Throwable var8) {
+-            CrashReport crashReport = CrashReport.forThrowable(var8, "Getting biome");
+-            CrashReportCategory crashReportCategory = crashReport.addCategory("Biome being got");
+-            crashReportCategory.setDetail("Location", () -> CrashReportCategory.formatLocation(this, x, y, z));
+-            throw new ReportedException(crashReport);
 +        // Paper start - get block chunk optimisation
-+        int sectionY = (biomeY >> 2) - this.minSection;
-+        int rel = biomeY & 3;
++        int sectionY = (y >> 2) - this.minSection;
++        int rel = y & 3;
 +
 +        final LevelChunkSection[] sections = this.sections;
 +
@@ -31831,12 +31331,12 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
 +            rel = 3;
          }
 +
-+        return sections[sectionY].getNoiseBiome(biomeX & 3, rel, biomeZ & 3);
++        return sections[sectionY].getNoiseBiome(x & 3, rel, z & 3);
 +        // Paper end - get block chunk optimisation
      }
- 
      // CraftBukkit start
-@@ -529,12 +590,12 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
+     public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+@@ -507,12 +572,12 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
      }
  
      public void initializeLightSources() {
@@ -31850,31 +31350,31 @@ index 9d240aa87101662480cdd510839e017aa9c58fcd..f87abb22dd161b2b74401086de80dc95
 +        return null; // Paper - rewrite chunk system
      }
  
-     public static record PackedTicks(List<SavedTick<Block>> blocks, List<SavedTick<Fluid>> fluids) {
+     public record PackedTicks(List<SavedTick<Block>> blocks, List<SavedTick<Fluid>> fluids) {
 diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
-index ca6928f959eb63ac9183ba6c95738609839a7d32..e0cb360ece042c4fc6aa0d10106923fe25288f5c 100644
+index 89f027f0edf2ca59966efe209e567108665cbe0c..7944c2bb854ee52a55d7c54e5f814e87d709e70e 100644
 --- a/net/minecraft/world/level/chunk/ChunkGenerator.java
 +++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
-@@ -120,7 +120,7 @@ public abstract class ChunkGenerator {
+@@ -116,7 +116,7 @@ public abstract class ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
-             chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler());
+             chunk.fillBiomesFromNoise(this.biomeSource, randomState.sampler());
              return chunk;
 -        }, Util.backgroundExecutor().forName("init_biomes"));
 +        }, Runnable::run);  // Paper - rewrite chunk system
      }
  
-     public abstract void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, StructureManager structureAccessor, ChunkAccess chunk);
+     public abstract void applyCarvers(
 @@ -315,7 +315,7 @@ public abstract class ChunkGenerator {
-                         return Pair.of(placement.getLocatePos(pos), holder);
-                     }
+                     return Pair.of(placement.getLocatePos(chunkPos), holder);
+                 }
  
--                    ChunkAccess ichunkaccess = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS);
-+                    ChunkAccess ichunkaccess = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader)world).moonrise$syncLoadNonFull(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); // Paper - rewrite chunk system
- 
-                     structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess);
-                 } while (structurestart == null);
+-                ChunkAccess chunk = level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS);
++                ChunkAccess chunk = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader)level).moonrise$syncLoadNonFull(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS); // Paper - rewrite chunk system
+                 StructureStart startForStructure = structureManager.getStartForStructure(SectionPos.bottomOf(chunk), holder.value(), chunk);
+                 if (startForStructure != null && startForStructure.isValid() && (!skipKnownStructures || tryAddReference(structureManager, startForStructure))) {
+                     return Pair.of(placement.getLocatePos(startForStructure.getChunkPos()), holder);
 diff --git a/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
-index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95a960cb19 100644
+index ec128412e4a0d3d21e3b6abea8cd06c03656f00c..07b7e82c7d24f52c0251e09195451841d47883c9 100644
 --- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 +++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
 @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState;
@@ -31885,9 +31385,9 @@ index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95
 +public class EmptyLevelChunk extends LevelChunk implements ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk { // Paper - rewrite chunk system
      private final Holder<Biome> biome;
  
-     public EmptyLevelChunk(Level world, ChunkPos pos, Holder<Biome> biomeEntry) {
+     public EmptyLevelChunk(Level level, ChunkPos pos, Holder<Biome> biome) {
 @@ -21,6 +21,40 @@ public class EmptyLevelChunk extends LevelChunk {
-         this.biome = biomeEntry;
+         this.biome = biome;
      }
  
 +    // Paper start - rewrite chunk system
@@ -31928,7 +31428,7 @@ index dcc0acd259920463a4464213b9a5e793603852f9..ef4161884574d3d137e12591d983dc95
      public BlockState getBlockState(BlockPos pos) {
          return Blocks.VOID_AIR.defaultBlockState();
 diff --git a/net/minecraft/world/level/chunk/HashMapPalette.java b/net/minecraft/world/level/chunk/HashMapPalette.java
-index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5708f2c4a 100644
+index 7cd5d42e0c28033ee80f18bd0031ed1241fb7aae..718d00a386f32423db9f6d6c95b4a20698b976f5 100644
 --- a/net/minecraft/world/level/chunk/HashMapPalette.java
 +++ b/net/minecraft/world/level/chunk/HashMapPalette.java
 @@ -8,12 +8,19 @@ import net.minecraft.network.FriendlyByteBuf;
@@ -31949,11 +31449,11 @@ index 98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d..7beea075b5a7ef738a4ac0558b99f4c5
 +    }
 +    // Paper end - optimise palette reads
 +
-     public HashMapPalette(IdMap<T> idList, int bits, PaletteResize<T> listener, List<T> entries) {
-         this(idList, bits, listener);
-         entries.forEach(this.values::add);
+     public HashMapPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler, List<T> values) {
+         this(registry, bits, resizeHandler);
+         values.forEach(this.values::add);
 diff --git a/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc9a11bbec 100644
+index e7c0f4da8508fbca467326f475668d66454d7b77..41856c98d97e7eb0782f8e441b9a269a47ed1914 100644
 --- a/net/minecraft/world/level/chunk/ImposterProtoChunk.java
 +++ b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
 @@ -30,7 +30,7 @@ import net.minecraft.world.level.material.FluidState;
@@ -31966,7 +31466,7 @@ index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc
      private final boolean allowWrites;
  
 @@ -46,6 +46,48 @@ public class ImposterProtoChunk extends ProtoChunk {
-         this.allowWrites = propagateToWrapped;
+         this.allowWrites = allowWrites;
      }
  
 +    // Paper start - rewrite chunk system
@@ -32015,34 +31515,19 @@ index f38700e5fbeeb8a913272d4464b8aa325d511dac..1eb8022f3e31603322e6c56516304afc
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf191005112080b 100644
+index d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba..72c0e031443ca42c2cfafddea9e04cfa4f93a0eb 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -55,7 +55,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
+@@ -52,7 +52,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
  import net.minecraft.world.ticks.TickContainerAccess;
  import org.slf4j.Logger;
  
 -public class LevelChunk extends ChunkAccess {
 +public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation
- 
      static final Logger LOGGER = LogUtils.getLogger();
      private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() {
-@@ -114,6 +114,14 @@ public class LevelChunk extends ChunkAccess {
-         this.postLoad = entityLoader;
-         this.blockTicks = blockTickScheduler;
-         this.fluidTicks = fluidTickScheduler;
-+        // Paper start - get block chunk optimisation
-+        this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level);
-+        this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(level);
-+
-+        final boolean empty = ((Object)this instanceof EmptyLevelChunk);
-+        this.debug = !empty && this.level.isDebug();
-+        this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE;
-+        // Paper end - get block chunk optimisation
-     }
- 
-     // CraftBukkit start
-@@ -124,6 +132,39 @@ public class LevelChunk extends ChunkAccess {
+         @Override
+@@ -93,6 +93,39 @@ public class LevelChunk extends ChunkAccess {
      // Paper start
      boolean loadedTicketLevel;
      // Paper end
@@ -32080,19 +31565,34 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
 +    }
 +    // Paper end - get block chunk optimisation
  
-     public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
-         this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
-@@ -157,13 +198,19 @@ public class LevelChunk extends ChunkAccess {
+     public LevelChunk(Level level, ChunkPos pos) {
+         this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
+@@ -122,6 +155,14 @@ public class LevelChunk extends ChunkAccess {
+         this.postLoad = postLoad;
+         this.blockTicks = blockTicks;
+         this.fluidTicks = fluidTicks;
++        // Paper start - get block chunk optimisation
++        this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level);
++        this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(level);
++
++        final boolean empty = ((Object)this instanceof EmptyLevelChunk);
++        this.debug = !empty && this.level.isDebug();
++        this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE;
++        // Paper end - get block chunk optimisation
+     }
+ 
+     public LevelChunk(ServerLevel level, ProtoChunk chunk, @Nullable LevelChunk.PostLoadProcessor postLoad) {
+@@ -159,13 +200,19 @@ public class LevelChunk extends ChunkAccess {
              }
          }
  
--        this.skyLightSources = protoChunk.skyLightSources;
+-        this.skyLightSources = chunk.skyLightSources;
 +        // Paper - rewrite chunk system
-         this.setLightCorrect(protoChunk.isLightCorrect());
+         this.setLightCorrect(chunk.isLightCorrect());
          this.markUnsaved();
          this.needsDecoration = true; // CraftBukkit
          // CraftBukkit start
-         this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
+         this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
          // CraftBukkit end
 +        // Paper start - rewrite chunk system
 +        this.starlight$setBlockNibbles(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)protoChunk).starlight$getBlockNibbles());
@@ -32103,16 +31603,16 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
      }
  
      public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
-@@ -366,7 +413,7 @@ public class LevelChunk extends ChunkAccess {
-                     ProfilerFiller gameprofilerfiller = Profiler.get();
- 
-                     gameprofilerfiller.push("updateSkyLightSources");
--                    this.skyLightSources.update(this, j, i, l);
+@@ -341,7 +388,7 @@ public class LevelChunk extends ChunkAccess {
+                 if (LightEngine.hasDifferentLightProperties(blockState, state)) {
+                     ProfilerFiller profilerFiller = Profiler.get();
+                     profilerFiller.push("updateSkyLightSources");
+-                    this.skyLightSources.update(this, i, y, i2);
 +                    // Paper - rewrite chunk system
-                     gameprofilerfiller.popPush("queueCheckLight");
-                     this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
-                     gameprofilerfiller.pop();
-@@ -632,11 +679,12 @@ public class LevelChunk extends ChunkAccess {
+                     profilerFiller.popPush("queueCheckLight");
+                     this.level.getChunkSource().getLightEngine().checkBlock(pos);
+                     profilerFiller.pop();
+@@ -573,11 +620,12 @@ public class LevelChunk extends ChunkAccess {
  
      // CraftBukkit start
      public void loadCallback() {
@@ -32126,7 +31626,7 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
          if (server != null) {
              /*
               * If it's a new world, the first few chunks are generated inside
-@@ -645,6 +693,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -586,6 +634,7 @@ public class LevelChunk extends ChunkAccess {
               */
              org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
              server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
@@ -32134,7 +31634,7 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
  
              if (this.needsDecoration) {
                  this.needsDecoration = false;
-@@ -671,13 +720,15 @@ public class LevelChunk extends ChunkAccess {
+@@ -612,13 +661,15 @@ public class LevelChunk extends ChunkAccess {
      }
  
      public void unloadCallback() {
@@ -32152,7 +31652,7 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
          // Paper start
          this.loadedTicketLevel = false;
          // Paper end
-@@ -685,8 +736,31 @@ public class LevelChunk extends ChunkAccess {
+@@ -626,8 +677,31 @@ public class LevelChunk extends ChunkAccess {
  
      @Override
      public boolean isUnsaved() {
@@ -32185,7 +31685,7 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
      // CraftBukkit end
  
      public boolean isEmpty() {
-@@ -794,6 +868,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -706,6 +780,7 @@ public class LevelChunk extends ChunkAccess {
  
          this.pendingBlockEntities.clear();
          this.upgradeData.upgrade(this);
@@ -32194,7 +31694,7 @@ index 0ade64bbdec563e555c981cee2208e6c72afe249..134d63076f231791988e67a5bdf19100
  
      @Nullable
 diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java
-index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef63cdb8511 100644
+index fc21c3268c4b4fda2933d71f0913db28e3796653..2ff691bcd32f587e8add2d8eda7e7339ccbde6e8 100644
 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java
 +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
 @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.Blocks;
@@ -32203,10 +31703,10 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
  
 -public class LevelChunkSection {
 +public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection { // Paper - block counting
- 
      public static final int SECTION_WIDTH = 16;
      public static final int SECTION_HEIGHT = 16;
-@@ -25,6 +25,30 @@ public class LevelChunkSection {
+     public static final int SECTION_SIZE = 4096;
+@@ -24,6 +24,30 @@ public class LevelChunkSection {
      public final PalettedContainer<BlockState> states;
      private PalettedContainer<Holder<Biome>> biomes; // CraftBukkit - read/write
  
@@ -32237,7 +31737,7 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
      private LevelChunkSection(LevelChunkSection section) {
          this.nonEmptyBlockCount = section.nonEmptyBlockCount;
          this.tickingBlockCount = section.tickingBlockCount;
-@@ -67,6 +91,45 @@ public class LevelChunkSection {
+@@ -69,6 +93,45 @@ public class LevelChunkSection {
          return this.setBlockState(x, y, z, state, true);
      }
  
@@ -32280,37 +31780,48 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
 +    }
 +    // Paper end - block counting
 +
-     public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
-         BlockState iblockdata1;
- 
+     public BlockState setBlockState(int x, int y, int z, BlockState state, boolean useLocks) {
+         BlockState blockState;
+         if (useLocks) {
 @@ -86,7 +149,7 @@ public class LevelChunkSection {
              }
          }
  
--        if (!fluid.isEmpty()) {
-+        if (!!fluid.isRandomlyTicking()) { // Paper - block counting
-             --this.tickingFluidCount;
+-        if (!fluidState.isEmpty()) {
++        if (!!fluidState.isRandomlyTicking()) { // Paper - block counting
+             this.tickingFluidCount--;
          }
  
 @@ -97,10 +160,12 @@ public class LevelChunkSection {
              }
          }
  
--        if (!fluid1.isEmpty()) {
-+        if (!!fluid1.isRandomlyTicking()) { // Paper - block counting
-             ++this.tickingFluidCount;
+-        if (!fluidState1.isEmpty()) {
++        if (!!fluidState1.isRandomlyTicking()) { // Paper - block counting
+             this.tickingFluidCount++;
          }
  
-+        this.updateBlockCallback(x, y, z, state, iblockdata1); // Paper - block counting
++        this.updateBlockCallback(x, y, z, state, blockState); // Paper - block counting
 +
-         return iblockdata1;
+         return blockState;
      }
  
-@@ -121,40 +186,70 @@ public class LevelChunkSection {
+@@ -121,35 +186,70 @@ public class LevelChunkSection {
      }
  
      public void recalcBlockCounts() {
--        class a implements PalettedContainer.CountConsumer<BlockState> {
+-        class BlockCounter implements PalettedContainer.CountConsumer<BlockState> {
+-            public int nonEmptyBlockCount;
+-            public int tickingBlockCount;
+-            public int tickingFluidCount;
+-
+-            @Override
+-            public void accept(BlockState state, int count) {
+-                FluidState fluidState = state.getFluidState();
+-                if (!state.isAir()) {
+-                    this.nonEmptyBlockCount += count;
+-                    if (state.isRandomlyTicking()) {
+-                        this.tickingBlockCount += count;
 +        // Paper start - block counting
 +        // reset, then recalculate
 +        this.nonEmptyBlockCount = (short)0;
@@ -32338,13 +31849,9 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
 +                final int paletteIdx = entry.getIntKey();
 +                final it.unimi.dsi.fastutil.shorts.ShortArrayList coordinates = entry.getValue();
 +                final int paletteCount = coordinates.size();
- 
--            public int nonEmptyBlockCount;
--            public int tickingBlockCount;
--            public int tickingFluidCount;
++
 +                final BlockState state = palette.valueFor(paletteIdx);
- 
--            a(final LevelChunkSection chunksection) {}
++
 +                if (state.isAir()) {
 +                    continue;
 +                }
@@ -32359,49 +31866,43 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
 +                    final int rawLen = raw.length;
 +
 +                    final ca.spottedleaf.moonrise.common.list.ShortList tickingBlocks = this.tickingBlocks;
- 
--            public void accept(BlockState iblockdata, int i) {
--                FluidState fluid = iblockdata.getFluidState();
++
 +                    tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16));
- 
--                if (!iblockdata.isAir()) {
--                    this.nonEmptyBlockCount += i;
--                    if (iblockdata.isRandomlyTicking()) {
--                        this.tickingBlockCount += i;
++
 +                    java.util.Objects.checkFromToIndex(0, paletteCount, raw.length);
 +                    for (int i = 0; i < paletteCount; ++i) {
 +                        tickingBlocks.add(raw[i]);
                      }
                  }
  
+-                if (!fluidState.isEmpty()) {
+-                    this.nonEmptyBlockCount += count;
+-                    if (fluidState.isRandomlyTicking()) {
+-                        this.tickingFluidCount += count;
 +                final FluidState fluid = state.getFluidState();
 +
-                 if (!fluid.isEmpty()) {
--                    this.nonEmptyBlockCount += i;
++                if (!fluid.isEmpty()) {
 +                    //this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct
-                     if (fluid.isRandomlyTicking()) {
--                        this.tickingFluidCount += i;
++                    if (fluid.isRandomlyTicking()) {
 +                        this.tickingFluidCount += (short)paletteCount;
                      }
                  }
--
              }
          }
 -
--        a a0 = new a(this);
--
--        this.states.count(a0);
--        this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
--        this.tickingBlockCount = (short) a0.tickingBlockCount;
--        this.tickingFluidCount = (short) a0.tickingFluidCount;
+-        BlockCounter blockCounter = new BlockCounter();
+-        this.states.count(blockCounter);
+-        this.nonEmptyBlockCount = (short)blockCounter.nonEmptyBlockCount;
+-        this.tickingBlockCount = (short)blockCounter.tickingBlockCount;
+-        this.tickingFluidCount = (short)blockCounter.tickingFluidCount;
 +        // Paper end - block counting
      }
  
      public PalettedContainer<BlockState> getStates() {
-@@ -172,6 +267,11 @@ public class LevelChunkSection {
- 
-         datapaletteblock.read(buf);
-         this.biomes = datapaletteblock;
+@@ -166,6 +266,11 @@ public class LevelChunkSection {
+         PalettedContainer<Holder<Biome>> palettedContainer = this.biomes.recreate();
+         palettedContainer.read(buffer);
+         this.biomes = palettedContainer;
 +        // Paper start - block counting
 +        this.isClient = true;
 +        // force has special colliding blocks to be true
@@ -32409,9 +31910,9 @@ index 3dab36d00ea48101807ba40c7a7358b7eed12747..e4ae25c83ab9dd1aaa530a5456275ef6
 +        // Paper end - block counting
      }
  
-     public void readBiomes(FriendlyByteBuf buf) {
+     public void readBiomes(FriendlyByteBuf buffer) {
 diff --git a/net/minecraft/world/level/chunk/LinearPalette.java b/net/minecraft/world/level/chunk/LinearPalette.java
-index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf34354ce1195 100644
+index 5ae2f38dc613ac6129af49084980d064f14ff153..2073f6ff41aa570102621d183ee890b076267d54 100644
 --- a/net/minecraft/world/level/chunk/LinearPalette.java
 +++ b/net/minecraft/world/level/chunk/LinearPalette.java
 @@ -7,13 +7,20 @@ import net.minecraft.network.FriendlyByteBuf;
@@ -32433,11 +31934,11 @@ index bc4d9452bbeb05a691fd285603e49491f41d3ad2..f8d9892970c9092f7cc84434d4fbf343
 +    }
 +    // Paper end - optimise palette reads
 +
-     private LinearPalette(IdMap<T> idList, int bits, PaletteResize<T> listener, List<T> list) {
-         this.registry = idList;
+     private LinearPalette(IdMap<T> registry, int bits, PaletteResize<T> resizeHandler, List<T> values) {
+         this.registry = registry;
          this.values = (T[])(new Object[1 << bits]);
 diff --git a/net/minecraft/world/level/chunk/Palette.java b/net/minecraft/world/level/chunk/Palette.java
-index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be2f08ecd1 100644
+index b4b973e453a093dcc04a6b7257883aa0065e2a89..a80b2e9dceea423180a9c390d1970317dff4f1b0 100644
 --- a/net/minecraft/world/level/chunk/Palette.java
 +++ b/net/minecraft/world/level/chunk/Palette.java
 @@ -5,7 +5,7 @@ import java.util.function.Predicate;
@@ -32446,23 +31947,23 @@ index b8922e4a13df535cdc5701e893a6e460b33ff90d..100807f8b8337f56f49cdb818ccc75be
  
 -public interface Palette<T> {
 +public interface Palette<T> extends ca.spottedleaf.moonrise.patches.fast_palette.FastPalette<T> { // Paper - optimise palette reads
-     int idFor(T object);
+     int idFor(T state);
  
-     boolean maybeHas(Predicate<T> predicate);
+     boolean maybeHas(Predicate<T> filter);
 diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java
-index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48a1cc6873 100644
+index a6028a54c75de068515e95913b21160ab4326985..f5da433050fd3060e0335d4002d520ebe8cd691f 100644
 --- a/net/minecraft/world/level/chunk/PalettedContainer.java
 +++ b/net/minecraft/world/level/chunk/PalettedContainer.java
 @@ -29,7 +29,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-     private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
+     private final PaletteResize<T> dummyPaletteResize = (bits, objectAdded) -> 0;
      public final IdMap<T> registry;
      private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
 -    private volatile PalettedContainer.Data<T> data;
 +    public volatile PalettedContainer.Data<T> data; // Paper - optimise collisions - public
      private final PalettedContainer.Strategy strategy;
-     // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+     //private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
  
-@@ -77,6 +77,33 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+@@ -75,6 +75,33 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              );
      }
  
@@ -32494,9 +31995,9 @@ index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48
 +    // Paper end - optimise palette reads
 +
      // Paper start - Anti-Xray - Add preset values
-     @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); }
-     public PalettedContainer(
-@@ -113,6 +140,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+     @Deprecated @io.papermc.paper.annotation.DoNotUse
+     public PalettedContainer(IdMap<T> registry, PalettedContainer.Strategy strategy, PalettedContainer.Configuration<T> configuration, BitStorage storage, List<T> values) {
+@@ -109,6 +136,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
              }
          }
          // Paper end
@@ -32504,47 +32005,47 @@ index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48
      }
  
      // Paper start - Anti-Xray - Add preset values
-@@ -122,6 +150,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-         this.registry = idList;
-         this.strategy = paletteProvider;
+@@ -118,6 +146,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         this.registry = registry;
+         this.strategy = strategy;
          this.data = data;
 +        this.updateData(this.data); // Paper - optimise palette reads
      }
  
-     private PalettedContainer(PalettedContainer<T> container, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values
-@@ -140,6 +169,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-         this.registry = idList;
+     private PalettedContainer(PalettedContainer<T> other, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values
+@@ -139,6 +168,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         this.registry = registry;
          this.data = this.createOrReuseData(null, 0);
-         this.data.palette.idFor(object);
+         this.data.palette.idFor(palette);
 +        this.updateData(this.data); // Paper - optimise palette reads
      }
  
-     private PalettedContainer.Data<T> createOrReuseData(@Nullable PalettedContainer.Data<T> previousData, int bits) {
-@@ -166,6 +196,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-         data2.copyFrom(data.palette, data.storage);
-         this.data = data2;
+     private PalettedContainer.Data<T> createOrReuseData(@Nullable PalettedContainer.Data<T> data, int id) {
+@@ -163,6 +193,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         this.data = data1;
+         // Paper start - Anti-Xray
          this.addPresetValues();
 +        this.updateData(this.data); // Paper - optimise palette reads
-         return object == null ? -1 : data2.palette.idFor(object);
-         // Paper end
+         return objectAdded == null ? -1 : data1.palette.idFor(objectAdded);
      }
-@@ -198,9 +229,12 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+     private void addPresetValues() {
+@@ -192,9 +223,12 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
      }
  
-     private synchronized T getAndSet(int index, T value) { // Paper - synchronize
--        int i = this.data.palette.idFor(value);
--        int j = this.data.storage.getAndSet(index, i);
--        return this.data.palette.valueFor(j);
+     private T getAndSet(int index, T state) {
+-        int i = this.data.palette.idFor(state);
+-        int andSet = this.data.storage.getAndSet(index, i);
+-        return this.data.palette.valueFor(andSet);
 +        // Paper start - optimise palette reads
-+        final int paletteIdx = this.data.palette.idFor(value);
++        final int paletteIdx = this.data.palette.idFor(state);
 +        final PalettedContainer.Data<T> data = this.data;
 +        final int prev = data.storage.getAndSet(index, paletteIdx);
 +        return this.readPalette(data, prev);
 +        // Paper end - optimise palette reads
      }
  
-     public void set(int x, int y, int z, T value) {
-@@ -223,9 +257,11 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+     public synchronized void set(int x, int y, int z, T state) { // Paper - synchronize
+@@ -217,9 +251,11 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
          return this.get(this.strategy.getIndex(x, y, z));
      }
  
@@ -32559,19 +32060,19 @@ index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48
      }
  
      @Override
-@@ -246,6 +282,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-             buf.readLongArray(data.storage.getRaw());
+@@ -240,6 +276,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+             buffer.readLongArray(data.storage.getRaw());
              this.data = data;
              this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server)
 +            this.updateData(this.data); // Paper - optimise palette reads
          } finally {
              this.release();
          }
-@@ -394,7 +431,44 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
-         void accept(T object, int count);
+@@ -390,7 +427,44 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+         void accept(T state, int count);
      }
  
--    static record Data<T>(PalettedContainer.Configuration<T> configuration, BitStorage storage, Palette<T> palette) {
+-    record Data<T>(PalettedContainer.Configuration<T> configuration, BitStorage storage, Palette<T> palette) {
 +    // Paper start - optimise palette reads
 +    public static final class Data<T> implements ca.spottedleaf.moonrise.patches.fast_palette.FastPaletteData<T> {
 +
@@ -32610,24 +32111,24 @@ index 69d6f203366df658e1ade55d917f0aa2b8a49be9..8b84bf2272556ac3321cbf16361d7f48
 +        }
 +        // Paper end - optimise palette reads
 +
-         public void copyFrom(Palette<T> palette, BitStorage storage) {
-             for (int i = 0; i < storage.getSize(); i++) {
-                 T object = palette.valueFor(storage.get(i));
+         public void copyFrom(Palette<T> palette, BitStorage bitStorage) {
+             for (int i = 0; i < bitStorage.getSize(); i++) {
+                 T object = palette.valueFor(bitStorage.get(i));
 diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java
-index 15e14f5d006389c823fa6baf8c1a4f22804d4aa8..759adee51bad99bd4bbee4f44247e8c8486cfbd6 100644
+index 8c333d7f390d823a7c7f303e2f444f52ec16f799..e66239e2da91bd3ddf358d239be796719c0da327 100644
 --- a/net/minecraft/world/level/chunk/ProtoChunk.java
 +++ b/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -149,7 +149,7 @@ public class ProtoChunk extends ChunkAccess {
+@@ -151,7 +151,7 @@ public class ProtoChunk extends ChunkAccess {
                      }
  
                      if (LightEngine.hasDifferentLightProperties(blockState, state)) {
--                        this.skyLightSources.update(this, m, j, o);
+-                        this.skyLightSources.update(this, relativeBlockPosX, y, relativeBlockPosZ);
 +                        // Paper - rewrite chunk system
                          this.lightEngine.checkBlock(pos);
                      }
                  }
 diff --git a/net/minecraft/world/level/chunk/SingleValuePalette.java b/net/minecraft/world/level/chunk/SingleValuePalette.java
-index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce7895529918073c 100644
+index bc84910dbc688331efaea76972a6625014ff76f5..2ffae24b0cb1a20c7d5a8520f1b5197c2cedea11 100644
 --- a/net/minecraft/world/level/chunk/SingleValuePalette.java
 +++ b/net/minecraft/world/level/chunk/SingleValuePalette.java
 @@ -8,12 +8,24 @@ import net.minecraft.network.FriendlyByteBuf;
@@ -32653,16 +32154,16 @@ index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce789552
 +    }
 +    // Paper end - optimise palette reads
 +
-     public SingleValuePalette(IdMap<T> idList, PaletteResize<T> listener, List<T> entries) {
-         this.registry = idList;
-         this.resizeHandler = listener;
+     public SingleValuePalette(IdMap<T> registry, PaletteResize<T> resizeHandler, List<T> value) {
+         this.registry = registry;
+         this.resizeHandler = resizeHandler;
 @@ -33,6 +45,11 @@ public class SingleValuePalette<T> implements Palette<T> {
-             return this.resizeHandler.onResize(1, object);
+             return this.resizeHandler.onResize(1, state);
          } else {
-             this.value = object;
+             this.value = state;
 +            // Paper start - optimise palette reads
 +            if (this.rawPalette != null) {
-+                this.rawPalette[0] = object;
++                this.rawPalette[0] = state;
 +            }
 +            // Paper end - optimise palette reads
              return 0;
@@ -32670,8 +32171,8 @@ index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce789552
      }
 @@ -58,6 +75,11 @@ public class SingleValuePalette<T> implements Palette<T> {
      @Override
-     public void read(FriendlyByteBuf buf) {
-         this.value = this.registry.byIdOrThrow(buf.readVarInt());
+     public void read(FriendlyByteBuf buffer) {
+         this.value = this.registry.byIdOrThrow(buffer.readVarInt());
 +        // Paper start - optimise palette reads
 +        if (this.rawPalette != null) {
 +            this.rawPalette[0] = this.value;
@@ -32681,7 +32182,7 @@ index a45e6410600afc5464e5d29932c193786ce0a6fb..a1ba68c95c2cdebdc0d7782cce789552
  
      @Override
 diff --git a/net/minecraft/world/level/chunk/status/ChunkPyramid.java b/net/minecraft/world/level/chunk/status/ChunkPyramid.java
-index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d6755a0d931a 100644
+index 9c6f4aa173fa25f9c8a3852d91a4585e069236b6..b14001afe0bf841dac7d0a1d1568fd10f6086237 100644
 --- a/net/minecraft/world/level/chunk/status/ChunkPyramid.java
 +++ b/net/minecraft/world/level/chunk/status/ChunkPyramid.java
 @@ -54,7 +54,7 @@ public record ChunkPyramid(ImmutableList<ChunkStep> steps) {
@@ -32694,7 +32195,7 @@ index b1058bf0dcda544a074f4d3772d7899b94f98927..b7bf82f6b6023bd628d3e7ea84d2d675
          .step(ChunkStatus.FULL, builder -> builder.setTask(ChunkStatusTasks::full))
          .build();
 diff --git a/net/minecraft/world/level/chunk/status/ChunkStatus.java b/net/minecraft/world/level/chunk/status/ChunkStatus.java
-index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eaedbf4b976 100644
+index 7a64b00ff31d1273d0b0b9a3cfd43808c88ef46a..c9d8a1c0a75c34ccd9f5cead02cccd776276f3cb 100644
 --- a/net/minecraft/world/level/chunk/status/ChunkStatus.java
 +++ b/net/minecraft/world/level/chunk/status/ChunkStatus.java
 @@ -11,7 +11,7 @@ import net.minecraft.resources.ResourceLocation;
@@ -32706,7 +32207,7 @@ index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eae
      public static final int MAX_STRUCTURE_DISTANCE = 8;
      private static final EnumSet<Heightmap.Types> WORLDGEN_HEIGHTMAPS = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG);
      public static final EnumSet<Heightmap.Types> FINAL_HEIGHTMAPS = EnumSet.of(
-@@ -51,8 +51,68 @@ public class ChunkStatus {
+@@ -51,8 +51,70 @@ public class ChunkStatus {
          return list;
      }
  
@@ -32764,50 +32265,52 @@ index 4f84ff9cdb3303251e035a12ce9d8b9a0b58f46e..d80b7d555e02d1d4b82945373d383eae
 +    // Paper end - rewrite chunk system
 +
      @VisibleForTesting
-     protected ChunkStatus(@Nullable ChunkStatus previous, EnumSet<Heightmap.Types> heightMapTypes, ChunkType chunkType) {
+     protected ChunkStatus(@Nullable ChunkStatus parent, EnumSet<Heightmap.Types> heightmapsAfter, ChunkType chunkType) {
++        // Paper start - rewrite chunk system
 +        this.isParallelCapable = false;
 +        this.writeRadius = -1;
 +        this.nextStatus = (ChunkStatus)(Object)this;
-+        if (previous != null) {
-+            previous.nextStatus = (ChunkStatus)(Object)this;
++        if (parent != null) {
++            parent.nextStatus = (ChunkStatus)(Object)this;
 +        }
 +        this.warnedAboutNoImmediateComplete = new java.util.concurrent.atomic.AtomicBoolean();
-         this.parent = previous == null ? this : previous;
++        // Paper end - rewrite chunk system
+         this.parent = parent == null ? this : parent;
          this.chunkType = chunkType;
-         this.heightmapsAfter = heightMapTypes;
+         this.heightmapsAfter = heightmapsAfter;
 diff --git a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-index 3d8a35d8cf29447ee7ac750dbc6ffcdb0f89b81b..9a3900e970f22892d8a3da8a28f922aa9b62765f 100644
+index c953bc93de8a42bcc12b7e8f46b3ae804e54964e..2ccbdfdcf81556306e098277ecf119d5fd02138c 100644
 --- a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
 +++ b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-@@ -152,7 +152,7 @@ public class ChunkStatusTasks {
-                 chunk1 = protochunkextension.getWrapped();
+@@ -182,7 +182,7 @@ public class ChunkStatusTasks {
+             if (protoChunk instanceof ImposterProtoChunk imposterProtoChunk) {
+                 wrapped = imposterProtoChunk.getWrapped();
              } else {
-                 chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix
--                    ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities());
-+                    ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities(), protochunk.getPos()); // Paper - rewrite chunk system
-                 });
-                 generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false));
+-                wrapped = new LevelChunk(serverLevel, protoChunk, chunk1 -> postLoadProtoChunk(serverLevel, protoChunk.getEntities()));
++                wrapped = new LevelChunk(serverLevel, protoChunk, chunk1 -> postLoadProtoChunk(serverLevel, protoChunk.getEntities(), protoChunk.getPos())); // Paper - rewrite chunk system
+                 generationChunkHolder.replaceProtoChunk(new ImposterProtoChunk(wrapped, false));
              }
-@@ -168,7 +168,7 @@ public class ChunkStatusTasks {
-         }, context.mainThreadExecutor());
+ 
+@@ -196,7 +196,7 @@ public class ChunkStatusTasks {
+         }, worldGenContext.mainThreadExecutor());
      }
  
--    public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) { // Paper - public
-+    public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities, ChunkPos pos) { // Paper - public // Paper - rewrite chunk system - add ChunkPos param
-         if (!entities.isEmpty()) {
+-    public static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> entityTags) { // Paper - public
++    public static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> entityTags, ChunkPos pos) { // Paper - public // Paper - rewrite chunk system - add ChunkPos param
+         if (!entityTags.isEmpty()) {
              // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
-             world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
-@@ -180,7 +180,7 @@ public class ChunkStatusTasks {
+             level.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entityTags, level, EntitySpawnReason.LOAD).filter((entity) -> {
+@@ -208,7 +208,7 @@ public class ChunkStatusTasks {
                  }
-                 checkDupeUUID(world, entity); // Paper - duplicate uuid resolving
+                 checkDupeUUID(level, entity); // Paper - duplicate uuid resolving
                  return !needsRemoval;
 -            }));
 +            }), pos); // Paper - rewrite chunk system
              // CraftBukkit end
          }
- 
+     }
 diff --git a/net/minecraft/world/level/chunk/status/ChunkStep.java b/net/minecraft/world/level/chunk/status/ChunkStep.java
-index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4fff0f3ed5 100644
+index 7a4d299d2ce36982204e30de9278ddfd5b37c3df..b8348976e80578d9eff64eea68c04c603fed49ad 100644
 --- a/net/minecraft/world/level/chunk/status/ChunkStep.java
 +++ b/net/minecraft/world/level/chunk/status/ChunkStep.java
 @@ -11,9 +11,50 @@ import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
@@ -32864,7 +32367,7 @@ index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4f
      public int getAccumulatedRadiusOf(ChunkStatus status) {
          return status == this.targetStatus ? 0 : this.accumulatedDependencies.getRadiusOf(status);
      }
-@@ -39,6 +80,56 @@ public record ChunkStep(
+@@ -40,6 +81,56 @@ public record ChunkStep(
          return chunk;
      }
  
@@ -32922,16 +32425,15 @@ index 3d37a0372cdd99e806a9651cc1cabaefa9338065..f9aad1b8c02b70e620efdc2a58cadf4f
          private final ChunkStatus status;
          @Nullable
 diff --git a/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a067109819f 100644
+index 80bc7ad9ad076968d06279dedd845d5946cf2501..433feab7f7c1931f79836164a0b8c4a1c3b75ba6 100644
 --- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
-@@ -28,21 +28,31 @@ import net.minecraft.world.level.dimension.LevelStem;
+@@ -22,20 +22,30 @@ import net.minecraft.world.level.chunk.ChunkGenerator;
  import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
  import net.minecraft.world.level.storage.DimensionDataStorage;
  
 -public class ChunkStorage implements AutoCloseable {
 +public class ChunkStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage { // Paper - rewrite chunk system
- 
      public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493;
 -    private final IOWorker worker;
 +    // Paper - rewrite chunk system
@@ -32949,29 +32451,29 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public ChunkStorage(RegionStorageInfo storageKey, Path directory, DataFixer dataFixer, boolean dsync) {
-         this.fixerUpper = dataFixer;
--        this.worker = new IOWorker(storageKey, directory, dsync);
-+        this.storage = new IOWorker(storageKey, directory, dsync).storage; // Paper - rewrite chunk system
+     public ChunkStorage(RegionStorageInfo info, Path folder, DataFixer fixerUpper, boolean sync) {
+         this.fixerUpper = fixerUpper;
+-        this.worker = new IOWorker(info, folder, sync);
++        this.storage = new IOWorker(info, folder, sync).storage; // Paper - rewrite chunk system
      }
  
-     public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) {
--        return this.worker.isOldChunkAround(chunkPos, checkRadius);
+     public boolean isOldChunkAround(ChunkPos pos, int radius) {
+-        return this.worker.isOldChunkAround(pos, radius);
 +        return true; // Paper - rewrite chunk system
      }
  
      // CraftBukkit start
-@@ -102,7 +112,9 @@ public class ChunkStorage implements AutoCloseable {
-                     if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
-                         LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
- 
-+                        synchronized (persistentstructurelegacy) { // Paper - rewrite chunk system
-                         nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound);
-+                        } // Paper - rewrite chunk system
+@@ -99,7 +109,9 @@ public class ChunkStorage implements AutoCloseable {
+                     chunkData = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, chunkData, version, 1493); // Paper - replace chunk converter
+                     if (chunkData.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+                         LegacyStructureDataHandler legacyStructureHandler = this.getLegacyStructureHandler(levelKey, storage);
++                        synchronized (legacyStructureHandler) { // Paper - rewrite chunk system
+                         chunkData = legacyStructureHandler.updateFromLegacy(chunkData);
++                        }
                      }
                  }
  
-@@ -169,7 +181,13 @@ public class ChunkStorage implements AutoCloseable {
+@@ -163,7 +175,13 @@ public class ChunkStorage implements AutoCloseable {
      }
  
      public CompletableFuture<Optional<CompoundTag>> read(ChunkPos chunkPos) {
@@ -32985,15 +32487,15 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
 +        // Paper end - rewrite chunk system
      }
  
-     public CompletableFuture<Void> write(ChunkPos chunkPos, Supplier<CompoundTag> nbtSupplier) {
-@@ -185,29 +203,54 @@ public class ChunkStorage implements AutoCloseable {
+     public CompletableFuture<Void> write(ChunkPos pos, Supplier<CompoundTag> tagSupplier) {
+@@ -179,29 +197,54 @@ public class ChunkStorage implements AutoCloseable {
          };
          // Paper end - guard against possible chunk pos desync
-         this.handleLegacyStructureIndex(chunkPos);
--        return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync
+         this.handleLegacyStructureIndex(pos);
+-        return this.worker.store(pos, guardedPosCheck); // Paper - guard against possible chunk pos desync
 +        // Paper start - rewrite chunk system
 +        try {
-+            this.storage.write(chunkPos, guardedPosCheck.get());
++            this.storage.write(pos, guardedPosCheck.get());
 +            return CompletableFuture.completedFuture(null);
 +        } catch (final Throwable throwable) {
 +            return CompletableFuture.failedFuture(throwable);
@@ -33007,7 +32509,6 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
              this.legacyStructureHandler.removeIndex(chunkPos.toLong());
 +            } // Paper - rewrite chunk system
          }
- 
      }
  
      public void flushWorker() {
@@ -33021,6 +32522,7 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
 +        // Paper end - rewrite chunk system
      }
  
+     @Override
      public void close() throws IOException {
 -        this.worker.close();
 +        this.storage.close(); // Paper - rewrite chunk system
@@ -33048,26 +32550,26 @@ index 092f7b6bba4e1291f76c2c09155f33803e93eb04..46f4b6706a1ca24ff6fc28960ad01a06
      }
  }
 diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java
-index a0cbccd2cf1ac785745d86c42b6f58fb8bad7ffa..16ca1c8672e5f0a27f8a30498c754a81cdec5191 100644
+index c3c9771138cb1712ea429d8c45596220830314eb..da05fb780c55381a7a08ced51d01764a645740b2 100644
 --- a/net/minecraft/world/level/chunk/storage/EntityStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java
 @@ -71,12 +71,12 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
          }
      }
  
--    private static ChunkPos readChunkPos(CompoundTag chunkNbt) {
-+    public static ChunkPos readChunkPos(CompoundTag chunkNbt) { // Paper - public
-         int[] is = chunkNbt.getIntArray("Position");
-         return new ChunkPos(is[0], is[1]);
+-    private static ChunkPos readChunkPos(CompoundTag tag) {
++    public static ChunkPos readChunkPos(CompoundTag tag) { // Paper - public
+         int[] intArray = tag.getIntArray("Position");
+         return new ChunkPos(intArray[0], intArray[1]);
      }
  
--    private static void writeChunkPos(CompoundTag chunkNbt, ChunkPos pos) {
-+    public static void writeChunkPos(CompoundTag chunkNbt, ChunkPos pos) { // Paper - public
-         chunkNbt.put("Position", new IntArrayTag(new int[]{pos.x, pos.z}));
+-    private static void writeChunkPos(CompoundTag tag, ChunkPos pos) {
++    public static void writeChunkPos(CompoundTag tag, ChunkPos pos) { // Paper - public
+         tag.put("Position", new IntArrayTag(new int[]{pos.x, pos.z}));
      }
  
 diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java
-index 1f2997cf5367200084f32c437f77040c8c6a18e6..a8a9e59a9721a76e34f78c1baa5026e5fe1d2bda 100644
+index 889e188e920edb284f04b264bcdd06146f54a4cb..2199a9e2a0141c646d108f2687a27f1d165453c5 100644
 --- a/net/minecraft/world/level/chunk/storage/IOWorker.java
 +++ b/net/minecraft/world/level/chunk/storage/IOWorker.java
 @@ -30,7 +30,7 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable {
@@ -33080,21 +32582,21 @@ index 1f2997cf5367200084f32c437f77040c8c6a18e6..a8a9e59a9721a76e34f78c1baa5026e5
      private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
      private static final int REGION_CACHE_SIZE = 1024;
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a129514760edb32a2 100644
+index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d94ba35329 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -28,7 +28,7 @@ import net.minecraft.nbt.NbtIo; // Paper
+@@ -23,7 +23,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler;
  import net.minecraft.world.level.ChunkPos;
  import org.slf4j.Logger;
  
 -public class RegionFile implements AutoCloseable {
 +public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system
- 
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int SECTOR_BYTES = 4096;
-@@ -52,6 +52,21 @@ public class RegionFile implements AutoCloseable {
      @VisibleForTesting
-     protected final RegionBitmap usedSectors;
+@@ -46,6 +46,21 @@ public class RegionFile implements AutoCloseable {
+     @VisibleForTesting
+     protected final RegionBitmap usedSectors = new RegionBitmap();
  
 +    // Paper start - rewrite chunk system
 +    @Override
@@ -33111,33 +32613,32 @@ index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a12951476
 +    }
 +    // Paper end - rewrite chunk system
 +
-     public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
-         this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
+     public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, boolean sync) throws IOException {
+         this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
      }
-@@ -224,6 +239,16 @@ public class RegionFile implements AutoCloseable {
+@@ -205,6 +220,16 @@ public class RegionFile implements AutoCloseable {
  
      @Nullable
-     private DataInputStream createExternalChunkInputStream(ChunkPos pos, byte flags) throws IOException {
+     private DataInputStream createExternalChunkInputStream(ChunkPos chunkPos, byte versionByte) throws IOException {
 +        // Paper start - rewrite chunk system
-+        final DataInputStream is = this.createExternalChunkInputStream0(pos, flags);
++        final DataInputStream is = this.createExternalChunkInputStream0(chunkPos, versionByte);
 +        if (is == null) {
 +            return is;
 +        }
 +        return new ca.spottedleaf.moonrise.patches.chunk_system.util.stream.ExternalChunkStreamMarker(is);
 +    }
 +    @Nullable
-+    private DataInputStream createExternalChunkInputStream0(ChunkPos pos, byte flags) throws IOException {
++    private DataInputStream createExternalChunkInputStream0(ChunkPos chunkPos, byte versionByte) throws IOException {
 +        // Paper end - rewrite chunk system
-         Path path = this.getExternalChunkPath(pos);
- 
-         if (!Files.isRegularFile(path, new LinkOption[0])) {
-@@ -514,10 +539,29 @@ public class RegionFile implements AutoCloseable {
- 
+         Path externalChunkPath = this.getExternalChunkPath(chunkPos);
+         if (!Files.isRegularFile(externalChunkPath)) {
+             LOGGER.error("External chunk path {} is not file", externalChunkPath);
+@@ -399,9 +424,28 @@ public class RegionFile implements AutoCloseable {
+         }
      }
-     // Paper end
--    private class ChunkBuffer extends ByteArrayOutputStream {
-+    private class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system
  
+-    class ChunkBuffer extends ByteArrayOutputStream {
++    class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system
          private final ChunkPos pos;
  
 +        // Paper start - rewrite chunk system
@@ -33159,36 +32660,36 @@ index 863960ead8deaa0553be1c98e4fa09f07fcb8ef0..057875cbbdc92ba49b429f9a12951476
 +        }
 +        // Paper end - rewrite chunk system
 +
-         public ChunkBuffer(final ChunkPos chunkcoordintpair) {
+         public ChunkBuffer(final ChunkPos pos) {
              super(8096);
              super.write(0);
-@@ -534,7 +578,7 @@ public class RegionFile implements AutoCloseable {
- 
+@@ -418,7 +462,7 @@ public class RegionFile implements AutoCloseable {
+             int i = this.count - 5 + 1;
              JvmProfiler.INSTANCE.onRegionFileWrite(RegionFile.this.info, this.pos, RegionFile.this.version, i);
-             bytebuffer.putInt(0, i);
--            RegionFile.this.write(this.pos, bytebuffer);
-+            if (this.writeOnClose) { RegionFile.this.write(this.pos, bytebuffer); } // Paper - rewrite chunk system
+             byteBuffer.putInt(0, i);
+-            RegionFile.this.write(this.pos, byteBuffer);
++            if (this.writeOnClose) { RegionFile.this.write(this.pos, byteBuffer); } // Paper - rewrite chunk system
          }
      }
  
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3306a32e3 100644
+index 51bf310423013d0ae9d3202d66e36a053a767197..2661a21703994a18c4a9a44fba0f6931fd37151e 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -17,7 +17,7 @@ import net.minecraft.nbt.StreamTagVisitor;
+@@ -14,7 +14,7 @@ import net.minecraft.nbt.StreamTagVisitor;
  import net.minecraft.util.ExceptionCollector;
  import net.minecraft.world.level.ChunkPos;
  
 -public final class RegionFileStorage implements AutoCloseable {
-+public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
- 
++public final class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
      public static final String ANVIL_EXTENSION = ".mca";
      private static final int MAX_CACHE_SIZE = 256;
-@@ -26,33 +26,219 @@ public final class RegionFileStorage implements AutoCloseable {
+     public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
+@@ -22,29 +22,218 @@ public final class RegionFileStorage implements AutoCloseable {
      private final Path folder;
      private final boolean sync;
  
--    RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) {
+-    RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) {
 +    // Paper start - rewrite chunk system
 +    private static final int REGION_SHIFT = 5;
 +    private static final int MAX_NON_EXISTING_CACHE = 1024 * 4;
@@ -33359,34 +32860,31 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
 +        }
 +    }
 +    // Paper end - rewrite chunk system
-+
-+    protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected
-         this.folder = directory;
-         this.sync = dsync;
-         this.info = storageKey;
-     }
- 
--    private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
--        long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
--        RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
 +    // Paper start - rewrite chunk system
 +    public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException {
 +        return this.getRegionFile(chunkcoordintpair, false);
 +    }
 +    // Paper end - rewrite chunk system
++
++    protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected
+         this.folder = folder;
+         this.sync = sync;
+         this.info = info;
+     }
  
--        if (regionfile != null) {
--            return regionfile;
+     @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit
+-        long packedChunkPos = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
+-        RegionFile regionFile = this.regionCache.getAndMoveToFirst(packedChunkPos);
+-        if (regionFile != null) {
+-            return regionFile;
 -        } else {
 -            if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable
--                ((RegionFile) this.regionCache.removeLast()).close();
-+    public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public
 +        // Paper start - rewrite chunk system
 +        if (existingOnly) {
-+            return this.moonrise$getRegionFileIfExists(chunkcoordintpair.x, chunkcoordintpair.z);
++            return this.moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z);
 +        }
 +        synchronized (this) {
-+            final long key = ChunkPos.asLong(chunkcoordintpair.x >> REGION_SHIFT, chunkcoordintpair.z >> REGION_SHIFT);
++            final long key = ChunkPos.asLong(chunkPos.x >> REGION_SHIFT, chunkPos.z >> REGION_SHIFT);
 +
 +            RegionFile ret = this.regionCache.getAndMoveToFirst(key);
 +            if (ret != null) {
@@ -33394,22 +32892,20 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
 +            }
 +
 +            if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper
-+                this.regionCache.removeLast().close();
+                 this.regionCache.removeLast().close();
              }
  
-+            final Path regionPath = this.folder.resolve(getRegionFileName(chunkcoordintpair.x, chunkcoordintpair.z));
++            final Path regionPath = this.folder.resolve(getRegionFileName(chunkPos.x, chunkPos.z));
 +
 +            this.createRegionFile(key);
 +
              FileUtil.createDirectoriesSafe(this.folder);
--            Path path = this.folder;
--            int j = chunkcoordintpair.getRegionX();
--            Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
--            if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
--            RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync);
- 
--            this.regionCache.putAndMoveToFirst(i, regionfile1);
--            return regionfile1;
+-            Path path = this.folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
+-            if (existingOnly && !java.nio.file.Files.exists(path)) return null; // CraftBukkit
+-            RegionFile regionFile1 = new RegionFile(this.info, path, this.folder, this.sync);
+-            this.regionCache.putAndMoveToFirst(packedChunkPos, regionFile1);
+-            return regionFile1;
++
 +            ret = new RegionFile(this.info, regionPath, this.folder, this.sync);
 +
 +            this.regionCache.putAndMoveToFirst(key, ret);
@@ -33420,37 +32916,34 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
      }
  
      // Paper start
-@@ -175,8 +361,14 @@ public final class RegionFileStorage implements AutoCloseable {
- 
+@@ -126,8 +315,14 @@ public final class RegionFileStorage implements AutoCloseable {
+         }
      }
  
--    protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
--        RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
-+    public void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { // Paper - rewrite chunk system - public
-+        RegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system
+-    protected void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException {
+-        RegionFile regionFile = this.getRegionFile(chunkPos, false); // CraftBukkit
++    public void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException { // Paper - rewrite chunk system - public
++        RegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system
 +        // Paper start - rewrite chunk system
-+        if (regionfile == null) {
++        if (regionFile == null) {
 +            // if the RegionFile doesn't exist, no point in deleting from it
 +            return;
 +        }
 +        // Paper end - rewrite chunk system
+         if (chunkData == null) {
+             regionFile.clear(chunkPos);
+         } else {
+@@ -140,23 +335,36 @@ public final class RegionFileStorage implements AutoCloseable {
  
-         if (nbt == null) {
-             regionfile.clear(pos);
-@@ -206,30 +398,37 @@ public final class RegionFileStorage implements AutoCloseable {
-     }
- 
+     @Override
      public void close() throws IOException {
--        ExceptionCollector<IOException> exceptionsuppressor = new ExceptionCollector<>();
--        ObjectIterator objectiterator = this.regionCache.values().iterator();
--
--        while (objectiterator.hasNext()) {
--            RegionFile regionfile = (RegionFile) objectiterator.next();
+-        ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
 -
+-        for (RegionFile regionFile : this.regionCache.values()) {
 -            try {
--                regionfile.close();
--            } catch (IOException ioexception) {
--                exceptionsuppressor.add(ioexception);
+-                regionFile.close();
+-            } catch (IOException var5) {
+-                exceptionCollector.add(var5);
 +        // Paper start - rewrite chunk system
 +        synchronized (this) {
 +            final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
@@ -33461,19 +32954,16 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
 +                    exceptionCollector.add(ex);
 +                }
              }
--        }
- 
--        exceptionsuppressor.throwIfPresent();
 +            exceptionCollector.throwIfPresent();
-+        }
+         }
+-
+-        exceptionCollector.throwIfPresent();
 +        // Paper end - rewrite chunk system
      }
  
      public void flush() throws IOException {
--        ObjectIterator objectiterator = this.regionCache.values().iterator();
--
--        while (objectiterator.hasNext()) {
--            RegionFile regionfile = (RegionFile) objectiterator.next();
+-        for (RegionFile regionFile : this.regionCache.values()) {
+-            regionFile.flush();
 +        // Paper start - rewrite chunk system
 +        synchronized (this) {
 +            final ExceptionCollector<IOException> exceptionCollector = new ExceptionCollector<>();
@@ -33484,16 +32974,15 @@ index e6abe35d6c43b7f76cf3da129ec9552e7b82453e..fdf8e18d24442178b52397acb482ffa3
 +                    exceptionCollector.add(ex);
 +                }
 +            }
- 
--            regionfile.flush();
++
 +            exceptionCollector.throwIfPresent();
          }
 +        // Paper end - rewrite chunk system
- 
      }
  
+     public RegionStorageInfo info() {
 diff --git a/net/minecraft/world/level/chunk/storage/SectionStorage.java b/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e5106056728 100644
+index 7dc1ffffd9d0fec54dbc254c154ee85ee750174d..778bd73a938c94ecb85ca0f8b686ff4e1baee040 100644
 --- a/net/minecraft/world/level/chunk/storage/SectionStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/SectionStorage.java
 @@ -40,10 +40,10 @@ import net.minecraft.world.level.ChunkPos;
@@ -33526,26 +33015,26 @@ index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e51
 +    // Paper end - rewrite chunk system
 +
      public SectionStorage(
-         SimpleRegionStorage storageAccess,
+         SimpleRegionStorage simpleRegionStorage,
          Codec<P> codec,
 @@ -67,7 +79,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
-         ChunkIOErrorReporter errorHandler,
-         LevelHeightAccessor world
+         ChunkIOErrorReporter errorReporter,
+         LevelHeightAccessor levelHeightAccessor
      ) {
--        this.simpleRegionStorage = storageAccess;
+-        this.simpleRegionStorage = simpleRegionStorage;
 +        // Paper - rewrite chunk system
          this.codec = codec;
-         this.packer = serializer;
-         this.unpacker = deserializer;
+         this.packer = packer;
+         this.unpacker = unpacker;
 @@ -75,6 +87,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
-         this.registryAccess = registryManager;
-         this.errorReporter = errorHandler;
-         this.levelHeightAccessor = world;
-+        this.regionStorage = storageAccess.worker.storage; // Paper - rewrite chunk system
+         this.registryAccess = registryAccess;
+         this.errorReporter = errorReporter;
+         this.levelHeightAccessor = levelHeightAccessor;
++        this.regionStorage = simpleRegionStorage.worker.storage; // Paper - rewrite chunk system
      }
  
-     protected void tick(BooleanSupplier shouldKeepTicking) {
-@@ -188,64 +201,15 @@ public class SectionStorage<R, P> implements AutoCloseable {
+     protected void tick(BooleanSupplier aheadOfTime) {
+@@ -188,65 +201,15 @@ public class SectionStorage<R, P> implements AutoCloseable {
      }
  
      private CompletableFuture<Optional<SectionStorage.PackedChunk<P>>> tryRead(ChunkPos chunkPos) {
@@ -33553,43 +33042,44 @@ index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e51
 -        return this.simpleRegionStorage
 -            .read(chunkPos)
 -            .thenApplyAsync(
--                chunkNbt -> chunkNbt.map(
--                        nbt -> SectionStorage.PackedChunk.parse(this.codec, registryOps, nbt, this.simpleRegionStorage, this.levelHeightAccessor)
--                    ),
+-                optional -> optional.map(
+-                    compoundTag -> SectionStorage.PackedChunk.parse(this.codec, registryOps, compoundTag, this.simpleRegionStorage, this.levelHeightAccessor)
+-                ),
 -                Util.backgroundExecutor().forName("parseSection")
 -            )
--            .exceptionally(throwable -> {
--                if (throwable instanceof CompletionException) {
--                    throwable = throwable.getCause();
+-            .exceptionally(cause -> {
+-                if (cause instanceof CompletionException) {
+-                    cause = cause.getCause();
 -                }
 -
--                if (throwable instanceof IOException iOException) {
--                    LOGGER.error("Error reading chunk {} data from disk", chunkPos, iOException);
--                    this.errorReporter.reportChunkLoadFailure(iOException, this.simpleRegionStorage.storageInfo(), chunkPos);
+-                if (cause instanceof IOException ioException) {
+-                    LOGGER.error("Error reading chunk {} data from disk", chunkPos, ioException);
+-                    this.errorReporter.reportChunkLoadFailure(ioException, this.simpleRegionStorage.storageInfo(), chunkPos);
 -                    return Optional.empty();
 -                } else {
--                    throw new CompletionException(throwable);
+-                    throw new CompletionException(cause);
 -                }
 -            });
 +        throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system
      }
  
-     private void unpackChunk(ChunkPos chunkPos, @Nullable SectionStorage.PackedChunk<P> result) {
--        if (result == null) {
--            for (int i = this.levelHeightAccessor.getMinSectionY(); i <= this.levelHeightAccessor.getMaxSectionY(); i++) {
--                this.storage.put(getKey(chunkPos, i), Optional.empty());
+     private void unpackChunk(ChunkPos pos, @Nullable SectionStorage.PackedChunk<P> packedChunk) {
+-        if (packedChunk == null) {
+-            for (int sectionY = this.levelHeightAccessor.getMinSectionY(); sectionY <= this.levelHeightAccessor.getMaxSectionY(); sectionY++) {
+-                this.storage.put(getKey(pos, sectionY), Optional.empty());
 -            }
 -        } else {
--            boolean bl = result.versionChanged();
+-            boolean versionChanged = packedChunk.versionChanged();
 -
--            for (int j = this.levelHeightAccessor.getMinSectionY(); j <= this.levelHeightAccessor.getMaxSectionY(); j++) {
--                long l = getKey(chunkPos, j);
--                Optional<R> optional = Optional.ofNullable(result.sectionsByY.get(j)).map(section -> this.unpacker.apply((P)section, () -> this.setDirty(l)));
--                this.storage.put(l, optional);
+-            for (int sectionY1 = this.levelHeightAccessor.getMinSectionY(); sectionY1 <= this.levelHeightAccessor.getMaxSectionY(); sectionY1++) {
+-                long key = getKey(pos, sectionY1);
+-                Optional<R> optional = Optional.ofNullable(packedChunk.sectionsByY.get(sectionY1))
+-                    .map(object -> this.unpacker.apply((P)object, () -> this.setDirty(key)));
+-                this.storage.put(key, optional);
 -                optional.ifPresent(object -> {
--                    this.onSectionLoad(l);
--                    if (bl) {
--                        this.setDirty(l);
+-                    this.onSectionLoad(key);
+-                    if (versionChanged) {
+-                        this.setDirty(key);
 -                    }
 -                });
 -            }
@@ -33612,17 +33102,17 @@ index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e51
 +        throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system
      }
  
-     private <T> Dynamic<T> writeChunk(ChunkPos chunkPos, DynamicOps<T> ops) {
-@@ -281,7 +245,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
-     protected void onSectionLoad(long pos) {
+     private <T> Dynamic<T> writeChunk(ChunkPos pos, DynamicOps<T> ops) {
+@@ -282,7 +245,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
+     protected void onSectionLoad(long sectionKey) {
      }
  
--    protected void setDirty(long pos) {
-+    public void setDirty(long pos) { // Paper - public
-         Optional<R> optional = this.storage.get(pos);
+-    protected void setDirty(long sectionPos) {
++    public void setDirty(long sectionPos) { // Paper - public
+         Optional<R> optional = this.storage.get(sectionPos);
          if (optional != null && !optional.isEmpty()) {
-             this.dirtyChunks.add(ChunkPos.asLong(SectionPos.x(pos), SectionPos.z(pos)));
-@@ -302,7 +266,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
+             this.dirtyChunks.add(ChunkPos.asLong(SectionPos.x(sectionPos), SectionPos.z(sectionPos)));
+@@ -303,7 +266,7 @@ public class SectionStorage<R, P> implements AutoCloseable {
  
      @Override
      public void close() throws IOException {
@@ -33630,27 +33120,27 @@ index 75b2cf0e13c23a8348b7ff55e72e5ee755aa7460..c3beb7fcad46a917d2b61bd0a0e98e51
 +        this.moonrise$close(); // Paper - rewrite chunk system
      }
  
-     static record PackedChunk<T>(Int2ObjectMap<T> sectionsByY, boolean versionChanged) {
+     record PackedChunk<T>(Int2ObjectMap<T> sectionsByY, boolean versionChanged) {
 diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77fab826cd7 100644
+index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..e8aafbd4bd9eaba4aaa448333b37c30a8dd719bf 100644
 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -129,7 +129,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-             long j = nbt.getLong("InhabitedTime");
-             ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
-             UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
--            boolean flag = nbt.getBoolean("isLightOn");
-+            boolean flag = chunkstatus.isOrAfter(ChunkStatus.LIGHT) && (nbt.get("isLightOn") != null && nbt.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG) == ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION); // Paper - starlight
-             DataResult dataresult;
-             Logger logger;
-             BlendingData.Packed blendingdata_d;
-@@ -246,7 +246,17 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-                 DataLayer nibblearray = nbttagcompound3.contains("BlockLight", 7) ? new DataLayer(nbttagcompound3.getByteArray("BlockLight")) : null;
-                 DataLayer nibblearray1 = nbttagcompound3.contains("SkyLight", 7) ? new DataLayer(nbttagcompound3.getByteArray("SkyLight")) : null;
+@@ -148,7 +148,7 @@ public record SerializableChunkData(
+             UpgradeData upgradeData = tag.contains("UpgradeData", 10)
+                 ? new UpgradeData(tag.getCompound("UpgradeData"), levelHeightAccessor)
+                 : UpgradeData.EMPTY;
+-            boolean _boolean = tag.getBoolean("isLightOn");
++            boolean _boolean = chunkstatus.isOrAfter(ChunkStatus.LIGHT) && (nbt.get("isLightOn") != null && nbt.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG) == ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION); // Paper - starlight
+             BlendingData.Packed packed;
+             if (tag.contains("blending_data", 10)) {
+                 packed = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, tag.getCompound("blending_data")).resultOrPartial(LOGGER::error).orElse(null);
+@@ -249,7 +249,17 @@ public record SerializableChunkData(
  
--                list4.add(new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1));
+                 DataLayer dataLayer = compound2.contains("BlockLight", 7) ? new DataLayer(compound2.getByteArray("BlockLight")) : null;
+                 DataLayer dataLayer1 = compound2.contains("SkyLight", 7) ? new DataLayer(compound2.getByteArray("SkyLight")) : null;
+-                list8.add(new SerializableChunkData.SectionData(_byte, levelChunkSection, dataLayer, dataLayer1));
 +                // Paper start - starlight
-+                SerializableChunkData.SectionData serializableChunkData = new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1);
++                SerializableChunkData.SectionData serializableChunkData = new SerializableChunkData.SectionData(_byte, levelChunkSection, dataLayer, dataLayer1);
 +                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.BLOCKLIGHT_STATE_TAG, Tag.TAG_ANY_NUMERIC)) {
 +                    ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)serializableChunkData).starlight$setBlockLightState(sectionData.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.BLOCKLIGHT_STATE_TAG));
 +                }
@@ -33658,12 +33148,12 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.SKYLIGHT_STATE_TAG, Tag.TAG_ANY_NUMERIC)) {
 +                    ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)serializableChunkData).starlight$setSkyLightState(sectionData.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.SKYLIGHT_STATE_TAG));
 +                }
-+                list4.add(serializableChunkData);
++                list8.add(serializableChunkData);
 +                // Paper end - starlight
              }
  
-             // CraftBukkit - ChunkBukkitValues
-@@ -254,6 +264,59 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+             return new SerializableChunkData(
+@@ -276,6 +286,59 @@ public record SerializableChunkData(
          }
      }
  
@@ -33720,56 +33210,59 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +    }
 +    // Paper end - starlight
 +
-     public ProtoChunk read(ServerLevel world, PoiManager poiStorage, RegionStorageInfo key, ChunkPos expectedPos) {
-         if (!Objects.equals(expectedPos, this.chunkPos)) {
-             SerializableChunkData.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{expectedPos, expectedPos, this.chunkPos});
-@@ -275,7 +338,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
- 
-             if (serializablechunkdata_b.chunkSection != null) {
-                 achunksection[world.getSectionIndexFromSectionY(serializablechunkdata_b.y)] = serializablechunkdata_b.chunkSection;
--                poiStorage.checkConsistencyWithBlocks(sectionposition, serializablechunkdata_b.chunkSection);
-+                //poiStorage.checkConsistencyWithBlocks(sectionposition, serializablechunkdata_b.chunkSection); // Paper - rewrite chunk system
+     public ProtoChunk read(ServerLevel level, PoiManager poiManager, RegionStorageInfo regionStorageInfo, ChunkPos pos) {
+         if (!Objects.equals(pos, this.chunkPos)) {
+             LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", pos, pos, this.chunkPos);
+@@ -294,7 +357,7 @@ public record SerializableChunkData(
+             SectionPos sectionPos = SectionPos.of(pos, sectionData.y);
+             if (sectionData.chunkSection != null) {
+                 levelChunkSections[level.getSectionIndexFromSectionY(sectionData.y)] = sectionData.chunkSection;
+-                poiManager.checkConsistencyWithBlocks(sectionPos, sectionData.chunkSection);
++                //poiManager.checkConsistencyWithBlocks(sectionPos, sectionData.chunkSection); // Paper - rewrite chunk system
              }
  
-             boolean flag2 = serializablechunkdata_b.blockLight != null;
-@@ -352,7 +415,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+             boolean flag1 = sectionData.blockLight != null;
+@@ -376,7 +439,7 @@ public record SerializableChunkData(
          }
  
-         if (chunktype == ChunkType.LEVELCHUNK) {
--            return new ImposterProtoChunk((LevelChunk) object, false);
-+            return this.loadStarlightLightData(world, new ImposterProtoChunk((LevelChunk) object, false)); // Paper - starlight
+         if (chunkType == ChunkType.LEVELCHUNK) {
+-            return new ImposterProtoChunk((LevelChunk)chunkAccess, false);
++            return this.loadStarlightLightData(new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight
          } else {
-             ProtoChunk protochunk1 = (ProtoChunk) object;
-             Iterator iterator2 = this.entities.iterator();
-@@ -382,7 +445,7 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-                 protochunk1.setCarvingMask(new CarvingMask(this.carvingMask, ((ChunkAccess) object).getMinY()));
+             ProtoChunk protoChunk1 = (ProtoChunk)chunkAccess;
+ 
+@@ -399,7 +462,7 @@ public record SerializableChunkData(
+                 protoChunk1.setCarvingMask(new CarvingMask(this.carvingMask, chunkAccess.getMinY()));
              }
  
--            return protochunk1;
-+            return this.loadStarlightLightData(world, protochunk1); // Paper - starlight
+-            return protoChunk1;
++            return this.loadStarlightLightData(level, protoChunk1); // Paper - starlight
          }
      }
  
-@@ -405,24 +468,48 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-             throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk));
+@@ -427,22 +490,48 @@ public record SerializableChunkData(
+             throw new IllegalArgumentException("Chunk can't be serialized: " + chunk);
          } else {
-             ChunkPos chunkcoordintpair = chunk.getPos();
--            List<SerializableChunkData.SectionData> list = new ArrayList();
-+            List<SerializableChunkData.SectionData> list = new ArrayList(); final List<SerializableChunkData.SectionData> sections = list; // Paper - starlight - OBFHELPER
-             LevelChunkSection[] achunksection = chunk.getSections();
-             ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
+             ChunkPos pos = chunk.getPos();
+-            List<SerializableChunkData.SectionData> list = new ArrayList<>();
++            List<SerializableChunkData.SectionData> list = new ArrayList<>(); final List<SerializableChunkData.SectionData> sectionsList = list; // Paper - starlight - OBFHELPER
+             LevelChunkSection[] sections = chunk.getSections();
+             LevelLightEngine lightEngine = level.getChunkSource().getLightEngine();
  
--            for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
--                int j = chunk.getSectionIndexFromSectionY(i);
--                boolean flag = j >= 0 && j < achunksection.length;
--                DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
--                DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
--                DataLayer nibblearray2 = nibblearray != null && !nibblearray.isEmpty() ? nibblearray.copy() : null;
--                DataLayer nibblearray3 = nibblearray1 != null && !nibblearray1.isEmpty() ? nibblearray1.copy() : null;
+-            for (int lightSection = lightEngine.getMinLightSection(); lightSection < lightEngine.getMaxLightSection(); lightSection++) {
+-                int sectionIndexFromSectionY = chunk.getSectionIndexFromSectionY(lightSection);
+-                boolean flag = sectionIndexFromSectionY >= 0 && sectionIndexFromSectionY < sections.length;
+-                DataLayer dataLayerData = lightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(pos, lightSection));
+-                DataLayer dataLayerData1 = lightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(pos, lightSection));
+-                DataLayer dataLayer = dataLayerData != null && !dataLayerData.isEmpty() ? dataLayerData.copy() : null;
+-                DataLayer dataLayer1 = dataLayerData1 != null && !dataLayerData1.isEmpty() ? dataLayerData1.copy() : null;
+-                if (flag || dataLayer != null || dataLayer1 != null) {
+-                    LevelChunkSection levelChunkSection = flag ? sections[sectionIndexFromSectionY].copy() : null;
+-                    list.add(new SerializableChunkData.SectionData(lightSection, levelChunkSection, dataLayer, dataLayer1));
 +            // Paper start - starlight
-+            final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(world);
-+            final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(world);
-+            final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world);
++            final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(level);
++            final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(level);
++            final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level);
 +
 +            final LevelChunkSection[] chunkSections = chunk.getSections();
 +            final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles = ((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles();
@@ -33778,17 +33271,14 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +            for (int lightSection = minLightSection; lightSection <= maxLightSection; ++lightSection) {
 +                final int lightSectionIdx = lightSection - minLightSection;
 +                final int blockSectionIdx = lightSection - minBlockSection;
- 
--                if (flag || nibblearray2 != null || nibblearray3 != null) {
--                    LevelChunkSection chunksection = flag ? achunksection[j].copy() : null;
++
 +                final LevelChunkSection chunkSection = (blockSectionIdx >= 0 && blockSectionIdx < chunkSections.length) ? chunkSections[blockSectionIdx].copy() : null;
 +                final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[lightSectionIdx].getSaveState();
 +                final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[lightSectionIdx].getSaveState();
- 
--                    list.add(new SerializableChunkData.SectionData(i, chunksection, nibblearray2, nibblearray3));
++
 +                if (chunkSection == null && blockNibble == null && skyNibble == null) {
 +                    continue;
-                 }
++                }
 +
 +                final SerializableChunkData.SectionData sectionData = new SerializableChunkData.SectionData(
 +                    lightSection, chunkSection,
@@ -33802,27 +33292,25 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +
 +                if (skyNibble != null) {
 +                    ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state);
-+                }
+                 }
 +
-+                sections.add(sectionData);
++                sectionsList.add(sectionData);
              }
 +            // Paper end - starlight
  
-             List<CompoundTag> list1 = new ArrayList(chunk.getBlockEntitiesPos().size());
-             Iterator iterator = chunk.getBlockEntitiesPos().iterator();
-@@ -521,8 +608,8 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-         Iterator iterator = this.sectionData.iterator();
+             List<CompoundTag> list1 = new ArrayList<>(chunk.getBlockEntitiesPos().size());
  
-         while (iterator.hasNext()) {
--            SerializableChunkData.SectionData serializablechunkdata_b = (SerializableChunkData.SectionData) iterator.next();
--            CompoundTag nbttagcompound1 = new CompoundTag();
-+            SerializableChunkData.SectionData serializablechunkdata_b = (SerializableChunkData.SectionData) iterator.next(); final SerializableChunkData.SectionData sectionData = serializablechunkdata_b; // Paper - starlight - OBFHELPER
-+            CompoundTag nbttagcompound1 = new CompoundTag(); final CompoundTag sectionNBT = nbttagcompound1; // Paper - starlight - OBFHELPER
-             LevelChunkSection chunksection = serializablechunkdata_b.chunkSection;
+@@ -540,7 +629,7 @@ public record SerializableChunkData(
+         Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(this.biomeRegistry);
  
-             if (chunksection != null) {
-@@ -538,6 +625,19 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-                 nbttagcompound1.putByteArray("SkyLight", serializablechunkdata_b.skyLight.getData());
+         for (SerializableChunkData.SectionData sectionData : this.sectionData) {
+-            CompoundTag compoundTag1 = new CompoundTag();
++            CompoundTag compoundTag1 = new CompoundTag(); final CompoundTag sectionNBT = compoundTag1; // Paper - starlight - OBFHELPER
+             LevelChunkSection levelChunkSection = sectionData.chunkSection;
+             if (levelChunkSection != null) {
+                 compoundTag1.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, levelChunkSection.getStates()).getOrThrow());
+@@ -555,6 +644,19 @@ public record SerializableChunkData(
+                 compoundTag1.putByteArray("SkyLight", sectionData.skyLight.getData());
              }
  
 +            // Paper start - starlight
@@ -33838,29 +33326,29 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +            }
 +            // Paper end - starlight
 +
-             if (!nbttagcompound1.isEmpty()) {
-                 nbttagcompound1.putByte("Y", (byte) serializablechunkdata_b.y);
-                 nbttaglist.add(nbttagcompound1);
-@@ -577,6 +677,14 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-             nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer);
+             if (!compoundTag1.isEmpty()) {
+                 compoundTag1.putByte("Y", (byte)sectionData.y);
+                 listTag.add(compoundTag1);
+@@ -589,6 +691,14 @@ public record SerializableChunkData(
+             compoundTag.put("ChunkBukkitValues", this.persistentDataContainer);
          }
          // CraftBukkit end
 +        // Paper start - starlight
 +        if (this.lightCorrect && !this.chunkStatus.isBefore(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) {
 +            // clobber vanilla value to force vanilla to relight
-+            nbttagcompound.putBoolean("isLightOn", false);
++            compoundTag.putBoolean("isLightOn", false);
 +            // store our light version
-+            nbttagcompound.putInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG, ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION);
++            compoundTag.putInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG, ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION);
 +        }
 +        // Paper end - starlight
-         return nbttagcompound;
+         return compoundTag;
      }
  
-@@ -763,7 +871,67 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-         return nbttaglist;
+@@ -747,6 +857,66 @@ public record SerializableChunkData(
+         }
      }
  
--    public static record SectionData(int y, @Nullable LevelChunkSection chunkSection, @Nullable DataLayer blockLight, @Nullable DataLayer skyLight) {
+-    public record SectionData(int y, @Nullable LevelChunkSection chunkSection, @Nullable DataLayer blockLight, @Nullable DataLayer skyLight) {
 +    // Paper start - starlight - convert from record
 +    public static final class SectionData implements ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData { // Paper - starlight - our diff
 +        private final int y;
@@ -33922,11 +33410,10 @@ index 4bc7fa3324e9af3abce2acf960c7b0650aca2e36..0296f52fb2c871adbf2ce73a64d8f77f
 +            return skyLight;
 +        }
 +        // Paper end - starlight - convert from record
- 
      }
- 
+ }
 diff --git a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
-index 578d270d5b7efb9ac8f5dde539170f6021e2b786..c5085ebf4e801837010f3750c5e89576bb0c27a5 100644
+index 41ddaceb7485626b1f2ee258c2142eb3114c106e..f883c6400281788982403d0af3ee28613e9a29b1 100644
 --- a/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 @@ -14,7 +14,7 @@ import net.minecraft.util.datafix.DataFixTypes;
@@ -33939,7 +33426,7 @@ index 578d270d5b7efb9ac8f5dde539170f6021e2b786..c5085ebf4e801837010f3750c5e89576
      private final DataFixTypes dataFixType;
  
 diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java
-index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2d9c609d9 100644
+index 342c83309b19c64d86e0dd97c1756c96be52772b..423779a2b690f387a4f0bd07b97b50e0baefda76 100644
 --- a/net/minecraft/world/level/entity/EntityTickList.java
 +++ b/net/minecraft/world/level/entity/EntityTickList.java
 @@ -9,52 +9,38 @@ import javax.annotation.Nullable;
@@ -33960,9 +33447,9 @@ index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2
 -                this.passive.put(entry.getIntKey(), entry.getValue());
 -            }
 -
--            Int2ObjectMap<Entity> int2ObjectMap = this.active;
+-            Int2ObjectMap<Entity> map = this.active;
 -            this.active = this.passive;
--            this.passive = int2ObjectMap;
+-            this.passive = map;
 -        }
 +        // Paper - rewrite chunk system
      }
@@ -33984,15 +33471,15 @@ index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2
 +        return this.entities.contains(entity); // Paper - rewrite chunk system
      }
  
-     public void forEach(Consumer<Entity> action) {
+     public void forEach(Consumer<Entity> entity) {
 -        if (this.iterated != null) {
 -            throw new UnsupportedOperationException("Only one concurrent iteration supported");
 -        } else {
 -            this.iterated = this.active;
 -
 -            try {
--                for (Entity entity : this.active.values()) {
--                    action.accept(entity);
+-                for (Entity entity1 : this.active.values()) {
+-                    entity.accept(entity1);
 -                }
 -            } finally {
 -                this.iterated = null;
@@ -34002,7 +33489,7 @@ index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2
 +        final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
 +        try {
 +            while (iterator.hasNext()) {
-+                action.accept(iterator.next());
++                entity.accept(iterator.next());
              }
 +        } finally {
 +            iterator.finishedIterating();
@@ -34011,29 +33498,29 @@ index 74a285b8b018a9c94ccea519f1ce8b9e2ef3cb64..d8b4196adf955f8d414688dc451caac2
      }
  }
 diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-index 1fcc2b287ed723cf51720f80e68f18f4a15cf429..3f39d6c786d9dfdd9ad591e08ff05fcbb41a1df6 100644
+index b50de067a2a968926bdda4174e42f2973c84ff8b..8b81257328771ad23b90d7670f13713f93eefa96 100644
 --- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 +++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-@@ -86,7 +86,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+@@ -78,7 +78,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
          return CompletableFuture.supplyAsync(() -> {
-             this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk);
+             this.doCreateBiomes(blender, randomState, structureManager, chunk);
              return chunk;
 -        }, Util.backgroundExecutor().forName("init_biomes"));
 +        }, Runnable::run); // Paper - rewrite chunk system
      }
  
-     private void doCreateBiomes(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) {
-@@ -311,7 +311,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
+     private void doCreateBiomes(Blender blender, RandomState random, StructureManager structureManager, ChunkAccess chunk) {
+@@ -318,7 +318,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
              }
  
-             return ichunkaccess1;
+             return var20;
 -        }, Util.backgroundExecutor().forName("wgen_fill_noise"));
 +        }, Runnable::run); // Paper - rewrite chunk system
      }
  
-     private ChunkAccess doFill(Blender blender, StructureManager structureAccessor, RandomState noiseConfig, ChunkAccess chunk, int minimumCellY, int cellHeight) {
+     private ChunkAccess doFill(Blender blender, StructureManager structureManager, RandomState random, ChunkAccess chunk, int minCellY, int cellCountY) {
 diff --git a/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
-index c3586281c9594769593a6027ea0a78f7c76c0262..decdb275e83fa6244aa3a24458872b42c49d04ed 100644
+index 06b54c0bec4031689d5c2da5cfea4ef28dbd16bc..f7dc4957b38878ddd3bfc7546be8a4e0af65c807 100644
 --- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 +++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 @@ -47,8 +47,13 @@ public class StructureCheck {
@@ -34051,63 +33538,63 @@ index c3586281c9594769593a6027ea0a78f7c76c0262..decdb275e83fa6244aa3a24458872b42
 +    // Paper end - rewrite chunk system
  
      public StructureCheck(
-         ChunkScanAccess chunkIoWorker,
+         ChunkScanAccess storageAccess,
 @@ -90,7 +95,7 @@ public class StructureCheck {
  
-     public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) {
-         long l = pos.toLong();
--        Object2IntMap<Structure> object2IntMap = this.loadedChunks.get(l);
-+        Object2IntMap<Structure> object2IntMap = this.loadedChunksSafe.get(l); // Paper - rewrite chunk system
-         if (object2IntMap != null) {
-             return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures);
+     public StructureCheckResult checkStart(ChunkPos chunkPos, Structure structure, StructurePlacement placement, boolean skipKnownStructures) {
+         long packedChunkPos = chunkPos.toLong();
+-        Object2IntMap<Structure> map = this.loadedChunks.get(packedChunkPos);
++        Object2IntMap<Structure> map = this.loadedChunksSafe.get(packedChunkPos); // Paper - rewrite chunk system
+         if (map != null) {
+             return this.checkStructureInfo(map, structure, skipKnownStructures);
          } else {
 @@ -100,9 +105,11 @@ public class StructureCheck {
-             } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs
+             } else if (!placement.applyAdditionalChunkRestrictions(chunkPos.x, chunkPos.z, this.seed, this.getSaltOverride(structure))) { // Paper - add missing structure seed configs
                  return StructureCheckResult.START_NOT_PRESENT;
              } else {
--                boolean bl = this.featureChecks
--                    .computeIfAbsent(type, structure2 -> new Long2BooleanOpenHashMap())
--                    .computeIfAbsent(l, chunkPos -> this.canCreateStructure(pos, type));
+-                boolean flag = this.featureChecks
+-                    .computeIfAbsent(structure, structure1 -> new Long2BooleanOpenHashMap())
+-                    .computeIfAbsent(packedChunkPos, l -> this.canCreateStructure(chunkPos, structure));
 +                // Paper start - rewrite chunk system
-+                boolean bl = this.featureChecksSafe
-+                    .computeIfAbsent(type, structure2 -> new ca.spottedleaf.moonrise.common.map.SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT))
-+                    .getOrCompute(l, chunkPos -> this.canCreateStructure(pos, type));
++                boolean flag = this.featureChecksSafe
++                    .computeIfAbsent(structure, structure1 -> new ca.spottedleaf.moonrise.common.map.SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT))
++                    .getOrCompute(packedChunkPos, l -> this.canCreateStructure(chunkPos, structure));
 +                // Paper end - rewrite chunk system
-                 return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED;
+                 return !flag ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED;
              }
          }
 @@ -228,15 +235,25 @@ public class StructureCheck {
      }
  
-     private void storeFullResults(long pos, Object2IntMap<Structure> referencesByStructure) {
--        this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure));
--        this.featureChecks.values().forEach(generationPossibilityByChunkPos -> generationPossibilityByChunkPos.remove(pos));
+     private void storeFullResults(long chunkPos, Object2IntMap<Structure> structureChunks) {
+-        this.loadedChunks.put(chunkPos, deduplicateEmptyMap(structureChunks));
+-        this.featureChecks.values().forEach(map -> map.remove(chunkPos));
 +        // Paper start - rewrite chunk system
-+        this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure));
++        this.loadedChunksSafe.put(chunkPos, deduplicateEmptyMap(structureChunks));
 +        // once we insert into loadedChunks, we don't really need to be very careful about removing everything
 +        // from this map, as everything that checks this map uses loadedChunks first
 +        // so, one way or another it's a race condition that doesn't matter
 +        for (ca.spottedleaf.moonrise.common.map.SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) {
-+            value.remove(pos);
++            value.remove(chunkPos);
 +        }
 +        // Paper end - rewrite chunk system
      }
  
      public void incrementReference(ChunkPos pos, Structure structure) {
--        this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> {
--            if (referencesByStructure == null || referencesByStructure.isEmpty()) {
-+        this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system
-+            if (referencesByStructure == null) {
-                 referencesByStructure = new Object2IntOpenHashMap<>();
+-        this.loadedChunks.compute(pos.toLong(), (_long, map) -> {
+-            if (map == null || map.isEmpty()) {
++        this.loadedChunksSafe.compute(pos.toLong(), (_long, map) -> { // Paper start - rewrite chunk system
++            if (map == null) {
+                 map = new Object2IntOpenHashMap<>();
 +            } else {
-+                referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap<Structure> fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure);
++                map = map instanceof Object2IntOpenHashMap<Structure> fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(map);
              }
 +            // Paper end - rewrite chunk system
  
-             referencesByStructure.computeInt(structure, (feature, references) -> references == null ? 1 : references + 1);
-             return referencesByStructure;
+             map.computeInt(structure, (structure1, integer) -> integer == null ? 1 : integer + 1);
+             return map;
 diff --git a/net/minecraft/world/level/lighting/LevelLightEngine.java b/net/minecraft/world/level/lighting/LevelLightEngine.java
-index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede341297bc58dba 100644
+index ca23af013967b50420ebee178878ea79333de53b..83c3ec06be51f632b7c1b682cfa8dce73ff7e0c0 100644
 --- a/net/minecraft/world/level/lighting/LevelLightEngine.java
 +++ b/net/minecraft/world/level/lighting/LevelLightEngine.java
 @@ -9,151 +9,111 @@ import net.minecraft.world.level.LightLayer;
@@ -34148,15 +33635,15 @@ index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede34129
 +    }
 +    // Paper end - rewrite chunk system
  
-     public LevelLightEngine(LightChunkGetter chunkProvider, boolean hasBlockLight, boolean hasSkyLight) {
-         this.levelHeightAccessor = chunkProvider.getLevel();
--        this.blockEngine = hasBlockLight ? new BlockLightEngine(chunkProvider) : null;
--        this.skyEngine = hasSkyLight ? new SkyLightEngine(chunkProvider) : null;
+     public LevelLightEngine(LightChunkGetter lightChunkGetter, boolean blockLight, boolean skyLight) {
+         this.levelHeightAccessor = lightChunkGetter.getLevel();
+-        this.blockEngine = blockLight ? new BlockLightEngine(lightChunkGetter) : null;
+-        this.skyEngine = skyLight ? new SkyLightEngine(lightChunkGetter) : null;
 +        // Paper start - rewrite chunk system
-+        if (chunkProvider.getLevel() instanceof net.minecraft.world.level.Level) {
-+            this.lightEngine = new ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface(chunkProvider, hasSkyLight, hasBlockLight, (LevelLightEngine)(Object)this);
++        if (lightChunkGetter.getLevel() instanceof net.minecraft.world.level.Level) {
++            this.lightEngine = new ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface(lightChunkGetter, skyLight, blockLight, (LevelLightEngine)(Object)this);
 +        } else {
-+            this.lightEngine = new ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface(null, hasSkyLight, hasBlockLight, (LevelLightEngine)(Object)this);
++            this.lightEngine = new ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface(null, skyLight, blockLight, (LevelLightEngine)(Object)this);
 +        }
 +        // Paper end - rewrite chunk system
      }
@@ -34206,25 +33693,25 @@ index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede34129
      }
  
      @Override
-     public void updateSectionStatus(SectionPos pos, boolean notReady) {
+     public void updateSectionStatus(SectionPos pos, boolean isEmpty) {
 -        if (this.blockEngine != null) {
--            this.blockEngine.updateSectionStatus(pos, notReady);
+-            this.blockEngine.updateSectionStatus(pos, isEmpty);
 -        }
 -
 -        if (this.skyEngine != null) {
--            this.skyEngine.updateSectionStatus(pos, notReady);
+-            this.skyEngine.updateSectionStatus(pos, isEmpty);
 -        }
 +        this.lightEngine.sectionChange(pos, notReady); // Paper - rewrite chunk system
      }
  
      @Override
-     public void setLightEnabled(ChunkPos pos, boolean retainData) {
+     public void setLightEnabled(ChunkPos chunkPos, boolean lightEnabled) {
 -        if (this.blockEngine != null) {
--            this.blockEngine.setLightEnabled(pos, retainData);
+-            this.blockEngine.setLightEnabled(chunkPos, lightEnabled);
 -        }
 -
 -        if (this.skyEngine != null) {
--            this.skyEngine.setLightEnabled(pos, retainData);
+-            this.skyEngine.setLightEnabled(chunkPos, lightEnabled);
 -        }
 +        // Paper - rewrite chunk system
      }
@@ -34241,82 +33728,82 @@ index 8d90e783967280025d711c709facbcc87f611f8a..987e3397503cd07d3a2f172cede34129
 +        // Paper - rewrite chunk system
      }
  
-     public LayerLightEventListener getLayerListener(LightLayer lightType) {
--        if (lightType == LightLayer.BLOCK) {
+     public LayerLightEventListener getLayerListener(LightLayer type) {
+-        if (type == LightLayer.BLOCK) {
 -            return (LayerLightEventListener)(this.blockEngine == null ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : this.blockEngine);
 -        } else {
 -            return (LayerLightEventListener)(this.skyEngine == null ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : this.skyEngine);
 -        }
-+        return lightType == LightLayer.BLOCK ? this.lightEngine.getBlockReader() : this.lightEngine.getSkyReader(); // Paper - rewrite chunk system
++        return type == LightLayer.BLOCK ? this.lightEngine.getBlockReader() : this.lightEngine.getSkyReader(); // Paper - rewrite chunk system
      }
  
-     public String getDebugData(LightLayer lightType, SectionPos pos) {
--        if (lightType == LightLayer.BLOCK) {
+     public String getDebugData(LightLayer lightLayer, SectionPos sectionPos) {
+-        if (lightLayer == LightLayer.BLOCK) {
 -            if (this.blockEngine != null) {
--                return this.blockEngine.getDebugData(pos.asLong());
+-                return this.blockEngine.getDebugData(sectionPos.asLong());
 -            }
 -        } else if (this.skyEngine != null) {
--            return this.skyEngine.getDebugData(pos.asLong());
+-            return this.skyEngine.getDebugData(sectionPos.asLong());
 -        }
 -
 -        return "n/a";
 +        return "n/a"; // Paper - rewrite chunk system
      }
  
-     public LayerLightSectionStorage.SectionType getDebugSectionType(LightLayer lightType, SectionPos pos) {
--        if (lightType == LightLayer.BLOCK) {
+     public LayerLightSectionStorage.SectionType getDebugSectionType(LightLayer lightLayer, SectionPos sectionPos) {
+-        if (lightLayer == LightLayer.BLOCK) {
 -            if (this.blockEngine != null) {
--                return this.blockEngine.getDebugSectionType(pos.asLong());
+-                return this.blockEngine.getDebugSectionType(sectionPos.asLong());
 -            }
 -        } else if (this.skyEngine != null) {
--            return this.skyEngine.getDebugSectionType(pos.asLong());
+-            return this.skyEngine.getDebugSectionType(sectionPos.asLong());
 -        }
 -
 -        return LayerLightSectionStorage.SectionType.EMPTY;
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) {
--        if (lightType == LightLayer.BLOCK) {
+     public void queueSectionData(LightLayer lightLayer, SectionPos sectionPos, @Nullable DataLayer dataLayer) {
+-        if (lightLayer == LightLayer.BLOCK) {
 -            if (this.blockEngine != null) {
--                this.blockEngine.queueSectionData(pos.asLong(), nibbles);
+-                this.blockEngine.queueSectionData(sectionPos.asLong(), dataLayer);
 -            }
 -        } else if (this.skyEngine != null) {
--            this.skyEngine.queueSectionData(pos.asLong(), nibbles);
+-            this.skyEngine.queueSectionData(sectionPos.asLong(), dataLayer);
 -        }
 +        // Paper - rewrite chunk system
      }
  
-     public void retainData(ChunkPos pos, boolean retainData) {
+     public void retainData(ChunkPos pos, boolean retain) {
 -        if (this.blockEngine != null) {
--            this.blockEngine.retainData(pos, retainData);
+-            this.blockEngine.retainData(pos, retain);
 -        }
 -
 -        if (this.skyEngine != null) {
--            this.skyEngine.retainData(pos, retainData);
+-            this.skyEngine.retainData(pos, retain);
 -        }
 +        // Paper - rewrite chunk system
      }
  
-     public int getRawBrightness(BlockPos pos, int ambientDarkness) {
--        int i = this.skyEngine == null ? 0 : this.skyEngine.getLightValue(pos) - ambientDarkness;
--        int j = this.blockEngine == null ? 0 : this.blockEngine.getLightValue(pos);
--        return Math.max(j, i);
-+        return this.lightEngine.getRawBrightness(pos, ambientDarkness); // Paper - rewrite chunk system
+     public int getRawBrightness(BlockPos blockPos, int amount) {
+-        int i = this.skyEngine == null ? 0 : this.skyEngine.getLightValue(blockPos) - amount;
+-        int i1 = this.blockEngine == null ? 0 : this.blockEngine.getLightValue(blockPos);
+-        return Math.max(i1, i);
++        return this.lightEngine.getRawBrightness(blockPos, amount); // Paper - rewrite chunk system
      }
  
-     public boolean lightOnInColumn(long sectionPos) {
+     public boolean lightOnInColumn(long columnPos) {
 -        return this.blockEngine == null
--            || this.blockEngine.storage.lightOnInColumn(sectionPos) && (this.skyEngine == null || this.skyEngine.storage.lightOnInColumn(sectionPos));
+-            || this.blockEngine.storage.lightOnInColumn(columnPos) && (this.skyEngine == null || this.skyEngine.storage.lightOnInColumn(columnPos));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system // Paper - not implemented on server
      }
  
      public int getLightSectionCount() {
 diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
-index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1 100644
+index b9ad980b9a203bbc295f780bf54941f19a2f6525..19beef65b292c474f3a25b6c3ff679e31c17c7f4 100644
 --- a/net/minecraft/world/level/material/FlowingFluid.java
 +++ b/net/minecraft/world/level/material/FlowingFluid.java
-@@ -55,6 +55,48 @@ public abstract class FlowingFluid extends Fluid {
+@@ -45,6 +45,48 @@ public abstract class FlowingFluid extends Fluid {
      });
      private final Map<FluidState, VoxelShape> shapes = Maps.newIdentityHashMap();
  
@@ -34362,97 +33849,97 @@ index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c
 +    }
 +    // Paper end - fluid method optimisations
 +
-     public FlowingFluid() {}
- 
      @Override
-@@ -246,65 +288,70 @@ public abstract class FlowingFluid extends Fluid {
+     protected void createFluidStateDefinition(StateDefinition.Builder<Fluid, FluidState> builder) {
+         builder.add(FALLING);
+@@ -209,61 +251,71 @@ public abstract class FlowingFluid extends Fluid {
          }
      }
  
--    private static boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) {
--        VoxelShape voxelshape = fromState.getCollisionShape(world, fromPos);
+-    private static boolean canPassThroughWall(
+-        Direction direction, BlockGetter level, BlockPos pos, BlockState state, BlockPos spreadPos, BlockState spreadState
+-    ) {
+-        VoxelShape collisionShape = spreadState.getCollisionShape(level, spreadPos);
+-        if (collisionShape == Shapes.block()) {
+-            return false;
+-        } else {
+-            VoxelShape collisionShape1 = state.getCollisionShape(level, pos);
+-            if (collisionShape1 == Shapes.block()) {
+-                return false;
+-            } else if (collisionShape1 == Shapes.empty() && collisionShape == Shapes.empty()) {
+-                return true;
+-            } else {
+-                Object2ByteLinkedOpenHashMap<FlowingFluid.BlockStatePairKey> map;
+-                if (!state.getBlock().hasDynamicShape() && !spreadState.getBlock().hasDynamicShape()) {
+-                    map = OCCLUSION_CACHE.get();
+-                } else {
+-                    map = null;
+-                }
 +    // Paper start - fluid method optimisations
 +    private static boolean canPassThroughWall(final Direction direction, final BlockGetter level,
-+                                             final BlockPos fromPos, final BlockState fromState,
-+                                             final BlockPos toPos, final BlockState toState) {
++                                              final BlockPos fromPos, final BlockState fromState,
++                                              final BlockPos toPos, final BlockState toState) {
 +        if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$emptyCollisionShape() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$emptyCollisionShape()) {
 +            // don't even try to cache simple cases
 +            return true;
 +        }
  
--        if (voxelshape == Shapes.block()) {
+-                FlowingFluid.BlockStatePairKey blockStatePairKey;
+-                if (map != null) {
+-                    blockStatePairKey = new FlowingFluid.BlockStatePairKey(state, spreadState, direction);
+-                    byte andMoveToFirst = map.getAndMoveToFirst(blockStatePairKey);
+-                    if (andMoveToFirst != 127) {
+-                        return andMoveToFirst != 0;
+-                    }
+-                } else {
+-                    blockStatePairKey = null;
+-                }
 +        if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$occludesFullBlock() | ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$occludesFullBlock()) {
 +            // don't even try to cache simple cases
-             return false;
--        } else {
--            VoxelShape voxelshape1 = state.getCollisionShape(world, pos);
--
--            if (voxelshape1 == Shapes.block()) {
--                return false;
--            } else if (voxelshape1 == Shapes.empty() && voxelshape == Shapes.empty()) {
--                return true;
--            } else {
--                Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap;
--
--                if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) {
--                    object2bytelinkedopenhashmap = (Object2ByteLinkedOpenHashMap) FlowingFluid.OCCLUSION_CACHE.get();
--                } else {
--                    object2bytelinkedopenhashmap = null;
--                }
++            return false;
 +        }
  
--                FlowingFluid.BlockStatePairKey fluidtypeflowing_a;
+-                boolean flag = !Shapes.mergedFaceOccludes(collisionShape1, collisionShape, direction);
+-                if (map != null) {
+-                    if (map.size() == 200) {
+-                        map.removeLastByte();
+-                    }
 +        final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey[] cache = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$hasCache() & ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$hasCache() ?
 +            COLLISION_OCCLUSION_CACHE.get() : null;
  
--                if (object2bytelinkedopenhashmap != null) {
--                    fluidtypeflowing_a = new FlowingFluid.BlockStatePairKey(state, fromState, face);
--                    byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(fluidtypeflowing_a);
+-                    map.putAndMoveToFirst(blockStatePairKey, (byte)(flag ? 1 : 0));
+-                }
 +        final int keyIndex
 +            = (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)fromState).moonrise$uniqueId1() ^ ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)toState).moonrise$uniqueId2() ^ ((ca.spottedleaf.moonrise.patches.collisions.util.CollisionDirection)(Object)direction).moonrise$uniqueId())
 +            & (COLLISION_OCCLUSION_CACHE_SIZE - 1);
  
--                    if (b0 != 127) {
--                        return b0 != 0;
--                    }
--                } else {
--                    fluidtypeflowing_a = null;
--                }
--
--                boolean flag = !Shapes.mergedFaceOccludes(voxelshape1, voxelshape, face);
+-                return flag;
 +        if (cache != null) {
 +            final ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey cached = cache[keyIndex];
 +            if (cached != null && cached.first() == fromState && cached.second() == toState && cached.direction() == direction) {
 +                return cached.result();
-+            }
-+        }
- 
--                if (object2bytelinkedopenhashmap != null) {
--                    if (object2bytelinkedopenhashmap.size() == 200) {
--                        object2bytelinkedopenhashmap.removeLastByte();
--                    }
+             }
+         }
++
 +        final VoxelShape shape1 = fromState.getCollisionShape(level, fromPos);
 +        final VoxelShape shape2 = toState.getCollisionShape(level, toPos);
- 
--                    object2bytelinkedopenhashmap.putAndMoveToFirst(fluidtypeflowing_a, (byte) (flag ? 1 : 0));
--                }
++
 +        final boolean result = !Shapes.mergedFaceOccludes(shape1, shape2, direction);
- 
--                return flag;
--            }
++
 +        if (cache != null) {
 +            // we can afford to replace in-use keys more often due to the excessive caching the collision patch does in mergedFaceOccludes
 +            cache[keyIndex] = new ca.spottedleaf.moonrise.patches.collisions.util.FluidOcclusionCacheKey(fromState, toState, direction, result);
-         }
++        }
 +
 +        return result;
      }
 +    // Paper end - fluid method optimisations
++
  
      public abstract Fluid getFlowing();
  
      public FluidState getFlowing(int level, boolean falling) {
--        return (FluidState) ((FluidState) this.getFlowing().defaultFluidState().setValue(FlowingFluid.LEVEL, level)).setValue(FlowingFluid.FALLING, falling);
+-        return this.getFlowing().defaultFluidState().setValue(LEVEL, Integer.valueOf(level)).setValue(FALLING, Boolean.valueOf(falling));
 +        // Paper start - fluid method optimisations
 +        final int amount = level;
 +        if (!this.init) {
@@ -34466,7 +33953,7 @@ index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c
      public abstract Fluid getSource();
  
      public FluidState getSource(boolean falling) {
--        return (FluidState) this.getSource().defaultFluidState().setValue(FlowingFluid.FALLING, falling);
+-        return this.getSource().defaultFluidState().setValue(FALLING, Boolean.valueOf(falling));
 +        // Paper start - fluid method optimisations
 +        if (!this.init) {
 +            this.init();
@@ -34475,9 +33962,9 @@ index 261e5994d13f8bc30490b86691c80c0a21e7640a..f4fbcbb8ff6d2677af1a02a0801a323c
 +        // Paper end - fluid method optimisations
      }
  
-     protected abstract boolean canConvertToSource(ServerLevel world);
+     protected abstract boolean canConvertToSource(ServerLevel level);
 diff --git a/net/minecraft/world/level/material/FluidState.java b/net/minecraft/world/level/material/FluidState.java
-index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc1859379bfd805 100644
+index d2d71b22666639c003d86a6b6403fcbd2912c5af..481cb46973acb9785fdee5732e98aac560c6ec08 100644
 --- a/net/minecraft/world/level/material/FluidState.java
 +++ b/net/minecraft/world/level/material/FluidState.java
 @@ -22,12 +22,30 @@ import net.minecraft.world.level.block.state.properties.Property;
@@ -34509,9 +33996,9 @@ index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc18593
 +    }
 +    // Paper end - fluid method optimisations
 +
-     public FluidState(Fluid fluid, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<FluidState> codec) {
-         super(fluid, propertyMap, codec);
-         this.isEmpty = fluid.isEmpty(); // Paper - Perf: moved from isEmpty()
+     public FluidState(Fluid owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<FluidState> propertiesCodec) {
+         super(owner, values, propertiesCodec);
+         this.isEmpty = owner.isEmpty(); // Paper - Perf: moved from isEmpty()
 @@ -38,11 +56,11 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
@@ -34539,7 +34026,7 @@ index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc18593
 +        return this.amount; // Paper - fluid method optimisations
      }
  
-     public boolean shouldRenderBackwardUpFace(BlockGetter world, BlockPos pos) {
+     public boolean shouldRenderBackwardUpFace(BlockGetter level, BlockPos pos) {
 @@ -84,7 +102,7 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
@@ -34548,7 +34035,7 @@ index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc18593
 +        return this.isRandomlyTicking; // Paper - fluid method optimisations
      }
  
-     public void randomTick(ServerLevel world, BlockPos pos, RandomSource random) {
+     public void randomTick(ServerLevel level, BlockPos pos, RandomSource random) {
 @@ -96,7 +114,12 @@ public final class FluidState extends StateHolder<Fluid, FluidState> {
      }
  
@@ -34564,31 +34051,31 @@ index 87adfe152abd1b8b4d547034576883c5d1cdf134..2d50d72bf026d0cf9c546a3c6fc18593
  
      @Nullable
 diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java
-index 5dc2674b537f4a61b2e21a21bdb2e8dc090d3a3c..6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130 100644
+index 047e1fd078d7f49a2547daeca9eec31306d25dd0..85148858db1fd5e9da8bbdde4b0d84110d80e373 100644
 --- a/net/minecraft/world/phys/AABB.java
 +++ b/net/minecraft/world/phys/AABB.java
-@@ -331,7 +331,7 @@ public class AABB {
+@@ -314,7 +314,7 @@ public class AABB {
      }
  
      @Nullable
--    private static Direction getDirection(
-+    public static Direction getDirection( // Paper - optimise collisions - public
-         AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ
-     ) {
-         return getDirection(
+-    private static Direction getDirection(AABB aabb, Vec3 start, double[] minDistance, @Nullable Direction facing, double deltaX, double deltaY, double deltaZ) {
++    public static Direction getDirection(AABB aabb, Vec3 start, double[] minDistance, @Nullable Direction facing, double deltaX, double deltaY, double deltaZ) { // Paper - optimise collisions - public
+         return getDirection(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ, start, minDistance, facing, deltaX, deltaY, deltaZ);
+     }
+ 
 diff --git a/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
-index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12243fea80 100644
+index adb5f1be35d3a712499076719a1bb819ef52b9a8..39a634e1392239e17818a11750ba869ea7d195ce 100644
 --- a/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
 +++ b/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
 @@ -20,7 +20,7 @@ public class ArrayVoxelShape extends VoxelShape {
          );
      }
  
--    ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) {
-+    public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { // Paper - optimise collisions - public
+-    ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xs, DoubleList ys, DoubleList zs) {
++    public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xs, DoubleList ys, DoubleList zs) { // Paper - optimise collisions - public
          super(shape);
          int i = shape.getXSize() + 1;
-         int j = shape.getYSize() + 1;
+         int i1 = shape.getYSize() + 1;
 @@ -34,6 +34,7 @@ public class ArrayVoxelShape extends VoxelShape {
                  new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")
              );
@@ -34598,7 +34085,7 @@ index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12
  
      @Override
 diff --git a/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
-index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f28cbaf30b 100644
+index 14a12bdaa428556fa7b0c43e37b79699ae2fcb92..3a56e4ad9b3cba0cdf4bc373f7d0457d8643fdc4 100644
 --- a/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
 +++ b/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
 @@ -4,13 +4,13 @@ import java.util.BitSet;
@@ -34620,14 +34107,14 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +    public int yMax; // Paper - optimise collisions - public
 +    public int zMax; // Paper - optimise collisions - public
  
-     public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
-         super(sizeX, sizeY, sizeZ);
+     public BitSetDiscreteVoxelShape(int xSize, int ySize, int zSize) {
+         super(xSize, ySize, zSize);
 @@ -150,47 +150,109 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
          return bitSetDiscreteVoxelShape;
      }
  
--    protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) {
--        BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet);
+-    protected static void forAllBoxes(DiscreteVoxelShape shape, DiscreteVoxelShape.IntLineConsumer consumer, boolean combine) {
+-        BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(shape);
 +    // Paper start - optimise collisions
 +    public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) {
 +        // Paper - remove debug
@@ -34669,19 +34156,19 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +            // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize())
  
 -        for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) {
--            for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) {
--                int k = -1;
+-            for (int i1 = 0; i1 < bitSetDiscreteVoxelShape.xSize; i1++) {
+-                int i2 = -1;
 +            // only clone when we may write to it
 +            bitset = ca.spottedleaf.moonrise.common.util.MixinWorkarounds.clone(bitset);
  
--                for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) {
--                    if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) {
--                        if (coalesce) {
--                            if (k == -1) {
--                                k = l;
+-                for (int i3 = 0; i3 <= bitSetDiscreteVoxelShape.zSize; i3++) {
+-                    if (bitSetDiscreteVoxelShape.isFullWide(i1, i, i3)) {
+-                        if (combine) {
+-                            if (i2 == -1) {
+-                                i2 = i3;
 -                            }
 -                        } else {
--                            callback.consume(j, i, l, j + 1, i + 1, l + 1);
+-                            consumer.consume(i1, i, i3, i1 + 1, i + 1, i3 + 1);
 +            for (int y = 0; y < sizeY; ++y, indexY += incY) {
 +                indexX = indexY;
 +                for (int x = 0; x < sizeX; ++x, indexX += incX) {
@@ -34696,14 +34183,14 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +                        if (lastSetZ == -1) {
 +                            lastSetZ = endIndex;
                          }
--                    } else if (k != -1) {
--                        int m = j;
--                        int n = i;
--                        bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i);
+-                    } else if (i2 != -1) {
+-                        int i4 = i1;
+-                        int i5 = i;
+-                        bitSetDiscreteVoxelShape.clearZStrip(i2, i3, i1, i);
 -
--                        while (bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) {
--                            bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i);
--                            m++;
+-                        while (bitSetDiscreteVoxelShape.isZStripFull(i2, i3, i4 + 1, i)) {
+-                            bitSetDiscreteVoxelShape.clearZStrip(i2, i3, i4 + 1, i);
+-                            i4++;
 +
 +                        ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ);
 +
@@ -34717,9 +34204,9 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +                            ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd);
                          }
  
--                        while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) {
--                            for (int o = j; o <= m; o++) {
--                                bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1);
+-                        while (bitSetDiscreteVoxelShape.isXZRectangleFull(i1, i4 + 1, i2, i3, i5 + 1)) {
+-                            for (int i6 = i1; i6 <= i4; i6++) {
+-                                bitSetDiscreteVoxelShape.clearZStrip(i2, i3, i6, i5 + 1);
 +                        // try to merge neighbouring on the Y axis
 +
 +                        int endY; // exclusive
@@ -34736,7 +34223,7 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +                                }
                              }
  
--                            n++;
+-                            i5++;
 +                            ++endY;
 +
 +                            // passed, so we can clear it
@@ -34746,8 +34233,8 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
 +                            }
                          }
  
--                        callback.consume(j, i, k, m + 1, n + 1, l);
--                        k = -1;
+-                        consumer.consume(i1, i, i2, i4 + 1, i5 + 1, i3);
+-                        i2 = -1;
 +                        consumer.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX);
 +                        zIdx = lastSetZ;
                      }
@@ -34757,22 +34244,22 @@ index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f2
      }
 +    // Paper end - optimise collisions
  
-     private boolean isZStripFull(int z1, int z2, int x, int y) {
-         return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2);
+     private boolean isZStripFull(int zMin, int zMax, int x, int y) {
+         return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, zMin)) >= this.getIndex(x, y, zMax);
 diff --git a/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/net/minecraft/world/phys/shapes/CubeVoxelShape.java
-index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7bbe0f4b38 100644
+index f6b6481591e009de80f6b6318d35f193aabb7df3..e9b5069dcd572966b2f5aa220cef30e7a328fa2c 100644
 --- a/net/minecraft/world/phys/shapes/CubeVoxelShape.java
 +++ b/net/minecraft/world/phys/shapes/CubeVoxelShape.java
 @@ -7,6 +7,7 @@ import net.minecraft.util.Mth;
  public final class CubeVoxelShape extends VoxelShape {
-     protected CubeVoxelShape(DiscreteVoxelShape voxels) {
-         super(voxels);
+     protected CubeVoxelShape(DiscreteVoxelShape shape) {
+         super(shape);
 +        ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions
      }
  
      @Override
 diff --git a/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
-index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63cbb420f2 100644
+index 4fc61b329ccb7c9aeb6105dc53d71545a3baea89..309a34f192f7737204ce7a5c3b4004bdd83842f2 100644
 --- a/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
 +++ b/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
 @@ -3,12 +3,79 @@ package net.minecraft.world.phys.shapes;
@@ -34853,11 +34340,11 @@ index 01693ba050b12b9debcdaefceeff9cbcd503b369..fbe0c4b0fdbb992b7002f6afe1e74d63
 +    }
 +    // Paper end - optimise collisions
 +
-     protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
-         if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) {
-             this.xSize = sizeX;
+     protected DiscreteVoxelShape(int xSize, int ySize, int zSize) {
+         if (xSize >= 0 && ySize >= 0 && zSize >= 0) {
+             this.xSize = xSize;
 diff --git a/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/net/minecraft/world/phys/shapes/OffsetDoubleList.java
-index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644
+index ac1488875537421b74f0c491c9b7a40e75539c92..9eb27eb8d6dcaad6ce02f8ce4546acc224c4196f 100644
 --- a/net/minecraft/world/phys/shapes/OffsetDoubleList.java
 +++ b/net/minecraft/world/phys/shapes/OffsetDoubleList.java
 @@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
@@ -34869,10 +34356,10 @@ index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e0
 +    public final DoubleList delegate; // Paper - optimise collisions - public
 +    public final double offset; // Paper - optimise collisions - public
  
-     public OffsetDoubleList(DoubleList oldList, double offset) {
-         this.delegate = oldList;
+     public OffsetDoubleList(DoubleList delegate, double offset) {
+         this.delegate = delegate;
 diff --git a/net/minecraft/world/phys/shapes/Shapes.java b/net/minecraft/world/phys/shapes/Shapes.java
-index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f175394c0 100644
+index e759221fb54aa510d2d8bbba47e1d794367aec6d..5665cfaae4fc9e72b77fd41e16e7f64460b099b0 100644
 --- a/net/minecraft/world/phys/shapes/Shapes.java
 +++ b/net/minecraft/world/phys/shapes/Shapes.java
 @@ -16,9 +16,15 @@ public final class Shapes {
@@ -34932,9 +34419,9 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        // Paper start - optimise collisions
          if (!(maxX - minX < 1.0E-7) && !(maxY - minY < 1.0E-7) && !(maxZ - minZ < 1.0E-7)) {
 -            int i = findBits(minX, maxX);
--            int j = findBits(minY, maxY);
--            int k = findBits(minZ, maxZ);
--            if (i < 0 || j < 0 || k < 0) {
+-            int i1 = findBits(minY, maxY);
+-            int i2 = findBits(minZ, maxZ);
+-            if (i < 0 || i1 < 0 || i2 < 0) {
 +            final int bitsX = findBits(minX, maxX);
 +            final int bitsY = findBits(minY, maxY);
 +            final int bitsZ = findBits(minZ, maxZ);
@@ -34967,22 +34454,22 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +                    minY == 0.0 && maxY == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }),
 +                    minZ == 0.0 && maxZ == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ })
                  );
--            } else if (i == 0 && j == 0 && k == 0) {
+-            } else if (i == 0 && i1 == 0 && i2 == 0) {
 -                return block();
 -            } else {
--                int l = 1 << i;
--                int m = 1 << j;
--                int n = 1 << k;
+-                int i3 = 1 << i;
+-                int i4 = 1 << i1;
+-                int i5 = 1 << i2;
 -                BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(
--                    l,
--                    m,
--                    n,
--                    (int)Math.round(minX * (double)l),
--                    (int)Math.round(minY * (double)m),
--                    (int)Math.round(minZ * (double)n),
--                    (int)Math.round(maxX * (double)l),
--                    (int)Math.round(maxY * (double)m),
--                    (int)Math.round(maxZ * (double)n)
+-                    i3,
+-                    i4,
+-                    i5,
+-                    (int)Math.round(minX * i3),
+-                    (int)Math.round(minY * i4),
+-                    (int)Math.round(minZ * i5),
+-                    (int)Math.round(maxX * i3),
+-                    (int)Math.round(maxY * i4),
+-                    (int)Math.round(maxZ * i5)
 -                );
 -                return new CubeVoxelShape(bitSetDiscreteVoxelShape);
              }
@@ -34993,18 +34480,15 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        // Paper end - optimise collisions
      }
  
-     public static VoxelShape create(AABB box) {
-@@ -119,80 +152,54 @@ public final class Shapes {
-         return join(first, second, BooleanOp.OR);
+     public static VoxelShape create(AABB aabb) {
+@@ -120,85 +153,52 @@ public final class Shapes {
      }
  
--    public static VoxelShape or(VoxelShape first, VoxelShape... others) {
--        return Arrays.stream(others).reduce(first, Shapes::or);
-+    // Paper start - optimise collisions
-+    public static VoxelShape or(VoxelShape shape, VoxelShape... others) {
+     public static VoxelShape or(VoxelShape shape1, VoxelShape... others) {
+-        return Arrays.stream(others).reduce(shape1, Shapes::or);
 +        int size = others.length;
 +        if (size == 0) {
-+            return shape;
++            return shape1;
 +        }
 +
 +        // reduce complexity of joins by splitting the merges
@@ -35013,7 +34497,7 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        ++size;
 +        final VoxelShape[] tmp = Arrays.copyOf(others, size);
 +        // insert first shape
-+        tmp[size - 1] = shape;
++        tmp[size - 1] = shape1;
 +
 +        while (size > 1) {
 +            int newSize = 0;
@@ -35038,88 +34522,94 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        // Paper end - optimise collisions
      }
  
-     public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) {
--        return joinUnoptimized(first, second, function).optimize();
-+        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions
+     public static VoxelShape join(VoxelShape shape1, VoxelShape shape2, BooleanOp function) {
+-        return joinUnoptimized(shape1, shape2, function).optimize();
++        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinOptimized(shape1, shape2, function); // Paper - optimise collisions
      }
  
-     public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) {
+     public static VoxelShape joinUnoptimized(VoxelShape shape1, VoxelShape shape2, BooleanOp function) {
 -        if (function.apply(false, false)) {
 -            throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
--        } else if (one == two) {
--            return function.apply(true, true) ? one : empty();
+-        } else if (shape1 == shape2) {
+-            return function.apply(true, true) ? shape1 : empty();
 -        } else {
--            boolean bl = function.apply(true, false);
--            boolean bl2 = function.apply(false, true);
--            if (one.isEmpty()) {
--                return bl2 ? two : empty();
--            } else if (two.isEmpty()) {
--                return bl ? one : empty();
+-            boolean flag = function.apply(true, false);
+-            boolean flag1 = function.apply(false, true);
+-            if (shape1.isEmpty()) {
+-                return flag1 ? shape2 : empty();
+-            } else if (shape2.isEmpty()) {
+-                return flag ? shape1 : empty();
 -            } else {
--                IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2);
--                IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2);
--                IndexMerger indexMerger3 = createIndexMerger(
--                    (indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2
+-                IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), flag, flag1);
+-                IndexMerger indexMerger1 = createIndexMerger(
+-                    indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), flag, flag1
+-                );
+-                IndexMerger indexMerger2 = createIndexMerger(
+-                    (indexMerger.size() - 1) * (indexMerger1.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), flag, flag1
 -                );
 -                BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join(
--                    one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function
+-                    shape1.shape, shape2.shape, indexMerger, indexMerger1, indexMerger2, function
 -                );
 -                return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger
+-                        && indexMerger1 instanceof DiscreteCubeMerger
 -                        && indexMerger2 instanceof DiscreteCubeMerger
--                        && indexMerger3 instanceof DiscreteCubeMerger
 -                    ? new CubeVoxelShape(bitSetDiscreteVoxelShape)
--                    : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList()));
+-                    : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger1.getList(), indexMerger2.getList()));
 -            }
 -        }
-+        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions
++        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinUnoptimized(shape1, shape2, function); // Paper - optimise collisions
      }
  
-     public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
--        if (predicate.apply(false, false)) {
+     public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp resultOperator) {
+-        if (resultOperator.apply(false, false)) {
 -            throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
 -        } else {
--            boolean bl = shape1.isEmpty();
--            boolean bl2 = shape2.isEmpty();
--            if (!bl && !bl2) {
+-            boolean isEmpty = shape1.isEmpty();
+-            boolean isEmpty1 = shape2.isEmpty();
+-            if (!isEmpty && !isEmpty1) {
 -                if (shape1 == shape2) {
--                    return predicate.apply(true, true);
+-                    return resultOperator.apply(true, true);
 -                } else {
--                    boolean bl3 = predicate.apply(true, false);
--                    boolean bl4 = predicate.apply(false, true);
+-                    boolean flag = resultOperator.apply(true, false);
+-                    boolean flag1 = resultOperator.apply(false, true);
 -
 -                    for (Direction.Axis axis : AxisCycle.AXIS_VALUES) {
 -                        if (shape1.max(axis) < shape2.min(axis) - 1.0E-7) {
--                            return bl3 || bl4;
+-                            return flag || flag1;
 -                        }
 -
 -                        if (shape2.max(axis) < shape1.min(axis) - 1.0E-7) {
--                            return bl3 || bl4;
+-                            return flag || flag1;
 -                        }
 -                    }
 -
--                    IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4);
+-                    IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), flag, flag1);
+-                    IndexMerger indexMerger1 = createIndexMerger(
+-                        indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), flag, flag1
+-                    );
 -                    IndexMerger indexMerger2 = createIndexMerger(
--                        indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4
+-                        (indexMerger.size() - 1) * (indexMerger1.size() - 1),
+-                        shape1.getCoords(Direction.Axis.Z),
+-                        shape2.getCoords(Direction.Axis.Z),
+-                        flag,
+-                        flag1
 -                    );
--                    IndexMerger indexMerger3 = createIndexMerger(
--                        (indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4
--                    );
--                    return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate);
+-                    return joinIsNotEmpty(indexMerger, indexMerger1, indexMerger2, shape1.shape, shape2.shape, resultOperator);
 -                }
 -            } else {
--                return predicate.apply(!bl, !bl2);
+-                return resultOperator.apply(!isEmpty, !isEmpty1);
 -            }
 -        }
-+        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions
++        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isJoinNonEmpty(shape1, shape2, resultOperator); // Paper - optimise collisions
      }
  
      private static boolean joinIsNotEmpty(
-@@ -219,51 +226,116 @@ public final class Shapes {
-         return maxDist;
+@@ -230,52 +230,116 @@ public final class Shapes {
+         return desiredOffset;
      }
  
--    public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) {
--        if (shape == block() && neighbor == block()) {
+-    public static boolean blockOccudes(VoxelShape shape, VoxelShape adjacentShape, Direction side) {
+-        if (shape == block() && adjacentShape == block()) {
 +    // Paper start - optimise collisions
 +    public static boolean blockOccudes(final VoxelShape first, final VoxelShape second, final Direction direction) {
 +        final boolean firstBlock = first == BLOCK;
@@ -35127,7 +34617,7 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +
 +        if (firstBlock & secondBlock) {
              return true;
--        } else if (neighbor.isEmpty()) {
+-        } else if (adjacentShape.isEmpty()) {
 +        }
 +
 +        if (first.isEmpty() | second.isEmpty()) {
@@ -35140,14 +34630,14 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        if (newFirst.isEmpty()) {
              return false;
 -        } else {
--            Direction.Axis axis = direction.getAxis();
--            Direction.AxisDirection axisDirection = direction.getAxisDirection();
--            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor;
--            VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape;
+-            Direction.Axis axis = side.getAxis();
+-            Direction.AxisDirection axisDirection = side.getAxisDirection();
+-            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : adjacentShape;
+-            VoxelShape voxelShape1 = axisDirection == Direction.AxisDirection.POSITIVE ? adjacentShape : shape;
 -            BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND;
 -            return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)
--                && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)
--                && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp);
+-                && DoubleMath.fuzzyEquals(voxelShape1.min(axis), 0.0, 1.0E-7)
+-                && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape1, axis, 0), booleanOp);
          }
 +        final VoxelShape newSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getFaceShapeClamped(direction.getOpposite());
 +        if (newSecond.isEmpty()) {
@@ -35158,12 +34648,12 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        // Paper end - optimise collisions
      }
  
--    public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) {
--        if (one != block() && two != block()) {
--            Direction.Axis axis = direction.getAxis();
--            Direction.AxisDirection axisDirection = direction.getAxisDirection();
--            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two;
--            VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one;
+-    public static boolean mergedFaceOccludes(VoxelShape shape, VoxelShape adjacentShape, Direction side) {
+-        if (shape != block() && adjacentShape != block()) {
+-            Direction.Axis axis = side.getAxis();
+-            Direction.AxisDirection axisDirection = side.getAxisDirection();
+-            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : adjacentShape;
+-            VoxelShape voxelShape1 = axisDirection == Direction.AxisDirection.POSITIVE ? adjacentShape : shape;
 -            if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)) {
 -                voxelShape = empty();
 -            }
@@ -35173,8 +34663,8 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +        final AABB bounds1 = shape1.bounds();
 +        final AABB bounds2 = shape2.bounds();
  
--            if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)) {
--                voxelShape2 = empty();
+-            if (!DoubleMath.fuzzyEquals(voxelShape1.min(axis), 0.0, 1.0E-7)) {
+-                voxelShape1 = empty();
 -            }
 +        final double minX = Math.min(bounds1.minX, bounds2.minX);
 +        final double minY = Math.min(bounds1.minY, bounds2.minY);
@@ -35182,7 +34672,7 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
  
 -            return !joinIsNotEmpty(
 -                block(),
--                joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR),
+-                joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape1, axis, 0), BooleanOp.OR),
 -                BooleanOp.ONLY_FIRST
 -            );
 -        } else {
@@ -35200,8 +34690,8 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +    public static boolean mergedFaceOccludes(final VoxelShape first, final VoxelShape second, final Direction direction) {
 +        // see if any of the shapes on their own occludes, only if cached
 +        if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$occludesFullBlockIfCached()) {
-+            return true;
-+        }
+             return true;
+         }
 +
 +        if (first.isEmpty() & second.isEmpty()) {
 +            return false;
@@ -35214,8 +34704,8 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +
 +        // see if any of the shapes on their own occludes, only if cached
 +        if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newSecond).moonrise$occludesFullBlockIfCached()) {
-             return true;
-         }
++            return true;
++        }
 +
 +        final boolean firstEmpty = newFirst.isEmpty();
 +        final boolean secondEmpty = newSecond.isEmpty();
@@ -35256,29 +34746,30 @@ index 76d7435e6fe81a3f1d24b35eae72d06232a1792b..ca3a2419252721bb3b3b719eb19afb5f
 +            return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlock();
 +        }
  
--    public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
--        return one == block()
--            || two == block()
--            || (!one.isEmpty() || !two.isEmpty()) && !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST);
+-    public static boolean faceShapeOccludes(VoxelShape voxelShape1, VoxelShape voxelShape2) {
+-        return voxelShape1 == block()
+-            || voxelShape2 == block()
+-            || (!voxelShape1.isEmpty() || !voxelShape2.isEmpty())
+-                && !joinIsNotEmpty(block(), joinUnoptimized(voxelShape1, voxelShape2, BooleanOp.OR), BooleanOp.ONLY_FIRST);
 +        return mergedMayOccludeBlock(shape1, shape2) && ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$orUnoptimized(shape2)).moonrise$occludesFullBlock();
 +        // Paper end - optimise collisions
      }
  
      @VisibleForTesting
 diff --git a/net/minecraft/world/phys/shapes/SliceShape.java b/net/minecraft/world/phys/shapes/SliceShape.java
-index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a004617eb53a2 100644
+index 79f7f04207891dd98cc0b2d93ecb2e07c8baa7b6..7ca12213c10f962ff597a8d51413a17b1827bbb4 100644
 --- a/net/minecraft/world/phys/shapes/SliceShape.java
 +++ b/net/minecraft/world/phys/shapes/SliceShape.java
 @@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape {
-         super(makeSlice(shape.shape, axis, sliceWidth));
-         this.delegate = shape;
+         super(makeSlice(delegate.shape, axis, index));
+         this.delegate = delegate;
          this.axis = axis;
 +        ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions
      }
  
-     private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) {
+     private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape shape, Direction.Axis axis, int index) {
 diff --git a/net/minecraft/world/phys/shapes/VoxelShape.java b/net/minecraft/world/phys/shapes/VoxelShape.java
-index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9cffc3071 100644
+index 006065c32baf3b1ddc5647196cb9f863c7969064..2c7e70675b62cb753447d2acebf2f36cdac74973 100644
 --- a/net/minecraft/world/phys/shapes/VoxelShape.java
 +++ b/net/minecraft/world/phys/shapes/VoxelShape.java
 @@ -15,61 +15,546 @@ import net.minecraft.world.phys.AABB;
@@ -35706,8 +35197,8 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +    }
 +    // Paper end - optimise collisions
 +
-     protected VoxelShape(DiscreteVoxelShape voxels) {
-         this.shape = voxels;
+     protected VoxelShape(DiscreteVoxelShape shape) {
+         this.shape = shape;
      }
  
      public double min(Direction.Axis axis) {
@@ -35777,11 +35268,11 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +        // Paper start - optimise collisions
 +        if (this.isEmpty) {
 +            throw Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape."));
-+        }
+         }
 +        AABB cached = this.cachedBounds;
 +        if (cached != null) {
 +            return cached;
-         }
++        }
 +
 +        final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData;
 +
@@ -35856,18 +35347,18 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +        return this.isEmpty; // Paper - optimise collisions
      }
  
-     public VoxelShape move(Vec3 vec3d) {
-@@ -77,24 +562,96 @@ public abstract class VoxelShape {
+     public VoxelShape move(Vec3 offset) {
+@@ -77,20 +562,96 @@ public abstract class VoxelShape {
      }
  
-     public VoxelShape move(double x, double y, double z) {
+     public VoxelShape move(double xOffset, double yOffset, double zOffset) {
 -        return (VoxelShape)(this.isEmpty()
 -            ? Shapes.empty()
 -            : new ArrayVoxelShape(
 -                this.shape,
--                new OffsetDoubleList(this.getCoords(Direction.Axis.X), x),
--                new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y),
--                new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z)
+-                new OffsetDoubleList(this.getCoords(Direction.Axis.X), xOffset),
+-                new OffsetDoubleList(this.getCoords(Direction.Axis.Y), yOffset),
+-                new OffsetDoubleList(this.getCoords(Direction.Axis.Z), zOffset)
 -            ));
 +        // Paper start - optimise collisions
 +        if (this.isEmpty) {
@@ -35876,14 +35367,14 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +
 +        final ArrayVoxelShape ret = new ArrayVoxelShape(
 +            this.shape,
-+            offsetList(this.rootCoordinatesX, this.offsetX + x),
-+            offsetList(this.rootCoordinatesY, this.offsetY + y),
-+            offsetList(this.rootCoordinatesZ, this.offsetZ + z)
++            offsetList(this.rootCoordinatesX, this.offsetX + xOffset),
++            offsetList(this.rootCoordinatesY, this.offsetY + yOffset),
++            offsetList(this.rootCoordinatesZ, this.offsetZ + zOffset)
 +        );
 +
 +        final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs;
 +        if (cachedToAABBs != null) {
-+            ((VoxelShape)(Object)ret).cachedToAABBs = ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs.offset(cachedToAABBs, x, y, z);
++            ((VoxelShape)(Object)ret).cachedToAABBs = ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs.offset(cachedToAABBs, xOffset, yOffset, zOffset);
 +        }
 +
 +        return ret;
@@ -35892,11 +35383,7 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
  
      public VoxelShape optimize() {
 -        VoxelShape[] voxelShapes = new VoxelShape[]{Shapes.empty()};
--        this.forAllBoxes(
--            (minX, minY, minZ, maxX, maxY, maxZ) -> voxelShapes[0] = Shapes.joinUnoptimized(
--                    voxelShapes[0], Shapes.box(minX, minY, minZ, maxX, maxY, maxZ), BooleanOp.OR
--                )
--        );
+-        this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> voxelShapes[0] = Shapes.joinUnoptimized(voxelShapes[0], Shapes.box(x1, y1, z1, x2, y2, z2), BooleanOp.OR));
 -        return voxelShapes[0];
 +        // Paper start - optimise collisions
 +        if (this.isEmpty) {
@@ -35968,8 +35455,8 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +        // Paper end - optimise collisions
      }
  
-     public void forAllEdges(Shapes.DoubleLineConsumer consumer) {
-@@ -131,9 +688,24 @@ public abstract class VoxelShape {
+     public void forAllEdges(Shapes.DoubleLineConsumer action) {
+@@ -122,9 +683,24 @@ public abstract class VoxelShape {
      }
  
      public List<AABB> toAabbs() {
@@ -35996,14 +35483,14 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +        // Paper end - optimise collisions
      }
  
-     public double min(Direction.Axis axis, double from, double to) {
-@@ -155,46 +727,92 @@ public abstract class VoxelShape {
+     public double min(Direction.Axis axis, double primaryPosition, double secondaryPosition) {
+@@ -146,46 +722,92 @@ public abstract class VoxelShape {
      }
  
-     protected int findIndex(Direction.Axis axis, double coord) {
--        return Mth.binarySearch(0, this.shape.getSize(axis) + 1, i -> coord < this.get(axis, i)) - 1;
+     protected int findIndex(Direction.Axis axis, double position) {
+-        return Mth.binarySearch(0, this.shape.getSize(axis) + 1, value -> position < this.get(axis, value)) - 1;
 +        // Paper start - optimise collisions
-+        final double value = coord;
++        final double value = position;
 +        switch (axis) {
 +            case X: {
 +                final double[] values = this.rootCoordinatesX;
@@ -36031,68 +35518,66 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
      }
  
      @Nullable
--    public BlockHitResult clip(Vec3 start, Vec3 end, BlockPos pos) {
+     public BlockHitResult clip(Vec3 startVec, Vec3 endVec, BlockPos pos) {
 -        if (this.isEmpty()) {
-+    // Paper start - optimise collisions
-+    public BlockHitResult clip(final Vec3 from, final Vec3 to, final BlockPos offset) {
++        // Paper start - optimise collisions
 +        if (this.isEmpty) {
              return null;
 -        } else {
--            Vec3 vec3 = end.subtract(start);
+-            Vec3 vec3 = endVec.subtract(startVec);
 -            if (vec3.lengthSqr() < 1.0E-7) {
 -                return null;
 -            } else {
--                Vec3 vec32 = start.add(vec3.scale(0.001));
+-                Vec3 vec31 = startVec.add(vec3.scale(0.001));
 -                return this.shape
 -                        .isFullWide(
--                            this.findIndex(Direction.Axis.X, vec32.x - (double)pos.getX()),
--                            this.findIndex(Direction.Axis.Y, vec32.y - (double)pos.getY()),
--                            this.findIndex(Direction.Axis.Z, vec32.z - (double)pos.getZ())
+-                            this.findIndex(Direction.Axis.X, vec31.x - pos.getX()),
+-                            this.findIndex(Direction.Axis.Y, vec31.y - pos.getY()),
+-                            this.findIndex(Direction.Axis.Z, vec31.z - pos.getZ())
 -                        )
--                    ? new BlockHitResult(vec32, Direction.getApproximateNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true)
--                    : AABB.clip(this.toAabbs(), start, end, pos);
+-                    ? new BlockHitResult(vec31, Direction.getApproximateNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true)
+-                    : AABB.clip(this.toAabbs(), startVec, endVec, pos);
 +        }
 +
-+        final Vec3 directionOpposite = to.subtract(from);
++        final Vec3 directionOpposite = endVec.subtract(startVec);
 +        if (directionOpposite.lengthSqr() < ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) {
 +            return null;
 +        }
 +
-+        final Vec3 fromBehind = from.add(directionOpposite.scale(0.001));
-+        final double fromBehindOffsetX = fromBehind.x - (double)offset.getX();
-+        final double fromBehindOffsetY = fromBehind.y - (double)offset.getY();
-+        final double fromBehindOffsetZ = fromBehind.z - (double)offset.getZ();
++        final Vec3 fromBehind = startVec.add(directionOpposite.scale(0.001));
++        final double fromBehindOffsetX = fromBehind.x - (double) pos.getX();
++        final double fromBehindOffsetY = fromBehind.y - (double) pos.getY();
++        final double fromBehindOffsetZ = fromBehind.z - (double) pos.getZ();
 +
 +        final AABB singleAABB = this.singleAABBRepresentation;
 +        if (singleAABB != null) {
 +            if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
-+                return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
++                return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true);
              }
-+            return clip(singleAABB, from, to, offset);
++            return clip(singleAABB, startVec, endVec, pos);
 +        }
 +
-+        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
-+            return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
++        if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape) (Object) this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
++            return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true);
          }
 +
-+        return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset);
++        return AABB.clip(((VoxelShape) (Object) this).toAabbs(), startVec, endVec, pos);
 +        // Paper end - optimise collisions
      }
  
--    public Optional<Vec3> closestPointTo(Vec3 target) {
--        if (this.isEmpty()) {
 +    // Paper start - optimise collisions
-+    public Optional<Vec3> closestPointTo(Vec3 point) {
+     public Optional<Vec3> closestPointTo(Vec3 point) {
+-        if (this.isEmpty()) {
 +        if (this.isEmpty) {
              return Optional.empty();
 -        } else {
 -            Vec3[] vec3s = new Vec3[1];
--            this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> {
--                double d = Mth.clamp(target.x(), minX, maxX);
--                double e = Mth.clamp(target.y(), minY, maxY);
--                double f = Mth.clamp(target.z(), minZ, maxZ);
--                if (vec3s[0] == null || target.distanceToSqr(d, e, f) < target.distanceToSqr(vec3s[0])) {
--                    vec3s[0] = new Vec3(d, e, f);
+-            this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> {
+-                double d = Mth.clamp(point.x(), x1, x2);
+-                double d1 = Mth.clamp(point.y(), y1, y2);
+-                double d2 = Mth.clamp(point.z(), z1, z2);
+-                if (vec3s[0] == null || point.distanceToSqr(d, d1, d2) < point.distanceToSqr(vec3s[0])) {
+-                    vec3s[0] = new Vec3(d, d1, d2);
 -                }
 -            });
 -            return Optional.of(vec3s[0]);
@@ -36119,35 +35604,33 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
 +        // Paper end - optimise collisions
      }
  
-     public VoxelShape getFaceShape(Direction facing) {
-@@ -216,20 +834,24 @@ public abstract class VoxelShape {
-         }
+     public VoxelShape getFaceShape(Direction side) {
+@@ -208,19 +830,23 @@ public abstract class VoxelShape {
      }
  
--    private VoxelShape calculateFace(Direction facing) {
--        Direction.Axis axis = facing.getAxis();
+     private VoxelShape calculateFace(Direction side) {
+-        Direction.Axis axis = side.getAxis();
 -        if (this.isCubeLikeAlong(axis)) {
 -            return this;
 -        } else {
--            Direction.AxisDirection axisDirection = facing.getAxisDirection();
+-            Direction.AxisDirection axisDirection = side.getAxisDirection();
 -            int i = this.findIndex(axis, axisDirection == Direction.AxisDirection.POSITIVE ? 0.9999999 : 1.0E-7);
 -            SliceShape sliceShape = new SliceShape(this, axis, i);
 -            if (sliceShape.isEmpty()) {
 -                return Shapes.empty();
 -            } else {
 -                return (VoxelShape)(sliceShape.isCubeLike() ? Shapes.block() : sliceShape);
-+    private VoxelShape calculateFace(Direction direction) {
 +        // Paper start - optimise collisions
-+        final Direction.Axis axis = direction.getAxis();
++        final Direction.Axis axis = side.getAxis();
 +        switch (axis) {
 +            case X: {
-+                return this.calculateFaceDirect(direction, axis, this.rootCoordinatesX, this.offsetX);
++                return this.calculateFaceDirect(side, axis, this.rootCoordinatesX, this.offsetX);
 +            }
 +            case Y: {
-+                return this.calculateFaceDirect(direction, axis, this.rootCoordinatesY, this.offsetY);
++                return this.calculateFaceDirect(side, axis, this.rootCoordinatesY, this.offsetY);
 +            }
 +            case Z: {
-+                return this.calculateFaceDirect(direction, axis, this.rootCoordinatesZ, this.offsetZ);
++                return this.calculateFaceDirect(side, axis, this.rootCoordinatesZ, this.offsetZ);
 +            }
 +            default: {
 +                throw new IllegalStateException("Unknown axis: " + axis);
@@ -36157,12 +35640,12 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
      }
  
      protected boolean isCubeLike() {
-@@ -249,9 +871,30 @@ public abstract class VoxelShape {
-             && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0, 1.0E-7);
+@@ -238,9 +864,30 @@ public abstract class VoxelShape {
+         return coords.size() == 2 && DoubleMath.fuzzyEquals(coords.getDouble(0), 0.0, 1.0E-7) && DoubleMath.fuzzyEquals(coords.getDouble(1), 1.0, 1.0E-7);
      }
  
--    public double collide(Direction.Axis axis, AABB box, double maxDist) {
--        return this.collideX(AxisCycle.between(axis, Direction.Axis.X), box, maxDist);
+-    public double collide(Direction.Axis movementAxis, AABB collisionBox, double desiredOffset) {
+-        return this.collideX(AxisCycle.between(movementAxis, Direction.Axis.X), collisionBox, desiredOffset);
 +    // Paper start - optimise collisions
 +    public double collide(final Direction.Axis axis, final AABB source, final double source_move) {
 +        if (this.isEmpty) {
@@ -36188,10 +35671,10 @@ index bcb79462c8b3309ae8701cba4753b27a9d22eb2e..6182f1d37c7a63479f6c6e7c37a7edc9
      }
 +    // Paper end - optimise collisions
  
-     protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) {
+     protected double collideX(AxisCycle movementAxis, AABB collisionBox, double desiredOffset) {
          if (this.isEmpty()) {
 diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java
-index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2f26d593d 100644
+index 5b6bd88a5bbbce6cce351938418eba4326e41002..faf45ac459f7c25309d6ef6dce371d484a0dae7b 100644
 --- a/net/minecraft/world/ticks/LevelChunkTicks.java
 +++ b/net/minecraft/world/ticks/LevelChunkTicks.java
 @@ -17,7 +17,7 @@ import net.minecraft.core.BlockPos;
@@ -36245,10 +35728,10 @@ index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2
          return scheduledTick;
 @@ -58,7 +82,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      @Override
-     public void schedule(ScheduledTick<T> orderedTick) {
-         if (this.ticksPerPosition.add(orderedTick)) {
--            this.scheduleUnchecked(orderedTick);
-+            this.scheduleUnchecked(orderedTick); this.dirty = true; // Paper - rewrite chunk system
+     public void schedule(ScheduledTick<T> tick) {
+         if (this.ticksPerPosition.add(tick)) {
+-            this.scheduleUnchecked(tick);
++            this.scheduleUnchecked(tick); this.dirty = true; // Paper - rewrite chunk system
          }
      }
  
@@ -36264,327 +35747,16 @@ index 26620c06d26a2c0eb957fbadc6ac3d7a309bff46..3858c83c58e78435a6e29de84c33faa2
 @@ -110,6 +134,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
      }
  
-     public ListTag save(long time, Function<T, String> typeToNameFunction) {
-+        this.lastSaved = time; // Paper - rewrite chunk system
+     public ListTag save(long gametime, Function<T, String> idGetter) {
++        this.lastSaved = gametime; // Paper - rewrite chunk system
          ListTag listTag = new ListTag();
  
-         for (SavedTick<T> savedTick : this.pack(time)) {
+         for (SavedTick<T> savedTick : this.pack(gametime)) {
 @@ -121,6 +146,7 @@ public class LevelChunkTicks<T> implements SerializableTickContainer<T>, TickCon
  
-     public void unpack(long time) {
+     public void unpack(long gameTime) {
          if (this.pendingTicks != null) {
-+            this.lastSaved = time; // Paper - rewrite chunk system
++            this.lastSaved = gameTime; // Paper - rewrite chunk system
              int i = -this.pendingTicks.size();
  
              for (SavedTick<T> savedTick : this.pendingTicks) {
-diff --git a/org/bukkit/craftbukkit/CraftChunk.java b/org/bukkit/craftbukkit/CraftChunk.java
-index f3ab07e44e2e912ea66c6148cfdb2a4a528741b2..c2bffe3450ee9f768e00a23ec09df74d7a06d49b 100644
---- a/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/org/bukkit/craftbukkit/CraftChunk.java
-@@ -83,6 +83,12 @@ public class CraftChunk implements Chunk {
-     }
- 
-     public ChunkAccess getHandle(ChunkStatus chunkStatus) {
-+        // Paper start - rewrite chunk system
-+        net.minecraft.world.level.chunk.LevelChunk full = this.worldServer.getChunkIfLoaded(this.x, this.z);
-+        if (full != null) {
-+            return full;
-+        }
-+        // Paper end - rewrite chunk system
-         ChunkAccess chunkAccess = this.worldServer.getChunk(this.x, this.z, chunkStatus);
- 
-         // SPIGOT-7332: Get unwrapped extension
-@@ -117,60 +123,12 @@ public class CraftChunk implements Chunk {
- 
-     @Override
-     public boolean isEntitiesLoaded() {
--        return this.getCraftWorld().getHandle().entityManager.areEntitiesLoaded(ChunkPos.asLong(this.x, this.z));
-+        return this.getCraftWorld().getHandle().areEntitiesLoaded(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(this.x, this.z)); // Paper - rewrite chunk system
-     }
- 
-     @Override
-     public Entity[] getEntities() {
--        if (!this.isLoaded()) {
--            this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick
--        }
--
--        PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = this.getCraftWorld().getHandle().entityManager;
--        long pair = ChunkPos.asLong(this.x, this.z);
--
--        if (entityManager.areEntitiesLoaded(pair)) {
--            return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
--                    .map(net.minecraft.world.entity.Entity::getBukkitEntity)
--                    .filter(Objects::nonNull).toArray(Entity[]::new);
--        }
--
--        entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
--
--        // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
--        ConsecutiveExecutor mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
--        BooleanSupplier supplier = () -> {
--            // only execute inbox if our entities are not present
--            if (entityManager.areEntitiesLoaded(pair)) {
--                return true;
--            }
--
--            if (!entityManager.isPending(pair)) {
--                // Our entities got unloaded, this should normally not happen.
--                entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
--            }
--
--            // tick loading inbox, which loads the created entities to the world
--            // (if present)
--            entityManager.tick();
--            // check if our entities are loaded
--            return entityManager.areEntitiesLoaded(pair);
--        };
--
--        // now we wait until the entities are loaded,
--        // the converting from NBT to entity object is done on the main Thread which is why we wait
--        while (!supplier.getAsBoolean()) {
--            if (mailbox.size() != 0) {
--                mailbox.run();
--            } else {
--                Thread.yield();
--                LockSupport.parkNanos("waiting for entity loading", 100000L);
--            }
--        }
--
--        return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
--                .map(net.minecraft.world.entity.Entity::getBukkitEntity)
--                .filter(Objects::nonNull).toArray(Entity[]::new);
-+        return this.getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - rewrite chunk system
-     }
- 
-     @Override
-diff --git a/org/bukkit/craftbukkit/CraftServer.java b/org/bukkit/craftbukkit/CraftServer.java
-index 5b64111bc8baca45ecc7bfa384e5f8a004163a0b..97b5d6ba2b19a7c730730c74175a29157aed1840 100644
---- a/org/bukkit/craftbukkit/CraftServer.java
-+++ b/org/bukkit/craftbukkit/CraftServer.java
-@@ -1448,7 +1448,7 @@ public final class CraftServer implements Server {
-         // Paper - Put world into worldlist before initing the world; move up
- 
-         this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal);
--        internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
-+        // Paper - rewrite chunk system
- 
-         this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
-         return internal.getWorld();
-@@ -1493,7 +1493,7 @@ public final class CraftServer implements Server {
-             }
- 
-             handle.getChunkSource().close(save);
--            handle.entityManager.close(save); // SPIGOT-6722: close entityManager
-+            // Paper - rewrite chunk system
-             handle.convertable.close();
-         } catch (Exception ex) {
-             this.getLogger().log(Level.SEVERE, null, ex);
-@@ -2531,7 +2531,7 @@ public final class CraftServer implements Server {
- 
-     @Override
-     public boolean isPrimaryThread() {
--        return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog)
-+        return ca.spottedleaf.moonrise.common.util.TickThread.isTickThread(); // Paper - rewrite chunk system
-     }
- 
-     // Paper start - Adventure
-diff --git a/org/bukkit/craftbukkit/CraftWorld.java b/org/bukkit/craftbukkit/CraftWorld.java
-index ca62105a0ff0aa69385cbf2018f8fe6a4bb69fd4..92d9f0ea8f7810ae20d3996f49aefa539b4bcb69 100644
---- a/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/org/bukkit/craftbukkit/CraftWorld.java
-@@ -507,15 +507,17 @@ public class CraftWorld extends CraftRegionAccessor implements World {
-         ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
-         if (playerChunk == null) return false;
- 
--        playerChunk.getTickingChunkFuture().thenAccept(either -> {
--            either.ifSuccess(chunk -> {
-+        // Paper start - chunk system
-+        net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getChunkToSend();
-+        if (chunk == null) {
-+            return false;
-+        }
-+        // Paper end - chunk system
-                 List<ServerPlayer> playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false);
--                if (playersInRange.isEmpty()) return;
-+                if (playersInRange.isEmpty()) return true; // Paper - chunk system
- 
-                 FeatureHooks.sendChunkRefreshPackets(playersInRange, chunk);
--            });
--        });
--
-+        // Paper - chunk system
-         return true;
-     }
- 
-@@ -618,20 +620,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
-     @Override
-     public Collection<Plugin> getPluginChunkTickets(int x, int z) {
-         DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
--        SortedArraySet<Ticket<?>> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z));
--
--        if (tickets == null) {
--            return Collections.emptyList();
--        }
- 
--        ImmutableList.Builder<Plugin> ret = ImmutableList.builder();
--        for (Ticket<?> ticket : tickets) {
--            if (ticket.getType() == TicketType.PLUGIN_TICKET) {
--                ret.add((Plugin) ticket.key);
--            }
--        }
--
--        return ret.build();
-+        return chunkDistanceManager.moonrise$getChunkHolderManager().getPluginChunkTickets(x, z); // Paper - rewrite chunk system
-     }
- 
-     @Override
-@@ -639,7 +629,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
-         Map<Plugin, ImmutableList.Builder<Chunk>> ret = new HashMap<>();
-         DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
- 
--        for (Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) {
-+        for (Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>> chunkTickets : chunkDistanceManager.moonrise$getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) {  // Paper - rewrite chunk system
-             long chunkKey = chunkTickets.getLongKey();
-             SortedArraySet<Ticket<?>> tickets = chunkTickets.getValue();
- 
-@@ -1342,12 +1332,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- 
-     @Override
-     public int getViewDistance() {
--        return this.world.getChunkSource().chunkMap.serverViewDistance;
-+        return this.getHandle().moonrise$getPlayerChunkLoader().getAPIViewDistance(); // Paper - rewrite chunk system
-     }
- 
-     @Override
-     public int getSimulationDistance() {
--        return this.world.getChunkSource().chunkMap.getDistanceManager().simulationDistance;
-+        return this.getHandle().moonrise$getPlayerChunkLoader().getAPITickDistance(); // Paper - rewrite chunk system
-     }
- 
-     public BlockMetadataStore getBlockMetadata() {
-@@ -2486,17 +2476,20 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- 
-     @Override
-     public void setSimulationDistance(final int simulationDistance) {
--        throw new UnsupportedOperationException("Not implemented yet");
-+        if (simulationDistance < 2 || simulationDistance > 32) {
-+            throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]");
-+        }
-+        this.getHandle().chunkSource.setSimulationDistance(simulationDistance); // Paper - rewrite chunk system
-     }
- 
-     @Override
-     public int getSendViewDistance() {
--        return this.getViewDistance();
-+        return this.getHandle().moonrise$getPlayerChunkLoader().getAPISendViewDistance(); // Paper - rewrite chunk system
-     }
- 
-     @Override
-     public void setSendViewDistance(final int viewDistance) {
--        throw new UnsupportedOperationException("Not implemented yet");
-+        this.getHandle().chunkSource.setSendViewDistance(viewDistance); // Paper - rewrite chunk system
-     }
- 
-     // Paper start - implement pointers
-diff --git a/org/bukkit/craftbukkit/entity/CraftPlayer.java b/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index e9df37ff66700278bc94ea1e42135b92d97d03f7..6a647cab8b2e476987931486e290703b8726f2c7 100644
---- a/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -3527,7 +3527,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- 
-     @Override
-     public void setViewDistance(final int viewDistance) {
--        throw new UnsupportedOperationException("Not implemented yet");
-+        // Paper - rewrite chunk system - TODO do this better
-+        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)this.getHandle())
-+            .moonrise$getViewDistanceHolder().setLoadViewDistance(viewDistance + 1);
-     }
- 
-     @Override
-@@ -3537,7 +3539,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- 
-     @Override
-     public void setSimulationDistance(final int simulationDistance) {
--        throw new UnsupportedOperationException("Not implemented yet");
-+        // Paper - rewrite chunk system - TODO do this better
-+        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)this.getHandle())
-+            .moonrise$getViewDistanceHolder().setTickViewDistance(simulationDistance);
-     }
- 
-     @Override
-@@ -3547,7 +3551,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- 
-     @Override
-     public void setSendViewDistance(final int viewDistance) {
--        throw new UnsupportedOperationException("Not implemented yet");
-+        // Paper - rewrite chunk system - TODO do this better
-+        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)this.getHandle())
-+            .moonrise$getViewDistanceHolder().setSendViewDistance(viewDistance);
-     }
- 
-     // Paper start - entity effect API
-diff --git a/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-index 39377ba0739f9660567b38475f101672f7b5e035..c025a4ff42257a4e84f0f9574b84f6987ef8ac11 100644
---- a/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-+++ b/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
-             return ichunkaccess1;
-         };
- 
--        return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), net.minecraft.Util.backgroundExecutor()) : future.thenApply(function);
-+        return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), Runnable::run) : future.thenApply(function); // Paper - rewrite chunk system
-     }
- 
-     @Override
-diff --git a/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-index 54c4434662d057a08800918641b95708cda61207..37458e8fd5d57acbf90a6bea4e66797cb07f69fa 100644
---- a/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-+++ b/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
-@@ -810,6 +810,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
-     public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) {
-         return this.handle.getChunkIfLoadedImmediately(x, z);
-     }
-+
-+    // Paper start - rewrite chunk system
-+    @Override
-+    public java.util.List<net.minecraft.world.entity.Entity> moonrise$getHardCollidingEntities(final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB box, final java.util.function.Predicate<? super net.minecraft.world.entity.Entity> predicate) {
-+        return this.handle.moonrise$getHardCollidingEntities(entity, box, predicate);
-+    }
-+    // Paper end - rewrite chunk system
-     // Paper end
- }
- 
-diff --git a/org/spigotmc/AsyncCatcher.java b/org/spigotmc/AsyncCatcher.java
-index ef2598760458833021ef1bee92137f42c9fe591f..1f23e775eba1c34e01145bd91b0ce26fed6ca9de 100644
---- a/org/spigotmc/AsyncCatcher.java
-+++ b/org/spigotmc/AsyncCatcher.java
-@@ -9,7 +9,7 @@ public class AsyncCatcher
- 
-     public static void catchOp(String reason)
-     {
--        if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
-+        if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) // Paper // Paper - rewrite chunk system
-         {
-             MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
-             throw new IllegalStateException( "Asynchronous " + reason + "!" );
-diff --git a/org/spigotmc/WatchdogThread.java b/org/spigotmc/WatchdogThread.java
-index ad282d34919716b75acd10426cd071da9d064a51..529df2a41dd93d6e1505053bd04032dbf0cdaa31 100644
---- a/org/spigotmc/WatchdogThread.java
-+++ b/org/spigotmc/WatchdogThread.java
-@@ -8,7 +8,7 @@ import java.util.logging.Logger;
- import net.minecraft.server.MinecraftServer;
- import org.bukkit.Bukkit;
- 
--public class WatchdogThread extends Thread
-+public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThread // Paper - rewrite chunk system
- {
- 
-     private static WatchdogThread instance;
-@@ -115,6 +115,7 @@ public class WatchdogThread extends Thread
-                 // Paper end - Different message for short timeout
-                 log.log( Level.SEVERE, "------------------------------" );
-                 log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
-+                ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - rewrite chunk system
-                 WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
-                 log.log( Level.SEVERE, "------------------------------" );
-                 //

From 3af380ba08c530e0799d5299bc3cd0582389eb43 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 20 Dec 2024 09:36:39 -0800
Subject: [PATCH 260/285] Rebase on latest

---
 ... 0021-Moonrise-optimisation-patches.patch} | 223 ++++++++++--------
 ...r-desync-when-new-players-are-added.patch} |  16 +-
 ...atch => 0023-Lag-compensation-ticks.patch} |  10 +-
 ...-Eigencraft-redstone-implementation.patch} |   0
 ...ate-Current-redstone-implementation.patch} |   8 +-
 ...ove-exact-choice-recipe-ingredients.patch} |   4 +-
 6 files changed, 138 insertions(+), 123 deletions(-)
 rename paper-server/patches/features/{0018-Moonrise-optimisation-patches.patch => 0021-Moonrise-optimisation-patches.patch} (99%)
 rename paper-server/patches/features/{0021-Fix-entity-tracker-desync-when-new-players-are-added.patch => 0022-Fix-entity-tracker-desync-when-new-players-are-added.patch} (90%)
 rename paper-server/patches/features/{0022-Lag-compensation-ticks.patch => 0023-Lag-compensation-ticks.patch} (94%)
 rename paper-server/patches/features/{0023-Eigencraft-redstone-implementation.patch => 0024-Eigencraft-redstone-implementation.patch} (100%)
 rename paper-server/patches/features/{0024-Add-Alternate-Current-redstone-implementation.patch => 0025-Add-Alternate-Current-redstone-implementation.patch} (99%)
 rename paper-server/patches/features/{0025-Improve-exact-choice-recipe-ingredients.patch => 0026-Improve-exact-choice-recipe-ingredients.patch} (99%)

diff --git a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
similarity index 99%
rename from paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
rename to paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index 16ce461d49..70f66817f7 100644
--- a/paper-server/patches/features/0018-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -22768,7 +22768,7 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +    private SaveUtil() {}
 +}
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index 764daee7cd619c56314bcea9a4c35702afcb262d..e54355728183c594643f2e28ba2e92b1502882ac 100644
+index d3aebc7f833764351c8e5fe1fad1aa2f8718ca37..046a6304ea7e9dd66cb9d4cb004a582f13018295 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -1,6 +1,8 @@
@@ -22780,7 +22780,7 @@ index 764daee7cd619c56314bcea9a4c35702afcb262d..e54355728183c594643f2e28ba2e92b1
  import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
  import it.unimi.dsi.fastutil.longs.LongSet;
  import it.unimi.dsi.fastutil.longs.LongSets;
-@@ -30,9 +32,12 @@ import org.bukkit.World;
+@@ -31,9 +33,12 @@ import org.bukkit.World;
  public final class FeatureHooks {
  
      public static void initChunkTaskScheduler(final boolean useParallelGen) {
@@ -23356,7 +23356,7 @@ index 47b1fafd91b39e73c4e9134b0b8048000fba108a..76994c1491221c06cca5405ba239e6ff
          }
      }
 diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index 4437283a5d157eede121b98be0112c1067eded5e..e627618f2368258d7cb9cd35908d0f42a9c504f5 100644
+index 47c62090b421ebea1253ee3f1c896ed84119cea6..e738405e5112584e02e01df2d5ede2676fa1bffb 100644
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
 @@ -320,6 +320,7 @@ public class Main {
@@ -23368,7 +23368,7 @@ index 4437283a5d157eede121b98be0112c1067eded5e..e627618f2368258d7cb9cd35908d0f42
                  thread1 -> {
                      DedicatedServer dedicatedServer1 = new DedicatedServer(
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3dd10c767e 100644
+index 5649482a8b85056bc009b868e19ca11f21d59fbf..0c35921acebd88f3a9a37676e47e7482dfea6d9c 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -23380,7 +23380,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
      private static MinecraftServer SERVER; // Paper
      public static final Logger LOGGER = LogUtils.getLogger();
      public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
-@@ -316,6 +316,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -318,6 +318,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          return minecraftServer;
      }
  
@@ -23458,7 +23458,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
      public MinecraftServer(
          // CraftBukkit start
          joptsimple.OptionSet options,
-@@ -626,7 +697,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -628,7 +699,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          this.forceDifficulty();
          for (ServerLevel serverLevel : this.getAllLevels()) {
              this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
@@ -23467,7 +23467,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
              this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
          }
  
-@@ -825,6 +896,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -827,6 +898,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public abstract boolean shouldRconBroadcast();
  
      public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
@@ -23479,7 +23479,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
          boolean flag = false;
  
          for (ServerLevel serverLevel : this.getAllLevels()) {
-@@ -832,7 +908,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -834,7 +910,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
              }
  
@@ -23488,7 +23488,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
              flag = true;
          }
  
-@@ -923,7 +999,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -925,7 +1001,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              }
          }
  
@@ -23497,7 +23497,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
              this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
  
              for (ServerLevel serverLevelx : this.getAllLevels()) {
-@@ -934,17 +1010,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -936,17 +1012,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.waitUntilNextTick();
          }
  
@@ -23516,7 +23516,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
  
          this.isSaving = false;
          this.resources.close();
-@@ -963,6 +1029,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -966,6 +1032,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
          // Spigot end
@@ -23531,7 +23531,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
      }
  
      public String getLocalIp() {
-@@ -1133,6 +1207,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1130,6 +1204,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      profilerFiller.push("tick");
                      this.tickFrame.start();
                      this.tickServer(flag ? () -> false : this::haveTime);
@@ -23545,7 +23545,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
                      this.tickFrame.end();
                      profilerFiller.popPush("nextTickWait");
                      this.mayHaveDelayedTasks = true;
-@@ -1305,6 +1386,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1302,6 +1383,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      private boolean pollTaskInternal() {
          if (super.pollTask()) {
@@ -23553,7 +23553,7 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
              return true;
          } else {
              boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
-@@ -2425,6 +2507,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2422,6 +2504,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
      }
  
@@ -23567,10 +23567,10 @@ index 646c2f2b617ed706021c83c9fc4492860dfdd4e9..b4dcb4178b49af49cf6e654788efda3d
      // CraftBukkit start
      public boolean isDebugging() {
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..ab356d1afb352f1f134d25dfa17ce5476a2039cd 100644
+index 72409db938babd2da64a20746911cb8d45452d7f..fbc9b4d99bd1913a243a8d0424eb6f165e535747 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -432,7 +432,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -433,7 +433,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
          return level.dimension() != Level.NETHER || this.getProperties().allowNether;
      }
  
@@ -23605,7 +23605,7 @@ index 1ad96b964cdcf10b9f81d32d07e03c1a0ab6fe0a..ab356d1afb352f1f134d25dfa17ce547
      }
  
 diff --git a/net/minecraft/server/level/ChunkHolder.java b/net/minecraft/server/level/ChunkHolder.java
-index cc63e49b7d1b4ba6e8df87aff4cf71036d3de5c5..a37cc96ec26c92a60b0f0ca43d48705cd2bb072e 100644
+index b95de132c53f82d270de396787dda3be8bc6c910..656041c9539b6834b4d37b353eb6b810a7763ff4 100644
 --- a/net/minecraft/server/level/ChunkHolder.java
 +++ b/net/minecraft/server/level/ChunkHolder.java
 @@ -29,27 +29,112 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -23775,7 +23775,7 @@ index cc63e49b7d1b4ba6e8df87aff4cf71036d3de5c5..a37cc96ec26c92a60b0f0ca43d48705c
      }
  
      @Nullable
-     public LevelChunk getTickingChunk() {
+     public final LevelChunk getTickingChunk() { // Paper - final for inline
 -        return this.getTickingChunkFuture().getNow(UNLOADED_LEVEL_CHUNK).orElse(null);
 +        // Paper start - rewrite chunk system
 +        if (this.newChunkHolder.isTickingReady()) {
@@ -24086,7 +24086,7 @@ index e823b8aac00158892538083bc877ccf99895909a..7d871318065f19540748363809de8265
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 8c1eea5339b650bd1527b5b3aa010c12d70a6de1..86aaf32de5d80ee222cbe7eff0f6c119a032e04f 100644
+index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9c2224276 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -96,7 +96,7 @@ import net.minecraft.world.level.storage.LevelStorageSource;
@@ -24745,7 +24745,7 @@ index 8c1eea5339b650bd1527b5b3aa010c12d70a6de1..86aaf32de5d80ee222cbe7eff0f6c119
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
      }
  
-     public void setServerViewDistance(int viewDistance) { // Paper - publi
+     public void setServerViewDistance(int viewDistance) {
 -        int i = Mth.clamp(viewDistance, 2, 32);
 -        if (i != this.serverViewDistance) {
 -            this.serverViewDistance = i;
@@ -25253,7 +25253,7 @@ index 8c1eea5339b650bd1527b5b3aa010c12d70a6de1..86aaf32de5d80ee222cbe7eff0f6c119
  
          public void updatePlayers(List<ServerPlayer> playersList) {
 diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java
-index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9873d2411 100644
+index 8ec20e372570d5eb720cdcdaed9c92946033be9b..5eab6179ce3913cb4e4d424f910ba423faf21c85 100644
 --- a/net/minecraft/server/level/DistanceManager.java
 +++ b/net/minecraft/server/level/DistanceManager.java
 @@ -34,56 +34,56 @@ import net.minecraft.world.level.ChunkPos;
@@ -25349,7 +25349,7 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
  
      private static int getTicketLevelAt(SortedArraySet<Ticket<?>> tickets) {
          return !tickets.isEmpty() ? tickets.first().getTicketLevel() : ChunkLevel.MAX_LEVEL + 1;
-@@ -98,81 +98,15 @@ public abstract class DistanceManager {
+@@ -98,77 +98,15 @@ public abstract class DistanceManager {
      protected abstract ChunkHolder updateChunkScheduling(long chunkPos, int i, @Nullable ChunkHolder newLevel, int holder);
  
      public boolean runAllUpdates(ChunkMap chunkMap) {
@@ -25405,7 +25405,7 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
 +        return this.moonrise$getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system
      }
  
-     boolean addTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+     void addTicket(long chunkPos, Ticket<?> ticket) {
 -        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
 -        int ticketLevelAt = getTicketLevelAt(tickets);
 -        Ticket<?> ticket1 = tickets.addOrGet(ticket);
@@ -25413,15 +25413,12 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
 -        if (ticket.getTicketLevel() < ticketLevelAt) {
 -            this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
 -        }
--        return ticket == ticket1; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
++        this.moonrise$getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
      }
  
-     boolean removeTicket(long chunkPos, Ticket<?> ticket) { // CraftBukkit - void -> boolean
+     void removeTicket(long chunkPos, Ticket<?> ticket) {
 -        SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
--        boolean removed = false; // CraftBukkit
 -        if (tickets.remove(ticket)) {
--            removed = true; // CraftBukkit
 -        }
 -
 -        if (tickets.isEmpty()) {
@@ -25429,38 +25426,57 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
 -        }
 -
 -        this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
--        return removed; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
++        this.moonrise$getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), chunkPos, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system
      }
  
      public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
-@@ -189,12 +123,7 @@ public abstract class DistanceManager {
-         this.addRegionTicketAtDistance(type, pos, distance, value);
+@@ -181,68 +119,43 @@ public abstract class DistanceManager {
      }
-     public <T> boolean addRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
--        // CraftBukkit end
+ 
+     public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
 -        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
 -        long packedChunkPos = pos.toLong();
--        final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit
+-        this.addTicket(packedChunkPos, ticket); // Paper - diff on change above
 -        this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
--        return addded; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().addTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
++        this.moonrise$getChunkHolderManager().addTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
      }
  
      public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
-@@ -202,37 +131,29 @@ public abstract class DistanceManager {
-         removeRegionTicketAtDistance(type, pos, distance, value);
-     }
-     public <T> boolean removeRegionTicketAtDistance(TicketType<T> type, ChunkPos pos, int distance, T value) {
--        // CraftBukkit end
 -        Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
 -        long packedChunkPos = pos.toLong();
--        final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit
+-        this.removeTicket(packedChunkPos, ticket); // Paper - diff on change above
 -        this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
--        return removed; // CraftBukkit
-+        return this.moonrise$getChunkHolderManager().removeTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
++        this.moonrise$getChunkHolderManager().removeTicketAtLevel(type, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); // Paper - rewrite chunk system
      }
  
+     // Paper start
+     public boolean addPluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+-        Ticket<org.bukkit.plugin.Plugin> ticket = new Ticket<>(TicketType.PLUGIN_TICKET, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Copied from below and keep in-line with force loading, add at level 31
+-        final long packedChunkPos = pos.toLong();
+-        final Set<Ticket<?>> tickets = this.getTickets(packedChunkPos);
+-        if (tickets.contains(ticket)) {
+-            return false;
+-        }
+-        this.addTicket(packedChunkPos, ticket);
+-        this.tickingTicketsTracker.addTicket(packedChunkPos, ticket);
+-        return true;
++        return this.moonrise$getChunkHolderManager().addTicketAtLevel(TicketType.PLUGIN_TICKET, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Paper - rewrite chunk system
+     }
+ 
+     public boolean removePluginRegionTicket(final ChunkPos pos, final org.bukkit.plugin.Plugin value) {
+-        Ticket<org.bukkit.plugin.Plugin> ticket = new Ticket<>(TicketType.PLUGIN_TICKET, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Copied from below and keep in-line with force loading, add at level 31
+-        final long packedChunkPos = pos.toLong();
+-        final Set<Ticket<?>> tickets = this.tickets.get(packedChunkPos); // Don't use getTickets, we don't want to create a new set
+-        if (tickets == null || !tickets.contains(ticket)) {
+-            return false;
+-        }
+-        this.removeTicket(packedChunkPos, ticket);
+-        this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket);
+-        return true;
++        return this.moonrise$getChunkHolderManager().removeTicketAtLevel(TicketType.PLUGIN_TICKET, pos, ChunkLevel.byStatus(FullChunkStatus.FULL) - 2, value); // Paper - rewrite chunk system
+     }
+     // Paper end
+ 
      private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
 -        return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
 +        throw new UnsupportedOperationException(); // Paper - rewrite chunk system
@@ -25494,9 +25510,9 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
      }
  
      public void removePlayer(SectionPos sectionPos, ServerPlayer player) {
-@@ -246,136 +167,90 @@ public abstract class DistanceManager {
+@@ -254,136 +167,90 @@ public abstract class DistanceManager {
          if (set == null || set.isEmpty()) {
-             // Paper end - some state corruption happens here, don't crash, clean up gracefully
+         // Paper end - some state corruption happens here, don't crash, clean up gracefully
              this.playersPerChunk.remove(packedChunkPos);
 -            this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false);
 -            this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false);
@@ -25661,7 +25677,7 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
      class ChunkTicketTracker extends ChunkTracker {
          private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
  
-@@ -420,7 +295,7 @@ public abstract class DistanceManager {
+@@ -428,7 +295,7 @@ public abstract class DistanceManager {
          public int runDistanceUpdates(int toUpdateCount) {
              return this.runUpdates(toUpdateCount);
          }
@@ -25670,7 +25686,7 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
  
      class FixedPlayerDistanceChunkTracker extends ChunkTracker {
          protected final Long2ByteMap chunks = new Long2ByteOpenHashMap();
-@@ -479,6 +354,7 @@ public abstract class DistanceManager {
+@@ -487,6 +354,7 @@ public abstract class DistanceManager {
          }
      }
  
@@ -25678,7 +25694,7 @@ index e50e96861c0aefdb0a79674b7790798f2571010e..73599e8adbee0957c1318ee6688e49b9
      class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker {
          private int viewDistance;
          private final Long2IntMap queueLevels = Long2IntMaps.synchronize(new Long2IntOpenHashMap());
-@@ -555,5 +431,5 @@ public abstract class DistanceManager {
+@@ -563,5 +431,5 @@ public abstract class DistanceManager {
          private boolean haveTicketFor(int level) {
              return level <= this.viewDistance;
          }
@@ -26019,7 +26035,7 @@ index cb66209c64b855dedf2e4e114a7716da13bc4587..da1366fdc4889d6a3befd43d81a19a81
      }
  }
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index 796b5f8541b0cf84482ab2b5a60adde544d43593..7ce641cc34ef95e248e62ebf0b7fdfb8b2924256 100644
+index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..6c9c7c3124d5990ea34368eb4578eac695abd658 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -26174,7 +26190,7 @@ index 796b5f8541b0cf84482ab2b5a60adde544d43593..7ce641cc34ef95e248e62ebf0b7fdfb8
 -            for (int i = 0; i < 4; i++) {
 -                if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) {
 -                    ChunkAccess chunkAccess = this.lastChunk[i];
--                    if (chunkAccess != null) {  // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
+-                    if (chunkAccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
 -                        return chunkAccess;
 -                    }
 -                }
@@ -26519,7 +26535,7 @@ index 796b5f8541b0cf84482ab2b5a60adde544d43593..7ce641cc34ef95e248e62ebf0b7fdfb8
      }
  }
 diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
-index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..d38ddfd7afba189a489701e62b5fa6db8df87a9d 100644
+index 70f6d068b3f3665b282d9750310c883839120ab2..870b9efd445ddadb3725e88351555ad986ce7c72 100644
 --- a/net/minecraft/server/level/ServerEntity.java
 +++ b/net/minecraft/server/level/ServerEntity.java
 @@ -91,6 +91,11 @@ public class ServerEntity {
@@ -26535,7 +26551,7 @@ index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..d38ddfd7afba189a489701e62b5fa6db
          if (!passengers.equals(this.lastPassengers)) {
              this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 08ee3583a07b6e4e877e530d422e386597bce6de..3927f5603d18f1c42eb29d916f287b6ef3f39612 100644
+index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..ffb5bfdd76a92bac61c7c352fdded4200d13b3ae 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -170,7 +170,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
@@ -27296,7 +27312,7 @@ index 08ee3583a07b6e4e877e530d422e386597bce6de..3927f5603d18f1c42eb29d916f287b6e
      }
  
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 940509d1f31aedf20b8f5b9192c34ad004875728..b455e0e0fea949bee1953324ae530c19405c5d3b 100644
+index 6238729e91ae4fd44a4e0bc73d4f042498c4cb8d..bf4deeac50197eeb83c5b1e458b609aac5ad8a97 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -178,7 +178,7 @@ import net.minecraft.world.scores.Team;
@@ -27308,7 +27324,7 @@ index 940509d1f31aedf20b8f5b9192c34ad004875728..b455e0e0fea949bee1953324ae530c19
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
-@@ -387,6 +387,36 @@ public class ServerPlayer extends Player {
+@@ -388,6 +388,36 @@ public class ServerPlayer extends Player {
      public @Nullable String clientBrandName = null; // Paper - Brand support
      public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
  
@@ -27776,7 +27792,7 @@ index 4eb040006f5d41b47e5ac9df5d9f19c4315d6343..7fa41dea184b01891f45d8e404bc1cba
          this.generatingStep = generatingStep;
          this.cache = cache;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index d227714de0fe13544779fae6cf0e9ff6af5469c7..4722230e74e0778ebdb2cfd383764b34004e9568 100644
+index bafeeab3edbc73f6f86474e18ab4a3d96ce17157..d322794c0d49daa212b8691f8f60f2276fe25a92 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -1318,7 +1318,7 @@ public abstract class PlayerList {
@@ -28172,7 +28188,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1d122428b 100644
+index d5b0db3095a563dd101a1b12b150401a7dc59f8e..eb333032d8117d8dec4a15f3f2803166431633bc 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
@@ -28266,7 +28282,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
      }
      // Paper end - Share random for entities to make them more random
      public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
-@@ -425,6 +381,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -419,6 +375,156 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return this.dimensions.makeBoundingBox(x, y, z);
      }
      // Paper end
@@ -28647,7 +28663,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
      }
  
      public InteractionResult interact(Player player, InteractionHand hand) {
-@@ -4063,15 +4298,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4062,15 +4297,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public Iterable<Entity> getIndirectPassengers() {
@@ -28673,7 +28689,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
      }
  
      public int countPlayerPassengers() {
-@@ -4209,77 +4446,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4208,77 +4445,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return Mth.lerp(partialTick, this.yRotO, this.yRot);
      }
  
@@ -28864,7 +28880,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
  
      public boolean touchingUnloadedChunk() {
          AABB aabb = this.getBoundingBox().inflate(1.0);
-@@ -4430,6 +4726,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4429,6 +4725,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          this.setPosRaw(x, y, z, false);
      }
      public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -28880,7 +28896,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
          if (!checkPosition(this, x, y, z)) {
              return;
          }
-@@ -4558,6 +4863,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4557,6 +4862,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      @Override
      public final void setRemoved(Entity.RemovalReason removalReason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
@@ -28893,7 +28909,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
          org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause);
          // CraftBukkit end
          final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
-@@ -4569,7 +4880,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4568,7 +4879,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              this.stopRiding();
          }
  
@@ -28902,7 +28918,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..77d2c86c8bb20e96e0eed32c430ef0c1
          this.levelCallback.onRemove(removalReason);
          this.onRemoval(removalReason);
          // Paper start - Folia schedulers
-@@ -4603,7 +4914,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4602,7 +4913,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      public boolean shouldBeSaved() {
          return (this.removalReason == null || this.removalReason.shouldSave())
              && !this.isPassenger()
@@ -29150,7 +29166,7 @@ index 324cc0686f0f5b1371b2bbea5b8c8fdb1f363006..39cd1e3d8192d7077d6b7864d3393309
          this(setDirty, true, ImmutableList.of());
      }
 diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java
-index 613675c86787dd1147e140f1ef4d17b09ab9a74b..5cc19a67c7a29f1bda8cdd6921201f47fe6bf91f 100644
+index 33f6d6862731d22d6d3eeb7cf39a4a42049afae3..a3cc0001a949597e345d7919c3f6109fa4a949ad 100644
 --- a/net/minecraft/world/entity/decoration/ArmorStand.java
 +++ b/net/minecraft/world/entity/decoration/ArmorStand.java
 @@ -316,7 +316,7 @@ public class ArmorStand extends LivingEntity {
@@ -29300,7 +29316,7 @@ index 300f3ed58109219d97846082941b860585f66fed..e81195df621159da67136f020fa7a6d3
  
      // Paper start - Affects Spawning API
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9befe6a2040 100644
+index 0e4ab448755632696c4326f1df9f3855cd38a64d..72646473019424de969756ae1d0e9f789310889b 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -79,6 +79,7 @@ import net.minecraft.world.level.storage.LevelData;
@@ -29992,17 +30008,16 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
                  this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
              }
  
-@@ -836,6 +1470,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-         // Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
+@@ -835,6 +1469,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+         // Spigot start
          boolean runsNormally = this.tickRateManager().runsNormally();
  
 +        int tickedEntities = 0; // Paper - rewrite chunk system
-+
-         int tilesThisCycle = 0;
          var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
          toRemove.add(null); // Paper - Fix MC-117075
-@@ -850,6 +1486,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-                 // Spigot end
+         for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
+@@ -845,6 +1480,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+                 toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
              } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
                  tickingBlockEntity.tick();
 +                // Paper start - rewrite chunk system
@@ -30013,7 +30028,7 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
              }
          }
          this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
-@@ -870,6 +1511,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -865,6 +1505,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
              entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
              // Paper end - Prevent block entity and entity crashes
          }
@@ -30021,7 +30036,7 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
      }
  
      // Paper start - Option to prevent armor stands from doing entity lookups
-@@ -877,7 +1519,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -872,7 +1513,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public boolean noCollision(@Nullable Entity entity, AABB box) {
          if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups)
              return false;
@@ -30037,7 +30052,7 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
      }
      // Paper end - Option to prevent armor stands from doing entity lookups
  
-@@ -1015,7 +1664,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1010,7 +1658,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          if (this.isOutsideBuildHeight(pos)) {
              return null;
          } else {
@@ -30046,7 +30061,7 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
                  ? null
                  : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE);
          }
-@@ -1108,22 +1757,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1103,22 +1751,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
      public List<Entity> getEntities(@Nullable Entity entity, AABB boundingBox, Predicate<? super Entity> predicate) {
          Profiler.get().incrementCounter("getEntities");
          List<Entity> list = Lists.newArrayList();
@@ -30077,7 +30092,7 @@ index 872c3b8826f436b15f6ab0a3619692c5202eadc3..b5efce6faf35dbfee9aa6c17f2e7e9be
      }
  
      @Override
-@@ -1137,33 +1780,94 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1132,33 +1774,94 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
          this.getEntities(entityTypeTest, bounds, predicate, output, Integer.MAX_VALUE);
      }
  
@@ -30217,10 +30232,10 @@ index 2709803b9266ff4a2034d83321cd0ba4e30fc0aa..26c8c1e5598daf3550aef05b12218c47
      ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk);
  
 diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java
-index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e997a5d9d1 100644
+index 3619509d51ebd2e5e36fe4b67e76c94a8d272d1b..7b132c55caf9d3c3df3b0a123f4b5bfc7ae35984 100644
 --- a/net/minecraft/world/level/ServerExplosion.java
 +++ b/net/minecraft/world/level/ServerExplosion.java
-@@ -64,6 +64,249 @@ public class ServerExplosion implements Explosion {
+@@ -63,6 +63,249 @@ public class ServerExplosion implements Explosion {
      public float yield;
      // CraftBukkit end
      public boolean excludeSourceFromDamage = true; // Paper - Allow explosions to damage source
@@ -30470,7 +30485,7 @@ index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e9
  
      public ServerExplosion(
          ServerLevel level,
-@@ -135,63 +378,102 @@ public class ServerExplosion implements Explosion {
+@@ -134,63 +377,102 @@ public class ServerExplosion implements Explosion {
      }
  
      private List<BlockPos> calculateExplodedPositions() {
@@ -30514,7 +30529,7 @@ index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e9
 -                            if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockPos, blockState, f)) {
 -                                set.add(blockPos);
 -                                // Paper start - prevent headless pistons from forming
--                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.getBlock() == Blocks.MOVING_PISTON) {
+-                                if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && blockState.is(Blocks.MOVING_PISTON)) {
 -                                    net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockPos);
 -                                    if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
 -                                        net.minecraft.core.Direction direction = blockState.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
@@ -30620,7 +30635,7 @@ index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e9
      }
  
      private void hurtEntities() {
-@@ -372,6 +654,14 @@ public class ServerExplosion implements Explosion {
+@@ -371,6 +653,14 @@ public class ServerExplosion implements Explosion {
              return;
          }
          // CraftBukkit end
@@ -30635,7 +30650,7 @@ index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e9
          this.level.gameEvent(this.source, GameEvent.EXPLODE, this.center);
          List<BlockPos> list = this.calculateExplodedPositions();
          this.hurtEntities();
-@@ -385,6 +675,13 @@ public class ServerExplosion implements Explosion {
+@@ -384,6 +674,13 @@ public class ServerExplosion implements Explosion {
          if (this.fire) {
              this.createFire(list);
          }
@@ -30649,7 +30664,7 @@ index 8b91574d4679f4e5a01b4bc3651069cd489b0336..fc08543bf5c8cbe338991795a9da28e9
      }
  
      private static void addOrAppendStack(List<ServerExplosion.StackCollector> stackCollectors, ItemStack stack, BlockPos pos) {
-@@ -475,12 +772,12 @@ public class ServerExplosion implements Explosion {
+@@ -474,12 +771,12 @@ public class ServerExplosion implements Explosion {
      // Paper start - Optimize explosions
      private float getBlockDensity(Vec3 vec3d, Entity entity) {
          if (!this.level.paperConfig().environment.optimizeExplosions) {
@@ -30705,7 +30720,7 @@ index 8d98cba3830dc5dfb5cae9a6f5fedfffee0d2cd8..73962e79a0f3d892e3155443a1b84508
  
      public interface NoiseBiomeSource {
 diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java
-index 3eedc6c2a1325a0fd1baa14e246aeda27de4117d..34177f27680612055526553c849777067882ad74 100644
+index 91d7d250f7c3de8a71aef26e23c12764b06b322b..0d36b1ac7d54283af71f2494accded11c059dba5 100644
 --- a/net/minecraft/world/level/block/Block.java
 +++ b/net/minecraft/world/level/block/Block.java
 @@ -259,7 +259,7 @@ public class Block extends BlockBehaviour implements ItemLike {
@@ -30718,10 +30733,10 @@ index 3eedc6c2a1325a0fd1baa14e246aeda27de4117d..34177f27680612055526553c84977706
  
      public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
 diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java
-index e0c3d8923f56c059b5d6111ab2ff01be3566f6d9..73b913da595e7ad2de8f363f342de98f6c647b71 100644
+index 25e49a24cedfa8ad04245d59fcac3231bcd62103..061d94a35d957ca72a01bae959d38aab59b1a64d 100644
 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java
 +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
-@@ -417,7 +417,7 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -416,7 +416,7 @@ public abstract class BlockBehaviour implements FeatureElement {
          return this.properties.destroyTime;
      }
  
@@ -30730,7 +30745,7 @@ index e0c3d8923f56c059b5d6111ab2ff01be3566f6d9..73b913da595e7ad2de8f363f342de98f
          private static final Direction[] DIRECTIONS = Direction.values();
          private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = Util.make(new VoxelShape[DIRECTIONS.length], shape -> Arrays.fill(shape, Shapes.empty()));
          private static final VoxelShape[] FULL_BLOCK_OCCLUSION_SHAPES = Util.make(
-@@ -456,6 +456,76 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -455,6 +455,76 @@ public abstract class BlockBehaviour implements FeatureElement {
          private boolean propagatesSkylightDown;
          private int lightBlock;
  
@@ -30807,7 +30822,7 @@ index e0c3d8923f56c059b5d6111ab2ff01be3566f6d9..73b913da595e7ad2de8f363f342de98f
          protected BlockStateBase(Block owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<BlockState> propertiesCodec) {
              super(owner, values, propertiesCodec);
              BlockBehaviour.Properties properties = owner.properties;
-@@ -534,6 +604,41 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -533,6 +603,41 @@ public abstract class BlockBehaviour implements FeatureElement {
  
              this.propagatesSkylightDown = this.owner.propagatesSkylightDown(this.asState());
              this.lightBlock = this.owner.getLightBlock(this.asState());
@@ -31197,7 +31212,7 @@ index 92350434746f06bbf4a161c6bc42602de7b45220..1c24f38d21da1be9740512981f219924
  
      public Property.Value<T> value(T value) {
 diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
-index bc688ad1097ef4159dfc5f96d963a9fa63262e20..6e0210f32c679e3a33256cb05dc282f2e8b7d856 100644
+index 860d1c9729c4ee97ec6f40f7aa969829070b27c0..94de1217d18e1a7a0fb7b83f21436eaf0a5998c6 100644
 --- a/net/minecraft/world/level/chunk/ChunkAccess.java
 +++ b/net/minecraft/world/level/chunk/ChunkAccess.java
 @@ -57,7 +57,7 @@ import net.minecraft.world.ticks.SavedTick;
@@ -31288,11 +31303,11 @@ index bc688ad1097ef4159dfc5f96d963a9fa63262e20..6e0210f32c679e3a33256cb05dc282f2
 @@ -116,6 +167,16 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
  
          this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method
-         this.biomeRegistry = biomeRegistry; // Craftbukkit
+         this.biomeRegistry = biomeRegistry; // CraftBukkit
 +        // Paper start - rewrite chunk system
 +        if (!((Object)this instanceof ImposterProtoChunk)) {
-+            this.starlight$setBlockNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(heightLimitView));
-+            this.starlight$setSkyNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(heightLimitView));
++            this.starlight$setBlockNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(levelHeightAccessor));
++            this.starlight$setSkyNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(levelHeightAccessor));
 +        }
 +        // Paper end - rewrite chunk system
 +        // Paper start - get block chunk optimisation
@@ -31352,7 +31367,7 @@ index bc688ad1097ef4159dfc5f96d963a9fa63262e20..6e0210f32c679e3a33256cb05dc282f2
  
      public record PackedTicks(List<SavedTick<Block>> blocks, List<SavedTick<Fluid>> fluids) {
 diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java
-index 89f027f0edf2ca59966efe209e567108665cbe0c..7944c2bb854ee52a55d7c54e5f814e87d709e70e 100644
+index 65117a9cb9d1b8684cae8d36ea3b8e2050fb928c..a9d65e28b849c9660a14ef7c16ed17bd5182bd7e 100644
 --- a/net/minecraft/world/level/chunk/ChunkGenerator.java
 +++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
 @@ -116,7 +116,7 @@ public abstract class ChunkGenerator {
@@ -31515,7 +31530,7 @@ index e7c0f4da8508fbca467326f475668d66454d7b77..41856c98d97e7eb0782f8e441b9a269a
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba..72c0e031443ca42c2cfafddea9e04cfa4f93a0eb 100644
+index 96b0342ab7b922aa16d07b6c00542e6cb66c974a..aff0937ad7eb81d9e32f56aa337a4ec7551f0faa 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
@@ -32582,10 +32597,10 @@ index 889e188e920edb284f04b264bcdd06146f54a4cb..2199a9e2a0141c646d108f2687a27f1d
      private final Long2ObjectLinkedOpenHashMap<CompletableFuture<BitSet>> regionCacheForBlender = new Long2ObjectLinkedOpenHashMap<>();
      private static final int REGION_CACHE_SIZE = 1024;
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d94ba35329 100644
+index 783a2d80f6197dd0af0dc81909f0353a8ea2ecf4..7da388ffab162c282cad0f297bb7304f3c2abbaf 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -23,7 +23,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler;
+@@ -22,7 +22,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler;
  import net.minecraft.world.level.ChunkPos;
  import org.slf4j.Logger;
  
@@ -32594,7 +32609,7 @@ index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d9
      private static final Logger LOGGER = LogUtils.getLogger();
      private static final int SECTOR_BYTES = 4096;
      @VisibleForTesting
-@@ -46,6 +46,21 @@ public class RegionFile implements AutoCloseable {
+@@ -45,6 +45,21 @@ public class RegionFile implements AutoCloseable {
      @VisibleForTesting
      protected final RegionBitmap usedSectors = new RegionBitmap();
  
@@ -32616,7 +32631,7 @@ index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d9
      public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, boolean sync) throws IOException {
          this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format
      }
-@@ -205,6 +220,16 @@ public class RegionFile implements AutoCloseable {
+@@ -204,6 +219,16 @@ public class RegionFile implements AutoCloseable {
  
      @Nullable
      private DataInputStream createExternalChunkInputStream(ChunkPos chunkPos, byte versionByte) throws IOException {
@@ -32633,7 +32648,7 @@ index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d9
          Path externalChunkPath = this.getExternalChunkPath(chunkPos);
          if (!Files.isRegularFile(externalChunkPath)) {
              LOGGER.error("External chunk path {} is not file", externalChunkPath);
-@@ -399,9 +424,28 @@ public class RegionFile implements AutoCloseable {
+@@ -398,9 +423,28 @@ public class RegionFile implements AutoCloseable {
          }
      }
  
@@ -32663,7 +32678,7 @@ index 7491644233d52dc56d83de5ad3373f6a9a455378..b76f0fcecd3de1e7a802a6006c8851d9
          public ChunkBuffer(final ChunkPos pos) {
              super(8096);
              super.write(0);
-@@ -418,7 +462,7 @@ public class RegionFile implements AutoCloseable {
+@@ -417,7 +461,7 @@ public class RegionFile implements AutoCloseable {
              int i = this.count - 5 + 1;
              JvmProfiler.INSTANCE.onRegionFileWrite(RegionFile.this.info, this.pos, RegionFile.this.version, i);
              byteBuffer.putInt(0, i);
@@ -33498,7 +33513,7 @@ index 342c83309b19c64d86e0dd97c1756c96be52772b..423779a2b690f387a4f0bd07b97b50e0
      }
  }
 diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
-index b50de067a2a968926bdda4174e42f2973c84ff8b..8b81257328771ad23b90d7670f13713f93eefa96 100644
+index 29d9f6e54421c539e9e55ab9f51b4c872da3fbb8..d77016287f5f9a0964d56f05d2d5256ef2e6e86c 100644
 --- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 +++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 @@ -78,7 +78,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator {
@@ -33800,7 +33815,7 @@ index ca23af013967b50420ebee178878ea79333de53b..83c3ec06be51f632b7c1b682cfa8dce7
  
      public int getLightSectionCount() {
 diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
-index b9ad980b9a203bbc295f780bf54941f19a2f6525..19beef65b292c474f3a25b6c3ff679e31c17c7f4 100644
+index 130ef38a50f1df1faa26b433b0c605a4507f71af..f6daca279788c3d983a9ee213df85d5d93fc6eed 100644
 --- a/net/minecraft/world/level/material/FlowingFluid.java
 +++ b/net/minecraft/world/level/material/FlowingFluid.java
 @@ -45,6 +45,48 @@ public abstract class FlowingFluid extends Fluid {
diff --git a/paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch b/paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch
similarity index 90%
rename from paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch
rename to paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch
index d7fc36933f..ff28bdb87b 100644
--- a/paper-server/patches/features/0021-Fix-entity-tracker-desync-when-new-players-are-added.patch
+++ b/paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch
@@ -48,10 +48,10 @@ index db31989ebe3d7021cfd2311439e9a00f819b0841..1373977b339405ef59bb3ea03d195285
              serverEntity.getLastSentYRot(),
              entity.getType(),
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index f8f145cd9614f7e38d6aa72501fe31837340a8bb..fa48e63fea42f387dcd154c08af54cfb0d3b08af 100644
+index 9faf0ece0f074b8709b4e4fad0cab3e9c2224276..d22459cb1d46ea7c969aa05cb9d7d12466e173da 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
-@@ -1419,6 +1419,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1208,6 +1208,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
                          this.serverEntity.addPairing(player);
                          }
                          // Paper end - entity tracking events
@@ -60,7 +60,7 @@ index f8f145cd9614f7e38d6aa72501fe31837340a8bb..fa48e63fea42f387dcd154c08af54cfb
                  } else if (this.seenBy.remove(player.connection)) {
                      this.serverEntity.removePairing(player);
 diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
-index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..66f7926a2639ade41cb89419e38e12053314c982 100644
+index 870b9efd445ddadb3725e88351555ad986ce7c72..a4da36060ca75968f5831adfc3f7117281649b7a 100644
 --- a/net/minecraft/server/level/ServerEntity.java
 +++ b/net/minecraft/server/level/ServerEntity.java
 @@ -90,6 +90,13 @@ public class ServerEntity {
@@ -75,9 +75,9 @@ index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..66f7926a2639ade41cb89419e38e1205
 +    // Paper end - fix desync when a player is added to the tracker
 +
      public void sendChanges() {
-         List<Entity> passengers = this.entity.getPassengers();
-         if (!passengers.equals(this.lastPassengers)) {
-@@ -125,7 +132,7 @@ public class ServerEntity {
+         // Paper start - optimise collisions
+         if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
+@@ -130,7 +137,7 @@ public class ServerEntity {
              this.sendDirtyEntityData();
          }
  
@@ -86,7 +86,7 @@ index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..66f7926a2639ade41cb89419e38e1205
              byte b = Mth.packDegrees(this.entity.getYRot());
              byte b1 = Mth.packDegrees(this.entity.getXRot());
              boolean flag = Math.abs(b - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1;
-@@ -160,7 +167,7 @@ public class ServerEntity {
+@@ -165,7 +172,7 @@ public class ServerEntity {
                  long l1 = this.positionCodec.encodeY(vec3);
                  long l2 = this.positionCodec.encodeZ(vec3);
                  boolean flag5 = l < -32768L || l > 32767L || l1 < -32768L || l1 > 32767L || l2 < -32768L || l2 > 32767L;
@@ -95,7 +95,7 @@ index 4c891706c2a3efc8e8c44fc1c031e8a1d21efb60..66f7926a2639ade41cb89419e38e1205
                      this.wasOnGround = this.entity.onGround();
                      this.teleportDelay = 0;
                      packet = ClientboundEntityPositionSyncPacket.of(this.entity);
-@@ -225,6 +232,7 @@ public class ServerEntity {
+@@ -230,6 +237,7 @@ public class ServerEntity {
              }
  
              this.entity.hasImpulse = false;
diff --git a/paper-server/patches/features/0022-Lag-compensation-ticks.patch b/paper-server/patches/features/0023-Lag-compensation-ticks.patch
similarity index 94%
rename from paper-server/patches/features/0022-Lag-compensation-ticks.patch
rename to paper-server/patches/features/0023-Lag-compensation-ticks.patch
index 65659dc68f..c764bdee4a 100644
--- a/paper-server/patches/features/0022-Lag-compensation-ticks.patch
+++ b/paper-server/patches/features/0023-Lag-compensation-ticks.patch
@@ -8,7 +8,7 @@ Areas affected by lag comepnsation:
  - Eating food items
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 5649482a8b85056bc009b868e19ca11f21d59fbf..f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203 100644
+index 0c35921acebd88f3a9a37676e47e7482dfea6d9c..1ff57c17f7fe3af3fb7fc5fbc5148ca333b5e618 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -19,7 +19,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..f4fba4e2d12c7ab4b4eb9858cd738a96
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -1561,6 +1562,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1643,6 +1644,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          for (ServerLevel serverLevel : this.getAllLevels()) {
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
@@ -28,10 +28,10 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..f4fba4e2d12c7ab4b4eb9858cd738a96
              /* Drop global time updates
              if (this.tickCount % 20 == 0) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 4d20bda4cba578c47216d450c99389b744a59008..47b7d487467225505e3f20cea92e114331d660fd 100644
+index ffb5bfdd76a92bac61c7c352fdded4200d13b3ae..651a65aaa37f2cf26d54f75529cbda71e6fc2bc4 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2362,4 +2362,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2678,4 +2678,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return this.server.getPlayerList().getPlayer(uuid);
      }
      // Paper end - check global player list where appropriate
@@ -49,7 +49,7 @@ index 4d20bda4cba578c47216d450c99389b744a59008..47b7d487467225505e3f20cea92e1143
 +    // Paper end - lag compensation
  }
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index fd7ad2b1bffe3880def0f0c9a7ed8de5088ecd71..e753849002b48d4a498dc93349e331bc26d39aff 100644
+index bf2a4c03afb73367a6d2530c78ff9f7c06f7f6a6..6207f45e7b84af4e6a2e4eb7b9fabf6b1e22a63a 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
 @@ -111,7 +111,7 @@ public class ServerPlayerGameMode {
diff --git a/paper-server/patches/features/0023-Eigencraft-redstone-implementation.patch b/paper-server/patches/features/0024-Eigencraft-redstone-implementation.patch
similarity index 100%
rename from paper-server/patches/features/0023-Eigencraft-redstone-implementation.patch
rename to paper-server/patches/features/0024-Eigencraft-redstone-implementation.patch
diff --git a/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch b/paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch
similarity index 99%
rename from paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
rename to paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch
index 1e6a539a98..07537f7485 100644
--- a/paper-server/patches/features/0024-Add-Alternate-Current-redstone-implementation.patch
+++ b/paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
 +    }
 +}
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 47b7d487467225505e3f20cea92e114331d660fd..c38eda42b33cfa4792625f40ebde6f30e591119b 100644
+index 651a65aaa37f2cf26d54f75529cbda71e6fc2bc4..61347004cb5b220e5596c053181a01ba5dd2b744 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -214,6 +214,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -2337,7 +2337,7 @@ index 47b7d487467225505e3f20cea92e114331d660fd..c38eda42b33cfa4792625f40ebde6f30
  
      public LevelChunk getChunkIfLoaded(int x, int z) {
          return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
-@@ -2213,6 +2214,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2529,6 +2530,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return this.chunkSource.getGenerator().getSeaLevel();
      }
  
@@ -2352,10 +2352,10 @@ index 47b7d487467225505e3f20cea92e114331d660fd..c38eda42b33cfa4792625f40ebde6f30
          @Override
          public void onCreated(Entity entity) {
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 0e4ab448755632696c4326f1df9f3855cd38a64d..8abb17d30373fab80b466891abd16b31ab536f64 100644
+index 72646473019424de969756ae1d0e9f789310889b..d5d18ff7c4c75ab50b3af624b831cb077584f9d1 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -1396,6 +1396,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -2099,6 +2099,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
  
      public abstract FuelValues fuelValues();
  
diff --git a/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch b/paper-server/patches/features/0026-Improve-exact-choice-recipe-ingredients.patch
similarity index 99%
rename from paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
rename to paper-server/patches/features/0026-Improve-exact-choice-recipe-ingredients.patch
index 8ff6dc6c20..19c90b0017 100644
--- a/paper-server/patches/features/0025-Improve-exact-choice-recipe-ingredients.patch
+++ b/paper-server/patches/features/0026-Improve-exact-choice-recipe-ingredients.patch
@@ -204,10 +204,10 @@ index 6475509689439636275b06b9eac33f0922d8fcfd..6c398c91909f42e352e80d0781549df9
          int i = this.inventory.findSlotMatchingCraftingIngredient(item, item1);
          if (i == -1) {
 diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
-index e8522f5ccd69ff5513782a31a3b53219bc17b0e5..80fa9153bdce9b0728cc1ee8908fe5481e84a1f8 100644
+index 8a29d771046667db22fba166fa5337de1896cd0d..839cbb67d3d38960d9114a4db5bab911b66a573c 100644
 --- a/net/minecraft/world/entity/player/Inventory.java
 +++ b/net/minecraft/world/entity/player/Inventory.java
-@@ -178,12 +178,12 @@ public class Inventory implements Container, Nameable {
+@@ -185,12 +185,12 @@ public class Inventory implements Container, Nameable {
          return !stack.isDamaged() && !stack.isEnchanted() && !stack.has(DataComponents.CUSTOM_NAME);
      }
  

From 48f34f8c903823cd39ad8bc8a5714485b84e21de Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 20 Dec 2024 10:52:51 -0800
Subject: [PATCH 261/285] Port CB changes from Moonrise patch

---
 .../0021-Moonrise-optimisation-patches.patch  | 155 +++++++++++++++++-
 .../io/papermc/paper/FeatureHooks.java.patch  | 153 ++++++++++++++++-
 .../org/bukkit/craftbukkit/CraftChunk.java    |  58 +------
 .../org/bukkit/craftbukkit/CraftServer.java   |   4 +-
 .../org/bukkit/craftbukkit/CraftWorld.java    |  67 ++------
 .../craftbukkit/entity/CraftPlayer.java       |   6 +-
 .../generator/CustomChunkGenerator.java       |   2 +-
 .../main/java/org/spigotmc/AsyncCatcher.java  |   2 +-
 .../java/org/spigotmc/WatchdogThread.java     |   2 +-
 9 files changed, 337 insertions(+), 112 deletions(-)

diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index 70f66817f7..fdbf5905fa 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -22768,7 +22768,7 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +    private SaveUtil() {}
 +}
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index d3aebc7f833764351c8e5fe1fad1aa2f8718ca37..046a6304ea7e9dd66cb9d4cb004a582f13018295 100644
+index e40b83502c74d7dcae05d9d0c865affeee186893..c4ddf4b6fee8086481581fab88a807f63e211b5c 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -1,6 +1,8 @@
@@ -22793,6 +22793,159 @@ index d3aebc7f833764351c8e5fe1fad1aa2f8718ca37..046a6304ea7e9dd66cb9d4cb004a582f
      }
  
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
+@@ -79,89 +84,30 @@ public final class FeatureHooks {
+     }
+ 
+     public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
+-        return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
++        return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)); // Paper - rewrite collision system
+     }
+ 
+     public static void dumpAllChunkLoadInfo(net.minecraft.server.MinecraftServer server, boolean isLongTimeout) {
++        ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(server, isLongTimeout); // Paper - rewrite chunk system
+     }
+ 
+     private static void dumpEntity(final Entity entity) {
+     }
+ 
+     public static org.bukkit.entity.Entity[] getChunkEntities(net.minecraft.server.level.ServerLevel world, int chunkX, int chunkZ) {
+-        world.getChunk(chunkX, chunkZ); // ensure full loaded
+-
+-        net.minecraft.world.level.entity.PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = world.entityManager;
+-        long pair = ChunkPos.asLong(chunkX, chunkZ);
+-
+-        if (entityManager.areEntitiesLoaded(pair)) {
+-            return entityManager.getEntities(new ChunkPos(chunkX, chunkZ)).stream()
+-                .map(net.minecraft.world.entity.Entity::getBukkitEntity)
+-                .filter(java.util.Objects::nonNull).toArray(org.bukkit.entity.Entity[]::new);
+-        }
+-
+-        entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
+-
+-        // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
+-        net.minecraft.util.thread.ConsecutiveExecutor mailbox = ((net.minecraft.world.level.chunk.storage.EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
+-        java.util.function.BooleanSupplier supplier = () -> {
+-            // only execute inbox if our entities are not present
+-            if (entityManager.areEntitiesLoaded(pair)) {
+-                return true;
+-            }
+-
+-            if (!entityManager.isPending(pair)) {
+-                // Our entities got unloaded, this should normally not happen.
+-                entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
+-            }
+-
+-            // tick loading inbox, which loads the created entities to the world
+-            // (if present)
+-            entityManager.tick();
+-            // check if our entities are loaded
+-            return entityManager.areEntitiesLoaded(pair);
+-        };
+-
+-        // now we wait until the entities are loaded,
+-        // the converting from NBT to entity object is done on the main Thread which is why we wait
+-        while (!supplier.getAsBoolean()) {
+-            if (mailbox.size() != 0) {
+-                mailbox.run();
+-            } else {
+-                Thread.yield();
+-                java.util.concurrent.locks.LockSupport.parkNanos("waiting for entity loading", 100000L);
+-            }
+-        }
+-
+-        return entityManager.getEntities(new ChunkPos(chunkX, chunkZ)).stream()
+-            .map(net.minecraft.world.entity.Entity::getBukkitEntity)
+-            .filter(java.util.Objects::nonNull).toArray(org.bukkit.entity.Entity[]::new);
++        return world.getChunkEntities(chunkX, chunkZ); // Paper - rewrite chunk system
+     }
+ 
+     public static java.util.Collection<org.bukkit.plugin.Plugin> getPluginChunkTickets(net.minecraft.server.level.ServerLevel world,
+                                                                                        int x, int z) {
+-        net.minecraft.server.level.DistanceManager chunkDistanceManager = world.getChunkSource().chunkMap.distanceManager;
+-        net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z));
+-
+-        if (tickets == null) {
+-            return java.util.Collections.emptyList();
+-        }
+-
+-        com.google.common.collect.ImmutableList.Builder<org.bukkit.plugin.Plugin> ret = com.google.common.collect.ImmutableList.builder();
+-        for (net.minecraft.server.level.Ticket<?> ticket : tickets) {
+-            if (ticket.getType() == net.minecraft.server.level.TicketType.PLUGIN_TICKET) {
+-                ret.add((org.bukkit.plugin.Plugin) ticket.key);
+-            }
+-        }
+-
+-        return ret.build();
++        return world.moonrise$getChunkTaskScheduler().chunkHolderManager.getPluginChunkTickets(x, z); // Paper - rewrite chunk system
+     }
+ 
+     public static Map<org.bukkit.plugin.Plugin, java.util.Collection<org.bukkit.Chunk>> getPluginChunkTickets(net.minecraft.server.level.ServerLevel world) {
+         Map<org.bukkit.plugin.Plugin, com.google.common.collect.ImmutableList.Builder<Chunk>> ret = new HashMap<>();
+         net.minecraft.server.level.DistanceManager chunkDistanceManager = world.getChunkSource().chunkMap.distanceManager;
+ 
+-        for (it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry<net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) {
++        for (it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry<net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>>> chunkTickets : chunkDistanceManager.moonrise$getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { // Paper - rewrite chunk system
+             long chunkKey = chunkTickets.getLongKey();
+             net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>> tickets = chunkTickets.getValue();
+ 
+@@ -183,15 +129,15 @@ public final class FeatureHooks {
+     }
+ 
+     public static int getViewDistance(net.minecraft.server.level.ServerLevel world) {
+-        return world.getChunkSource().chunkMap.serverViewDistance;
++        return world.moonrise$getPlayerChunkLoader().getAPIViewDistance(); // Paper - rewrite chunk system
+     }
+ 
+     public static int getSimulationDistance(net.minecraft.server.level.ServerLevel world) {
+-        return world.getChunkSource().chunkMap.getDistanceManager().simulationDistance;
++        return world.moonrise$getPlayerChunkLoader().getAPITickDistance(); // Paper - rewrite chunk system
+     }
+ 
+     public static int getSendViewDistance(net.minecraft.server.level.ServerLevel world) {
+-        return getViewDistance(world);
++        return world.moonrise$getPlayerChunkLoader().getAPISendViewDistance(); // Paper - rewrite chunk system
+     }
+ 
+     public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
+@@ -209,31 +155,31 @@ public final class FeatureHooks {
+     }
+ 
+     public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
+-        throw new UnsupportedOperationException("Not implemented yet");
++        world.chunkSource.setSendViewDistance(distance); // Paper - rewrite chunk system
+     }
+ 
+     public static void tickEntityManager(net.minecraft.server.level.ServerLevel world) {
+-        world.entityManager.tick();
++        // Paper - rewrite chunk system
+     }
+ 
+     public static void closeEntityManager(net.minecraft.server.level.ServerLevel world, boolean save) {
+-        world.entityManager.close(save);
++        // Paper - rewrite chunk system
+     }
+ 
+     public static java.util.concurrent.Executor getWorldgenExecutor() {
+-        return net.minecraft.Util.backgroundExecutor();
++        return Runnable::run; // Paper - rewrite chunk system
+     }
+ 
+     public static void setViewDistance(ServerPlayer player, int distance) {
+-        throw new UnsupportedOperationException("Not implemented yet");
++        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setLoadViewDistance(distance == -1 ? distance : distance + 1); // Paper - rewrite chunk system
+     }
+ 
+     public static void setSimulationDistance(ServerPlayer player, int distance) {
+-        throw new UnsupportedOperationException("Not implemented yet");
++        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setTickViewDistance(distance); // Paper - rewrite chunk system
+     }
+ 
+     public static void setSendViewDistance(ServerPlayer player, int distance) {
+-        throw new UnsupportedOperationException("Not implemented yet");
++        ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setSendViewDistance(distance); // Paper - rewrite chunk system
+     }
+ 
+ }
 diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc
diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
index 5c3b4d6856..f9337524bb 100644
--- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
+++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch
@@ -1,6 +1,6 @@
 --- /dev/null
 +++ b/io/papermc/paper/FeatureHooks.java
-@@ -1,0 +_,84 @@
+@@ -1,0 +_,233 @@
 +package io.papermc.paper;
 +
 +import io.papermc.paper.command.PaperSubcommand;
@@ -79,9 +79,158 @@
 +        return true; // ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(spider.level().getWorldBorder(), spider.getBoundingBox().inflate(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON))
 +    }
 +
-+    public static void dumpTickingInfo() {
++    public static void dumpAllChunkLoadInfo(net.minecraft.server.MinecraftServer server, boolean isLongTimeout) {
 +    }
 +
 +    private static void dumpEntity(final Entity entity) {
 +    }
++
++    public static org.bukkit.entity.Entity[] getChunkEntities(net.minecraft.server.level.ServerLevel world, int chunkX, int chunkZ) {
++        world.getChunk(chunkX, chunkZ); // ensure full loaded
++
++        net.minecraft.world.level.entity.PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = world.entityManager;
++        long pair = ChunkPos.asLong(chunkX, chunkZ);
++
++        if (entityManager.areEntitiesLoaded(pair)) {
++            return entityManager.getEntities(new ChunkPos(chunkX, chunkZ)).stream()
++                .map(net.minecraft.world.entity.Entity::getBukkitEntity)
++                .filter(java.util.Objects::nonNull).toArray(org.bukkit.entity.Entity[]::new);
++        }
++
++        entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
++
++        // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
++        net.minecraft.util.thread.ConsecutiveExecutor mailbox = ((net.minecraft.world.level.chunk.storage.EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
++        java.util.function.BooleanSupplier supplier = () -> {
++            // only execute inbox if our entities are not present
++            if (entityManager.areEntitiesLoaded(pair)) {
++                return true;
++            }
++
++            if (!entityManager.isPending(pair)) {
++                // Our entities got unloaded, this should normally not happen.
++                entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
++            }
++
++            // tick loading inbox, which loads the created entities to the world
++            // (if present)
++            entityManager.tick();
++            // check if our entities are loaded
++            return entityManager.areEntitiesLoaded(pair);
++        };
++
++        // now we wait until the entities are loaded,
++        // the converting from NBT to entity object is done on the main Thread which is why we wait
++        while (!supplier.getAsBoolean()) {
++            if (mailbox.size() != 0) {
++                mailbox.run();
++            } else {
++                Thread.yield();
++                java.util.concurrent.locks.LockSupport.parkNanos("waiting for entity loading", 100000L);
++            }
++        }
++
++        return entityManager.getEntities(new ChunkPos(chunkX, chunkZ)).stream()
++            .map(net.minecraft.world.entity.Entity::getBukkitEntity)
++            .filter(java.util.Objects::nonNull).toArray(org.bukkit.entity.Entity[]::new);
++    }
++
++    public static java.util.Collection<org.bukkit.plugin.Plugin> getPluginChunkTickets(net.minecraft.server.level.ServerLevel world,
++                                                                                       int x, int z) {
++        net.minecraft.server.level.DistanceManager chunkDistanceManager = world.getChunkSource().chunkMap.distanceManager;
++        net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z));
++
++        if (tickets == null) {
++            return java.util.Collections.emptyList();
++        }
++
++        com.google.common.collect.ImmutableList.Builder<org.bukkit.plugin.Plugin> ret = com.google.common.collect.ImmutableList.builder();
++        for (net.minecraft.server.level.Ticket<?> ticket : tickets) {
++            if (ticket.getType() == net.minecraft.server.level.TicketType.PLUGIN_TICKET) {
++                ret.add((org.bukkit.plugin.Plugin) ticket.key);
++            }
++        }
++
++        return ret.build();
++    }
++
++    public static Map<org.bukkit.plugin.Plugin, java.util.Collection<org.bukkit.Chunk>> getPluginChunkTickets(net.minecraft.server.level.ServerLevel world) {
++        Map<org.bukkit.plugin.Plugin, com.google.common.collect.ImmutableList.Builder<Chunk>> ret = new HashMap<>();
++        net.minecraft.server.level.DistanceManager chunkDistanceManager = world.getChunkSource().chunkMap.distanceManager;
++
++        for (it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry<net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) {
++            long chunkKey = chunkTickets.getLongKey();
++            net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>> tickets = chunkTickets.getValue();
++
++            org.bukkit.Chunk chunk = null;
++            for (net.minecraft.server.level.Ticket<?> ticket : tickets) {
++                if (ticket.getType() != net.minecraft.server.level.TicketType.PLUGIN_TICKET) {
++                    continue;
++                }
++
++                if (chunk == null) {
++                    chunk = world.getWorld().getChunkAt(ChunkPos.getX(chunkKey), ChunkPos.getZ(chunkKey));
++                }
++
++                ret.computeIfAbsent((org.bukkit.plugin.Plugin) ticket.key, (key) -> com.google.common.collect.ImmutableList.builder()).add(chunk);
++            }
++        }
++
++        return ret.entrySet().stream().collect(com.google.common.collect.ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build()));
++    }
++
++    public static int getViewDistance(net.minecraft.server.level.ServerLevel world) {
++        return world.getChunkSource().chunkMap.serverViewDistance;
++    }
++
++    public static int getSimulationDistance(net.minecraft.server.level.ServerLevel world) {
++        return world.getChunkSource().chunkMap.getDistanceManager().simulationDistance;
++    }
++
++    public static int getSendViewDistance(net.minecraft.server.level.ServerLevel world) {
++        return getViewDistance(world);
++    }
++
++    public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
++        if (distance < 2 || distance > 32) {
++            throw new IllegalArgumentException("View distance " + distance + " is out of range of [2, 32]");
++        }
++        world.chunkSource.chunkMap.setServerViewDistance(distance);
++    }
++
++    public static void setSimulationDistance(net.minecraft.server.level.ServerLevel world, int distance) {
++        if (distance < 2 || distance > 32) {
++            throw new IllegalArgumentException("Simulation distance " + distance + " is out of range of [2, 32]");
++        }
++        world.chunkSource.chunkMap.distanceManager.updateSimulationDistance(distance);
++    }
++
++    public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
++        throw new UnsupportedOperationException("Not implemented yet");
++    }
++
++    public static void tickEntityManager(net.minecraft.server.level.ServerLevel world) {
++        world.entityManager.tick();
++    }
++
++    public static void closeEntityManager(net.minecraft.server.level.ServerLevel world, boolean save) {
++        world.entityManager.close(save);
++    }
++
++    public static java.util.concurrent.Executor getWorldgenExecutor() {
++        return net.minecraft.Util.backgroundExecutor();
++    }
++
++    public static void setViewDistance(ServerPlayer player, int distance) {
++        throw new UnsupportedOperationException("Not implemented yet");
++    }
++
++    public static void setSimulationDistance(ServerPlayer player, int distance) {
++        throw new UnsupportedOperationException("Not implemented yet");
++    }
++
++    public static void setSendViewDistance(ServerPlayer player, int distance) {
++        throw new UnsupportedOperationException("Not implemented yet");
++    }
++
 +}
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index f3ab07e44e..de8b9048c8 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -83,6 +83,12 @@ public class CraftChunk implements Chunk {
     }
 
     public ChunkAccess getHandle(ChunkStatus chunkStatus) {
+        // Paper start - chunk system
+        net.minecraft.world.level.chunk.LevelChunk full = this.worldServer.getChunkIfLoaded(this.x, this.z);
+        if (full != null) {
+            return full;
+        }
+        // Paper end - chunk system
         ChunkAccess chunkAccess = this.worldServer.getChunk(this.x, this.z, chunkStatus);
 
         // SPIGOT-7332: Get unwrapped extension
@@ -117,60 +123,12 @@ public class CraftChunk implements Chunk {
 
     @Override
     public boolean isEntitiesLoaded() {
-        return this.getCraftWorld().getHandle().entityManager.areEntitiesLoaded(ChunkPos.asLong(this.x, this.z));
+        return this.getCraftWorld().getHandle().areEntitiesLoaded(ChunkPos.asLong(this.x, this.z)); // Paper - chunk system
     }
 
     @Override
     public Entity[] getEntities() {
-        if (!this.isLoaded()) {
-            this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick
-        }
-
-        PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = this.getCraftWorld().getHandle().entityManager;
-        long pair = ChunkPos.asLong(this.x, this.z);
-
-        if (entityManager.areEntitiesLoaded(pair)) {
-            return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
-                    .map(net.minecraft.world.entity.Entity::getBukkitEntity)
-                    .filter(Objects::nonNull).toArray(Entity[]::new);
-        }
-
-        entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
-
-        // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
-        ConsecutiveExecutor mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
-        BooleanSupplier supplier = () -> {
-            // only execute inbox if our entities are not present
-            if (entityManager.areEntitiesLoaded(pair)) {
-                return true;
-            }
-
-            if (!entityManager.isPending(pair)) {
-                // Our entities got unloaded, this should normally not happen.
-                entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
-            }
-
-            // tick loading inbox, which loads the created entities to the world
-            // (if present)
-            entityManager.tick();
-            // check if our entities are loaded
-            return entityManager.areEntitiesLoaded(pair);
-        };
-
-        // now we wait until the entities are loaded,
-        // the converting from NBT to entity object is done on the main Thread which is why we wait
-        while (!supplier.getAsBoolean()) {
-            if (mailbox.size() != 0) {
-                mailbox.run();
-            } else {
-                Thread.yield();
-                LockSupport.parkNanos("waiting for entity loading", 100000L);
-            }
-        }
-
-        return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
-                .map(net.minecraft.world.entity.Entity::getBukkitEntity)
-                .filter(Objects::nonNull).toArray(Entity[]::new);
+        return FeatureHooks.getChunkEntities(this.worldServer, this.x, this.z); // Paper - chunk system
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index d3ad9f5097..104caaf260 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1448,7 +1448,7 @@ public final class CraftServer implements Server {
         // Paper - Put world into worldlist before initing the world; move up
 
         this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal);
-        internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
+        io.papermc.paper.FeatureHooks.tickEntityManager(internal); // SPIGOT-6526: Load pending entities so they are available to the API // Paper - chunk system
 
         this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
         return internal.getWorld();
@@ -1493,7 +1493,7 @@ public final class CraftServer implements Server {
             }
 
             handle.getChunkSource().close(save);
-            handle.entityManager.close(save); // SPIGOT-6722: close entityManager
+            io.papermc.paper.FeatureHooks.closeEntityManager(handle, save); // SPIGOT-6722: close entityManager // Paper - chunk system
             handle.levelStorageAccess.close();
         } catch (Exception ex) {
             this.getLogger().log(Level.SEVERE, null, ex);
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 9649f41a95..7ebe1d8090 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -502,14 +502,17 @@ public class CraftWorld extends CraftRegionAccessor implements World {
         ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
         if (playerChunk == null) return false;
 
-        playerChunk.getTickingChunkFuture().thenAccept(either -> {
-            either.ifSuccess(chunk -> {
+        // Paper start - chunk system
+        net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getChunkToSend();
+        if (chunk == null) {
+            return false;
+        }
+        // Paper end - chunk system
                 List<ServerPlayer> playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false);
-                if (playersInRange.isEmpty()) return;
+                if (playersInRange.isEmpty()) return true; // Paper - chunk system
 
                 FeatureHooks.sendChunkRefreshPackets(playersInRange, chunk);
-            });
-        });
+        // Paper - chunk system
 
         return true;
     }
@@ -611,47 +614,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public Collection<Plugin> getPluginChunkTickets(int x, int z) {
-        DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
-        SortedArraySet<Ticket<?>> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z));
-
-        if (tickets == null) {
-            return Collections.emptyList();
-        }
-
-        ImmutableList.Builder<Plugin> ret = ImmutableList.builder();
-        for (Ticket<?> ticket : tickets) {
-            if (ticket.getType() == TicketType.PLUGIN_TICKET) {
-                ret.add((Plugin) ticket.key);
-            }
-        }
-
-        return ret.build();
+        return FeatureHooks.getPluginChunkTickets(this.world, x, z); // Paper - chunk system
     }
 
     @Override
     public Map<Plugin, Collection<Chunk>> getPluginChunkTickets() {
-        Map<Plugin, ImmutableList.Builder<Chunk>> ret = new HashMap<>();
-        DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager;
-
-        for (Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) {
-            long chunkKey = chunkTickets.getLongKey();
-            SortedArraySet<Ticket<?>> tickets = chunkTickets.getValue();
-
-            Chunk chunk = null;
-            for (Ticket<?> ticket : tickets) {
-                if (ticket.getType() != TicketType.PLUGIN_TICKET) {
-                    continue;
-                }
-
-                if (chunk == null) {
-                    chunk = this.getChunkAt(ChunkPos.getX(chunkKey), ChunkPos.getZ(chunkKey));
-                }
-
-                ret.computeIfAbsent((Plugin) ticket.key, (key) -> ImmutableList.builder()).add(chunk);
-            }
-        }
-
-        return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build()));
+        return FeatureHooks.getPluginChunkTickets(this.world); // Paper - chunk system
     }
 
     @NotNull
@@ -1336,12 +1304,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public int getViewDistance() {
-        return this.world.getChunkSource().chunkMap.serverViewDistance;
+        return FeatureHooks.getViewDistance(this.world); // Paper - chunk system
     }
 
     @Override
     public int getSimulationDistance() {
-        return this.world.getChunkSource().chunkMap.getDistanceManager().simulationDistance;
+        return FeatureHooks.getSimulationDistance(this.world); // Paper - chunk system
     }
 
     public BlockMetadataStore getBlockMetadata() {
@@ -2472,25 +2440,22 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public void setViewDistance(final int viewDistance) {
-        if (viewDistance < 2 || viewDistance > 32) {
-            throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
-        }
-        this.getHandle().chunkSource.chunkMap.setServerViewDistance(viewDistance);
+        FeatureHooks.setViewDistance(this.world, viewDistance); // Paper - chunk system
     }
 
     @Override
     public void setSimulationDistance(final int simulationDistance) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        FeatureHooks.setSimulationDistance(this.world, simulationDistance); // Paper - chunk system
     }
 
     @Override
     public int getSendViewDistance() {
-        return this.getViewDistance();
+        return FeatureHooks.getSendViewDistance(this.world); // Paper - chunk system
     }
 
     @Override
     public void setSendViewDistance(final int viewDistance) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        FeatureHooks.setSendViewDistance(this.world, viewDistance); // Paper - chunk system
     }
 
     // Paper start - implement pointers
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 039e17ad5d..18100c2c7c 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -3527,7 +3527,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public void setViewDistance(final int viewDistance) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        FeatureHooks.setViewDistance(this.getHandle(), viewDistance); // Paper - chunk system
     }
 
     @Override
@@ -3537,7 +3537,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public void setSimulationDistance(final int simulationDistance) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        FeatureHooks.setSimulationDistance(this.getHandle(), simulationDistance); // Paper - chunk system
     }
 
     @Override
@@ -3547,7 +3547,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
     @Override
     public void setSendViewDistance(final int viewDistance) {
-        throw new UnsupportedOperationException("Not implemented yet");
+        FeatureHooks.setSendViewDistance(this.getHandle(), viewDistance); // Paper - chunk system
     }
 
     // Paper start - entity effect API
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/paper-server/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
index 39377ba073..6bf553bd44 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator {
             return ichunkaccess1;
         };
 
-        return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), net.minecraft.Util.backgroundExecutor()) : future.thenApply(function);
+        return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), io.papermc.paper.FeatureHooks.getWorldgenExecutor()) : future.thenApply(function); // Paper - chunk system
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
index 86fba4d18a..50f01faa88 100644
--- a/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/paper-server/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -7,7 +7,7 @@ public class AsyncCatcher {
     public static boolean enabled = true;
 
     public static void catchOp(String reason) {
-        if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
+        if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { // Paper - chunk system
             MinecraftServer.LOGGER.error("Thread {} failed main thread check: {}", Thread.currentThread().getName(), reason, new Throwable()); // Paper
             throw new IllegalStateException("Asynchronous " + reason + "!");
         }
diff --git a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
index 0c67e3297e..a9339f59f8 100644
--- a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java
@@ -110,7 +110,7 @@ public class WatchdogThread extends Thread {
                 // Paper end - Different message for short timeout
                 logger.log(Level.SEVERE, "------------------------------");
                 logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):"); // Paper
-                FeatureHooks.dumpTickingInfo(); // Paper - log detailed tick information
+                FeatureHooks.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - log detailed tick information
                 WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger);
                 logger.log(Level.SEVERE, "------------------------------");
 

From 86378c66b3d71cc91722d9464260a991d6ab1142 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 20 Dec 2024 11:06:19 -0800
Subject: [PATCH 262/285] Fix compile errors in Moonrise patch

---
 .../0021-Moonrise-optimisation-patches.patch  | 83 ++++++++++---------
 1 file changed, 43 insertions(+), 40 deletions(-)

diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index fdbf5905fa..01488729ee 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -23521,7 +23521,7 @@ index 47c62090b421ebea1253ee3f1c896ed84119cea6..e738405e5112584e02e01df2d5ede267
                  thread1 -> {
                      DedicatedServer dedicatedServer1 = new DedicatedServer(
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 5649482a8b85056bc009b868e19ca11f21d59fbf..0c35921acebd88f3a9a37676e47e7482dfea6d9c 100644
+index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc8040c2bb 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -23677,7 +23677,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..0c35921acebd88f3a9a37676e47e7482
 +        LOGGER.info("Waiting for I/O tasks to complete...");
 +        ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.flush((MinecraftServer)(Object)this);
 +        LOGGER.info("All I/O tasks to complete");
-+        if ((Object)this instanceof DedicatedServer) {
++        if ((Object)this instanceof net.minecraft.server.dedicated.DedicatedServer) {
 +            ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
 +        }
 +        // Paper end - rewrite chunk system
@@ -23720,7 +23720,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..0c35921acebd88f3a9a37676e47e7482
      // CraftBukkit start
      public boolean isDebugging() {
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 72409db938babd2da64a20746911cb8d45452d7f..fbc9b4d99bd1913a243a8d0424eb6f165e535747 100644
+index 72409db938babd2da64a20746911cb8d45452d7f..55d3f79af2e683b983d4d3f731bb9649dfe76f59 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
 @@ -433,7 +433,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -23736,7 +23736,7 @@ index 72409db938babd2da64a20746911cb8d45452d7f..fbc9b4d99bd1913a243a8d0424eb6f16
 +            Runnable run = () -> {
 +                LOGGER.info("Async debug chunks executing");
 +                ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(this, false);
-+                CommandSender sender = MinecraftServer.getServer().console;
++                org.bukkit.command.CommandSender sender = MinecraftServer.getServer().console;
 +                java.io.File file = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getChunkDebugFile();
 +                sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN));
 +                try {
@@ -24239,7 +24239,7 @@ index e823b8aac00158892538083bc877ccf99895909a..7d871318065f19540748363809de8265
      private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
      public static final int RADIUS_AROUND_FULL_CHUNK = FULL_CHUNK_STEP.accumulatedDependencies().getRadius();
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9c2224276 100644
+index ad665c7535c615d2b03a3e7864be435f933235dd..3dff97f13586be3b52bbe786852c185f6753a019 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -96,7 +96,7 @@ import net.minecraft.world.level.storage.LevelStorageSource;
@@ -24367,7 +24367,7 @@ index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9
      protected ChunkHolder getUpdatingChunkIfPresent(long chunkPos) {
 -        return this.updatingChunkMap.get(chunkPos);
 +        // Paper start - rewrite chunk system
-+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
++        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
 +        return holder == null ? null : holder.vanillaChunkHolder;
 +        // Paper end - rewrite chunk system
      }
@@ -24376,7 +24376,7 @@ index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9
      public ChunkHolder getVisibleChunkIfPresent(long chunkPos) {
 -        return this.visibleChunkMap.get(chunkPos);
 +        // Paper start - rewrite chunk system
-+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(pos);
++        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
 +        return holder == null ? null : holder.vanillaChunkHolder;
 +        // Paper end - rewrite chunk system
      }
@@ -25121,7 +25121,7 @@ index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9
          boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong();
          if (flag2 || flag != flag1) {
              this.updatePlayerPos(player);
-+            ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, sectionposition, sectionposition1, flag, flag1); // Paper - chunk tick iteration optimisation
++            ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, lastSectionPos, sectionPos, flag, flag1); // Paper - chunk tick iteration optimisation
              if (!flag) {
                  this.distanceManager.removePlayer(lastSectionPos, player);
              }
@@ -25194,7 +25194,7 @@ index ad665c7535c615d2b03a3e7864be435f933235dd..9faf0ece0f074b8709b4e4fad0cab3e9
 +                    if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) {
 +                        throw new IllegalStateException("Entity is already tracked");
 +                    }
-+                    ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(playerchunkmap_entitytracker);
++                    ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(trackedEntity);
 +                    // Paper end - optimise entity tracker
                      trackedEntity.updatePlayers(this.level.players());
                      if (entity instanceof ServerPlayer serverPlayer) {
@@ -26188,7 +26188,7 @@ index cb66209c64b855dedf2e4e114a7716da13bc4587..da1366fdc4889d6a3befd43d81a19a81
      }
  }
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..6c9c7c3124d5990ea34368eb4578eac695abd658 100644
+index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..87d4291a3944f706a694536da6de0f28c548ab8d 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -26294,7 +26294,7 @@ index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..6c9c7c3124d5990ea34368eb4578eac6
 +        final ServerPlayer[] raw = players.getRawDataUnchecked();
 +        final int len = players.size();
 +
-+        Objects.checkFromIndexSize(0, len, raw.length);
++        java.util.Objects.checkFromIndexSize(0, len, raw.length);
 +        for (int i = 0; i < len; ++i) {
 +            if (chunkMap.playerIsCloseEnoughForSpawning(raw[i], chunkPos, 16384.0D)) { // Spigot (reducedRange = false)
 +                return true;
@@ -26704,7 +26704,7 @@ index 70f6d068b3f3665b282d9750310c883839120ab2..870b9efd445ddadb3725e88351555ad9
          if (!passengers.equals(this.lastPassengers)) {
              this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..ffb5bfdd76a92bac61c7c352fdded4200d13b3ae 100644
+index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82ef1934903 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -170,7 +170,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
@@ -27449,7 +27449,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..ffb5bfdd76a92bac61c7c352fdded420
      public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) {
 -        return this.entityManager.canPositionTick(chunkPos);
 +        // Paper start - rewrite chunk system
-+        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos));
++        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos));
 +        return chunkHolder != null && chunkHolder.isEntityTickingReady();
 +        // Paper end - rewrite chunk system
      }
@@ -28341,7 +28341,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index d5b0db3095a563dd101a1b12b150401a7dc59f8e..eb333032d8117d8dec4a15f3f2803166431633bc 100644
+index d5b0db3095a563dd101a1b12b150401a7dc59f8e..997158b88a2ac8fc3b40589e615f85fd8e42d146 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
@@ -28592,10 +28592,11 @@ index d5b0db3095a563dd101a1b12b150401a7dc59f8e..eb333032d8117d8dec4a15f3f2803166
  
      public Entity(EntityType<?> entityType, Level level) {
          this.type = entityType;
-@@ -1286,34 +1392,76 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1285,35 +1391,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+         return distance;
      }
  
-     private Vec3 collide(Vec3 vec) {
+-    private Vec3 collide(Vec3 vec) {
 -        AABB boundingBox = this.getBoundingBox();
 -        List<VoxelShape> entityCollisions = this.level().getEntityCollisions(this, boundingBox.expandTowards(vec));
 -        Vec3 vec3 = vec.lengthSqr() == 0.0 ? vec : collideBoundingBox(this, vec, boundingBox, this.level(), entityCollisions);
@@ -28609,7 +28610,8 @@ index d5b0db3095a563dd101a1b12b150401a7dc59f8e..eb333032d8117d8dec4a15f3f2803166
 -            if (!flag3) {
 -                aabb1 = aabb1.expandTowards(0.0, -1.0E-5F, 0.0);
 -            }
-+        // Paper start - optimise collisions
++    // Paper start - optimise collisions
++    private Vec3 collide(Vec3 movement) {
 +        final boolean xZero = movement.x == 0.0;
 +        final boolean yZero = movement.y == 0.0;
 +        final boolean zZero = movement.z == 0.0;
@@ -29081,7 +29083,7 @@ index d5b0db3095a563dd101a1b12b150401a7dc59f8e..eb333032d8117d8dec4a15f3f2803166
  
      @Override
 diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index 7d590dd06cc69c0925d22708425520c38e3cda25..c8590e0175dacd6d7efdb1830bd60c92325083f9 100644
+index 7d590dd06cc69c0925d22708425520c38e3cda25..5c5724f5e3ad640f55aecbc1d8f71d1f59ecdc62 100644
 --- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 +++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
 @@ -38,12 +38,137 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
@@ -29227,7 +29229,7 @@ index 7d590dd06cc69c0925d22708425520c38e3cda25..c8590e0175dacd6d7efdb1830bd60c92
              levelHeightAccessor
          );
          this.distanceTracker = new PoiManager.DistanceTracker();
-+        this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system
++        this.world = (net.minecraft.server.level.ServerLevel)levelHeightAccessor; // Paper - rewrite chunk system
      }
  
      public void add(BlockPos pos, Holder<PoiType> type) {
@@ -29254,18 +29256,19 @@ index 7d590dd06cc69c0925d22708425520c38e3cda25..c8590e0175dacd6d7efdb1830bd60c92
      }
  
      @Override
-     protected void setDirty(long sectionPos) {
+-    protected void setDirty(long sectionPos) {
 -        super.setDirty(sectionPos);
 -        this.distanceTracker.update(sectionPos, this.distanceTracker.getLevelFromSource(sectionPos), false);
++    public void setDirty(long sectionPos) { // Paper - public
 +        // Paper start - rewrite chunk system
-+        final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionX(pos);
-+        final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionZ(pos);
++        final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionX(sectionPos);
++        final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionZ(sectionPos);
 +        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager manager = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
 +        final ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk chunk = manager.getPoiChunkIfLoaded(chunkX, chunkZ, false);
 +        if (chunk != null) {
 +            chunk.setDirty(true);
 +        }
-+        this.updateDistanceTracking(pos);
++        this.updateDistanceTracking(sectionPos);
 +        // Paper end - rewrite chunk system
      }
  
@@ -29469,7 +29472,7 @@ index 300f3ed58109219d97846082941b860585f66fed..e81195df621159da67136f020fa7a6d3
  
      // Paper start - Affects Spawning API
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 0e4ab448755632696c4326f1df9f3855cd38a64d..72646473019424de969756ae1d0e9f789310889b 100644
+index 0e4ab448755632696c4326f1df9f3855cd38a64d..aff78499b73a98f7405aead0886c51e8ac262884 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -79,6 +79,7 @@ import net.minecraft.world.level.storage.LevelData;
@@ -29592,7 +29595,7 @@ index 0e4ab448755632696c4326f1df9f3855cd38a64d..72646473019424de969756ae1d0e9f78
 +
 +    @Override
 +    public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
-+        final ChunkSource chunkSource = this.getChunkSource();
++        final net.minecraft.world.level.chunk.ChunkSource chunkSource = this.getChunkSource();
 +
 +        for (int currZ = fromZ; currZ <= toZ; ++currZ) {
 +            for (int currX = fromX; currX <= toX; ++currX) {
@@ -29932,7 +29935,7 @@ index 0e4ab448755632696c4326f1df9f3855cd38a64d..72646473019424de969756ae1d0e9f78
 +        final int minChunkZ = minBlockZ >> 4;
 +        final int maxChunkZ = maxBlockZ >> 4;
 +
-+        final ChunkSource chunkSource = this.getChunkSource();
++        final net.minecraft.world.level.chunk.ChunkSource chunkSource = this.getChunkSource();
 +
 +        for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
 +            for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
@@ -31683,7 +31686,7 @@ index e7c0f4da8508fbca467326f475668d66454d7b77..41856c98d97e7eb0782f8e441b9a269a
      @Override
      public BlockEntity getBlockEntity(BlockPos pos) {
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index 96b0342ab7b922aa16d07b6c00542e6cb66c974a..aff0937ad7eb81d9e32f56aa337a4ec7551f0faa 100644
+index 96b0342ab7b922aa16d07b6c00542e6cb66c974a..c1ae7755e8d6fa8501d2210dab7605d993c55722 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
 @@ -52,7 +52,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
@@ -31763,10 +31766,10 @@ index 96b0342ab7b922aa16d07b6c00542e6cb66c974a..aff0937ad7eb81d9e32f56aa337a4ec7
          this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
          // CraftBukkit end
 +        // Paper start - rewrite chunk system
-+        this.starlight$setBlockNibbles(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)protoChunk).starlight$getBlockNibbles());
-+        this.starlight$setSkyNibbles(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)protoChunk).starlight$getSkyNibbles());
-+        this.starlight$setSkyEmptinessMap(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)protoChunk).starlight$getSkyEmptinessMap());
-+        this.starlight$setBlockEmptinessMap(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)protoChunk).starlight$getBlockEmptinessMap());
++        this.starlight$setBlockNibbles(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles());
++        this.starlight$setSkyNibbles(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getSkyNibbles());
++        this.starlight$setSkyEmptinessMap(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getSkyEmptinessMap());
++        this.starlight$setBlockEmptinessMap(((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockEmptinessMap());
 +        // Paper end - rewrite chunk system
      }
  
@@ -32841,7 +32844,7 @@ index 783a2d80f6197dd0af0dc81909f0353a8ea2ecf4..7da388ffab162c282cad0f297bb7304f
      }
  
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 51bf310423013d0ae9d3202d66e36a053a767197..2661a21703994a18c4a9a44fba0f6931fd37151e 100644
+index 51bf310423013d0ae9d3202d66e36a053a767197..e35bb5534e2fbd2e30154a15ff6d39baa121608f 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 +++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 @@ -14,7 +14,7 @@ import net.minecraft.nbt.StreamTagVisitor;
@@ -32849,7 +32852,7 @@ index 51bf310423013d0ae9d3202d66e36a053a767197..2661a21703994a18c4a9a44fba0f6931
  import net.minecraft.world.level.ChunkPos;
  
 -public final class RegionFileStorage implements AutoCloseable {
-+public final class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
++public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
      public static final String ANVIL_EXTENSION = ".mca";
      private static final int MAX_CACHE_SIZE = 256;
      public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
@@ -33290,7 +33293,7 @@ index 7dc1ffffd9d0fec54dbc254c154ee85ee750174d..778bd73a938c94ecb85ca0f8b686ff4e
  
      record PackedChunk<T>(Int2ObjectMap<T> sectionsByY, boolean versionChanged) {
 diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..e8aafbd4bd9eaba4aaa448333b37c30a8dd719bf 100644
+index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..70a9972252576e039ac126f6057a6ed66b80cdfc 100644
 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
 @@ -148,7 +148,7 @@ public record SerializableChunkData(
@@ -33298,7 +33301,7 @@ index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..e8aafbd4bd9eaba4aaa448333b37c30a
                  ? new UpgradeData(tag.getCompound("UpgradeData"), levelHeightAccessor)
                  : UpgradeData.EMPTY;
 -            boolean _boolean = tag.getBoolean("isLightOn");
-+            boolean _boolean = chunkstatus.isOrAfter(ChunkStatus.LIGHT) && (nbt.get("isLightOn") != null && nbt.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG) == ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION); // Paper - starlight
++            boolean _boolean = chunkStatus.isOrAfter(ChunkStatus.LIGHT) && (tag.get("isLightOn") != null && tag.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_VERSION_TAG) == ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.STARLIGHT_LIGHT_VERSION); // Paper - starlight
              BlendingData.Packed packed;
              if (tag.contains("blending_data", 10)) {
                  packed = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, tag.getCompound("blending_data")).resultOrPartial(LOGGER::error).orElse(null);
@@ -33309,11 +33312,11 @@ index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..e8aafbd4bd9eaba4aaa448333b37c30a
 -                list8.add(new SerializableChunkData.SectionData(_byte, levelChunkSection, dataLayer, dataLayer1));
 +                // Paper start - starlight
 +                SerializableChunkData.SectionData serializableChunkData = new SerializableChunkData.SectionData(_byte, levelChunkSection, dataLayer, dataLayer1);
-+                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.BLOCKLIGHT_STATE_TAG, Tag.TAG_ANY_NUMERIC)) {
++                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.BLOCKLIGHT_STATE_TAG, net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
 +                    ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)serializableChunkData).starlight$setBlockLightState(sectionData.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.BLOCKLIGHT_STATE_TAG));
 +                }
 +
-+                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.SKYLIGHT_STATE_TAG, Tag.TAG_ANY_NUMERIC)) {
++                if (sectionData.contains(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.SKYLIGHT_STATE_TAG, net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
 +                    ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)serializableChunkData).starlight$setSkyLightState(sectionData.getInt(ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.SKYLIGHT_STATE_TAG));
 +                }
 +                list8.add(serializableChunkData);
@@ -33395,7 +33398,7 @@ index cf6e2053d81f7b0f8c8e58b9c0fad3285ebc047d..e8aafbd4bd9eaba4aaa448333b37c30a
  
          if (chunkType == ChunkType.LEVELCHUNK) {
 -            return new ImposterProtoChunk((LevelChunk)chunkAccess, false);
-+            return this.loadStarlightLightData(new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight
++            return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight
          } else {
              ProtoChunk protoChunk1 = (ProtoChunk)chunkAccess;
  
@@ -33762,7 +33765,7 @@ index 06b54c0bec4031689d5c2da5cfea4ef28dbd16bc..f7dc4957b38878ddd3bfc7546be8a4e0
              map.computeInt(structure, (structure1, integer) -> integer == null ? 1 : integer + 1);
              return map;
 diff --git a/net/minecraft/world/level/lighting/LevelLightEngine.java b/net/minecraft/world/level/lighting/LevelLightEngine.java
-index ca23af013967b50420ebee178878ea79333de53b..83c3ec06be51f632b7c1b682cfa8dce73ff7e0c0 100644
+index ca23af013967b50420ebee178878ea79333de53b..d41b9266625ca6c5e32c5126f35a1f7733159cfc 100644
 --- a/net/minecraft/world/level/lighting/LevelLightEngine.java
 +++ b/net/minecraft/world/level/lighting/LevelLightEngine.java
 @@ -9,151 +9,111 @@ import net.minecraft.world.level.LightLayer;
@@ -33869,7 +33872,7 @@ index ca23af013967b50420ebee178878ea79333de53b..83c3ec06be51f632b7c1b682cfa8dce7
 -        if (this.skyEngine != null) {
 -            this.skyEngine.updateSectionStatus(pos, isEmpty);
 -        }
-+        this.lightEngine.sectionChange(pos, notReady); // Paper - rewrite chunk system
++        this.lightEngine.sectionChange(pos, isEmpty); // Paper - rewrite chunk system
      }
  
      @Override

From 2213ce5bbccbb8a466c394eff31091a2cabe2f1b Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Fri, 20 Dec 2024 21:22:58 +0100
Subject: [PATCH 263/285] fix enderpearl across portals

---
 .../0021-Moonrise-optimisation-patches.patch       |  5 +++--
 .../ServerGamePacketListenerImpl.java.patch        | 14 ++++++--------
 .../net/minecraft/world/entity/Entity.java.patch   |  6 ++++--
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index 01488729ee..491cf50e1c 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -22768,7 +22768,7 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +    private SaveUtil() {}
 +}
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index e40b83502c74d7dcae05d9d0c865affeee186893..c4ddf4b6fee8086481581fab88a807f63e211b5c 100644
+index 184e6c6fe2ba522d0ea0774604839320c4152371..b329eb069f5b3d4f33a94d2045cb8f250d2a5684 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -1,6 +1,8 @@
@@ -22946,6 +22946,7 @@ index e40b83502c74d7dcae05d9d0c865affeee186893..c4ddf4b6fee8086481581fab88a807f6
      }
  
  }
+\ No newline at end of file
 diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc
@@ -28341,7 +28342,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index d5b0db3095a563dd101a1b12b150401a7dc59f8e..997158b88a2ac8fc3b40589e615f85fd8e42d146 100644
+index 069d0d0ddeceb0de2300a3354fed218407d88938..3fd7f6bcdeff271a9843b2f2454f92d92069f539 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index fbea2e5964..565e6dcb4b 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -167,7 +167,7 @@
          this.player.setLastClientInput(packet.input());
      }
  
-@@ -390,25 +_,84 @@
+@@ -390,17 +_,29 @@
      public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) {
@@ -201,23 +201,21 @@
                  float f = Mth.wrapDegrees(packet.yRot());
                  float f1 = Mth.wrapDegrees(packet.xRot());
                  double d3 = d - this.vehicleFirstGoodX;
-                 double d4 = d1 - this.vehicleFirstGoodY;
+@@ -408,7 +_,53 @@
                  double d5 = d2 - this.vehicleFirstGoodZ;
                  double d6 = rootVehicle.getDeltaMovement().lengthSqr();
--                double d7 = d3 * d3 + d4 * d4 + d5 * d5;
+                 double d7 = d3 * d3 + d4 * d4 + d5 * d5;
 -                if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
-+                double d7 = d3 * d3 + d4 * d4 + d5 * d5; final double vehicleFirstGoodDistanceSquared = d7; // Paper - OBFHELPER
 +                // Paper start - fix large move vectors killing the server
 +                double currDeltaX = toX - x;
 +                double currDeltaY = toY - y;
 +                double currDeltaZ = toZ - z;
-+                double d10 = Math.max(vehicleFirstGoodDistanceSquared, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
++                d7 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
 +                double otherFieldX = d3 - this.vehicleLastGoodX;
 +                double otherFieldY = d4 - this.vehicleLastGoodY;
 +                double otherFieldZ = d5 - this.vehicleLastGoodZ;
-+                d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
++                d7 = Math.max(d7, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
 +                // Paper end - fix large move vectors killing the server
-+                //if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
 +
 +                // CraftBukkit start - handle custom speeds and skipped ticks
 +                this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
@@ -231,7 +229,7 @@
 +                    i = 1;
 +                }
 +
-+                if (d10 > 0) {
++                if (d7 > 0) {
 +                    this.allowedPlayerTicks -= 1;
 +                } else {
 +                    this.allowedPlayerTicks = 20;
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 6bd4be5cfe..6bbcc9ce75 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -1119,15 +1119,17 @@
      }
  
      protected boolean canAddPassenger(Entity passenger) {
-@@ -2295,7 +_,7 @@
+@@ -2295,8 +_,8 @@
                      TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this);
                      if (portalDestination != null) {
                          ServerLevel level = portalDestination.newLevel();
 -                        if (serverLevel.getServer().isLevelEnabled(level)
+-                            && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level))) {
 +                        if (this instanceof ServerPlayer // CraftBukkit - always call event for players
-                             && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level))) {
++                            || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit
                              this.teleport(portalDestination);
                          }
+                     }
 @@ -2377,7 +_,7 @@
      }
  

From b0cef6818d3836e6330d9ba11815aebc69045515 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 20 Dec 2024 12:43:02 -0800
Subject: [PATCH 264/285] Update Gradle wrapper and paperweight

---
 build.gradle.kts                         | 2 +-
 gradle/wrapper/gradle-wrapper.properties | 2 +-
 gradlew                                  | 3 +--
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 3ae1c411ae..23e8eafd5d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,7 +12,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.6" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.7" apply false
 }
 
 subprojects {
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e2847c8200..cea7a793a8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index f5feea6d6b..f3b75f3b0d 100755
--- a/gradlew
+++ b/gradlew
@@ -86,8 +86,7 @@ done
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
 # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
-' "$PWD" ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum

From c45286cb08881ab4d28a16b8ef9c6a4fd0984ce8 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 20 Dec 2024 23:02:39 +0100
Subject: [PATCH 265/285] Update remaining feature patches

---
 ...-data-to-disk-if-it-serializes-witho.patch |  90 +++--
 ...063-Entity-load-save-limit-per-chunk.patch |  55 +--
 ...culate-regionfile-header-if-it-is-co.patch | 321 +++++++++---------
 .../1067-Optimise-general-POI-access.patch    | 216 ++++++------
 4 files changed, 329 insertions(+), 353 deletions(-)

diff --git a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
index d336b76564..0561b403fb 100644
--- a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
+++ b/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
@@ -7,22 +7,23 @@ Subject: [PATCH] Only write chunk data to disk if it serializes without
 This ensures at least a valid version of the chunk exists
 on disk, even if outdated
 
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
+index 7da388ffab162c282cad0f297bb7304f3c2abbaf..ff4fc280409f680f3879a495e37cf1925b1a38f1 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -24,6 +24,7 @@ import org.slf4j.Logger;
  
-     }
-     // Paper end
+ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system
+     private static final Logger LOGGER = LogUtils.getLogger();
 +    public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails
-     private class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system
- 
-         private final ChunkPos pos;
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-             super.write(RegionFile.this.version.getId());
-             this.pos = chunkcoordintpair;
+     private static final int SECTOR_BYTES = 4096;
+     @VisibleForTesting
+     protected static final int SECTOR_INTS = 1024;
+@@ -455,6 +456,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+             this.pos = pos;
          }
+ 
 +        // Paper start - don't write garbage data to disk if writing serialization fails
 +        @Override
 +        public void write(final int b) {
@@ -40,23 +41,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            super.write(b, off, len);
 +        }
 +        // Paper end - don't write garbage data to disk if writing serialization fails
- 
++
+         @Override
          public void close() throws IOException {
-             ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos;
+             ByteBuffer byteBuffer = ByteBuffer.wrap(this.buf, 0, this.count);
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+index e35bb5534e2fbd2e30154a15ff6d39baa121608f..d263f78fa610ce6f6fb5a0f5e064e3d8335c2199 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -15,6 +15,7 @@ import net.minecraft.util.ExceptionCollector;
+ import net.minecraft.world.level.ChunkPos;
  
  public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system
- 
 +    private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
-+
      public static final String ANVIL_EXTENSION = ".mca";
      private static final int MAX_CACHE_SIZE = 256;
-     public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap();
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+     public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
+@@ -119,11 +120,24 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
          // (and, the regionfile parameter is unused for writing until the write call)
          final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile)regionFile).moonrise$startWrite(compound, pos);
  
@@ -81,39 +82,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
          return writeData;
      }
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
-             try {
-                 NbtIo.write(nbt, (DataOutput) dataoutputstream);
-                 regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
+@@ -326,9 +340,17 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+         if (chunkData == null) {
+             regionFile.clear(chunkPos);
+         } else {
+-            try (DataOutputStream chunkDataOutputStream = regionFile.getChunkDataOutputStream(chunkPos)) {
++            DataOutputStream chunkDataOutputStream = regionFile.getChunkDataOutputStream(chunkPos); // Paper - Only write if successful
++            try { // Paper - Only write if successful
+                 NbtIo.write(chunkData, chunkDataOutputStream);
+                 regionFile.setOversized(chunkPos.x, chunkPos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
 +                // Paper start - don't write garbage data to disk if writing serialization fails
-+                dataoutputstream.close(); // Only write if successful
++                chunkDataOutputStream.close();
 +            } catch (final RegionFileSizeException ex) {
-+                regionfile.clear(pos);
++                regionFile.clear(chunkPos);
 +                final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024);
-+                LOGGER.error("Chunk at (" + pos.x + "," + pos.z + ") in regionfile '" + regionfile.getPath().toString() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk.");
-+                return;
++                LOGGER.error("Chunk at (" + chunkPos.x + "," + chunkPos.z + ") in regionfile '" + regionFile.getPath().toString() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk.");
 +                // Paper end - don't write garbage data to disk if writing serialization fails
-             } catch (Throwable throwable) {
-                 if (dataoutputstream != null) {
-                     try {
--                        dataoutputstream.close();
-+                        //dataoutputstream.close(); // Paper - don't write garbage data to disk if writing serialization fails
-                     } catch (Throwable throwable1) {
-                         throwable.addSuppressed(throwable1);
-                     }
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
- 
-                 throw throwable;
              }
--
--            if (dataoutputstream != null) {
--                dataoutputstream.close();
--            }
-+            // Paper - don't write garbage data to disk if writing serialization fails; move into try block to only write if successfully serialized
          }
- 
      }
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+@@ -370,4 +392,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
      public RegionStorageInfo info() {
          return this.info;
      }
@@ -121,7 +109,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper start - don't write garbage data to disk if writing serialization fails
 +    public static final class RegionFileSizeException extends RuntimeException {
 +
-+        public RegionFileSizeException(String message) {
++        public RegionFileSizeException(final String message) {
 +            super(message);
 +        }
 +    }
diff --git a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch b/feature-patches/1063-Entity-load-save-limit-per-chunk.patch
index 78788ef3c8..a931c2cffe 100644
--- a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch
+++ b/feature-patches/1063-Entity-load-save-limit-per-chunk.patch
@@ -8,11 +8,12 @@ to a chunk. The default values of -1 disable the limit. Although
 defaults are only included for certain entites, this allows setting
 limits for any entity type.
 
-diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
-+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
-@@ -0,0 +0,0 @@ public final class ChunkEntitySlices {
+
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+index 7aea4e343581b977d11af90f9f65eac3532eade1..d21ce54ebb5724c04eadf56a2cde701d5eeb5db2 100644
+--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+@@ -104,7 +104,18 @@ public final class ChunkEntitySlices {
          }
  
          final ListTag entitiesTag = new ListTag();
@@ -31,21 +32,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
              CompoundTag compoundTag = new CompoundTag();
              if (entity.save(compoundTag)) {
                  entitiesTag.add(compoundTag);
-diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/EntityType.java
-+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
-@@ -0,0 +0,0 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
-         final Spliterator<? extends Tag> spliterator = entityNbtList.spliterator();
- 
+diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java
+index 73cdfa5a315ed259b38dfa946a0b7955d9ac9f50..49201d6664656ebe34c84c1c84b5ea4878729062 100644
+--- a/net/minecraft/world/entity/EntityType.java
++++ b/net/minecraft/world/entity/EntityType.java
+@@ -1420,9 +1420,20 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
+     public static Stream<Entity> loadEntitiesRecursive(final List<? extends Tag> entityTags, final Level level, final EntitySpawnReason spawnReason) {
+         final Spliterator<? extends Tag> spliterator = entityTags.spliterator();
          return StreamSupport.stream(new Spliterator<Entity>() {
 +            final java.util.Map<EntityType<?>, Integer> loadedEntityCounts = new java.util.HashMap<>(); // Paper - Entity load/save limit per chunk
+             @Override
              public boolean tryAdvance(Consumer<? super Entity> consumer) {
-                 return spliterator.tryAdvance((nbtbase) -> {
-                     EntityType.loadEntityRecursive((CompoundTag) nbtbase, world, reason, (entity) -> {
+                 return spliterator.tryAdvance(tag -> EntityType.loadEntityRecursive((CompoundTag)tag, level, spawnReason, entity -> {
 +                        // Paper start - Entity load/save limit per chunk
 +                        final EntityType<?> entityType = entity.getType();
-+                        final int saveLimit = world.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
++                        final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
 +                        if (saveLimit > -1) {
 +                            if (this.loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) {
 +                                return null;
@@ -53,19 +54,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                            this.loadedEntityCounts.merge(entityType, 1, Integer::sum);
 +                        }
 +                        // Paper end - Entity load/save limit per chunk
-                         consumer.accept(entity);
-                         return entity;
-                     });
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-@@ -0,0 +0,0 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
+                     consumer.accept(entity);
+                     return entity;
+                 }));
+diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java
+index da05fb780c55381a7a08ced51d01764a645740b2..2856206eafddfcbcc1b65408deda40357f43a6f8 100644
+--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java
++++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java
+@@ -93,7 +93,18 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
              }
          } else {
              ListTag listTag = new ListTag();
 +            final java.util.Map<net.minecraft.world.entity.EntityType<?>, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper - Entity load/save limit per chunk
-             dataList.getEntities().forEach(entity -> {
+             entities.getEntities().forEach(entity -> {
 +                // Paper start - Entity load/save limit per chunk
 +                final EntityType<?> entityType = entity.getType();
 +                final int saveLimit = this.level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
@@ -76,6 +77,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    savedEntityCounts.merge(entityType, 1, Integer::sum);
 +                }
 +                // Paper end - Entity load/save limit per chunk
-                 CompoundTag compoundTagx = new CompoundTag();
-                 if (entity.save(compoundTagx)) {
-                     listTag.add(compoundTagx);
+                 CompoundTag compoundTag1 = new CompoundTag();
+                 if (entity.save(compoundTag1)) {
+                     listTag.add(compoundTag1);
diff --git a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
index 0aa11765a3..83577de6ed 100644
--- a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
+++ b/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
@@ -9,11 +9,12 @@ we instead drop the current regionfile header and recalculate -
 hoping that at least then we don't swap chunks, and maybe recover
 them all.
 
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
-@@ -0,0 +0,0 @@ import java.util.BitSet;
+
+diff --git a/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
+index 64a718c98f799c62a5bb28e1e8e5f66cc96c915d..666f2e967c99f78422c83fb20e1a3bf3efa7845e 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionBitmap.java
++++ b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
+@@ -9,6 +9,27 @@ import java.util.BitSet;
  public class RegionBitmap {
      private final BitSet used = new BitSet();
  
@@ -38,17 +39,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    // Paper end - Attempt to recalculate regionfile header if it is corrupt
 +
-     public void force(int start, int size) {
-         this.used.set(start, start + size);
+     public void force(int sectorOffset, int sectorCount) {
+         this.used.set(sectorOffset, sectorOffset + sectorCount);
      }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-     private final IntBuffer timestamps;
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
+index ff4fc280409f680f3879a495e37cf1925b1a38f1..a4621c96fd456c5cdd1b6847931806e677b26b30 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -46,6 +46,355 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
      @VisibleForTesting
-     protected final RegionBitmap usedSectors;
+     protected final RegionBitmap usedSectors = new RegionBitmap();
+ 
 +    // Paper start - Attempt to recalculate regionfile header if it is corrupt
 +    private static long roundToSectors(long bytes) {
 +        long sectors = bytes >>> 12; // 4096 = 2^12
@@ -57,9 +58,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return sectors + (sign >>> 63);
 +    }
 +
-+    private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag();
++    private static final net.minecraft.nbt.CompoundTag OVERSIZED_COMPOUND = new net.minecraft.nbt.CompoundTag();
 +
-+    private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
++    private @Nullable net.minecraft.nbt.CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
 +        try {
 +            if (chunkDataLength < 0) {
 +                return null;
@@ -91,7 +92,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +            InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
 +
-+            return NbtIo.read(new DataInputStream(input));
++            return net.minecraft.nbt.NbtIo.read(new DataInputStream(input));
 +        } catch (Exception ex) {
 +            return null;
 +        }
@@ -142,7 +143,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // try to backup file so maybe it could be sent to us for further investigation
 +
 +            this.backupRegionFile();
-+            CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
++            net.minecraft.nbt.CompoundTag[] compounds = new net.minecraft.nbt.CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
 +            int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
 +            int[] sectorOffsets = new int[32 * 32]; // in sectors
 +            boolean[] hasAikarOversized = new boolean[32 * 32];
@@ -154,7 +155,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +            for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
 +                int chunkDataLength = this.getLength(i);
-+                CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
++                net.minecraft.nbt.CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
 +                if (compound == null || compound == OVERSIZED_COMPOUND) {
 +                    continue;
 +                }
@@ -166,7 +167,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +                int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5);
 +
-+                CompoundTag otherCompound = compounds[location];
++                net.minecraft.nbt.CompoundTag otherCompound = compounds[location];
 +
 +                if (otherCompound != null && SerializableChunkData.getLastWorldSaveTime(otherCompound) > SerializableChunkData.getLastWorldSaveTime(compound)) {
 +                    continue; // don't overwrite newer data.
@@ -177,7 +178,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                boolean isAikarOversized = false;
 +                if (Files.exists(aikarOversizedFile)) {
 +                    try {
-+                        CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
++                        net.minecraft.nbt.CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
 +                        if (SerializableChunkData.getLastWorldSaveTime(compound) == SerializableChunkData.getLastWorldSaveTime(aikarOversizedCompound)) {
 +                            // best we got for an id. hope it's good enough
 +                            isAikarOversized = true;
@@ -236,14 +237,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                        continue;
 +                    }
 +
-+                    CompoundTag compound = null;
++                    net.minecraft.nbt.CompoundTag compound = null;
 +
 +                    // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
 +                    RegionFileVersion compression = null;
 +                    for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
 +                        try {
 +                            DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
-+                            compound = NbtIo.read((java.io.DataInput)in);
++                            compound = net.minecraft.nbt.NbtIo.read((java.io.DataInput)in);
 +                            compression = compressionType;
 +                            break; // reaches here iff readNBT does not throw
 +                        } catch (Exception ex) {
@@ -397,63 +398,55 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    final boolean canRecalcHeader; // final forces compile fail on new constructor
 +    // Paper end - Attempt to recalculate regionfile header if it is corrupt
- 
++
      // Paper start - rewrite chunk system
      @Override
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-             throw new IllegalArgumentException("Expected directory, got " + String.valueOf(directory.toAbsolutePath()));
+     public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException {
+@@ -74,6 +423,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+             throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath());
          } else {
-             this.externalFileDir = directory;
+             this.externalFileDir = externalFileDir;
 +            this.canRecalcHeader = RegionFileStorage.isChunkDataFolder(this.externalFileDir); // Paper - add can recalc flag
              this.offsets = this.header.asIntBuffer();
-             ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error
-             ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-                     RegionFile.LOGGER.warn("Region file {} has truncated header: {}", path, i);
-                 }
+             this.offsets.limit(1024);
+             this.header.position(4096);
+@@ -94,11 +444,13 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
  
--                long j = Files.size(path);
-+                final long j = Files.size(path); final long regionFileSize = j; // Paper - recalculate header on header corruption
+                 long size = Files.size(path);
  
--                for (int k = 0; k < 1024; ++k) {
--                    int l = this.offsets.get(k);
+-                for (int i1 = 0; i1 < 1024; i1++) {
 +                boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
 +                boolean hasBackedUp = false; // Paper - recalculate header on header corruption
-+                for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location
-+                    final int l = this.offsets.get(k);
- 
-                     if (l != 0) {
--                        int i1 = RegionFile.getSectorNumber(l);
--                        int j1 = RegionFile.getNumSectors(l);
-+                        final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors
-+                        int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
++                for (int i1 = 0; i1 < 1024; i1++) { final int headerLocation = i1; // Paper - we expect this to be the header location
+                     int i2 = this.offsets.get(i1);
+                     if (i2 != 0) {
+-                        int sectorNumber = getSectorNumber(i2);
+-                        int numSectors = getNumSectors(i2);
++                        final int sectorNumber = getSectorNumber(i2); // Paper - we expect this to be offset in file in sectors
++                        int numSectors = getNumSectors(i2); // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
                          // Spigot start
-                         if (j1 == 255) {
+                         if (numSectors == 255) {
                              // We're maxed out, so we need to read the proper length from the section
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-                             j1 = (realLen.getInt(0) + 4) / 4096 + 1;
-                         }
+@@ -109,18 +461,62 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
                          // Spigot end
-+                        sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
- 
-                         if (i1 < 2) {
-                             RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1});
--                            this.offsets.put(k, 0);
-+                            //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
-                         } else if (j1 == 0) {
-                             RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, k);
--                            this.offsets.put(k, 0);
-+                            //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
-                         } else if ((long) i1 * 4096L > j) {
-                             RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{path, k, i1});
--                            this.offsets.put(k, 0);
-+                            //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
+                         if (sectorNumber < 2) {
+                             LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i1, sectorNumber);
+-                            this.offsets.put(i1, 0);
++                            //this.offsets.put(i1, 0); // Paper - we catch this, but need it in the header for the summary change
+                         } else if (numSectors == 0) {
+                             LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, i1);
+-                            this.offsets.put(i1, 0);
++                            //this.offsets.put(i1, 0); // Paper - we catch this, but need it in the header for the summary change
+                         } else if (sectorNumber * 4096L > size) {
+                             LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", path, i1, sectorNumber);
+-                            this.offsets.put(i1, 0);
++                            //this.offsets.put(i1, 0); // Paper - we catch this, but need it in the header for the summary change
                          } else {
--                            this.usedSectors.force(i1, j1);
-+                            //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate
+-                            this.usedSectors.force(sectorNumber, numSectors);
++                            //this.usedSectors.force(sectorNumber, numSectors); // Paper - move this down so we can check if it fails to allocate
 +                        }
 +                        // Paper start - recalculate header on header corruption
-+                        if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
++                        if (sectorNumber < 2 || numSectors <= 0 || ((long)sectorNumber * 4096L) > size) {
 +                            if (canRecalcHeader) {
 +                                LOGGER.error("Detected invalid header for regionfile " + this.path.toAbsolutePath() + "! Recalculating header...");
 +                                needsHeaderRecalc = true;
@@ -471,7 +464,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                                continue;
 +                            }
 +                        }
-+                        boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength);
++                        boolean failedToAllocate = !this.usedSectors.tryAllocate(sectorNumber, numSectors);
 +                        if (failedToAllocate) {
 +                            LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.path.toAbsolutePath());
                          }
@@ -499,20 +492,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +                // Paper end
              }
- 
          }
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+     }
+@@ -130,10 +526,35 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
      }
  
      private Path getExternalChunkPath(ChunkPos chunkPos) {
--        String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
-+        String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
- 
-         return this.externalFileDir.resolve(s);
+-        String string = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
++        String string = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
+         return this.externalFileDir.resolve(string);
      }
  
 +    // Paper start
-+    private static ChunkPos getOversizedChunkPair(Path file) {
++    private static @Nullable ChunkPos getOversizedChunkPair(Path file) {
 +        String fileName = file.getFileName().toString();
 +
 +        if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
@@ -537,81 +529,79 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // Paper end
 +
      @Nullable
-     public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
-         int i = this.getOffset(pos);
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-             ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
-             if (bytebuffer.remaining() < 5) {
-                 RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
+     public synchronized DataInputStream getChunkDataInputStream(ChunkPos chunkPos) throws IOException {
+         int offset = this.getOffset(chunkPos);
+@@ -155,30 +576,67 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+             byteBuffer.flip();
+             if (byteBuffer.remaining() < 5) {
+                 LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkPos, i, byteBuffer.remaining());
 +                // Paper start - recalculate header on regionfile corruption
 +                if (this.canRecalcHeader && this.recalculateHeader()) {
-+                    return this.getChunkDataInputStream(pos);
++                    return this.getChunkDataInputStream(chunkPos);
 +                }
 +                // Paper end - recalculate header on regionfile corruption
                  return null;
              } else {
-                 int i1 = bytebuffer.getInt();
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
- 
-                 if (i1 == 0) {
-                     RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
+                 int _int = byteBuffer.getInt();
+                 byte b = byteBuffer.get();
+                 if (_int == 0) {
+                     LOGGER.warn("Chunk {} is allocated, but stream is missing", chunkPos);
 +                    // Paper start - recalculate header on regionfile corruption
 +                    if (this.canRecalcHeader && this.recalculateHeader()) {
-+                        return this.getChunkDataInputStream(pos);
++                        return this.getChunkDataInputStream(chunkPos);
 +                    }
 +                    // Paper end - recalculate header on regionfile corruption
                      return null;
                  } else {
-                     int j1 = i1 - 1;
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
-                     if (RegionFile.isExternalStreamChunk(b0)) {
-                         if (j1 != 0) {
-                             RegionFile.LOGGER.warn("Chunk has both internal and external streams");
+                     int i1 = _int - 1;
+                     if (isExternalStreamChunk(b)) {
+                         if (i1 != 0) {
+                             LOGGER.warn("Chunk has both internal and external streams");
 +                            // Paper start - recalculate header on regionfile corruption
 +                            if (this.canRecalcHeader && this.recalculateHeader()) {
-+                                return this.getChunkDataInputStream(pos);
++                                return this.getChunkDataInputStream(chunkPos);
 +                            }
 +                            // Paper end - recalculate header on regionfile corruption
                          }
  
--                        return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
+-                        return this.createExternalChunkInputStream(chunkPos, getExternalChunkVersion(b));
 +                        // Paper start - recalculate header on regionfile corruption
-+                        final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
++                        final DataInputStream ret = this.createExternalChunkInputStream(chunkPos, getExternalChunkVersion(b));
 +                        if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
-+                            return this.getChunkDataInputStream(pos);
++                            return this.getChunkDataInputStream(chunkPos);
 +                        }
 +                        return ret;
 +                        // Paper end - recalculate header on regionfile corruption
-                     } else if (j1 > bytebuffer.remaining()) {
-                         RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()});
+                     } else if (i1 > byteBuffer.remaining()) {
+                         LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", chunkPos, i1, byteBuffer.remaining());
 +                        // Paper start - recalculate header on regionfile corruption
 +                        if (this.canRecalcHeader && this.recalculateHeader()) {
-+                            return this.getChunkDataInputStream(pos);
++                            return this.getChunkDataInputStream(chunkPos);
 +                        }
 +                        // Paper end - recalculate header on regionfile corruption
                          return null;
-                     } else if (j1 < 0) {
-                         RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos);
+                     } else if (i1 < 0) {
+                         LOGGER.error("Declared size {} of chunk {} is negative", _int, chunkPos);
 +                        // Paper start - recalculate header on regionfile corruption
 +                        if (this.canRecalcHeader && this.recalculateHeader()) {
-+                            return this.getChunkDataInputStream(pos);
++                            return this.getChunkDataInputStream(chunkPos);
 +                        }
 +                        // Paper end - recalculate header on regionfile corruption
                          return null;
                      } else {
-                         JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, j1);
--                        return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
+                         JvmProfiler.INSTANCE.onRegionFileRead(this.info, chunkPos, this.version, i1);
+-                        return this.createChunkInputStream(chunkPos, b, createStream(byteBuffer, i1));
 +                        // Paper start - recalculate header on regionfile corruption
-+                        final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
++                        final DataInputStream ret = this.createChunkInputStream(chunkPos, b, createStream(byteBuffer, i1));
 +                        if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
-+                            return this.getChunkDataInputStream(pos);
++                            return this.getChunkDataInputStream(chunkPos);
 +                        }
 +                        return ret;
 +                        // Paper end - recalculate header on regionfile corruption
                      }
                  }
              }
-@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
+@@ -361,9 +819,14 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche
      }
  
      private ByteBuffer createExternalStub() {
@@ -620,22 +610,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +    private ByteBuffer createExternalStub(RegionFileVersion version) {
 +        // Paper end - add version param
-         ByteBuffer bytebuffer = ByteBuffer.allocate(5);
+         ByteBuffer byteBuffer = ByteBuffer.allocate(5);
+         byteBuffer.putInt(1);
+-        byteBuffer.put((byte)(this.version.getId() | 128));
++        byteBuffer.put((byte)(version.getId() | 128));
+         byteBuffer.flip();
+         return byteBuffer;
+     }
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+index d263f78fa610ce6f6fb5a0f5e064e3d8335c2199..dad7f94b611cf0fc68b1a3878c458233f6bb6d61 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -23,6 +23,36 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+     private final Path folder;
+     private final boolean sync;
  
-         bytebuffer.putInt(1);
--        bytebuffer.put((byte) (this.version.getId() | 128));
-+        bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param
-         ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
-         return bytebuffer;
-     }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
-         }
-     }
-     // Paper end - rewrite chunk system
 +    // Paper start - recalculate region file headers
 +    private final boolean isChunkData;
 +
@@ -666,40 +655,42 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +    // Paper end
- 
-     protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected
-         this.folder = directory;
-         this.sync = dsync;
-         this.info = storageKey;
+     // Paper start - rewrite chunk system
+     private static final int REGION_SHIFT = 5;
+     private static final int MAX_NON_EXISTING_CACHE = 1024 * 4;
+@@ -216,6 +246,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+         this.folder = folder;
+         this.sync = sync;
+         this.info = info;
 +        this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers
      }
  
-     // Paper start - rewrite chunk system
-@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
-             try {
-                 if (datainputstream != null) {
-                     nbttagcompound = NbtIo.read((DataInput) datainputstream);
-+                    // Paper start - recover from corrupt regionfile header
-+                    if (this.isChunkData) {
-+                        ChunkPos chunkPos = SerializableChunkData.getChunkCoordinate(nbttagcompound);
-+                        if (!chunkPos.equals(pos)) {
-+                            net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.getPath().toAbsolutePath());
-+                            if (regionfile.recalculateHeader()) {
-+                                return this.read(pos);
-+                            }
-+                            net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.getPath().toAbsolutePath());
-+                            return null;
-+                        }
-+                    }
-+                    // Paper end - recover from corrupt regionfile header
-                     break label43;
-                 }
+     @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit
+@@ -309,6 +340,19 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise
+             }
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
-@@ -0,0 +0,0 @@ import org.slf4j.Logger;
+             var4 = NbtIo.read(chunkDataInputStream);
++            // Paper start - recover from corrupt regionfile header
++            if (this.isChunkData) {
++                ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(var4);
++                if (!headerChunkPos.equals(chunkPos)) {
++                    net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + chunkPos + " but got chunk data for " + headerChunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionFile.getPath().toAbsolutePath());
++                    if (regionFile.recalculateHeader()) {
++                        return this.read(chunkPos);
++                    }
++                    net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + chunkPos + " for " + regionFile.getPath().toAbsolutePath());
++                    return null;
++                }
++            }
++            // Paper end - recover from corrupt regionfile header
+         }
+ 
+         return var4;
+diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+index 0c739ca5b01ac0ec35a11fd01c5fc65de97c2852..de7deee4b79c969a7797bd57b657a16404c15303 100644
+--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+@@ -21,7 +21,7 @@ import org.slf4j.Logger;
  
  public class RegionFileVersion {
      private static final Logger LOGGER = LogUtils.getLogger();
@@ -708,11 +699,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private static final Object2ObjectMap<String, RegionFileVersion> VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>();
      public static final RegionFileVersion VERSION_GZIP = register(
          new RegionFileVersion(
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
+diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+index 70a9972252576e039ac126f6057a6ed66b80cdfc..d783c3580ea274a0a9cb07449eb8037bc5a04d76 100644
+--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+@@ -120,6 +120,18 @@ public record SerializableChunkData(
          }
      }
      // Paper end - guard against serializing mismatching coordinates
@@ -731,12 +722,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
      // Paper start - Do not let the server load chunks from newer versions
      private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
-@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chun
-         nbttagcompound.putInt("xPos", this.chunkPos.x);
-         nbttagcompound.putInt("yPos", this.minSectionY);
-         nbttagcompound.putInt("zPos", this.chunkPos.z);
--        nbttagcompound.putLong("LastUpdate", this.lastUpdateTime);
-+        nbttagcompound.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change
-         nbttagcompound.putLong("InhabitedTime", this.inhabitedTime);
-         nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
-         DataResult<Tag> dataresult; // CraftBukkit - decompile error
+@@ -604,7 +616,7 @@ public record SerializableChunkData(
+         compoundTag.putInt("xPos", this.chunkPos.x);
+         compoundTag.putInt("yPos", this.minSectionY);
+         compoundTag.putInt("zPos", this.chunkPos.z);
+-        compoundTag.putLong("LastUpdate", this.lastUpdateTime);
++        compoundTag.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change
+         compoundTag.putLong("InhabitedTime", this.inhabitedTime);
+         compoundTag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
+         if (this.blendingData != null) {
diff --git a/feature-patches/1067-Optimise-general-POI-access.patch b/feature-patches/1067-Optimise-general-POI-access.patch
index 92dfac7dd4..9b56c38a8e 100644
--- a/feature-patches/1067-Optimise-general-POI-access.patch
+++ b/feature-patches/1067-Optimise-general-POI-access.patch
@@ -30,12 +30,13 @@ This patch also specifically optimises other areas of code to
 use PoiAccess. For example, some villager AI and portaling code
 had to be specifically modified.
 
-diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java
+
+diff --git a/io/papermc/paper/util/PoiAccess.java b/io/papermc/paper/util/PoiAccess.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+index 0000000000000000000000000000000000000000..f39294b1f83c4022be5ced4da781103a1eee2daf
 --- /dev/null
-+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java
-@@ -0,0 +0,0 @@
++++ b/io/papermc/paper/util/PoiAccess.java
+@@ -0,0 +1,806 @@
 +package io.papermc.paper.util;
 +
 +import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
@@ -842,38 +843,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        throw new RuntimeException();
 +    }
 +}
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
-@@ -0,0 +0,0 @@ public class AcquirePoi {
-                                             return true;
-                                         }
-                                     };
--                                    Set<Pair<Holder<PoiType>, BlockPos>> set = poiManager.findAllClosestFirstWithType(
--                                            poiPredicate, predicate2, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE
--                                        )
--                                        .limit(5L)
--                                        .filter(pairx -> worldPosBiPredicate.test(world, (BlockPos)pairx.getSecond()))
--                                        .collect(Collectors.toSet());
-+                                    // Paper start - optimise POI access
-+                                    final java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
-+                                    io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
-+                                    final Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes.size());
-+                                    for (final Pair<Holder<PoiType>, BlockPos> poiPose : poiposes) {
-+                                        if (worldPosBiPredicate.test(world, poiPose.getSecond())) {
-+                                            set.add(poiPose);
-+                                        }
-+                                    }
-+                                    // Paper end - optimise POI access
-                                     Path path = findPathToPois(entity, set);
-                                     if (path != null && path.canReach()) {
-                                         BlockPos blockPos = path.getTarget();
-diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
-@@ -0,0 +0,0 @@ public class NearestBedSensor extends Sensor<Mob> {
+diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+index 9de13a78b2a8be181c02ab330bfa9abb936a83db..b9174ae7e3a3e2de2d570b95ab5012ac3c3a2eda 100644
+--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+@@ -84,12 +84,16 @@ public class AcquirePoi {
+                                     return true;
+                                 }
+                             };
+-                            Set<Pair<Holder<PoiType>, BlockPos>> set = poiManager.findAllClosestFirstWithType(
+-                                    acquirablePois, predicate1, mob.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE
+-                                )
+-                                .limit(5L)
+-                                .filter(pair1 -> predicate.test(level, pair1.getSecond()))
+-                                .collect(Collectors.toSet());
++                            // Paper start - optimise POI access
++                            final java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
++                            io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
++                            final Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes.size());
++                            for (final Pair<Holder<PoiType>, BlockPos> poiPose : poiposes) {
++                                if (predicate.test(level, poiPose.getSecond())) {
++                                    set.add(poiPose);
++                                }
++                            }
++                            // Paper end - optimise POI access
+                             Path path = findPathToPois(mob, set);
+                             if (path != null && path.canReach()) {
+                                 BlockPos target = path.getTarget();
+diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+index 6e9325f0800a35637fdec5edb8a514ea03741762..066faa704338c573472381e1ebd063e0d52aaaa4 100644
+--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+@@ -53,11 +53,12 @@ public class NearestBedSensor extends Sensor<Mob> {
                      return true;
                  }
              };
@@ -889,82 +890,82 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
 +            // Paper end - optimise POI access
              if (path != null && path.canReach()) {
-                 BlockPos blockPos = path.getTarget();
-                 Optional<Holder<PoiType>> optional = poiManager.getType(blockPos);
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> im
+                 BlockPos target = path.getTarget();
+                 Optional<Holder<PoiType>> type = poiManager.getType(target);
+diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+index 5c5724f5e3ad640f55aecbc1d8f71d1f59ecdc62..618fc0eb4fe70e46e55f3aa28e8eac1d2d01b6d9 100644
+--- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java
++++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+@@ -254,36 +254,47 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> im
      public Optional<BlockPos> find(
-         Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
+         Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int distance, PoiManager.Occupancy status
      ) {
--        return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst();
+-        return this.findAll(typePredicate, posPredicate, pos, distance, status).findFirst();
 +        // Paper start - re-route to faster logic
-+        BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false);
++        BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, distance, status, false);
 +        return Optional.ofNullable(ret);
 +        // Paper end
      }
  
-     public Optional<BlockPos> findClosest(Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
--        return this.getInRange(typePredicate, pos, radius, occupationStatus)
--            .map(PoiRecord::getPos)
--            .min(Comparator.comparingDouble(poiPos -> poiPos.distSqr(pos)));
+     public Optional<BlockPos> findClosest(Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int distance, PoiManager.Occupancy status) {
+-        return this.getInRange(typePredicate, pos, distance, status).map(PoiRecord::getPos).min(Comparator.comparingDouble(blockPos -> blockPos.distSqr(pos)));
 +        // Paper start - re-route to faster logic
-+        BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false);
++        BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, distance, distance * distance, status, false);
 +        return Optional.ofNullable(closestPos);
 +        // Paper end - re-route to faster logic
      }
  
      public Optional<Pair<Holder<PoiType>, BlockPos>> findClosestWithType(
-         Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
+         Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int distance, PoiManager.Occupancy status
      ) {
--        return this.getInRange(typePredicate, pos, radius, occupationStatus)
--            .min(Comparator.comparingDouble(poi -> poi.getPos().distSqr(pos)))
--            .map(poi -> Pair.of(poi.getPoiType(), poi.getPos()));
+-        return this.getInRange(typePredicate, pos, distance, status)
+-            .min(Comparator.comparingDouble(poiRecord -> poiRecord.getPos().distSqr(pos)))
+-            .map(poiRecord -> Pair.of(poiRecord.getPoiType(), poiRecord.getPos()));
 +        // Paper start - re-route to faster logic
 +        return Optional.ofNullable(io.papermc.paper.util.PoiAccess.findClosestPoiDataTypeAndPosition(
-+            this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false
++            this, typePredicate, null, pos, distance, distance * distance, status, false
 +        ));
 +        // Paper end - re-route to faster logic
      }
  
      public Optional<BlockPos> findClosest(
-         Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
+         Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int distance, PoiManager.Occupancy status
      ) {
--        return this.getInRange(typePredicate, pos, radius, occupationStatus)
+-        return this.getInRange(typePredicate, pos, distance, status)
 -            .map(PoiRecord::getPos)
 -            .filter(posPredicate)
--            .min(Comparator.comparingDouble(poiPos -> poiPos.distSqr(pos)));
+-            .min(Comparator.comparingDouble(blockPos -> blockPos.distSqr(pos)));
 +        // Paper start - re-route to faster logic
-+        BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false);
++        BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, distance, distance * distance, status, false);
 +        return Optional.ofNullable(closestPos);
 +        // Paper end - re-route to faster logic
      }
  
-     public Optional<BlockPos> take(Predicate<Holder<PoiType>> typePredicate, BiPredicate<Holder<PoiType>, BlockPos> posPredicate, BlockPos pos, int radius) {
--        return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE)
--            .filter(poi -> posPredicate.test(poi.getPoiType(), poi.getPos()))
+     public Optional<BlockPos> take(
+         Predicate<Holder<PoiType>> typePredicate, BiPredicate<Holder<PoiType>, BlockPos> combinedTypePosPredicate, BlockPos pos, int distance
+     ) {
+-        return this.getInRange(typePredicate, pos, distance, PoiManager.Occupancy.HAS_SPACE)
+-            .filter(poiRecord -> combinedTypePosPredicate.test(poiRecord.getPoiType(), poiRecord.getPos()))
 -            .findFirst()
 +        // Paper start - re-route to faster logic
 +        final @javax.annotation.Nullable PoiRecord closest = io.papermc.paper.util.PoiAccess.findClosestPoiDataRecord(
-+            this, typePredicate, posPredicate, pos, radius, radius * radius, Occupancy.HAS_SPACE, false
++            this, typePredicate, combinedTypePosPredicate, pos, distance, distance * distance, Occupancy.HAS_SPACE, false
 +        );
 +        return Optional.ofNullable(closest)
 +            // Paper end - re-route to faster logic
-             .map(poi -> {
-                 poi.acquireTicket();
-                 return poi.getPos();
-@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> im
-         int radius,
+             .map(poiRecord -> {
+                 poiRecord.acquireTicket();
+                 return poiRecord.getPos();
+@@ -298,8 +309,21 @@ public class PoiManager extends SectionStorage<PoiSection, PoiSection.Packed> im
+         int distance,
          RandomSource random
      ) {
--        List<PoiRecord> list = Util.toShuffledList(this.getInRange(typePredicate, pos, radius, occupationStatus), random);
--        return list.stream().filter(poi -> positionPredicate.test(poi.getPos())).findFirst().map(PoiRecord::getPos);
+-        List<PoiRecord> list = Util.toShuffledList(this.getInRange(typePredicate, pos, distance, status), random);
+-        return list.stream().filter(poiRecord -> posPredicate.test(poiRecord.getPos())).findFirst().map(PoiRecord::getPos);
 +        // Paper start - re-route to faster logic
 +        List<PoiRecord> list = new java.util.ArrayList<>();
 +        io.papermc.paper.util.PoiAccess.findAnyPoiRecords(
-+            this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list
++            this, typePredicate, posPredicate, pos, distance, status, false, Integer.MAX_VALUE, list
 +        );
 +
 +        // the old method shuffled the list and then tried to find the first element in it that
@@ -979,11 +980,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      }
  
      public boolean release(BlockPos pos) {
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
-@@ -0,0 +0,0 @@ import org.slf4j.Logger;
+diff --git a/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+index 39cd1e3d8192d7077d6b7864d33933097cc6b986..b92ba4d194fd3af94c7af5d8e150fc4297c73ab8 100644
+--- a/net/minecraft/world/entity/ai/village/poi/PoiSection.java
++++ b/net/minecraft/world/entity/ai/village/poi/PoiSection.java
+@@ -26,7 +26,7 @@ import org.slf4j.Logger;
  public class PoiSection implements ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection { // Paper - rewrite chunk system
      private static final Logger LOGGER = LogUtils.getLogger();
      private final Short2ObjectMap<PoiRecord> records = new Short2ObjectOpenHashMap<>();
@@ -992,48 +993,43 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private final Runnable setDirty;
      private boolean isValid;
  
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-@@ -0,0 +0,0 @@ public class SectionStorage<R, P> implements AutoCloseable, ca.spottedleaf.moonr
+diff --git a/net/minecraft/world/level/chunk/storage/SectionStorage.java b/net/minecraft/world/level/chunk/storage/SectionStorage.java
+index 778bd73a938c94ecb85ca0f8b686ff4e1baee040..79d4ce7712f16995b0de3be86477fb43ab3961d7 100644
+--- a/net/minecraft/world/level/chunk/storage/SectionStorage.java
++++ b/net/minecraft/world/level/chunk/storage/SectionStorage.java
+@@ -131,11 +131,11 @@ public class SectionStorage<R, P> implements AutoCloseable, ca.spottedleaf.moonr
      }
  
      @Nullable
--    protected Optional<R> get(long pos) {
-+    public Optional<R> get(long pos) { // Paper - public
-         return this.storage.get(pos);
+-    protected Optional<R> get(long sectionKey) {
++    public Optional<R> get(long sectionKey) { // Paper - public
+         return this.storage.get(sectionKey);
      }
  
--    protected Optional<R> getOrLoad(long pos) {
-+    public Optional<R> getOrLoad(long pos) { // Paper - public
-         if (this.outsideStoredRange(pos)) {
+-    protected Optional<R> getOrLoad(long sectionKey) {
++    public Optional<R> getOrLoad(long sectionKey) { // Paper - public
+         if (this.outsideStoredRange(sectionKey)) {
              return Optional.empty();
          } else {
-diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
-+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
-@@ -0,0 +0,0 @@ public class PortalForcer {
-         // int i = flag ? 16 : 128;
+diff --git a/net/minecraft/world/level/portal/PortalForcer.java b/net/minecraft/world/level/portal/PortalForcer.java
+index ada2da62d3a40d67e64f5f8d7299f78b5c6f53cb..90ae71eb8cc7f925eb212f39731d70f3bff5ef0a 100644
+--- a/net/minecraft/world/level/portal/PortalForcer.java
++++ b/net/minecraft/world/level/portal/PortalForcer.java
+@@ -48,13 +48,38 @@ public class PortalForcer {
+         PoiManager poiManager = this.level.getPoiManager();
+         // int i = isNether ? 16 : 128;
          // CraftBukkit end
- 
--        villageplace.ensureLoadedAndValid(this.level, blockposition, i);
--        Stream<BlockPos> stream = villageplace.getInSquare((holder) -> { // CraftBukkit - decompile error
--            return holder.is(PoiTypes.NETHER_PORTAL);
--        }, blockposition, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos);
--
--        Objects.requireNonNull(worldborder);
--        return stream.filter(worldborder::isWithinBounds).filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))).filter((blockposition1) -> { // Paper - Configurable nether ceiling damage
--            return this.level.getBlockState(blockposition1).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
--        }).min(Comparator.comparingDouble((BlockPos blockposition1) -> { // CraftBukkit - decompile error
--            return blockposition1.distSqr(blockposition);
--        }).thenComparingInt(Vec3i::getY));
+-        poiManager.ensureLoadedAndValid(this.level, exitPos, i);
+-        return poiManager.getInSquare(holder -> holder.is(PoiTypes.NETHER_PORTAL), exitPos, i, PoiManager.Occupancy.ANY)
+-            .map(PoiRecord::getPos)
+-            .filter(worldBorder::isWithinBounds)
+-            .filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) // Paper - Configurable nether ceiling damage
+-            .filter(blockPos -> this.level.getBlockState(blockPos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS))
+-            .min(Comparator.<BlockPos>comparingDouble(blockPos -> blockPos.distSqr(exitPos)).thenComparingInt(Vec3i::getY));
 +        // Paper start - optimise portals
-+        Optional<PoiRecord> optional;
 +        java.util.List<PoiRecord> records = new java.util.ArrayList<>();
 +        io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords(
-+            villageplace,
++            poiManager,
 +            type -> type.is(PoiTypes.NETHER_PORTAL),
 +            (BlockPos pos) -> {
 +                net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY);
@@ -1042,12 +1038,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    // why would we generate the chunk?
 +                    return false;
 +                }
-+                if (!worldborder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage
++                if (!worldBorder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage
 +                    return false;
 +                }
 +                return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
 +            },
-+            blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records
++            exitPos, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records
 +        );
 +
 +        // this gets us most of the way there, but we bias towards lower y values.

From 13b890950daa67c2c6d3d7c2e2fa78b6542ef698 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Fri, 20 Dec 2024 23:18:34 +0100
Subject: [PATCH 266/285] Apply remaining feature patches

---
 .../1076-Implement-chunk-view-API.patch       |  51 ---------
 .../0021-Moonrise-optimisation-patches.patch  |  41 +++++--
 ...-data-to-disk-if-it-serializes-witho.patch |   1 -
 .../0028-Improved-Watchdog-Support.patch      |  63 +++++------
 ...l-more-information-in-watchdog-dumps.patch | 102 ++++--------------
 ...030-Entity-load-save-limit-per-chunk.patch |   1 -
 ...culate-regionfile-header-if-it-is-co.patch |   1 -
 ...-Incremental-chunk-and-player-saving.patch |  22 ++--
 .../0033-Optimise-general-POI-access.patch    |   1 -
 .../0034-Optional-per-player-mob-spawns.patch |  26 ++---
 ...ng-PreCreatureSpawnEvent-with-per-pl.patch |  14 +--
 .../features/0036-Optimize-Hoppers.patch      |  12 +--
 ...on-checking-in-player-move-packet-ha.patch |  22 ++--
 13 files changed, 133 insertions(+), 224 deletions(-)
 delete mode 100644 feature-patches/1076-Implement-chunk-view-API.patch
 rename feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch => paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch (99%)
 rename feature-patches/1061-Improved-Watchdog-Support.patch => paper-server/patches/features/0028-Improved-Watchdog-Support.patch (86%)
 rename feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch => paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch (62%)
 rename feature-patches/1063-Entity-load-save-limit-per-chunk.patch => paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch (99%)
 rename feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch => paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch (99%)
 rename feature-patches/1066-Incremental-chunk-and-player-saving.patch => paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch (85%)
 rename feature-patches/1067-Optimise-general-POI-access.patch => paper-server/patches/features/0033-Optimise-general-POI-access.patch (99%)
 rename feature-patches/1071-Optional-per-player-mob-spawns.patch => paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch (92%)
 rename feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch => paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch (88%)
 rename feature-patches/2017-Optimize-Hoppers.patch => paper-server/patches/features/0036-Optimize-Hoppers.patch (98%)
 rename feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch => paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch (92%)

diff --git a/feature-patches/1076-Implement-chunk-view-API.patch b/feature-patches/1076-Implement-chunk-view-API.patch
deleted file mode 100644
index ede618a2ed..0000000000
--- a/feature-patches/1076-Implement-chunk-view-API.patch
+++ /dev/null
@@ -1,51 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Flo0 <flo.roma@web.de>
-Date: Thu, 5 Dec 2024 12:15:07 +0100
-Subject: [PATCH] Implement chunk view API
-
-
-diff --git a/src/main/java/io/papermc/paper/FeatureHooks.java b/src/main/java/io/papermc/paper/FeatureHooks.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/io/papermc/paper/FeatureHooks.java
-+++ b/src/main/java/io/papermc/paper/FeatureHooks.java
-@@ -0,0 +0,0 @@ package io.papermc.paper;
- import io.papermc.paper.command.PaperSubcommand;
- import io.papermc.paper.command.subcommands.ChunkDebugCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
-+import it.unimi.dsi.fastutil.longs.LongIterator;
- import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
--import it.unimi.dsi.fastutil.longs.LongSet;
- import it.unimi.dsi.fastutil.longs.LongSets;
- import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
- import it.unimi.dsi.fastutil.objects.ObjectSet;
-@@ -0,0 +0,0 @@ public final class FeatureHooks {
-     }
- 
-     public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
--        final LongSet keys = new LongOpenHashSet();
--        player.getChunkTrackingView().forEach(pos -> keys.add(pos.longKey));
--        return LongSets.unmodifiable(keys);
-+        return LongSets.unmodifiable(player.moonrise$getChunkLoader().getSentChunksRaw().clone());
-     }
- 
-     public static Set<Chunk> getSentChunks(final ServerPlayer player) {
--        final ObjectSet<Chunk> chunks = new ObjectOpenHashSet<>();
-+        final LongOpenHashSet rawChunkKeys = player.moonrise$getChunkLoader().getSentChunksRaw();
-+        final ObjectSet<org.bukkit.Chunk> chunks = new ObjectOpenHashSet<>(rawChunkKeys.size());
-         final World world = player.serverLevel().getWorld();
--        player.getChunkTrackingView().forEach(pos -> {
--            final org.bukkit.Chunk chunk = world.getChunkAt(pos.longKey);
--            chunks.add(chunk);
--        });
-+        final LongIterator iter = player.moonrise$getChunkLoader().getSentChunksRaw().longIterator();
-+        while (iter.hasNext()) {
-+            chunks.add(world.getChunkAt(iter.nextLong(), false));
-+        }
-         return ObjectSets.unmodifiable(chunks);
-     }
- 
-     public static boolean isChunkSent(final ServerPlayer player, final long chunkKey) {
--        return player.getChunkTrackingView().contains(new ChunkPos(chunkKey));
-+        return player.moonrise$getChunkLoader().getSentChunksRaw().contains(chunkKey);
-     }
- }
diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index 491cf50e1c..8bdfa4ff3f 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -22768,19 +22768,20 @@ index 0000000000000000000000000000000000000000..689ce367164e79e0426eeecb81dbbc52
 +    private SaveUtil() {}
 +}
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index 184e6c6fe2ba522d0ea0774604839320c4152371..b329eb069f5b3d4f33a94d2045cb8f250d2a5684 100644
+index 184e6c6fe2ba522d0ea0774604839320c4152371..460bb584db04b582f3297ae419183f430aff1ec0 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
-@@ -1,6 +1,8 @@
+@@ -1,6 +1,9 @@
  package io.papermc.paper;
  
  import io.papermc.paper.command.PaperSubcommand;
 +import io.papermc.paper.command.subcommands.ChunkDebugCommand;
 +import io.papermc.paper.command.subcommands.FixLightCommand;
++import it.unimi.dsi.fastutil.longs.LongIterator;
  import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
  import it.unimi.dsi.fastutil.longs.LongSet;
  import it.unimi.dsi.fastutil.longs.LongSets;
-@@ -31,9 +33,12 @@ import org.bukkit.World;
+@@ -31,9 +34,12 @@ import org.bukkit.World;
  public final class FeatureHooks {
  
      public static void initChunkTaskScheduler(final boolean useParallelGen) {
@@ -22793,7 +22794,35 @@ index 184e6c6fe2ba522d0ea0774604839320c4152371..b329eb069f5b3d4f33a94d2045cb8f25
      }
  
      public static LevelChunkSection createSection(final Registry<Biome> biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) {
-@@ -79,89 +84,30 @@ public final class FeatureHooks {
+@@ -59,18 +65,19 @@ public final class FeatureHooks {
+     }
+ 
+     public static Set<Long> getSentChunkKeys(final ServerPlayer player) {
+-        final LongSet keys = new LongOpenHashSet();
+-        player.getChunkTrackingView().forEach(pos -> keys.add(pos.longKey));
+-        return LongSets.unmodifiable(keys);
++        return LongSets.unmodifiable(player.moonrise$getChunkLoader().getSentChunksRaw().clone()); // Paper - rewrite chunk system
+     }
+ 
+     public static Set<Chunk> getSentChunks(final ServerPlayer player) {
+-        final ObjectSet<Chunk> chunks = new ObjectOpenHashSet<>();
++        // Paper start - rewrite chunk system
++        final LongOpenHashSet rawChunkKeys = player.moonrise$getChunkLoader().getSentChunksRaw();
++        final ObjectSet<org.bukkit.Chunk> chunks = new ObjectOpenHashSet<>(rawChunkKeys.size());
+         final World world = player.serverLevel().getWorld();
+-        player.getChunkTrackingView().forEach(pos -> {
+-            final org.bukkit.Chunk chunk = world.getChunkAt(pos.longKey);
+-            chunks.add(chunk);
+-        });
++        final LongIterator iter = player.moonrise$getChunkLoader().getSentChunksRaw().longIterator();
++        while (iter.hasNext()) {
++            chunks.add(world.getChunkAt(iter.nextLong(), false));
++        }
++        // Paper end - rewrite chunk system
+         return ObjectSets.unmodifiable(chunks);
+     }
+ 
+@@ -79,89 +86,30 @@ public final class FeatureHooks {
      }
  
      public static boolean isSpiderCollidingWithWorldBorder(final Spider spider) {
@@ -22888,7 +22917,7 @@ index 184e6c6fe2ba522d0ea0774604839320c4152371..b329eb069f5b3d4f33a94d2045cb8f25
              long chunkKey = chunkTickets.getLongKey();
              net.minecraft.util.SortedArraySet<net.minecraft.server.level.Ticket<?>> tickets = chunkTickets.getValue();
  
-@@ -183,15 +129,15 @@ public final class FeatureHooks {
+@@ -183,15 +131,15 @@ public final class FeatureHooks {
      }
  
      public static int getViewDistance(net.minecraft.server.level.ServerLevel world) {
@@ -22907,7 +22936,7 @@ index 184e6c6fe2ba522d0ea0774604839320c4152371..b329eb069f5b3d4f33a94d2045cb8f25
      }
  
      public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
-@@ -209,31 +155,31 @@ public final class FeatureHooks {
+@@ -209,31 +157,31 @@ public final class FeatureHooks {
      }
  
      public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) {
diff --git a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
similarity index 99%
rename from feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
rename to paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
index 0561b403fb..454bf1d95d 100644
--- a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
+++ b/paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
@@ -7,7 +7,6 @@ Subject: [PATCH] Only write chunk data to disk if it serializes without
 This ensures at least a valid version of the chunk exists
 on disk, even if outdated
 
-
 diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java
 index 7da388ffab162c282cad0f297bb7304f3c2abbaf..ff4fc280409f680f3879a495e37cf1925b1a38f1 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java
diff --git a/feature-patches/1061-Improved-Watchdog-Support.patch b/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
similarity index 86%
rename from feature-patches/1061-Improved-Watchdog-Support.patch
rename to paper-server/patches/features/0028-Improved-Watchdog-Support.patch
index c19faff35a..601c2bb5ea 100644
--- a/feature-patches/1061-Improved-Watchdog-Support.patch
+++ b/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
@@ -88,7 +88,7 @@ index 3e0e88afcf010d9a3d46e48bca5cbdf98fe97544..8bd7999c17c8772451f873966f8c9096
              cause = cause.getCause();
          }
 diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index 4437283a5d157eede121b98be0112c1067eded5e..fc9ec242743f755a1f0c9ec6bccd11c82375d655 100644
+index e738405e5112584e02e01df2d5ede2676fa1bffb..560d80cb1177297210646b44ce25fd2fa3766d40 100644
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
 @@ -68,6 +68,7 @@ public class Main {
@@ -100,19 +100,18 @@ index 4437283a5d157eede121b98be0112c1067eded5e..fc9ec242743f755a1f0c9ec6bccd11c8
          /* CraftBukkit start - Replace everything
          OptionParser optionParser = new OptionParser();
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae741beb75 100644
+index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f764967b5d3f5c 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -302,6 +302,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+     // Spigot end
+     public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
+     public volatile boolean abnormalExit; // Paper - Improved watchdog support
++    public volatile Thread shutdownThread; // Paper - Improved watchdog support
+     public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
      public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
      private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
-     public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
-+    public volatile Thread shutdownThread; // Paper
-+    public volatile boolean abnormalExit; // Paper
- 
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
-         ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -395,6 +397,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -466,6 +467,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
          */
          // Paper end
@@ -120,15 +119,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
          Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
          // CraftBukkit end
          this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
-@@ -879,6 +882,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     // CraftBukkit start
-     private boolean hasStopped = false;
-     private boolean hasLoggedStop = false; // Paper - Debugging
-+    public volatile boolean hasFullyShutdown = false; // Paper
-     private final Object stopLock = new Object();
-     public final boolean hasStopped() {
-         synchronized (this.stopLock) {
-@@ -894,6 +898,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -970,6 +972,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.hasStopped = true;
          }
          if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -139,10 +130,10 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
          // CraftBukkit end
          if (this.metricsRecorder.isRecording()) {
              this.cancelRecordingMetrics();
-@@ -966,6 +974,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
+@@ -1041,6 +1047,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+             ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
          }
-         // Spigot end
+         // Paper end - rewrite chunk system
 +        // Paper start - Improved watchdog support - move final shutdown items here
 +        Util.shutdownExecutors();
 +        try {
@@ -155,7 +146,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
      }
  
      public String getLocalIp() {
-@@ -1058,6 +1075,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1127,6 +1142,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      protected void runServer() {
          try {
@@ -163,7 +154,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
              if (!this.initServer()) {
                  throw new IllegalStateException("Failed to initialize server");
              }
-@@ -1068,6 +1086,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1137,6 +1153,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
              this.server.spark.enableBeforePlugins(); // Paper - spark
              // Spigot start
@@ -179,9 +170,9 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
 +            org.spigotmc.WatchdogThread.tick();
 +            // Paper end - Improved Watchdog Support
              org.spigotmc.WatchdogThread.hasStarted = true; // Paper
-             Arrays.fill( this.recentTps, 20 );
+             Arrays.fill(this.recentTps, 20);
              // Paper start - further improve server tick loop
-@@ -1157,6 +1186,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1233,6 +1260,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
              }
          } catch (Throwable var69) {
@@ -194,7 +185,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
              LOGGER.error("Encountered an unexpected exception", var69);
              CrashReport crashReport = constructOrExtractCrashReport(var69);
              this.fillSystemReport(crashReport.getSystemReport());
-@@ -1179,15 +1214,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1255,15 +1288,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.services.profileCache().clearExecutor();
                  }
  
@@ -214,7 +205,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
              }
          }
      }
-@@ -1291,6 +1326,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1367,6 +1400,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      @Override
      public TickTask wrapRunnable(Runnable runnable) {
@@ -227,7 +218,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
          return new TickTask(this.tickCount, runnable);
      }
  
-@@ -2087,7 +2128,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2164,7 +2203,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.resources.managers.updateStaticRegistryTags();
                      this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
                      this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
@@ -245,7 +236,7 @@ index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae
                      this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
                      this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 118f4ebb617d304e9a1cac2f9a853dc219a42456..ef39268caa59836506928582e88bc81e9fb22e88 100644
+index 55d3f79af2e683b983d4d3f731bb9649dfe76f59..d900469dafd430ec3eba10d6f83bd8759f7a7edf 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
 @@ -322,7 +322,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -266,7 +257,7 @@ index 118f4ebb617d304e9a1cac2f9a853dc219a42456..ef39268caa59836506928582e88bc81e
      }
  
      @Override
-@@ -727,7 +727,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+@@ -753,7 +753,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
      @Override
      public void stopServer() {
          super.stopServer();
@@ -276,7 +267,7 @@ index 118f4ebb617d304e9a1cac2f9a853dc219a42456..ef39268caa59836506928582e88bc81e
      }
  
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index d227714de0fe13544779fae6cf0e9ff6af5469c7..393bd2ec0962d3870f5b4cb74200e5467b50cdb8 100644
+index d322794c0d49daa212b8691f8f60f2276fe25a92..e5ae5e1161396280ffea1009f40dafa0398050bb 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -513,7 +513,7 @@ public abstract class PlayerList {
@@ -301,10 +292,10 @@ index 186c1b2e3599770385150eb7acdcd890aa5835eb..bfea9a2ae5e0bd5dae2873f715d192df
      }
  }
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index cb6ca60af3d3f90501e4693a78466b9f7462362d..127e25dab3a5e4df9cdf8eefd0485ea07b7696d9 100644
+index 2c7b6034852216fc5aa5c3f42a70ebd8e8317a17..3bf79eedfc358f54bfe23b5a75b3ad121558f6c6 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
-@@ -863,6 +863,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1498,6 +1498,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
          try {
              consumerEntity.accept(entity);
          } catch (Throwable var6) {
@@ -313,10 +304,10 @@ index cb6ca60af3d3f90501e4693a78466b9f7462362d..127e25dab3a5e4df9cdf8eefd0485ea0
              final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
              MinecraftServer.LOGGER.error(msg, var6);
 diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba..205f5a687eb685284a2e403f3eb6bdc694fc5423 100644
+index c1ae7755e8d6fa8501d2210dab7605d993c55722..b10890c5a7e42163e419e74596b952525c3ed3eb 100644
 --- a/net/minecraft/world/level/chunk/LevelChunk.java
 +++ b/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -855,6 +855,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -929,6 +929,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
  
                          profilerFiller.pop();
                      } catch (Throwable var5) {
diff --git a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch b/paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
similarity index 62%
rename from feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
rename to paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
index aea5521520..0f0a58ea62 100644
--- a/feature-patches/1062-Detail-more-information-in-watchdog-dumps.patch
+++ b/paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
@@ -7,89 +7,31 @@ Subject: [PATCH] Detail more information in watchdog dumps
 - Dump player name, player uuid, position, and world for packet handling
 
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index d3aebc7f833764351c8e5fe1fad1aa2f8718ca37..5a561c8f48f4b5f7a3077c21f5ddd19cbcaa6aac 100644
+index 460bb584db04b582f3297ae419183f430aff1ec0..c2d2bfa3c18daf27c163e5d11e8cea1f31b86c0a 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
-@@ -83,8 +83,72 @@ public final class FeatureHooks {
+@@ -93,9 +93,6 @@ public final class FeatureHooks {
+         ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(server, isLongTimeout); // Paper - rewrite chunk system
      }
  
-     public static void dumpTickingInfo() {
-+        java.util.logging.Logger log = org.bukkit.Bukkit.getServer().getLogger();
-+
-+        // ticking entities
-+        for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) {
-+            dumpEntity(entity);
-+            net.minecraft.world.entity.Entity vehicle = entity.getVehicle();
-+            if (vehicle != null) {
-+                log.log(java.util.logging.Level.SEVERE, "Detailing vehicle for above entity:");
-+                dumpEntity(vehicle);
-+            }
-+        }
-+
-+        // packet processors
-+        for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) {
-+            if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) {
-+                net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player;
-+                long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets();
-+                if (player == null) {
-+                    log.log(java.util.logging.Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener);
-+                    log.log(java.util.logging.Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
-+                } else {
-+                    dumpEntity(player);
-+                    net.minecraft.world.entity.Entity vehicle = player.getVehicle();
-+                    if (vehicle != null) {
-+                        log.log(java.util.logging.Level.SEVERE, "Detailing vehicle for above entity:");
-+                        dumpEntity(vehicle);
-+                    }
-+                    log.log(java.util.logging.Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
-+                }
-+            } else {
-+                log.log(java.util.logging.Level.SEVERE, "Handling packet for connection: " + packetListener);
-+            }
-+        }
+-    private static void dumpEntity(final Entity entity) {
+-    }
+-
+     public static org.bukkit.entity.Entity[] getChunkEntities(net.minecraft.server.level.ServerLevel world, int chunkX, int chunkZ) {
+         return world.getChunkEntities(chunkX, chunkZ); // Paper - rewrite chunk system
+     }
+@@ -184,4 +181,4 @@ public final class FeatureHooks {
+         ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setSendViewDistance(distance); // Paper - rewrite chunk system
      }
  
-     private static void dumpEntity(final Entity entity) {
-+        java.util.logging.Logger log = org.bukkit.Bukkit.getServer().getLogger();
-+        double posX, posY, posZ;
-+        net.minecraft.world.phys.Vec3 mot;
-+        double moveStartX, moveStartY, moveStartZ;
-+        net.minecraft.world.phys.Vec3 moveVec;
-+        synchronized (entity.posLock) {
-+            posX = entity.getX();
-+            posY = entity.getY();
-+            posZ = entity.getZ();
-+            mot = entity.getDeltaMovement();
-+            moveStartX = entity.getMoveStartX();
-+            moveStartY = entity.getMoveStartY();
-+            moveStartZ = entity.getMoveStartZ();
-+            moveVec = entity.getMoveVector();
-+        }
-+
-+        String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString();
-+        java.util.UUID entityUUID = entity.getUUID();
-+        net.minecraft.world.level.Level world = entity.level();
-+
-+        log.log(java.util.logging.Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName());
-+        log.log(java.util.logging.Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger());
-+        log.log(java.util.logging.Level.SEVERE, "Entity UUID: " + entityUUID);
-+        log.log(java.util.logging.Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
-+        log.log(java.util.logging.Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
-+        log.log(java.util.logging.Level.SEVERE, "Entity AABB: " + entity.getBoundingBox());
-+        if (moveVec != null) {
-+            log.log(java.util.logging.Level.SEVERE, "Move call information: ");
-+            log.log(java.util.logging.Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
-+            log.log(java.util.logging.Level.SEVERE, "Move vector: " + moveVec.toString());
-+        }
-     }
 -}
 \ No newline at end of file
 +}
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index 8fe485c5bf79804bb4d1f774f95a92b14a576e80..0bcae6256d3b3fb6b2e0c2f23907d4659b236ef3 100644
+index bfdc637a750602c00919422ca0e3943ba34db832..208efae06c7c44f220d4192219a86ec55c98a2fe 100644
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
-@@ -603,7 +603,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -601,7 +601,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
              if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
                  || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
                  || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
@@ -153,11 +95,11 @@ index e65c62dbe4c1560ae153e4c4344e9194c783a2f4..4535858701b2bb232b9d2feb2af65515
 +    // Paper end - detailed watchdog information
  }
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 9cc47bda7197ca3f63b0ede9905c9a13931f84ed..05f45b490e823a455b23b23b26a7da3b447059ea 100644
+index 131ebdaec9ff09635689001e3b85bbe5845fbf98..9caa06f09409d36abf9e0a770ba004f4049e8e09 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -956,7 +956,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         }
+@@ -1239,7 +1239,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ 
      }
  
 +    // Paper start - log detailed entity tick information
@@ -183,7 +125,7 @@ index 9cc47bda7197ca3f63b0ede9905c9a13931f84ed..05f45b490e823a455b23b23b26a7da3b
          entity.setOldPosAndRot();
          ProfilerFiller profilerFiller = Profiler.get();
          entity.tickCount++;
-@@ -972,6 +991,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1255,6 +1274,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          for (Entity entity1 : entity.getPassengers()) {
              this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
          }
@@ -198,10 +140,10 @@ index 9cc47bda7197ca3f63b0ede9905c9a13931f84ed..05f45b490e823a455b23b23b26a7da3b
  
      private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..1ff09959aa95d9822ccb6724bbb3f441c768511a 100644
+index 3fd7f6bcdeff271a9843b2f2454f92d92069f539..3cefe3de62e3d6af7b514eb2f3df8e63c5aa5c1f 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
-@@ -956,8 +956,43 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1062,8 +1062,43 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return this.onGround;
      }
  
@@ -245,7 +187,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..1ff09959aa95d9822ccb6724bbb3f441
          if (this.noPhysics) {
              this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
          } else {
-@@ -1075,6 +1110,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1181,6 +1216,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
                  profilerFiller.pop();
              }
          }
@@ -259,7 +201,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..1ff09959aa95d9822ccb6724bbb3f441
      }
  
      private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
-@@ -4348,7 +4390,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4643,7 +4685,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public void setDeltaMovement(Vec3 deltaMovement) {
@@ -269,7 +211,7 @@ index 5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688..1ff09959aa95d9822ccb6724bbb3f441
      }
  
      public void addDeltaMovement(Vec3 addend) {
-@@ -4445,7 +4489,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4749,7 +4793,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          }
          // Paper end - Fix MC-4
          if (this.position.x != x || this.position.y != y || this.position.z != z) {
diff --git a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch b/paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch
similarity index 99%
rename from feature-patches/1063-Entity-load-save-limit-per-chunk.patch
rename to paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch
index a931c2cffe..7f3828e1a2 100644
--- a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch
+++ b/paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch
@@ -8,7 +8,6 @@ to a chunk. The default values of -1 disable the limit. Although
 defaults are only included for certain entites, this allows setting
 limits for any entity type.
 
-
 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
 index 7aea4e343581b977d11af90f9f65eac3532eade1..d21ce54ebb5724c04eadf56a2cde701d5eeb5db2 100644
 --- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
diff --git a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
similarity index 99%
rename from feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
rename to paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
index 83577de6ed..e35d6a31a9 100644
--- a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
+++ b/paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
@@ -9,7 +9,6 @@ we instead drop the current regionfile header and recalculate -
 hoping that at least then we don't swap chunks, and maybe recover
 them all.
 
-
 diff --git a/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/net/minecraft/world/level/chunk/storage/RegionBitmap.java
 index 64a718c98f799c62a5bb28e1e8e5f66cc96c915d..666f2e967c99f78422c83fb20e1a3bf3efa7845e 100644
 --- a/net/minecraft/world/level/chunk/storage/RegionBitmap.java
diff --git a/feature-patches/1066-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
similarity index 85%
rename from feature-patches/1066-Incremental-chunk-and-player-saving.patch
rename to paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
index 17ee1796ca..282b9eb257 100644
--- a/feature-patches/1066-Incremental-chunk-and-player-saving.patch
+++ b/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
 
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203..a0a75c84379432ccc525ab22d476c358c77f663b 100644
+index d077debf5936050484856e0b84f764967b5d3f5c..75efd8f11a42696319c0908b7cf911cf976a31b6 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -862,7 +862,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -940,7 +940,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          boolean var4;
          try {
              this.isSaving = true;
@@ -17,7 +17,7 @@ index f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203..a0a75c84379432ccc525ab22d476c358
              var4 = this.saveAllChunks(suppressLog, flush, forced);
          } finally {
              this.isSaving = false;
-@@ -1409,9 +1409,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1530,9 +1530,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
  
          this.ticksUntilAutosave--;
@@ -50,10 +50,10 @@ index f4fba4e2d12c7ab4b4eb9858cd738a9678a2d203..a0a75c84379432ccc525ab22d476c358
          ProfilerFiller profilerFiller = Profiler.get();
          this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index c38eda42b33cfa4792625f40ebde6f30e591119b..c8129f0d8218daff9123f1ad2d8ca321a02e1c7e 100644
+index 9caa06f09409d36abf9e0a770ba004f4049e8e09..fc3b0db0f0a85fb42cb5fa8a1d78d4c4691ab519 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1007,6 +1007,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1316,6 +1316,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos);
      }
  
@@ -73,29 +73,29 @@ index c38eda42b33cfa4792625f40ebde6f30e591119b..c8129f0d8218daff9123f1ad2d8ca321
 +            ServerLevel serverLevel1 = this;
 +            this.serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
 +            this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
-+            this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
++            this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
 +        }
 +        // CraftBukkit end
 +    }
 +    // Paper end - Incremental chunk and player saving
 +
      public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
-         ServerChunkCache chunkSource = this.getChunkSource();
-         if (!skipSave) {
+         // Paper start - add close param
+         this.save(progress, flush, skipSave, false);
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 92bd46cca1956f327fb0b407e988d68782f441a4..0f00db82e85c9e510c2e4fe4065291971c408dad 100644
+index bf4deeac50197eeb83c5b1e458b609aac5ad8a97..a97b0b177a1fb0557af2af4d1f192513d7c0390d 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -180,6 +180,7 @@ import org.slf4j.Logger;
  
- public class ServerPlayer extends Player {
+ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system
      private static final Logger LOGGER = LogUtils.getLogger();
 +    public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
      private static final int FLY_STAT_RECORDING_SPEED = 25;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index bafeeab3edbc73f6f86474e18ab4a3d96ce17157..aaa6b8eee7b34fe6efa76f1fe997dcece827d5dd 100644
+index e5ae5e1161396280ffea1009f40dafa0398050bb..d74242a0d25c906d74b018c24d4b21d64415617d 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -483,6 +483,7 @@ public abstract class PlayerList {
diff --git a/feature-patches/1067-Optimise-general-POI-access.patch b/paper-server/patches/features/0033-Optimise-general-POI-access.patch
similarity index 99%
rename from feature-patches/1067-Optimise-general-POI-access.patch
rename to paper-server/patches/features/0033-Optimise-general-POI-access.patch
index 9b56c38a8e..7387de347a 100644
--- a/feature-patches/1067-Optimise-general-POI-access.patch
+++ b/paper-server/patches/features/0033-Optimise-general-POI-access.patch
@@ -30,7 +30,6 @@ This patch also specifically optimises other areas of code to
 use PoiAccess. For example, some villager AI and portaling code
 had to be specifically modified.
 
-
 diff --git a/io/papermc/paper/util/PoiAccess.java b/io/papermc/paper/util/PoiAccess.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..f39294b1f83c4022be5ced4da781103a1eee2daf
diff --git a/feature-patches/1071-Optional-per-player-mob-spawns.patch b/paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch
similarity index 92%
rename from feature-patches/1071-Optional-per-player-mob-spawns.patch
rename to paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch
index 2604d7bb97..6ee8bdf5ed 100644
--- a/feature-patches/1071-Optional-per-player-mob-spawns.patch
+++ b/paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch
@@ -5,11 +5,11 @@ Subject: [PATCH] Optional per player mob spawns
 
 
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 809f3fe1285e347f18709c2368923fcc8f953ded..6c5b2eb411fb60babbb0c74d5c075696ef70b38d 100644
+index ff6503bf8eb88d1264c3d848a89d0255b4b3ae68..9eed24939fc09f00a9dbce1be2ab9c34d024fd29 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
-@@ -237,11 +237,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-         this.chunksToEagerlySave.add(chunkPos.toLong());
+@@ -236,11 +236,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+         // Paper - rewrite chunk system
      }
  
 -    // Paper start
@@ -43,19 +43,19 @@ index 809f3fe1285e347f18709c2368923fcc8f953ded..6c5b2eb411fb60babbb0c74d5c075696
      protected ChunkGenerator generator() {
          return this.worldGenContext.generator();
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..078f208e104a652ce48458150389d19ede6808ef 100644
+index 87d4291a3944f706a694536da6de0f28c548ab8d..5576bf1d1d70ab7a010653d3207909b5de867e70 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
-@@ -435,7 +435,7 @@ public class ServerChunkCache extends ChunkSource {
-                     profilerFiller.push("filteringTickingChunks");
-                     this.collectTickingChunks(list);
+@@ -517,7 +517,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
                      profilerFiller.popPush("shuffleChunks");
--                    Util.shuffle(list, this.level.random);
+                     // Paper start - chunk tick iteration optimisation
+                     this.shuffleRandom.setSeed(this.level.random.nextLong());
+-                    Util.shuffle(list, this.shuffleRandom);
 +                    if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled
+                     // Paper end - chunk tick iteration optimisation
                      this.tickChunks(profilerFiller, l, list);
                      profilerFiller.pop();
-                 } finally {
-@@ -474,9 +474,18 @@ public class ServerChunkCache extends ChunkSource {
+@@ -571,9 +571,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
      private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
          profiler.popPush("naturalSpawnCount");
          int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
@@ -78,10 +78,10 @@ index 2f49dbc919f7f5eea9abce6106723c72f5ae45fb..078f208e104a652ce48458150389d19e
          profiler.popPush("spawnAndTick");
          boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 0f00db82e85c9e510c2e4fe4065291971c408dad..dab58457ed02d3f8153c07de101262b1a0182d71 100644
+index a97b0b177a1fb0557af2af4d1f192513d7c0390d..4f8ef57d66a4562df0f5447988797cbdfbd3c9d5 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -368,6 +368,10 @@ public class ServerPlayer extends Player {
+@@ -368,6 +368,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
      public boolean queueHealthUpdatePacket;
      public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
      // Paper end - cancellable death event
@@ -93,7 +93,7 @@ index 0f00db82e85c9e510c2e4fe4065291971c408dad..dab58457ed02d3f8153c07de101262b1
      public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
      public String displayName;
 diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
-index 6e6e028621ccc4597b2c24f54f53cb7f3de603e2..14e99450a8522b79e4c3805bd91439a950bc8f99 100644
+index 913ea92ace9d610c25bf28f703a3b227044aea63..ef8bacbbb43a9b80281a313ca43b7efff5a93e03 100644
 --- a/net/minecraft/world/level/NaturalSpawner.java
 +++ b/net/minecraft/world/level/NaturalSpawner.java
 @@ -72,6 +72,14 @@ public final class NaturalSpawner {
diff --git a/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
similarity index 88%
rename from feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
rename to paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
index d94b29471c..a3eacdf9c5 100644
--- a/feature-patches/1072-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
+++ b/paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
@@ -6,10 +6,10 @@ Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob
 
 
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 6c5b2eb411fb60babbb0c74d5c075696ef70b38d..cf439285e4ba9babda228c36aa81dfc49db2c22a 100644
+index 9eed24939fc09f00a9dbce1be2ab9c34d024fd29..b3f498558614243cf633dcd71e3c49c2c55e6e0f 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
-@@ -256,8 +256,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -255,8 +255,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
          }
      }
  
@@ -37,10 +37,10 @@ index 6c5b2eb411fb60babbb0c74d5c075696ef70b38d..cf439285e4ba9babda228c36aa81dfc4
      // Paper end - Optional per player mob spawns
  
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index 078f208e104a652ce48458150389d19ede6808ef..aa141c00a41d49daee8e4ab7be70ce4e4767b105 100644
+index 5576bf1d1d70ab7a010653d3207909b5de867e70..6540b2d6a1062d883811ce240c49d30d1925b291 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
-@@ -479,7 +479,17 @@ public class ServerChunkCache extends ChunkSource {
+@@ -576,7 +576,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
          if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
              // re-set mob counts
              for (ServerPlayer player : this.level.players) {
@@ -60,10 +60,10 @@ index 078f208e104a652ce48458150389d19ede6808ef..aa141c00a41d49daee8e4ab7be70ce4e
              spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
          } else {
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index dab58457ed02d3f8153c07de101262b1a0182d71..2d20f42fbcfb67845323d994843d7b977aa867e0 100644
+index 4f8ef57d66a4562df0f5447988797cbdfbd3c9d5..e350c6ba7bd638d27abe34afd375903e603ad682 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -372,6 +372,7 @@ public class ServerPlayer extends Player {
+@@ -372,6 +372,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
      public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length;
      public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS];
      // Paper end - Optional per player mob spawns
@@ -72,7 +72,7 @@ index dab58457ed02d3f8153c07de101262b1a0182d71..2d20f42fbcfb67845323d994843d7b97
      public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection;
      public String displayName;
 diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
-index 5e82a8fdaec5a6750040ebb687aa35bba4dcc2ba..23f7fb22906e49babc7784f4b3d1f8ea8e187b1d 100644
+index ef8bacbbb43a9b80281a313ca43b7efff5a93e03..17ce115e887cbbb06ad02ab7ddb488e27342c0e4 100644
 --- a/net/minecraft/world/level/NaturalSpawner.java
 +++ b/net/minecraft/world/level/NaturalSpawner.java
 @@ -285,6 +285,11 @@ public final class NaturalSpawner {
diff --git a/feature-patches/2017-Optimize-Hoppers.patch b/paper-server/patches/features/0036-Optimize-Hoppers.patch
similarity index 98%
rename from feature-patches/2017-Optimize-Hoppers.patch
rename to paper-server/patches/features/0036-Optimize-Hoppers.patch
index c501740535..eb195ce201 100644
--- a/feature-patches/2017-Optimize-Hoppers.patch
+++ b/paper-server/patches/features/0036-Optimize-Hoppers.patch
@@ -48,22 +48,22 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
 +    }
 +}
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index d450d4af96716caff4b29a84d1d83ec4010854f0..8657e4fd7c5e0e23b69d9a982408a7d038f0a787 100644
+index 75efd8f11a42696319c0908b7cf911cf976a31b6..b7bb1b6b2c3c892712c44b4e006d9ceebbd8491a 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -1563,6 +1563,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-         for (ServerLevel serverLevel : this.getAllLevels()) {
+@@ -1704,6 +1704,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
+             serverLevel.updateLagCompensationTick(); // Paper - lag compensation
 +            net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
              profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
              /* Drop global time updates
              if (this.tickCount % 20 == 0) {
 diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
-index 50cd12def88c9449cad8875c553f5ed9ef1cd791..3d93bb1aac5ad4830fc1dceddb6bebacee28f72a 100644
+index c255e11cb0981bd7e0456d4fd401beb5257be597..d6361863d6a1e364de262d6199373cbd68d1c699 100644
 --- a/net/minecraft/world/item/ItemStack.java
 +++ b/net/minecraft/world/item/ItemStack.java
-@@ -815,10 +815,16 @@ public final class ItemStack implements DataComponentHolder {
+@@ -808,10 +808,16 @@ public final class ItemStack implements DataComponentHolder {
      }
  
      public ItemStack copy() {
@@ -103,7 +103,7 @@ index 2ebdf1ad323bb53dfe9eed319e25856b35a1443c..77618757c0e678532dbab814aceed83f
          }
      }
 diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-index 60e1e44f328e66d52ebf08476b533fef83bc5eba..eb02249b518c2d262315c4cd5965bec5d81166a1 100644
+index e58a32593e8b42bfc534d13457240860293dd3f4..5cd1326ad5d046c88b2b3449d610a78fa880b4cd 100644
 --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
 @@ -139,18 +139,56 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
diff --git a/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch
similarity index 92%
rename from feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch
rename to paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch
index 9f8d28a29d..a2b8f8a47e 100644
--- a/feature-patches/1070-Optimise-collision-checking-in-player-move-packet-ha.patch
+++ b/paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch
@@ -1,13 +1,15 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
 Date: Thu, 2 Jul 2020 12:02:43 -0700
 Subject: [PATCH] Optimise collision checking in player move packet handling
 
 Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
 
 diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f82dbbefb 100644
+index e4869774b2a8096467e5913d73af5bde93dbcccf..e3c855b9335f3d86ba933e7acdd3fa2981919c99 100644
 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -562,7 +562,7 @@ public class ServerGamePacketListenerImpl
+@@ -561,7 +561,7 @@ public class ServerGamePacketListenerImpl
                      return;
                  }
  
@@ -16,7 +18,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                  d3 = d - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
                  d4 = d1 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
                  d5 = d2 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
-@@ -572,6 +572,7 @@ public class ServerGamePacketListenerImpl
+@@ -571,6 +571,7 @@ public class ServerGamePacketListenerImpl
                  }
  
                  rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
@@ -24,7 +26,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                  d3 = d - rootVehicle.getX();
                  d4 = d1 - rootVehicle.getY();
                  if (d4 > -0.5 || d4 < 0.5) {
-@@ -582,14 +583,22 @@ public class ServerGamePacketListenerImpl
+@@ -581,14 +582,22 @@ public class ServerGamePacketListenerImpl
                  d7 = d3 * d3 + d4 * d4 + d5 * d5;
                  boolean flag2 = false;
                  if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
@@ -50,7 +52,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                      rootVehicle.absMoveTo(x, y, z, f, f1);
                      this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
                      this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
-@@ -667,9 +676,32 @@ public class ServerGamePacketListenerImpl
+@@ -666,9 +675,32 @@ public class ServerGamePacketListenerImpl
      }
  
      private boolean noBlocksAround(Entity entity) {
@@ -86,7 +88,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
      }
  
      @Override
-@@ -1361,7 +1393,7 @@ public class ServerGamePacketListenerImpl
+@@ -1360,7 +1392,7 @@ public class ServerGamePacketListenerImpl
                                  }
                              }
  
@@ -95,7 +97,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                              d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
                              d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
                              d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
-@@ -1400,6 +1432,7 @@ public class ServerGamePacketListenerImpl
+@@ -1399,6 +1431,7 @@ public class ServerGamePacketListenerImpl
                              boolean flag1 = this.player.verticalCollisionBelow;
                              this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
                              this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
@@ -103,7 +105,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                              // Paper start - prevent position desync
                              if (this.awaitingPositionFromClient != null) {
                                  return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1431,7 +1464,17 @@ public class ServerGamePacketListenerImpl
+@@ -1430,7 +1463,17 @@ public class ServerGamePacketListenerImpl
                              }
  
                              // Paper start - Add fail move event
@@ -122,7 +124,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                              if (teleportBack) {
                                  io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
                                      toX, toY, toZ, toYaw, toPitch, false);
-@@ -1567,7 +1610,7 @@ public class ServerGamePacketListenerImpl
+@@ -1566,7 +1609,7 @@ public class ServerGamePacketListenerImpl
  
      private boolean updateAwaitingTeleport() {
          if (this.awaitingPositionFromClient != null) {
@@ -131,7 +133,7 @@ index 083585a6ab84394a2bc2de6f317c69c7b596e917..b7365e8b410a9e37257cf0a4e79a6f4f
                  this.awaitingTeleportTime = this.tickCount;
                  this.teleport(
                      this.awaitingPositionFromClient.x,
-@@ -1586,6 +1629,33 @@ public class ServerGamePacketListenerImpl
+@@ -1585,6 +1628,33 @@ public class ServerGamePacketListenerImpl
          }
      }
  

From 051ab9117a681907da5be7424754d162bac0bed2 Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Fri, 20 Dec 2024 23:39:43 +0100
Subject: [PATCH 267/285] fix more diff

---
 .../minecraft/server/network/LegacyQueryHandler.java.patch    | 4 ++--
 .../server/network/ServerGamePacketListenerImpl.java.patch    | 2 +-
 .../server/network/ServerLoginPacketListenerImpl.java.patch   | 3 +--
 .../net/minecraft/server/players/StoredUserList.java.patch    | 2 +-
 .../src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java | 2 +-
 .../main/java/org/bukkit/craftbukkit/entity/CraftStrider.java | 2 +-
 6 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
index 31868aa929..9bda2a7540 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -34,7 +34,7 @@
                  if (i == 0) {
 -                    LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
 -                    String string = createVersion0Response(this.server);
-+                    LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress: "<ip address withheld>"); // Paper - Respect logIPs option
++                    LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress : "<ip address withheld>"); // Paper - Respect logIPs option
 +                    // Paper start - Call PaperServerListPingEvent and use results
 +                    com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null);
 +                    if (event == null) {
@@ -157,7 +157,7 @@
 +        buf.release();
 +        this.buf = null;
 +
-+        LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): "<ip address withheld>"); // Paper - Respect logIPs option
++        LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress() : "<ip address withheld>"); // Paper - Respect logIPs option
 +
 +        net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer();
 +        java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port);
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 565e6dcb4b..c4a14444de 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -740,7 +740,7 @@
 -                                        LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
 -                                        this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot());
 -                                        return;
-+                                    if (d7 - d6 > Math.max(f2, Mth.square((org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed)))) {
++                                    if (d7 - d6 > Math.max(f2, Mth.square(org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed))) {
 +                                        // CraftBukkit end
 +                                        // Paper start - Add fail move event
 +                                        io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY,
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
index 47090dba31..4360d8653f 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -293,7 +293,6 @@
  
      @Override
      public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) {
--        this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
 +        // Paper start - Add Velocity IP Forwarding Support
 +        if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) {
 +            ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload();
@@ -338,7 +337,7 @@
 +            return;
 +        }
 +        // Paper end - Add Velocity IP Forwarding Support
-+        // this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
+         this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
      }
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
index 1e359d0ca2..cfe6571868 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch
@@ -70,8 +70,8 @@
                  }
 +            // Spigot start
 +            } catch (com.google.gson.JsonParseException | NullPointerException ex) {
-+                LOGGER.warn("Unable to read file {}, backing it up to {0}.backup and creating new copy.", this.file, ex);
 +                File backup = new File(this.file + ".backup");
++                LOGGER.warn("Unable to read file {}, backing it up to {} and creating new copy.", this.file, backup, ex);
 +                this.file.renameTo(backup);
 +                this.file.delete();
              }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java
index 8016c810ae..fd4f13e8ea 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java
@@ -45,7 +45,7 @@ public class CraftPig extends CraftAnimals implements Pig {
         }
 
         int max = this.getHandle().steering.boostTimeTotal();
-        Preconditions.checkArgument(ticks >= 0 && ticks <= max, "boost ticks must not exceed 0 or %d (inclusive)", max);
+        Preconditions.checkArgument(ticks >= 0 && ticks <= max, "boost ticks must not exceed 0 or %s (inclusive)", max);
 
         this.getHandle().steering.boostTime = ticks;
     }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java
index 9472a6f9c9..74fac97231 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java
@@ -55,7 +55,7 @@ public class CraftStrider extends CraftAnimals implements Strider {
         }
 
         int max = this.getHandle().steering.boostTimeTotal();
-        Preconditions.checkArgument(ticks >= 0 && ticks <= max, "boost ticks must not exceed 0 or %d (inclusive)", max);
+        Preconditions.checkArgument(ticks >= 0 && ticks <= max, "boost ticks must not exceed 0 or %s (inclusive)", max);
 
         this.getHandle().steering.boostTime = ticks;
     }

From a18b1b7ef1dd6d16a4224fca17d56733b5fb7852 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 20 Dec 2024 15:07:43 -0800
Subject: [PATCH 268/285] Add capabilities for old API coordinates

This will cause Gradle to throw a selection error when for example paper-api and spigot-api are on the classpath. Users will need to add selection rules or excludes for certain transitive deps to solve this rather than build with a broken classpath.
---
 paper-api/build.gradle.kts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts
index 5608f8d01d..7dc323ba31 100644
--- a/paper-api/build.gradle.kts
+++ b/paper-api/build.gradle.kts
@@ -121,8 +121,13 @@ configurations {
             }
             outgoing {
                 capability(mainCapability)
+                // Paper-MojangAPI has been merged into Paper-API
                 capability("io.papermc.paper:paper-mojangapi:${project.version}")
                 capability("com.destroystokyo.paper:paper-mojangapi:${project.version}")
+                // Conflict with old coordinates
+                capability("com.destroystokyo.paper:paper-api:${project.version}")
+                capability("org.spigotmc:spigot-api:${project.version}")
+                capability("org.bukkit:bukkit:${project.version}")
             }
         }
     }

From 1dc6ad1df07e018ee68d95464022285f27fff916 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 00:16:45 +0100
Subject: [PATCH 269/285] Fix vehicle movement check

---
 .../network/ServerGamePacketListenerImpl.java.patch       | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index c4a14444de..c53210937f 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -211,9 +211,9 @@
 +                double currDeltaY = toY - y;
 +                double currDeltaZ = toZ - z;
 +                d7 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
-+                double otherFieldX = d3 - this.vehicleLastGoodX;
-+                double otherFieldY = d4 - this.vehicleLastGoodY;
-+                double otherFieldZ = d5 - this.vehicleLastGoodZ;
++                double otherFieldX = toX - this.vehicleLastGoodX;
++                double otherFieldY = toY - this.vehicleLastGoodY;
++                double otherFieldZ = toZ - this.vehicleLastGoodZ;
 +                d7 = Math.max(d7, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
 +                // Paper end - fix large move vectors killing the server
 +
@@ -251,7 +251,7 @@
 +                    return;
 +                }
 +                // Paper end - Prevent moving into unloaded chunks
-+                if (d7 - d6 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
++                if (d7 - d6 > Math.max(100.0, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
 +                    // CraftBukkit end
                      LOGGER.warn(
                          "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5

From 85c428b0bee7983ba7858aad9196cb2bf9e10233 Mon Sep 17 00:00:00 2001
From: Isaac - The456 <the456@the456gamer.dev>
Date: Sat, 21 Dec 2024 09:25:14 +0000
Subject: [PATCH 270/285] Fix unintentional change to default
 "overridePermissions" argument (#11759)

---
 .../net/minecraft/commands/arguments/EntityArgument.java.patch  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
index e2b9c3d155..d39db32c3a 100644
--- a/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
+++ b/paper-server/patches/sources/net/minecraft/commands/arguments/EntityArgument.java.patch
@@ -5,7 +5,7 @@
  
      private EntitySelector parse(StringReader reader, boolean allowSelectors) throws CommandSyntaxException {
 +        // CraftBukkit start
-+        return this.parse(reader, allowSelectors, true);
++        return this.parse(reader, allowSelectors, false);
 +    }
 +    public EntitySelector parse(StringReader reader, boolean allowSelectors, boolean overridePermissions) throws CommandSyntaxException {
 +        // CraftBukkit end

From e0593e9286838e696db5efd2883cf8ef3d41528e Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 12:15:25 +0100
Subject: [PATCH 271/285] More diff/changed variable name cleanup

---
 ...item-frames-performance-and-bug-fixe.patch |   6 +-
 .../patches/features/0007-Anti-Xray.patch     |  10 +-
 .../0021-Moonrise-optimisation-patches.patch  |  30 +--
 .../0023-Lag-compensation-ticks.patch         |   6 +-
 .../0028-Improved-Watchdog-Support.patch      |  22 +-
 ...-Incremental-chunk-and-player-saving.patch |  14 +-
 .../features/0036-Optimize-Hoppers.patch      |   4 +-
 .../server/MinecraftServer.java.patch         | 217 +++++++++---------
 .../server/level/ServerPlayer.java.patch      |  29 +--
 .../ServerGamePacketListenerImpl.java.patch   |  16 +-
 .../server/players/PlayerList.java.patch      |  18 +-
 .../minecraft/world/entity/Entity.java.patch  |   6 +-
 .../org/bukkit/craftbukkit/CraftServer.java   | 183 ++++++++-------
 13 files changed, 284 insertions(+), 277 deletions(-)

diff --git a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index 77da1105f4..a547df5f1b 100644
--- a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -13,7 +13,7 @@ custom renderers are in use, defaulting to the much simpler Vanilla system.
 Additionally, numerous issues to player position tracking on maps has been fixed.
 
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..cce78d73e8adafd66d0f3ffb3fabb5e6c025c7df 100644
+index 1f66adcd70aae7eac3d9f9539163905695581631..34a53bf34d10c56e6f53ce9aab2fc2780509f2f1 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -2282,7 +2282,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -28,10 +28,10 @@ index 678b3027e8c53e6021ea49afa69cdbe5f60dcf25..cce78d73e8adafd66d0f3ffb3fabb5e6
                  }
              }
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 68e3282a0aa23bd41ab7c77be287d2b49461e33c..def43c515030412b147afd6049b100a3153733d2 100644
+index 36e0d3f225a71b75ece7bf3fceeba47af948a6df..ff5889f8fed0707a6654d9d21862e32e2ebc866d 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -2607,6 +2607,14 @@ public class ServerPlayer extends Player {
+@@ -2594,6 +2594,14 @@ public class ServerPlayer extends Player {
                  this.awardStat(Stats.DROP);
              }
  
diff --git a/paper-server/patches/features/0007-Anti-Xray.patch b/paper-server/patches/features/0007-Anti-Xray.patch
index bb7e6f43a4..46c23e31dd 100644
--- a/paper-server/patches/features/0007-Anti-Xray.patch
+++ b/paper-server/patches/features/0007-Anti-Xray.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Anti-Xray
 
 
 diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index 1e5d94cdcdffb4d2940f17bacdf0e6a488e84318..d3aebc7f833764351c8e5fe1fad1aa2f8718ca37 100644
+index b97e0a8d4c429776b86def10739faee089e2bc9b..184e6c6fe2ba522d0ea0774604839320c4152371 100644
 --- a/io/papermc/paper/FeatureHooks.java
 +++ b/io/papermc/paper/FeatureHooks.java
 @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.longs.LongSets;
@@ -153,7 +153,7 @@ index 3a384175f8e7f204234bbaf3081bdc20c47a0d4b..5699bc15eba92e22433a20cb8326b59f
  
      private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 7702004b68b7735043914f93b54b4413cd21ba41..4d20bda4cba578c47216d450c99389b744a59008 100644
+index a43b8febcf616aa1662ea126ce60f7973799ea46..cdda7f6272cfc48638df4e0e51b496e91ed77ba5 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -348,7 +348,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -166,7 +166,7 @@ index 7702004b68b7735043914f93b54b4413cd21ba41..4d20bda4cba578c47216d450c99389b7
          this.levelStorageAccess = levelStorageAccess;
          this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index 23d241e98f37979701f80fb6f7b76954bd699ad6..fd7ad2b1bffe3880def0f0c9a7ed8de5088ecd71 100644
+index 1178af2ba50ad71556e0a5cbde3e5dc290396148..bf2a4c03afb73367a6d2530c78ff9f7c06f7f6a6 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
 @@ -298,6 +298,7 @@ public class ServerPlayerGameMode {
@@ -196,10 +196,10 @@ index 342bc843c384761e883de861044f4f8930ae8763..14878690a88fd4de3e2c127086607e6c
          if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
              new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 8a93d1034eb0dab4b4010d52ddbb0caa5697416d..bafeeab3edbc73f6f86474e18ab4a3d96ce17157 100644
+index 0837c732e60d82c36793a5a030f56d9004a5a83f..b0df94fb5f933491af8b024cc8ecf8b98e448f16 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -404,7 +404,7 @@ public abstract class PlayerList {
+@@ -403,7 +403,7 @@ public abstract class PlayerList {
                      .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
              player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
                      new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, player.chunkPosition(), plains),
diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
index 8bdfa4ff3f..4e71dab65e 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
@@ -23551,7 +23551,7 @@ index 47c62090b421ebea1253ee3f1c896ed84119cea6..e738405e5112584e02e01df2d5ede267
                  thread1 -> {
                      DedicatedServer dedicatedServer1 = new DedicatedServer(
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc8040c2bb 100644
+index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae6d15ef2c 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -23641,7 +23641,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
      public MinecraftServer(
          // CraftBukkit start
          joptsimple.OptionSet options,
-@@ -628,7 +699,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -648,7 +719,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          this.forceDifficulty();
          for (ServerLevel serverLevel : this.getAllLevels()) {
              this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
@@ -23650,7 +23650,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
              this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
          }
  
-@@ -827,6 +898,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -847,6 +918,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public abstract boolean shouldRconBroadcast();
  
      public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
@@ -23662,7 +23662,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
          boolean flag = false;
  
          for (ServerLevel serverLevel : this.getAllLevels()) {
-@@ -834,7 +910,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -854,7 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
              }
  
@@ -23671,7 +23671,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
              flag = true;
          }
  
-@@ -925,7 +1001,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -945,7 +1021,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              }
          }
  
@@ -23680,7 +23680,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
              this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
  
              for (ServerLevel serverLevelx : this.getAllLevels()) {
-@@ -936,17 +1012,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -956,17 +1032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.waitUntilNextTick();
          }
  
@@ -23699,7 +23699,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
  
          this.isSaving = false;
          this.resources.close();
-@@ -966,6 +1032,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -986,6 +1052,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
          // Spigot end
@@ -23714,7 +23714,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
      }
  
      public String getLocalIp() {
-@@ -1130,6 +1204,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1150,6 +1224,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      profilerFiller.push("tick");
                      this.tickFrame.start();
                      this.tickServer(flag ? () -> false : this::haveTime);
@@ -23728,7 +23728,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
                      this.tickFrame.end();
                      profilerFiller.popPush("nextTickWait");
                      this.mayHaveDelayedTasks = true;
-@@ -1302,6 +1383,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1322,6 +1403,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      private boolean pollTaskInternal() {
          if (super.pollTask()) {
@@ -23736,7 +23736,7 @@ index 5649482a8b85056bc009b868e19ca11f21d59fbf..c3318c2fa121d75363c6bc9eadf408dc
              return true;
          } else {
              boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
-@@ -2422,6 +2504,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2442,6 +2524,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
      }
  
@@ -27495,7 +27495,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
      }
  
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 6238729e91ae4fd44a4e0bc73d4f042498c4cb8d..bf4deeac50197eeb83c5b1e458b609aac5ad8a97 100644
+index ff5889f8fed0707a6654d9d21862e32e2ebc866d..e61fe83479f095e8addbd3e8f1d5179c998ae1eb 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -178,7 +178,7 @@ import net.minecraft.world.scores.Team;
@@ -27975,10 +27975,10 @@ index 4eb040006f5d41b47e5ac9df5d9f19c4315d6343..7fa41dea184b01891f45d8e404bc1cba
          this.generatingStep = generatingStep;
          this.cache = cache;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index bafeeab3edbc73f6f86474e18ab4a3d96ce17157..d322794c0d49daa212b8691f8f60f2276fe25a92 100644
+index b0df94fb5f933491af8b024cc8ecf8b98e448f16..0a2f4af548561144607d24d3bd92270ce630fc95 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -1318,7 +1318,7 @@ public abstract class PlayerList {
+@@ -1317,7 +1317,7 @@ public abstract class PlayerList {
  
      public void setViewDistance(int viewDistance) {
          this.viewDistance = viewDistance;
@@ -27987,7 +27987,7 @@ index bafeeab3edbc73f6f86474e18ab4a3d96ce17157..d322794c0d49daa212b8691f8f60f227
  
          for (ServerLevel serverLevel : this.server.getAllLevels()) {
              if (serverLevel != null) {
-@@ -1329,7 +1329,7 @@ public abstract class PlayerList {
+@@ -1328,7 +1328,7 @@ public abstract class PlayerList {
  
      public void setSimulationDistance(int simulationDistance) {
          this.simulationDistance = simulationDistance;
@@ -28371,7 +28371,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 069d0d0ddeceb0de2300a3354fed218407d88938..3fd7f6bcdeff271a9843b2f2454f92d92069f539 100644
+index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0ebdfc56a 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
diff --git a/paper-server/patches/features/0023-Lag-compensation-ticks.patch b/paper-server/patches/features/0023-Lag-compensation-ticks.patch
index c764bdee4a..0e33a6864f 100644
--- a/paper-server/patches/features/0023-Lag-compensation-ticks.patch
+++ b/paper-server/patches/features/0023-Lag-compensation-ticks.patch
@@ -8,7 +8,7 @@ Areas affected by lag comepnsation:
  - Eating food items
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 0c35921acebd88f3a9a37676e47e7482dfea6d9c..1ff57c17f7fe3af3fb7fc5fbc5148ca333b5e618 100644
+index 9c5305b4542483efeeeab19a79eb8eae6d15ef2c..ddd23354d68f5cbdc9f72c11246ab26a6c0bbe16 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -19,7 +19,7 @@ index 0c35921acebd88f3a9a37676e47e7482dfea6d9c..1ff57c17f7fe3af3fb7fc5fbc5148ca3
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -1643,6 +1644,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1663,6 +1664,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          for (ServerLevel serverLevel : this.getAllLevels()) {
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
@@ -28,7 +28,7 @@ index 0c35921acebd88f3a9a37676e47e7482dfea6d9c..1ff57c17f7fe3af3fb7fc5fbc5148ca3
              /* Drop global time updates
              if (this.tickCount % 20 == 0) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index ffb5bfdd76a92bac61c7c352fdded4200d13b3ae..651a65aaa37f2cf26d54f75529cbda71e6fc2bc4 100644
+index bbb4bb0940765a12c45a99c8234ca82ef1934903..7dbf6113d79ab826ba5bb9b648b557f625e2b438 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -2678,4 +2678,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/paper-server/patches/features/0028-Improved-Watchdog-Support.patch b/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
index 601c2bb5ea..4a0d417552 100644
--- a/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
+++ b/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
@@ -100,7 +100,7 @@ index e738405e5112584e02e01df2d5ede2676fa1bffb..560d80cb1177297210646b44ce25fd2f
          /* CraftBukkit start - Replace everything
          OptionParser optionParser = new OptionParser();
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f764967b5d3f5c 100644
+index ddd23354d68f5cbdc9f72c11246ab26a6c0bbe16..3f880bdfd95f7556d1d76e4602a6d24dda78438c 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -119,7 +119,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
          Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
          // CraftBukkit end
          this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
-@@ -970,6 +972,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -990,6 +992,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.hasStopped = true;
          }
          if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -130,7 +130,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
          // CraftBukkit end
          if (this.metricsRecorder.isRecording()) {
              this.cancelRecordingMetrics();
-@@ -1041,6 +1047,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1061,6 +1067,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
          }
          // Paper end - rewrite chunk system
@@ -146,7 +146,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
      }
  
      public String getLocalIp() {
-@@ -1127,6 +1142,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1147,6 +1162,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      protected void runServer() {
          try {
@@ -154,7 +154,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
              if (!this.initServer()) {
                  throw new IllegalStateException("Failed to initialize server");
              }
-@@ -1137,6 +1153,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1157,6 +1173,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
              this.server.spark.enableBeforePlugins(); // Paper - spark
              // Spigot start
@@ -172,7 +172,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
              org.spigotmc.WatchdogThread.hasStarted = true; // Paper
              Arrays.fill(this.recentTps, 20);
              // Paper start - further improve server tick loop
-@@ -1233,6 +1260,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1253,6 +1280,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
              }
          } catch (Throwable var69) {
@@ -185,7 +185,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
              LOGGER.error("Encountered an unexpected exception", var69);
              CrashReport crashReport = constructOrExtractCrashReport(var69);
              this.fillSystemReport(crashReport.getSystemReport());
-@@ -1255,15 +1288,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1275,15 +1308,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.services.profileCache().clearExecutor();
                  }
  
@@ -205,7 +205,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
              }
          }
      }
-@@ -1367,6 +1400,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1387,6 +1420,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      @Override
      public TickTask wrapRunnable(Runnable runnable) {
@@ -218,7 +218,7 @@ index 43306cac3549a03612077df3aacf501051d05a01..d077debf5936050484856e0b84f76496
          return new TickTask(this.tickCount, runnable);
      }
  
-@@ -2164,7 +2203,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2184,7 +2223,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      this.resources.managers.updateStaticRegistryTags();
                      this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
                      this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
@@ -267,10 +267,10 @@ index 55d3f79af2e683b983d4d3f731bb9649dfe76f59..d900469dafd430ec3eba10d6f83bd875
      }
  
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index d322794c0d49daa212b8691f8f60f2276fe25a92..e5ae5e1161396280ffea1009f40dafa0398050bb 100644
+index 0a2f4af548561144607d24d3bd92270ce630fc95..42c1f5d0f384e0e4f348e161b4461858a6d84227 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -513,7 +513,7 @@ public abstract class PlayerList {
+@@ -512,7 +512,7 @@ public abstract class PlayerList {
          this.cserver.getPluginManager().callEvent(playerQuitEvent);
          player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
  
diff --git a/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
index 282b9eb257..964ec03fd8 100644
--- a/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
+++ b/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
 
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index d077debf5936050484856e0b84f764967b5d3f5c..75efd8f11a42696319c0908b7cf911cf976a31b6 100644
+index 3f880bdfd95f7556d1d76e4602a6d24dda78438c..79d050947960b26ef375c6f1986b617ad2ff081b 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -940,7 +940,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -960,7 +960,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          boolean var4;
          try {
              this.isSaving = true;
@@ -17,7 +17,7 @@ index d077debf5936050484856e0b84f764967b5d3f5c..75efd8f11a42696319c0908b7cf911cf
              var4 = this.saveAllChunks(suppressLog, flush, forced);
          } finally {
              this.isSaving = false;
-@@ -1530,9 +1530,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1550,9 +1550,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
  
          this.ticksUntilAutosave--;
@@ -83,7 +83,7 @@ index 9caa06f09409d36abf9e0a770ba004f4049e8e09..fc3b0db0f0a85fb42cb5fa8a1d78d4c4
          // Paper start - add close param
          this.save(progress, flush, skipSave, false);
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index bf4deeac50197eeb83c5b1e458b609aac5ad8a97..a97b0b177a1fb0557af2af4d1f192513d7c0390d 100644
+index e61fe83479f095e8addbd3e8f1d5179c998ae1eb..0a7e5106a1d39150326e7c323030df5d32ecef1e 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -180,6 +180,7 @@ import org.slf4j.Logger;
@@ -95,10 +95,10 @@ index bf4deeac50197eeb83c5b1e458b609aac5ad8a97..a97b0b177a1fb0557af2af4d1f192513
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
      private static final int FLY_STAT_RECORDING_SPEED = 25;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index e5ae5e1161396280ffea1009f40dafa0398050bb..d74242a0d25c906d74b018c24d4b21d64415617d 100644
+index 42c1f5d0f384e0e4f348e161b4461858a6d84227..2f7f5f933cf3adb6022664a0ad9b97c8f8bc8fd4 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -483,6 +483,7 @@ public abstract class PlayerList {
+@@ -482,6 +482,7 @@ public abstract class PlayerList {
  
      protected void save(ServerPlayer player) {
          if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
@@ -106,7 +106,7 @@ index e5ae5e1161396280ffea1009f40dafa0398050bb..d74242a0d25c906d74b018c24d4b21d6
          this.playerIo.save(player);
          ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
          if (serverStatsCounter != null) {
-@@ -1070,9 +1071,23 @@ public abstract class PlayerList {
+@@ -1069,9 +1070,23 @@ public abstract class PlayerList {
      }
  
      public void saveAll() {
diff --git a/paper-server/patches/features/0036-Optimize-Hoppers.patch b/paper-server/patches/features/0036-Optimize-Hoppers.patch
index eb195ce201..bb158407dc 100644
--- a/paper-server/patches/features/0036-Optimize-Hoppers.patch
+++ b/paper-server/patches/features/0036-Optimize-Hoppers.patch
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
 +    }
 +}
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 75efd8f11a42696319c0908b7cf911cf976a31b6..b7bb1b6b2c3c892712c44b4e006d9ceebbd8491a 100644
+index 79d050947960b26ef375c6f1986b617ad2ff081b..12f14284655845d59d44c52c65435fa91e2d7d6f 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -1704,6 +1704,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1724,6 +1724,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
              serverLevel.updateLagCompensationTick(); // Paper - lag compensation
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index ca5ee37782..55497abf8a 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -153,7 +153,7 @@
      protected abstract boolean initServer() throws IOException;
  
 -    protected void loadLevel() {
-+    protected void loadLevel(String s) { // CraftBukkit
++    protected void loadLevel(String levelId) { // CraftBukkit
          if (!JvmProfiler.INSTANCE.isRunning()) {
          }
  
@@ -165,11 +165,11 @@
 -        this.createLevels(chunkProgressListener);
 -        this.forceDifficulty();
 -        this.prepareLevels(chunkProgressListener);
-+        this.loadWorld0(s); // CraftBukkit
++        this.loadWorld0(levelId); // CraftBukkit
          if (profiledDuration != null) {
              profiledDuration.finish(true);
          }
-@@ -364,25 +_,245 @@
+@@ -364,25 +_,265 @@
      protected void forceDifficulty() {
      }
  
@@ -193,37 +193,39 @@
 -        this.commandStorage = new CommandStorage(dataStorage);
 -        WorldBorder worldBorder = serverLevel.getWorldBorder();
 +    // CraftBukkit start
-+    private void loadWorld0(String s) {
-+        LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
-+        RegistryAccess.Frozen iregistrycustom_dimension = this.registries.compositeAccess();
-+        Registry<LevelStem> dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
-+        for (LevelStem worldDimension : dimensions) {
-+            ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
-+            ServerLevel world;
++    private void loadWorld0(String levelId) {
++        // Mostly modelled off of net.minecraft.server.Main
++        LevelStorageSource.LevelStorageAccess levelStorageAccess = this.storageSource;
++        RegistryAccess.Frozen registryAccess = this.registries.compositeAccess();
++        Registry<LevelStem> levelStemRegistry = registryAccess.lookupOrThrow(Registries.LEVEL_STEM);
++        for (LevelStem levelStem : levelStemRegistry) {
++            ResourceKey<LevelStem> levelStemKey = levelStemRegistry.getResourceKey(levelStem).get();
++            ServerLevel serverLevel;
 +            int dimension = 0;
 +
-+            if (dimensionKey == LevelStem.NETHER) {
++            if (levelStemKey == LevelStem.NETHER) {
 +                if (this.server.getAllowNether()) {
 +                    dimension = -1;
 +                } else {
 +                    continue;
 +                }
-+            } else if (dimensionKey == LevelStem.END) {
++            } else if (levelStemKey == LevelStem.END) {
 +                if (this.server.getAllowEnd()) {
 +                    dimension = 1;
 +                } else {
 +                    continue;
 +                }
-+            } else if (dimensionKey != LevelStem.OVERWORLD) {
++            } else if (levelStemKey != LevelStem.OVERWORLD) {
 +                dimension = -999;
 +            }
 +
-+            String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT);
-+            String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
++            // Migration of old CB world folders...
++            String worldType = (dimension == -999) ? levelStemKey.location().getNamespace() + "_" + levelStemKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase(Locale.ROOT);
++            String name = (levelStemKey == LevelStem.OVERWORLD) ? levelId : levelId + "_" + worldType;
 +            if (dimension != 0) {
-+                java.io.File newWorld = LevelStorageSource.getStorageFolder(new java.io.File(name).toPath(), dimensionKey).toFile();
-+                java.io.File oldWorld = LevelStorageSource.getStorageFolder(new java.io.File(s).toPath(), dimensionKey).toFile();
-+                java.io.File oldLevelDat = new java.io.File(new java.io.File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
++                java.io.File newWorld = LevelStorageSource.getStorageFolder(new java.io.File(name).toPath(), levelStemKey).toFile();
++                java.io.File oldWorld = LevelStorageSource.getStorageFolder(new java.io.File(levelId).toPath(), levelStemKey).toFile();
++                java.io.File oldLevelDat = new java.io.File(new java.io.File(levelId), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
 +
 +                if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
 +                    MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
@@ -240,7 +242,7 @@
 +                            // Migrate world data too.
 +                            try {
 +                                com.google.common.io.Files.copy(oldLevelDat, new java.io.File(new java.io.File(name), "level.dat"));
-+                                org.apache.commons.io.FileUtils.copyDirectory(new java.io.File(new java.io.File(s), "data"), new java.io.File(new java.io.File(name), "data"));
++                                org.apache.commons.io.FileUtils.copyDirectory(new java.io.File(new java.io.File(levelId), "data"), new java.io.File(new java.io.File(name), "data"));
 +                            } catch (IOException exception) {
 +                                MinecraftServer.LOGGER.warn("Unable to migrate world data.");
 +                            }
@@ -256,137 +258,155 @@
 +                }
 +
 +                try {
-+                    worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
++                    levelStorageAccess = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, levelStemKey);
 +                } catch (IOException | net.minecraft.world.level.validation.ContentValidationException ex) {
 +                    throw new RuntimeException(ex);
 +                }
 +            }
 +
-+            com.mojang.serialization.Dynamic<?> dynamic;
-+            if (worldSession.hasWorldData()) {
-+                net.minecraft.world.level.storage.LevelSummary worldinfo;
-+
++            com.mojang.serialization.Dynamic<?> dataTag;
++            if (levelStorageAccess.hasWorldData()) {
++                net.minecraft.world.level.storage.LevelSummary summary;
 +                try {
-+                    dynamic = worldSession.getDataTag();
-+                    worldinfo = worldSession.getSummary(dynamic);
-+                } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception) {
-+                    LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
-+
-+                    MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
++                    dataTag = levelStorageAccess.getDataTag();
++                    summary = levelStorageAccess.getSummary(dataTag);
++                } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException e) {
++                    LevelStorageSource.LevelDirectory levelDirectory = levelStorageAccess.getLevelDirectory();
++                    MinecraftServer.LOGGER.warn("Failed to load world data from {}", levelDirectory.dataFile(), e);
 +                    MinecraftServer.LOGGER.info("Attempting to use fallback");
 +
 +                    try {
-+                        dynamic = worldSession.getDataTagFallback();
-+                        worldinfo = worldSession.getSummary(dynamic);
-+                    } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException ioexception1) {
-+                        MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
-+                        MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
++                        dataTag = levelStorageAccess.getDataTagFallback();
++                        summary = levelStorageAccess.getSummary(dataTag);
++                    } catch (net.minecraft.nbt.NbtException | net.minecraft.nbt.ReportedNbtException | IOException e1) {
++                        MinecraftServer.LOGGER.error("Failed to load world data from {}", levelDirectory.oldDataFile(), e1);
++                        MinecraftServer.LOGGER.error(
++                            "Failed to load world data from {} and {}. World files may be corrupted. Shutting down.",
++                            levelDirectory.dataFile(),
++                            levelDirectory.oldDataFile()
++                        );
 +                        return;
 +                    }
 +
-+                    worldSession.restoreLevelDataFromOld();
++                    levelStorageAccess.restoreLevelDataFromOld();
 +                }
 +
-+                if (worldinfo.requiresManualConversion()) {
++                if (summary.requiresManualConversion()) {
 +                    MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted");
 +                    return;
 +                }
 +
-+                if (!worldinfo.isCompatible()) {
++                if (!summary.isCompatible()) {
 +                    MinecraftServer.LOGGER.info("This world was created by an incompatible version.");
 +                    return;
 +                }
 +            } else {
-+                dynamic = null;
++                dataTag = null;
 +            }
 +
-+            org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
++            org.bukkit.generator.ChunkGenerator chunkGenerator = this.server.getGenerator(name);
 +            org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name);
 +
-+            net.minecraft.world.level.storage.PrimaryLevelData worlddata;
-+            WorldLoader.DataLoadContext worldloader_a = this.worldLoader;
-+            Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
-+            if (dynamic != null) {
-+                net.minecraft.world.level.storage.LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
-+
-+                worlddata = (net.minecraft.world.level.storage.PrimaryLevelData) leveldataanddimensions.worldData();
++            net.minecraft.world.level.storage.PrimaryLevelData primaryLevelData;
++            WorldLoader.DataLoadContext context = this.worldLoader;
++            Registry<LevelStem> contextLevelStemRegistry = context.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
++            if (dataTag != null) {
++                net.minecraft.world.level.storage.LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
++                    dataTag, context.dataConfiguration(), contextLevelStemRegistry, context.datapackWorldgen()
++                );
++                primaryLevelData = (net.minecraft.world.level.storage.PrimaryLevelData) levelDataAndDimensions.worldData();
 +            } else {
-+                LevelSettings worldsettings;
-+                WorldOptions worldoptions;
-+                net.minecraft.world.level.levelgen.WorldDimensions worlddimensions;
-+
++                LevelSettings levelSettings;
++                WorldOptions worldOptions;
++                net.minecraft.world.level.levelgen.WorldDimensions worldDimensions;
 +                if (this.isDemo()) {
-+                    worldsettings = MinecraftServer.DEMO_SETTINGS;
-+                    worldoptions = WorldOptions.DEMO_OPTIONS;
-+                    worlddimensions = net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
++                    levelSettings = MinecraftServer.DEMO_SETTINGS;
++                    worldOptions = WorldOptions.DEMO_OPTIONS;
++                    worldDimensions = net.minecraft.world.level.levelgen.presets.WorldPresets.createNormalWorldDimensions(context.datapackWorldgen());
 +                } else {
-+                    net.minecraft.server.dedicated.DedicatedServerProperties dedicatedserverproperties = ((net.minecraft.server.dedicated.DedicatedServer) this).getProperties();
-+
-+                    worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
-+                    worldoptions = this.options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
-+                    worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
++                    net.minecraft.server.dedicated.DedicatedServerProperties properties = ((net.minecraft.server.dedicated.DedicatedServer) this).getProperties();
++                    levelSettings = new LevelSettings(
++                        properties.levelName,
++                        properties.gamemode,
++                        properties.hardcore,
++                        properties.difficulty,
++                        false,
++                        new GameRules(context.dataConfiguration().enabledFeatures()),
++                        context.dataConfiguration()
++                    );
++                    worldOptions = this.options.has("bonusChest") ? properties.worldOptions.withBonusChest(true) : properties.worldOptions; // CraftBukkit
++                    worldDimensions = properties.createDimensions(context.datapackWorldgen());
 +                }
 +
-+                net.minecraft.world.level.levelgen.WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
-+                com.mojang.serialization.Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
++                net.minecraft.world.level.levelgen.WorldDimensions.Complete complete = worldDimensions.bake(contextLevelStemRegistry);
++                com.mojang.serialization.Lifecycle lifecycle = complete.lifecycle().add(context.datapackWorldgen().allRegistriesLifecycle());
 +
-+                worlddata = new net.minecraft.world.level.storage.PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
++                primaryLevelData = new net.minecraft.world.level.storage.PrimaryLevelData(levelSettings, worldOptions, complete.specialWorldProperty(), lifecycle);
 +            }
-+            worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
++
++            primaryLevelData.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
 +            if (this.options.has("forceUpgrade")) {
-+                net.minecraft.server.Main.forceUpgrade(worldSession, net.minecraft.util.datafix.DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
-+                    return true;
-+                }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
++                net.minecraft.server.Main.forceUpgrade(levelStorageAccess, net.minecraft.util.datafix.DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> true, registryAccess, this.options.has("recreateRegionFiles"));
 +            }
 +
-+            net.minecraft.world.level.storage.PrimaryLevelData iworlddataserver = worlddata;
-+            boolean flag = worlddata.isDebugWorld();
-+            WorldOptions worldoptions = worlddata.worldGenOptions();
-+            long i = worldoptions.seed();
-+            long j = BiomeManager.obfuscateSeed(i);
-+            List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
-+            LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey);
++            // Now modelled off the createLevels method
++            net.minecraft.world.level.storage.PrimaryLevelData serverLevelData = primaryLevelData;
++            boolean isDebugWorld = primaryLevelData.isDebugWorld();
++            WorldOptions worldOptions = primaryLevelData.worldGenOptions();
++            long seed = worldOptions.seed();
++            long l = BiomeManager.obfuscateSeed(seed);
++            List<CustomSpawner> list = ImmutableList.of(
++                new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverLevelData)
++            );
++            LevelStem customStem = levelStemRegistry.getValue(levelStemKey);
 +
-+            org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
-+            if (biomeProvider == null && gen != null) {
-+                biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
++            org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(serverLevelData, levelStorageAccess, org.bukkit.World.Environment.getEnvironment(dimension), customStem.type().value(), customStem.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
++            if (biomeProvider == null && chunkGenerator != null) {
++                biomeProvider = chunkGenerator.getDefaultBiomeProvider(worldInfo);
 +            }
 +
-+            ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
++            ResourceKey<Level> dimensionKey = ResourceKey.create(Registries.DIMENSION, levelStemKey.location());
 +
-+            if (dimensionKey == LevelStem.OVERWORLD) {
-+                this.worldData = worlddata;
++            if (levelStemKey == LevelStem.OVERWORLD) {
++                this.worldData = primaryLevelData;
 +                this.worldData.setGameType(((net.minecraft.server.dedicated.DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
 +
-+                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                ChunkProgressListener listener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +
-+                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
-+                DimensionDataStorage worldpersistentdata = world.getDataStorage();
-+                this.readScoreboard(worldpersistentdata);
-+                this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
-+                this.commandStorage = new CommandStorage(worldpersistentdata);
++                serverLevel = new ServerLevel(
++                    this, this.executor, levelStorageAccess, serverLevelData, dimensionKey, customStem, listener, isDebugWorld, l, list, true, null,
++                    org.bukkit.World.Environment.getEnvironment(dimension), chunkGenerator, biomeProvider
++                );
++                DimensionDataStorage dataStorage = serverLevel.getDataStorage();
++                this.readScoreboard(dataStorage);
++                this.commandStorage = new CommandStorage(dataStorage);
++                this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, serverLevel.getScoreboard());
 +            } else {
-+                ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
++                ChunkProgressListener listener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
 +                // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
 +                final List<CustomSpawner> spawners;
-+                if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
++                if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(customStem.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
 +                    spawners = list;
 +                } else {
 +                    spawners = Collections.emptyList();
 +                }
-+                world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++                serverLevel = new ServerLevel(
++                    this, this.executor, levelStorageAccess, serverLevelData, dimensionKey, customStem, listener, isDebugWorld, l, spawners, true, this.overworld().getRandomSequences(),
++                    org.bukkit.World.Environment.getEnvironment(dimension), chunkGenerator, biomeProvider
++                );
 +                // Paper end - option to use the dimension_type to check if spawners should be added
 +            }
 +
-+            worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
-+            this.addLevel(world); // Paper - Put world into worldlist before initing the world; move up
-+            this.initWorld(world, worlddata, this.worldData, worldoptions);
++            // Back to the createLevels method without crazy modifications
++            primaryLevelData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
++            this.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up
++            this.initWorld(serverLevel, primaryLevelData, this.worldData, worldOptions);
 +
 +            // Paper - Put world into worldlist before initing the world; move up
-+            this.getPlayerList().addWorldborderListener(world);
++            this.getPlayerList().addWorldborderListener(serverLevel);
 +
-+            if (worlddata.getCustomBossEvents() != null) {
-+                this.getCustomBossEvents().load(worlddata.getCustomBossEvents(), this.registryAccess());
++            if (primaryLevelData.getCustomBossEvents() != null) {
++                this.getCustomBossEvents().load(primaryLevelData.getCustomBossEvents(), this.registryAccess());
 +            }
 +        }
 +        this.forceDifficulty();
@@ -1209,15 +1229,6 @@
      private ProfilerFiller createProfiler() {
          if (this.willStartRecordingMetrics) {
              this.metricsRecorder = ActiveMetricsRecorder.createStarted(
-@@ -1941,7 +_,7 @@
-     }
- 
-     public ServerPlayerGameMode createGameModeForPlayer(ServerPlayer player) {
--        return (ServerPlayerGameMode)(this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
-+        return (this.isDemo() ? new DemoMode(player) : new ServerPlayerGameMode(player));
-     }
- 
-     @Nullable
 @@ -1980,16 +_,22 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index ce890ce4a0..7c69b49dd9 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -206,30 +206,17 @@
                  if (thrownEnderpearl.isRemoved()) {
                      LOGGER.warn("Trying to save removed ender pearl, skipping");
                  } else {
-@@ -593,6 +_,29 @@
+@@ -593,6 +_,16 @@
          }
      }
  
-+    // CraftBukkit start - World fallback code, either respawn location or global spawn
-+    public void spawnIn(Level world) {
-+        this.setLevel(world);
-+        if (world == null) {
-+            this.unsetRemoved();
-+            Vec3 position = null;
-+            if (this.respawnDimension != null) {
-+                world = this.server.getLevel(this.respawnDimension);
-+                if (world != null && this.getRespawnPosition() != null) {
-+                    position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null);
-+                }
-+            }
-+            if (world == null || position == null) {
-+                world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getServer().getWorlds().get(0)).getHandle();
-+                position = Vec3.atCenterOf(world.getSharedSpawnPos());
-+            }
-+            this.setLevel(world);
-+            this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet
++    // CraftBukkit start
++    public void spawnIn(final ServerLevel level) {
++        if (level == null) {
++            throw new IllegalArgumentException("level can't be null");
 +        }
-+        this.gameMode.setLevel((ServerLevel) world);
++        this.setLevel(level);
++        this.gameMode.setLevel(level);
 +    }
 +    // CraftBukkit end
 +
@@ -840,7 +827,7 @@
 +
 +        {
 +            {
-+                Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at, force).ifRight((unit) -> {
++                Either<net.minecraft.world.entity.player.Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at, force).ifRight(unit -> {
 +                    // CraftBukkit end
                      this.awardStat(Stats.SLEEP_IN_BED);
                      CriteriaTriggers.SLEPT_IN_BED.trigger(this);
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index c53210937f..5c3d9c3714 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -808,7 +808,7 @@
                              d3 = d - this.player.getX();
                              d4 = d1 - this.player.getY();
                              if (d4 > -0.5 || d4 < 0.5) {
-@@ -970,20 +_,104 @@
+@@ -970,20 +_,101 @@
  
                              d5 = d2 - this.player.getZ();
                              d7 = d3 * d3 + d4 * d4 + d5 * d5;
@@ -847,11 +847,8 @@
 +                                    teleportBack = false;
 +                                }
 +                            }
-+                            if (teleportBack) {
++                            if (!teleportBack) {
 +                                // Paper end - Add fail move event
-+                                this.internalTeleport(x, y, z, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet.
-+                                this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
-+                            } else {
 +                                // CraftBukkit start - fire PlayerMoveEvent
 +                                // Reset to old location first
 +                                this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch);
@@ -922,16 +919,15 @@
                                  this.player.absMoveTo(d, d1, d2, f, f1);
                                  boolean isAutoSpinAttack = this.player.isAutoSpinAttack();
                                  this.clientIsFloating = d4 >= -0.03125
-@@ -1018,9 +_,6 @@
-                                 this.lastGoodX = this.player.getX();
+@@ -1019,7 +_,7 @@
                                  this.lastGoodY = this.player.getY();
                                  this.lastGoodZ = this.player.getZ();
--                            } else {
+                             } else {
 -                                this.teleport(x, y, z, f, f1);
--                                this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
++                                this.internalTeleport(x, y, z, f, f1); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet.
+                                 this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
                              }
                          }
-                     }
 @@ -1053,6 +_,7 @@
                      this.player.getXRot()
                  );
diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
index 8239aa71e1..2ddcf75909 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -47,7 +47,7 @@
          GameProfile gameProfile = player.getGameProfile();
          GameProfileCache profileCache = this.server.getProfileCache();
          String string;
-@@ -150,30 +_,94 @@
+@@ -150,30 +_,93 @@
          }
  
          Optional<CompoundTag> optional = this.load(player);
@@ -136,7 +136,6 @@
 +        serverLevel = ((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle();
 +
 +        player.spawnIn(serverLevel);
-+        player.gameMode.setLevel((ServerLevel) player.level());
 +        // Paper start - set raw so we aren't fully joined to the world (not added to chunk or world)
 +        player.setPosRaw(loc.getX(), loc.getY(), loc.getZ());
 +        player.setRot(loc.getYaw(), loc.getPitch());
@@ -334,7 +333,7 @@
              }
  
              @Override
-@@ -309,67 +_,175 @@
+@@ -309,56 +_,162 @@
      }
  
      protected void save(ServerPlayer player) {
@@ -469,8 +468,6 @@
 -    public Component canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) {
 -        if (this.bans.isBanned(gameProfile)) {
 -            UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
--            MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
--            if (userBanListEntry.getExpires() != null) {
 +    // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
 +    public ServerPlayer canPlayerLogin(net.minecraft.server.network.ServerLoginPacketListenerImpl loginlistener, GameProfile gameProfile) {
 +        // if (this.bans.isBanned(gameProfile)) {
@@ -507,14 +504,13 @@
 +        org.bukkit.event.player.PlayerLoginEvent event = new org.bukkit.event.player.PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketAddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress());
 +
 +        // Paper start - Fix MC-158900
-+        UserBanListEntry gameprofilebanentry;
-+        if (this.bans.isBanned(gameProfile) && (gameprofilebanentry = this.bans.get(gameProfile)) != null) {
++        UserBanListEntry userBanListEntry;
++        if (this.bans.isBanned(gameProfile) && (userBanListEntry = this.bans.get(gameProfile)) != null) {
 +            // Paper end - Fix MC-158900
-+            MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason());
-+            if (gameprofilebanentry.getExpires() != null) {
+             MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
+             if (userBanListEntry.getExpires() != null) {
                  mutableComponent.append(
--                    Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(userBanListEntry.getExpires()))
-+                    Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires()))
+@@ -366,10 +_,12 @@
                  );
              }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 6bbcc9ce75..3576351376 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -306,11 +306,10 @@
      }
  
      public void onClientRemoval() {
-@@ -367,7 +_,18 @@
+@@ -367,6 +_,17 @@
      }
  
      public void setPose(Pose pose) {
--        this.entityData.set(DATA_POSE, pose);
 +        if (this.fixedPose) return; // Paper - Expand Pose API
 +        // CraftBukkit start
 +        if (pose == this.getPose()) {
@@ -322,10 +321,9 @@
 +        }
 +        // Paper end - Don't fire sync event during generation
 +        // CraftBukkit end
-+        this.entityData.set(Entity.DATA_POSE, pose);
+         this.entityData.set(DATA_POSE, pose);
      }
  
-     public Pose getPose() {
 @@ -390,6 +_,32 @@
      }
  
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 104caaf260..f59b4a6998 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1288,7 +1288,7 @@ public final class CraftServer implements Server {
         Preconditions.checkArgument(creator != null, "WorldCreator cannot be null");
 
         String name = creator.name();
-        ChunkGenerator generator = creator.generator();
+        ChunkGenerator chunkGenerator = creator.generator();
         BiomeProvider biomeProvider = creator.biomeProvider();
         File folder = new File(this.getWorldContainer(), name);
         World world = this.getWorld(name);
@@ -1307,151 +1307,170 @@ public final class CraftServer implements Server {
             Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name);
         }
 
-        if (generator == null) {
-            generator = this.getGenerator(name);
+        if (chunkGenerator == null) {
+            chunkGenerator = this.getGenerator(name);
         }
 
         if (biomeProvider == null) {
             biomeProvider = this.getBiomeProvider(name);
         }
 
-        ResourceKey<LevelStem> actualDimension;
-        switch (creator.environment()) {
-            case NORMAL:
-                actualDimension = LevelStem.OVERWORLD;
-                break;
-            case NETHER:
-                actualDimension = LevelStem.NETHER;
-                break;
-            case THE_END:
-                actualDimension = LevelStem.END;
-                break;
-            default:
-                throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")");
-        }
+        ResourceKey<LevelStem> actualDimension = switch (creator.environment()) {
+            case NORMAL -> LevelStem.OVERWORLD;
+            case NETHER -> LevelStem.NETHER;
+            case THE_END -> LevelStem.END;
+            default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")");
+        };
 
-        LevelStorageSource.LevelStorageAccess worldSession;
+        LevelStorageSource.LevelStorageAccess levelStorageAccess;
         try {
-            worldSession = LevelStorageSource.createDefault(this.getWorldContainer().toPath()).validateAndCreateAccess(name, actualDimension);
+            levelStorageAccess = LevelStorageSource.createDefault(this.getWorldContainer().toPath()).validateAndCreateAccess(name, actualDimension);
         } catch (IOException | ContentValidationException ex) {
             throw new RuntimeException(ex);
         }
 
-        Dynamic<?> dynamic;
-        if (worldSession.hasWorldData()) {
-            net.minecraft.world.level.storage.LevelSummary worldinfo;
-
+        Dynamic<?> dataTag;
+        if (levelStorageAccess.hasWorldData()) {
+            net.minecraft.world.level.storage.LevelSummary summary;
             try {
-                dynamic = worldSession.getDataTag();
-                worldinfo = worldSession.getSummary(dynamic);
-            } catch (NbtException | ReportedNbtException | IOException ioexception) {
-                LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
-
-                MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
+                dataTag = levelStorageAccess.getDataTag();
+                summary = levelStorageAccess.getSummary(dataTag);
+            } catch (NbtException | ReportedNbtException | IOException e) {
+                LevelStorageSource.LevelDirectory levelDirectory = levelStorageAccess.getLevelDirectory();
+                MinecraftServer.LOGGER.warn("Failed to load world data from {}", levelDirectory.dataFile(), e);
                 MinecraftServer.LOGGER.info("Attempting to use fallback");
 
                 try {
-                    dynamic = worldSession.getDataTagFallback();
-                    worldinfo = worldSession.getSummary(dynamic);
-                } catch (NbtException | ReportedNbtException | IOException ioexception1) {
-                    MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
-                    MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
+                    dataTag = levelStorageAccess.getDataTagFallback();
+                    summary = levelStorageAccess.getSummary(dataTag);
+                } catch (NbtException | ReportedNbtException | IOException e1) {
+                    MinecraftServer.LOGGER.error("Failed to load world data from {}", levelDirectory.oldDataFile(), e1);
+                    MinecraftServer.LOGGER.error(
+                        "Failed to load world data from {} and {}. World files may be corrupted. Shutting down.",
+                        levelDirectory.dataFile(),
+                        levelDirectory.oldDataFile()
+                    );
                     return null;
                 }
 
-                worldSession.restoreLevelDataFromOld();
+                levelStorageAccess.restoreLevelDataFromOld();
             }
 
-            if (worldinfo.requiresManualConversion()) {
+            if (summary.requiresManualConversion()) {
                 MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted");
                 return null;
             }
 
-            if (!worldinfo.isCompatible()) {
+            if (!summary.isCompatible()) {
                 MinecraftServer.LOGGER.info("This world was created by an incompatible version.");
                 return null;
             }
         } else {
-            dynamic = null;
+            dataTag = null;
         }
 
         boolean hardcore = creator.hardcore();
 
-        PrimaryLevelData worlddata;
-        WorldLoader.DataLoadContext worldloader_a = this.console.worldLoader;
-        RegistryAccess.Frozen iregistrycustom_dimension = worldloader_a.datapackDimensions();
-        net.minecraft.core.Registry<LevelStem> iregistry = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
-        if (dynamic != null) {
-            LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
-
-            worlddata = (PrimaryLevelData) leveldataanddimensions.worldData();
-            iregistrycustom_dimension = leveldataanddimensions.dimensions().dimensionsRegistryAccess();
+        PrimaryLevelData primaryLevelData;
+        WorldLoader.DataLoadContext context = this.console.worldLoader;
+        RegistryAccess.Frozen registryAccess = context.datapackDimensions();
+        net.minecraft.core.Registry<LevelStem> contextLevelStemRegistry = registryAccess.lookupOrThrow(Registries.LEVEL_STEM);
+        if (dataTag != null) {
+            LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
+                dataTag, context.dataConfiguration(), contextLevelStemRegistry, context.datapackWorldgen()
+            );
+            primaryLevelData = (PrimaryLevelData) levelDataAndDimensions.worldData();
+            registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess();
         } else {
-            LevelSettings worldsettings;
-            WorldOptions worldoptions = new WorldOptions(creator.seed(), creator.generateStructures(), false);
-            WorldDimensions worlddimensions;
+            LevelSettings levelSettings;
+            WorldOptions worldOptions = new WorldOptions(creator.seed(), creator.generateStructures(), false);
+            WorldDimensions worldDimensions;
 
             DedicatedServerProperties.WorldDimensionData properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse((creator.generatorSettings().isEmpty()) ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
+            levelSettings = new LevelSettings(
+                name,
+                GameType.byId(this.getDefaultGameMode().getValue()),
+                hardcore, Difficulty.EASY,
+                false,
+                new GameRules(context.dataConfiguration().enabledFeatures()),
+                context.dataConfiguration())
+            ;
+            worldDimensions = properties.create(context.datapackWorldgen());
 
-            worldsettings = new LevelSettings(name, GameType.byId(this.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(worldloader_a.dataConfiguration().enabledFeatures()), worldloader_a.dataConfiguration());
-            worlddimensions = properties.create(worldloader_a.datapackWorldgen());
+            WorldDimensions.Complete complete = worldDimensions.bake(contextLevelStemRegistry);
+            Lifecycle lifecycle = complete.lifecycle().add(context.datapackWorldgen().allRegistriesLifecycle());
 
-            WorldDimensions.Complete worlddimensions_b = worlddimensions.bake(iregistry);
-            Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
-
-            worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
-            iregistrycustom_dimension = worlddimensions_b.dimensionsRegistryAccess();
+            primaryLevelData = new PrimaryLevelData(levelSettings, worldOptions, complete.specialWorldProperty(), lifecycle);
+            registryAccess = complete.dimensionsRegistryAccess();
         }
-        iregistry = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM);
-        worlddata.customDimensions = iregistry;
-        worlddata.checkName(name);
-        worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified());
+
+        contextLevelStemRegistry = registryAccess.lookupOrThrow(Registries.LEVEL_STEM);
+        primaryLevelData.customDimensions = contextLevelStemRegistry;
+        primaryLevelData.checkName(name);
+        primaryLevelData.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified());
 
         if (this.console.options.has("forceUpgrade")) {
-            net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistrycustom_dimension, this.console.options.has("recreateRegionFiles"));
+            net.minecraft.server.Main.forceUpgrade(levelStorageAccess, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, registryAccess, this.console.options.has("recreateRegionFiles"));
         }
 
-        long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed
-        List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata));
-        LevelStem worlddimension = iregistry.getValue(actualDimension);
+        long i = BiomeManager.obfuscateSeed(primaryLevelData.worldGenOptions().seed());
+        List<CustomSpawner> list = ImmutableList.of(
+            new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(primaryLevelData)
+        );
+        LevelStem customStem = contextLevelStemRegistry.getValue(actualDimension);
 
-        WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
-        if (biomeProvider == null && generator != null) {
-            biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
+        WorldInfo worldInfo = new CraftWorldInfo(primaryLevelData, levelStorageAccess, creator.environment(), customStem.type().value(), customStem.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
+        if (biomeProvider == null && chunkGenerator != null) {
+            biomeProvider = chunkGenerator.getDefaultBiomeProvider(worldInfo);
         }
 
-        ResourceKey<net.minecraft.world.level.Level> worldKey;
+        ResourceKey<net.minecraft.world.level.Level> dimensionKey;
         String levelName = this.getServer().getProperties().levelName;
         if (name.equals(levelName + "_nether")) {
-            worldKey = net.minecraft.world.level.Level.NETHER;
+            dimensionKey = net.minecraft.world.level.Level.NETHER;
         } else if (name.equals(levelName + "_the_end")) {
-            worldKey = net.minecraft.world.level.Level.END;
+            dimensionKey = net.minecraft.world.level.Level.END;
         } else {
-            worldKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value()));
+            dimensionKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.fromNamespaceAndPath(creator.key().namespace(), creator.key().value()));
         }
 
         // If set to not keep spawn in memory (changed from default) then adjust rule accordingly
         if (creator.keepSpawnLoaded() == net.kyori.adventure.util.TriState.FALSE) { // Paper
-            worlddata.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
+            primaryLevelData.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
         }
-        ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(worlddata.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)),
-                worlddata.isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, this.console.overworld().getRandomSequences(), creator.environment(), generator, biomeProvider);
+
+        ServerLevel serverLevel = new ServerLevel(
+            this.console,
+            this.console.executor,
+            levelStorageAccess,
+            primaryLevelData,
+            dimensionKey,
+            customStem,
+            this.getServer().progressListenerFactory.create(primaryLevelData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)),
+            primaryLevelData.isDebugWorld(),
+            i,
+            creator.environment() == Environment.NORMAL ? list : ImmutableList.of(),
+            true,
+            this.console.overworld().getRandomSequences(),
+            creator.environment(),
+            chunkGenerator, biomeProvider
+        );
 
         if (!(this.worlds.containsKey(name.toLowerCase(Locale.ROOT)))) {
             return null;
         }
 
-        this.console.addLevel(internal); // Paper - Put world into worldlist before initing the world; move up
-        this.console.initWorld(internal, worlddata, worlddata, worlddata.worldGenOptions());
+        this.console.addLevel(serverLevel); // Paper - Put world into worldlist before initing the world; move up
+        this.console.initWorld(serverLevel, primaryLevelData, primaryLevelData, primaryLevelData.worldGenOptions());
 
-        internal.setSpawnSettings(true);
+        serverLevel.setSpawnSettings(true);
         // Paper - Put world into worldlist before initing the world; move up
 
-        this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal);
-        io.papermc.paper.FeatureHooks.tickEntityManager(internal); // SPIGOT-6526: Load pending entities so they are available to the API // Paper - chunk system
+        this.getServer().prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
+        io.papermc.paper.FeatureHooks.tickEntityManager(serverLevel); // SPIGOT-6526: Load pending entities so they are available to the API // Paper - chunk system
 
-        this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
-        return internal.getWorld();
+        this.pluginManager.callEvent(new WorldLoadEvent(serverLevel.getWorld()));
+        return serverLevel.getWorld();
     }
 
     @Override

From 82216a59fef17863c86192c2754417480baa71c1 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 13:21:47 +0100
Subject: [PATCH 272/285] Apply some feature patches to files instead They're
 small and/or really shouldn't be left unapplied

---
 ...ldBounds-and-getBlockState-for-inlin.patch |  98 -------
 ...item-frames-performance-and-bug-fixe.patch | 135 ---------
 ...heck-distance-in-entity-interactions.patch |  68 -----
 .../moonrise/paper/PaperHooks.java.patch      | 244 +++++++++++++++++
 .../util/BaseChunkSystemHooks.java.patch}     | 258 +-----------------
 .../sources/net/minecraft/Util.java.patch     |   8 +
 .../net/minecraft/core/Vec3i.java.patch       |  11 +
 .../server/level/ServerLevel.java.patch       |   9 +-
 .../server/level/ServerPlayer.java.patch      |  15 +-
 .../world/entity/LivingEntity.java.patch      |  21 +-
 .../entity/vehicle/AbstractBoat.java.patch    |   9 +
 .../minecraft/world/level/Level.java.patch    |   9 +-
 .../world/level/chunk/ChunkAccess.java.patch  |   8 +
 .../level/chunk/ImposterProtoChunk.java.patch |  15 +
 .../world/level/chunk/ProtoChunk.java.patch   |  21 +-
 .../maps/MapItemSavedData.java.patch          |  73 ++++-
 16 files changed, 424 insertions(+), 578 deletions(-)
 delete mode 100644 paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
 delete mode 100644 paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
 delete mode 100644 paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
 create mode 100644 paper-server/patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch
 rename paper-server/patches/{features/0001-Add-PaperHooks.patch => sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch} (59%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/ImposterProtoChunk.java.patch

diff --git a/paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
deleted file mode 100644
index 4f1d53e0e3..0000000000
--- a/paper-server/patches/features/0002-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
+++ /dev/null
@@ -1,98 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <aikar@aikar.co>
-Date: Thu, 3 Mar 2016 02:07:55 -0600
-Subject: [PATCH] Optimize isInWorldBounds and getBlockState for inlining
-
-Hot methods, so reduce # of instructions for the method.
-
-Move is valid location test to the BlockPosition class so that it can access local variables.
-
-Replace all calls to the new place to the unnecessary forward.
-
-Optimize getType and getBlockData to manually inline and optimize the calls
-
-diff --git a/net/minecraft/core/Vec3i.java b/net/minecraft/core/Vec3i.java
-index 03e2178430849d26c9826517e34ad069c94fc00a..11555ce7159ca6c8ddfe9691f86d3720c07cb086 100644
---- a/net/minecraft/core/Vec3i.java
-+++ b/net/minecraft/core/Vec3i.java
-@@ -28,6 +28,12 @@ public class Vec3i implements Comparable<Vec3i> {
-         );
-     }
- 
-+    // Paper start
-+    public final boolean isInsideBuildHeightAndWorldBoundsHorizontal(net.minecraft.world.level.LevelHeightAccessor levelHeightAccessor) {
-+        return getX() >= -30000000 && getZ() >= -30000000 && getX() < 30000000 && getZ() < 30000000 && !levelHeightAccessor.isOutsideBuildHeight(getY());
-+    }
-+    // Paper end
-+
-     public Vec3i(int x, int y, int z) {
-         this.x = x;
-         this.y = y;
-diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index e0239091729f6be138c037951fd5c138497ee358..691fee2e2097244126f4fac0f5d00bf6916b9766 100644
---- a/net/minecraft/world/level/Level.java
-+++ b/net/minecraft/world/level/Level.java
-@@ -350,7 +350,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-     // Paper end
- 
-     public boolean isInWorldBounds(BlockPos pos) {
--        return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
-+        return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - use better/optimized check
-     }
- 
-     public static boolean isInSpawnableBounds(BlockPos pos) {
-diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java
-index f68f3f5e8ef39a0dc371e75110227a39791c04c8..12d9b532e466ec4e46920d409b5f1b3ae60b80f8 100644
---- a/net/minecraft/world/level/chunk/ChunkAccess.java
-+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -130,6 +130,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh
-         return GameEventListenerRegistry.NOOP;
-     }
- 
-+    public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper
-     @Nullable
-     public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving);
- 
-diff --git a/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-index 6c43e5e685871289968db8171342fd84189edcba..e7c0f4da8508fbca467326f475668d66454d7b77 100644
---- a/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-+++ b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
-@@ -56,6 +56,12 @@ public class ImposterProtoChunk extends ProtoChunk {
-     public BlockState getBlockState(BlockPos pos) {
-         return this.wrapped.getBlockState(pos);
-     }
-+    // Paper start
-+    @Override
-+    public final BlockState getBlockState(final int x, final int y, final int z) {
-+        return this.wrapped.getBlockStateFinal(x, y, z);
-+    }
-+    // Paper end
- 
-     @Override
-     public FluidState getFluidState(BlockPos pos) {
-diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java
-index e359b5f694210f05e5675a995dbfc1a95cec76db..8c333d7f390d823a7c7f303e2f444f52ec16f799 100644
---- a/net/minecraft/world/level/chunk/ProtoChunk.java
-+++ b/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -99,12 +99,18 @@ public class ProtoChunk extends ChunkAccess {
- 
-     @Override
-     public BlockState getBlockState(BlockPos pos) {
--        int y = pos.getY();
-+        // Paper start
-+        return getBlockState(pos.getX(), pos.getY(), pos.getZ());
-+    }
-+    public BlockState getBlockState(final int x, final int y, final int z) {
-+        // Paper end
-         if (this.isOutsideBuildHeight(y)) {
-             return Blocks.VOID_AIR.defaultBlockState();
-         } else {
--            LevelChunkSection section = this.getSection(this.getSectionIndex(y));
--            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(pos.getX() & 15, y & 15, pos.getZ() & 15);
-+            // Paper start
-+            LevelChunkSection section = this.getSections()[this.getSectionIndex(y)];
-+            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(x & 15, y & 15, z & 15);
-+            // Paper end
-         }
-     }
- 
diff --git a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
deleted file mode 100644
index a547df5f1b..0000000000
--- a/paper-server/patches/features/0003-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ /dev/null
@@ -1,135 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <aikar@aikar.co>
-Date: Fri, 29 Apr 2016 20:02:00 -0400
-Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes
-
-Maps used a modified version of rendering to support plugin controlled
-imaging on maps. The Craft Map Renderer is much slower than Vanilla,
-causing maps in item frames to cause a noticeable hit on server performance.
-
-This updates the map system to not use the Craft system if we detect that no
-custom renderers are in use, defaulting to the much simpler Vanilla system.
-
-Additionally, numerous issues to player position tracking on maps has been fixed.
-
-diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 1f66adcd70aae7eac3d9f9539163905695581631..34a53bf34d10c56e6f53ce9aab2fc2780509f2f1 100644
---- a/net/minecraft/server/level/ServerLevel.java
-+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2282,7 +2282,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-                         }
- 
-                         map.carriedByPlayers.remove(player);
--                        map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player);
-+                        if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) {
-+                            map.decorations.remove(player.getName().getString()); // Paper
-+                        }
-                     }
-                 }
-             }
-diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 36e0d3f225a71b75ece7bf3fceeba47af948a6df..ff5889f8fed0707a6654d9d21862e32e2ebc866d 100644
---- a/net/minecraft/server/level/ServerPlayer.java
-+++ b/net/minecraft/server/level/ServerPlayer.java
-@@ -2594,6 +2594,14 @@ public class ServerPlayer extends Player {
-                 this.awardStat(Stats.DROP);
-             }
- 
-+            // Paper start - remove player from map on drop
-+            if (item.getItem() == net.minecraft.world.item.Items.FILLED_MAP) {
-+                final MapItemSavedData mapData = MapItem.getSavedData(item, this.level());
-+                if (mapData != null) {
-+                    mapData.tickCarriedBy(this, item);
-+                }
-+            }
-+            // Paper end - remove player from map on drop
-             return itemEntity;
-         }
-     }
-diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-index fd50bd77e4dc7b18240afe5505c6e6f0f869c127..f60c2f3a3dfc69f50225b6ee7333ada5e9dfd090 100644
---- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
-@@ -67,6 +67,7 @@ public class MapItemSavedData extends SavedData {
-     public final Map<String, MapDecoration> decorations = Maps.newLinkedHashMap();
-     private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
-     private int trackedDecorationCount;
-+    private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper
- 
-     // CraftBukkit start
-     public final org.bukkit.craftbukkit.map.CraftMapView mapView;
-@@ -92,6 +93,7 @@ public class MapItemSavedData extends SavedData {
-         // CraftBukkit start
-         this.mapView = new org.bukkit.craftbukkit.map.CraftMapView(this);
-         this.server = (org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer();
-+        this.vanillaRender.buffer = colors; // Paper
-         // CraftBukkit end
-     }
- 
-@@ -163,6 +165,7 @@ public class MapItemSavedData extends SavedData {
-         if (byteArray.length == 16384) {
-             mapItemSavedData.colors = byteArray;
-         }
-+        mapItemSavedData.vanillaRender.buffer = byteArray; // Paper
- 
-         RegistryOps<Tag> registryOps = levelRegistry.createSerializationContext(NbtOps.INSTANCE);
- 
-@@ -337,7 +340,7 @@ public class MapItemSavedData extends SavedData {
-             this.trackedDecorationCount--;
-         }
- 
--        this.setDecorationsDirty();
-+        if (mapDecoration != null) this.setDecorationsDirty(); // Paper - only mark dirty if a change occurs
-     }
- 
-     public static void addTargetDecoration(ItemStack stack, BlockPos pos, String type, Holder<MapDecorationType> mapDecorationType) {
-@@ -610,7 +613,16 @@ public class MapItemSavedData extends SavedData {
-         @Nullable
-         Packet<?> nextUpdatePacket(MapId mapId) {
-             MapItemSavedData.MapPatch mapPatch;
--            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
-+            // Paper start
-+            if (!this.dirtyData && this.tick % 5 != 0) {
-+                // this won't end up sending, so don't render it!
-+                this.tick++;
-+                return null;
-+            }
-+
-+            final boolean vanillaMaps = this.shouldUseVanillaMap();
-+            org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper
-+            // Paper end
-             if (this.dirtyData) {
-                 this.dirtyData = false;
-                 mapPatch = this.createPatch(render.buffer); // CraftBukkit
-@@ -623,6 +635,7 @@ public class MapItemSavedData extends SavedData {
-                 this.dirtyDecorations = false;
-                 // CraftBukkit start
-                 java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
-+                if (vanillaMaps) this.addSeenPlayers(icons); // Paper
- 
-                 for (org.bukkit.map.MapCursor cursor : render.cursors) {
-                     if (cursor.isVisible()) {
-@@ -658,6 +671,23 @@ public class MapItemSavedData extends SavedData {
-         private void markDecorationsDirty() {
-             this.dirtyDecorations = true;
-         }
-+
-+        // Paper start
-+        private void addSeenPlayers(java.util.Collection<MapDecoration> icons) {
-+            org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity();
-+            MapItemSavedData.this.decorations.forEach((name, mapIcon) -> {
-+                // If this cursor is for a player check visibility with vanish system
-+                org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot
-+                if (other == null || player.canSee(other)) {
-+                    icons.add(mapIcon);
-+                }
-+            });
-+        }
-+
-+        private boolean shouldUseVanillaMap() {
-+            return mapView.getRenderers().size() == 1 && mapView.getRenderers().getFirst().getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class;
-+        }
-+        // Paper end
-     }
- 
-     record MapDecorationLocation(Holder<MapDecorationType> type, byte x, byte y, byte rot) {
diff --git a/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch b/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
deleted file mode 100644
index 39272633a7..0000000000
--- a/paper-server/patches/features/0014-Check-distance-in-entity-interactions.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Mon, 2 Aug 2021 10:10:40 +0200
-Subject: [PATCH] Check distance in entity interactions
-
-
-diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java
-index ae1d53cefb9cede1c93cb8b22122a4a2d2d9a40c..80a7a85e1a03a1ca406259207e1ae3b909b3284f 100644
---- a/net/minecraft/Util.java
-+++ b/net/minecraft/Util.java
-@@ -130,6 +130,7 @@ public class Util {
-         .findFirst()
-         .orElseThrow(() -> new IllegalStateException("No jar file system provider found"));
-     private static Consumer<String> thePauser = string -> {};
-+    public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Check distance in entity interactions
- 
-     public static <K, V> Collector<Entry<? extends K, ? extends V>, ?, Map<K, V>> toMap() {
-         return Collectors.toMap(Entry::getKey, Entry::getValue);
-diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 9bf5addb07f40cc50e80a46c0ee7944e938620fb..635e6e49483194c43b0ab47b5e1bacfe84613c3a 100644
---- a/net/minecraft/world/entity/LivingEntity.java
-+++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -1385,7 +1385,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                 this.hurtCurrentlyUsedShield(amount);
-                 f1 = amount;
-                 amount = 0.0F;
--                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity) {
-+                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity && livingEntity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Check distance in entity interactions
-                     this.blockUsingShield(livingEntity);
-                 }
- 
-@@ -1470,6 +1470,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                         d = damageSource.getSourcePosition().x() - this.getX();
-                         d1 = damageSource.getSourcePosition().z() - this.getZ();
-                     }
-+                    // Paper start - Check distance in entity interactions; see for loop in knockback method
-+                    if (Math.abs(d) > 200) {
-+                        d = Math.random() - Math.random();
-+                    }
-+                    if (Math.abs(d1) > 200) {
-+                        d1 = Math.random() - Math.random();
-+                    }
-+                    // Paper end - Check distance in entity interactions
- 
-                     this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
-                     if (!flag) {
-@@ -2342,7 +2350,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                 this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
-                 Entity entity = damageSource.getDirectEntity();
- 
--                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
-+                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Fix shield disable inconsistency & Check distance in entity interactions
-                     this.blockUsingShield((LivingEntity) entity);
-                 }
-             }
-diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-index 3bdb3b0984d0fee21b2c094e1d4c1f917ab68f92..54a4bf2f7df87b4a694187ade81ba158f83f0246 100644
---- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
-+++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
-@@ -638,7 +638,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable {
-             this.waterLevel = this.getY(1.0);
-             double d2 = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
-             if (this.level().noCollision(this, this.getBoundingBox().move(0.0, d2 - this.getY(), 0.0))) {
--                this.setPos(this.getX(), d2, this.getZ());
-+                this.move(MoverType.SELF, new Vec3(0.0D, d2 - this.getY(), 0.0D)); // Paper - Check distance in entity interactions // TODO Still needed??
-                 this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
-                 this.lastYd = 0.0;
-             }
diff --git a/paper-server/patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch b/paper-server/patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch
new file mode 100644
index 0000000000..d736409b62
--- /dev/null
+++ b/paper-server/patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch
@@ -0,0 +1,244 @@
+--- /dev/null
++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
+@@ -1,0 +_,241 @@
++package ca.spottedleaf.moonrise.paper;
++
++import ca.spottedleaf.moonrise.common.PlatformHooks;
++import ca.spottedleaf.moonrise.paper.util.BaseChunkSystemHooks;
++import com.mojang.datafixers.DSL;
++import com.mojang.datafixers.DataFixer;
++import com.mojang.serialization.Dynamic;
++import java.util.Collection;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.GenerationChunkHolder;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.boss.EnderDragonPart;
++import net.minecraft.world.level.BlockGetter;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.ProtoChunk;
++import net.minecraft.world.level.chunk.storage.SerializableChunkData;
++import net.minecraft.world.level.entity.EntityTypeTest;
++import net.minecraft.world.phys.AABB;
++import java.util.List;
++import java.util.function.Predicate;
++
++public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks {
++
++    @Override
++    public String getBrand() {
++        return "Paper";
++    }
++
++    @Override
++    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
++        return blockState.getLightEmission();
++    }
++
++    @Override
++    public Predicate<BlockState> maybeHasLightEmission() {
++        return (final BlockState state) -> {
++            return state.getLightEmission() != 0;
++        };
++    }
++
++    @Override
++    public boolean hasCurrentlyLoadingChunk() {
++        return false;
++    }
++
++    @Override
++    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
++        return null;
++    }
++
++    @Override
++    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
++
++    }
++
++    @Override
++    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
++
++    }
++
++    @Override
++    public boolean allowAsyncTicketUpdates() {
++        return true;
++    }
++
++    @Override
++    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
++
++    }
++
++    @Override
++    public void chunkUnloadFromWorld(final LevelChunk chunk) {
++
++    }
++
++    @Override
++    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
++
++    }
++
++    @Override
++    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
++
++    }
++
++    @Override
++    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
++        final Collection<EnderDragonPart> parts = world.dragonParts();
++        if (parts.isEmpty()) {
++            return;
++        }
++
++        for (final EnderDragonPart part : parts) {
++            if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
++                into.add(part);
++            }
++        }
++    }
++
++    @Override
++    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
++        if (into.size() >= maxCount) {
++            // fix neoforge issue: do not add if list is already full
++            return;
++        }
++
++        final Collection<EnderDragonPart> parts = world.dragonParts();
++        if (parts.isEmpty()) {
++            return;
++        }
++        for (final EnderDragonPart part : parts) {
++            if (!part.getBoundingBox().intersects(boundingBox)) {
++                continue;
++            }
++            final T casted = (T)entityTypeTest.tryCast(part);
++            if (casted != null && (predicate == null || predicate.test(casted))) {
++                into.add(casted);
++                if (into.size() >= maxCount) {
++                    break;
++                }
++            }
++        }
++    }
++
++    @Override
++    public void entityMove(final Entity entity, final long oldSection, final long newSection) {
++
++    }
++
++    @Override
++    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
++        return true;
++    }
++
++    @Override
++    public boolean configFixMC224294() {
++        return true;
++    }
++
++    @Override
++    public boolean configAutoConfigSendDistance() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
++    }
++
++    @Override
++    public double configPlayerMaxLoadRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
++    }
++
++    @Override
++    public double configPlayerMaxGenRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
++    }
++
++    @Override
++    public double configPlayerMaxSendRate() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
++    }
++
++    @Override
++    public int configPlayerMaxConcurrentLoads() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
++    }
++
++    @Override
++    public int configPlayerMaxConcurrentGens() {
++        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
++    }
++
++    @Override
++    public long configAutoSaveInterval(final ServerLevel world) {
++        return world.paperConfig().chunks.autoSaveInterval.value();
++    }
++
++    @Override
++    public int configMaxAutoSavePerTick(final ServerLevel world) {
++        return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
++    }
++
++    @Override
++    public boolean configFixMC159283() {
++        return true;
++    }
++
++    @Override
++    public boolean forceNoSave(final ChunkAccess chunk) {
++        return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
++    }
++
++    @Override
++    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
++                                  final int fromVersion, final int toVersion) {
++        return (CompoundTag)dataFixer.update(
++            type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
++        ).getValue();
++    }
++
++    @Override
++    public boolean hasMainChunkLoadHook() {
++        return false;
++    }
++
++    @Override
++    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
++
++    }
++
++    @Override
++    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
++        return entities;
++    }
++
++    @Override
++    public void unloadEntity(final Entity entity) {
++        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
++    }
++
++    @Override
++    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
++        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
++    }
++
++    @Override
++    public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
++        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
++    }
++}
diff --git a/paper-server/patches/features/0001-Add-PaperHooks.patch b/paper-server/patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch
similarity index 59%
rename from paper-server/patches/features/0001-Add-PaperHooks.patch
rename to paper-server/patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch
index 5df4d535a5..1835941481 100644
--- a/paper-server/patches/features/0001-Add-PaperHooks.patch
+++ b/paper-server/patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch
@@ -1,262 +1,6 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Mon, 16 Dec 2024 09:03:35 -0800
-Subject: [PATCH] Add PaperHooks
-
-
-diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..2988c418b34d6f699a9c24406cfd6949465b64f0
---- /dev/null
-+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-@@ -0,0 +1,241 @@
-+package ca.spottedleaf.moonrise.paper;
-+
-+import ca.spottedleaf.moonrise.common.PlatformHooks;
-+import ca.spottedleaf.moonrise.paper.util.BaseChunkSystemHooks;
-+import com.mojang.datafixers.DSL;
-+import com.mojang.datafixers.DataFixer;
-+import com.mojang.serialization.Dynamic;
-+import java.util.Collection;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.NbtOps;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.GenerationChunkHolder;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.boss.EnderDragonPart;
-+import net.minecraft.world.level.BlockGetter;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.chunk.ProtoChunk;
-+import net.minecraft.world.level.chunk.storage.SerializableChunkData;
-+import net.minecraft.world.level.entity.EntityTypeTest;
-+import net.minecraft.world.phys.AABB;
-+import java.util.List;
-+import java.util.function.Predicate;
-+
-+public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks {
-+
-+    @Override
-+    public String getBrand() {
-+        return "Paper";
-+    }
-+
-+    @Override
-+    public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
-+        return blockState.getLightEmission();
-+    }
-+
-+    @Override
-+    public Predicate<BlockState> maybeHasLightEmission() {
-+        return (final BlockState state) -> {
-+            return state.getLightEmission() != 0;
-+        };
-+    }
-+
-+    @Override
-+    public boolean hasCurrentlyLoadingChunk() {
-+        return false;
-+    }
-+
-+    @Override
-+    public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
-+        return null;
-+    }
-+
-+    @Override
-+    public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
-+
-+    }
-+
-+    @Override
-+    public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
-+
-+    }
-+
-+    @Override
-+    public boolean allowAsyncTicketUpdates() {
-+        return true;
-+    }
-+
-+    @Override
-+    public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
-+
-+    }
-+
-+    @Override
-+    public void chunkUnloadFromWorld(final LevelChunk chunk) {
-+
-+    }
-+
-+    @Override
-+    public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
-+
-+    }
-+
-+    @Override
-+    public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
-+
-+    }
-+
-+    @Override
-+    public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
-+
-+    }
-+
-+    @Override
-+    public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate, final List<Entity> into) {
-+        final Collection<EnderDragonPart> parts = world.dragonParts();
-+        if (parts.isEmpty()) {
-+            return;
-+        }
-+
-+        for (final EnderDragonPart part : parts) {
-+            if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) {
-+                into.add(part);
-+            }
-+        }
-+    }
-+
-+    @Override
-+    public <T extends Entity> void addToGetEntities(final Level world, final EntityTypeTest<Entity, T> entityTypeTest, final AABB boundingBox, final Predicate<? super T> predicate, final List<? super T> into, final int maxCount) {
-+        if (into.size() >= maxCount) {
-+            // fix neoforge issue: do not add if list is already full
-+            return;
-+        }
-+
-+        final Collection<EnderDragonPart> parts = world.dragonParts();
-+        if (parts.isEmpty()) {
-+            return;
-+        }
-+        for (final EnderDragonPart part : parts) {
-+            if (!part.getBoundingBox().intersects(boundingBox)) {
-+                continue;
-+            }
-+            final T casted = (T)entityTypeTest.tryCast(part);
-+            if (casted != null && (predicate == null || predicate.test(casted))) {
-+                into.add(casted);
-+                if (into.size() >= maxCount) {
-+                    break;
-+                }
-+            }
-+        }
-+    }
-+
-+    @Override
-+    public void entityMove(final Entity entity, final long oldSection, final long newSection) {
-+
-+    }
-+
-+    @Override
-+    public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
-+        return true;
-+    }
-+
-+    @Override
-+    public boolean configFixMC224294() {
-+        return true;
-+    }
-+
-+    @Override
-+    public boolean configAutoConfigSendDistance() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
-+    }
-+
-+    @Override
-+    public double configPlayerMaxLoadRate() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
-+    }
-+
-+    @Override
-+    public double configPlayerMaxGenRate() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
-+    }
-+
-+    @Override
-+    public double configPlayerMaxSendRate() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
-+    }
-+
-+    @Override
-+    public int configPlayerMaxConcurrentLoads() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
-+    }
-+
-+    @Override
-+    public int configPlayerMaxConcurrentGens() {
-+        return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
-+    }
-+
-+    @Override
-+    public long configAutoSaveInterval(final ServerLevel world) {
-+        return world.paperConfig().chunks.autoSaveInterval.value();
-+    }
-+
-+    @Override
-+    public int configMaxAutoSavePerTick(final ServerLevel world) {
-+        return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
-+    }
-+
-+    @Override
-+    public boolean configFixMC159283() {
-+        return true;
-+    }
-+
-+    @Override
-+    public boolean forceNoSave(final ChunkAccess chunk) {
-+        return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
-+    }
-+
-+    @Override
-+    public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
-+                                  final int fromVersion, final int toVersion) {
-+        return (CompoundTag)dataFixer.update(
-+            type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
-+        ).getValue();
-+    }
-+
-+    @Override
-+    public boolean hasMainChunkLoadHook() {
-+        return false;
-+    }
-+
-+    @Override
-+    public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
-+
-+    }
-+
-+    @Override
-+    public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
-+        return entities;
-+    }
-+
-+    @Override
-+    public void unloadEntity(final Entity entity) {
-+        entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
-+    }
-+
-+    @Override
-+    public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
-+        net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
-+    }
-+
-+    @Override
-+    public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
-+        return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
-+    }
-+}
-diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..34b45bc11124efb22f0f3ae5b2ad8f445c719476
 --- /dev/null
 +++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
-@@ -0,0 +1,332 @@
+@@ -1,0 +_,332 @@
 +package ca.spottedleaf.moonrise.paper.util;
 +
 +import ca.spottedleaf.concurrentutil.util.Priority;
diff --git a/paper-server/patches/sources/net/minecraft/Util.java.patch b/paper-server/patches/sources/net/minecraft/Util.java.patch
index 946139d552..ebf86ad6c2 100644
--- a/paper-server/patches/sources/net/minecraft/Util.java.patch
+++ b/paper-server/patches/sources/net/minecraft/Util.java.patch
@@ -28,6 +28,14 @@
      private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT);
      public static final int LINEAR_LOOKUP_THRESHOLD = 8;
      private static final Set<String> ALLOWED_UNTRUSTED_LINK_PROTOCOLS = Set.of("http", "https");
+@@ -113,6 +_,7 @@
+         .findFirst()
+         .orElseThrow(() -> new IllegalStateException("No jar file system provider found"));
+     private static Consumer<String> thePauser = string -> {};
++    public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Check distance in entity interactions
+ 
+     public static <K, V> Collector<Entry<? extends K, ? extends V>, ?, Map<K, V>> toMap() {
+         return Collectors.toMap(Entry::getKey, Entry::getValue);
 @@ -135,7 +_,7 @@
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
index cd374686c0..09caa51586 100644
--- a/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/Vec3i.java.patch
@@ -47,3 +47,14 @@
          return this.z;
      }
  
+@@ -235,4 +_,10 @@
+     public String toShortString() {
+         return this.getX() + ", " + this.getY() + ", " + this.getZ();
+     }
++
++    // Paper start - Perf: Optimize isInWorldBounds
++    public final boolean isInsideBuildHeightAndWorldBoundsHorizontal(final net.minecraft.world.level.LevelHeightAccessor levelHeightAccessor) {
++        return this.getX() >= -30000000 && this.getZ() >= -30000000 && this.getX() < 30000000 && this.getZ() < 30000000 && !levelHeightAccessor.isOutsideBuildHeight(this.getY());
++    }
++    // Paper end - Perf: Optimize isInWorldBounds
+ }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index b8d9ca742e..6bb71f81a0 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -1059,7 +1059,7 @@
                      String string = "onTrackingStart called during navigation iteration";
                      Util.logAndPauseIfInIde(
                          "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")
-@@ -1757,10 +_,51 @@
+@@ -1757,10 +_,52 @@
              }
  
              entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
@@ -1081,8 +1081,7 @@
          @Override
          public void onTrackingEnd(Entity entity) {
 +            org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
-+            // Spigot start
-+            // TODO I don't think this is needed anymore
++            // Spigot start // TODO I don't think this is needed anymore
 +            if (entity instanceof Player player) {
 +                for (final ServerLevel level : ServerLevel.this.getServer().getAllLevels()) {
 +                    for (final Optional<net.minecraft.world.level.saveddata.SavedData> savedData : level.getDataStorage().cache.values()) {
@@ -1091,7 +1090,9 @@
 +                        }
 +
 +                        map.carriedByPlayers.remove(player);
-+                        map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player);
++                        if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) {
++                            map.decorations.remove(player.getName().getString());
++                        }
 +                    }
 +                }
 +            }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
index 7c69b49dd9..c32e052b23 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch
@@ -1342,7 +1342,7 @@
      }
  
      public SectionPos getLastSectionPos() {
-@@ -1930,16 +_,41 @@
+@@ -1930,21 +_,54 @@
      }
  
      @Override
@@ -1386,6 +1386,19 @@
                  }
  
                  this.awardStat(Stats.DROP);
+             }
+ 
++            // Paper start - remove player from map on drop
++            if (item.getItem() == net.minecraft.world.item.Items.FILLED_MAP) {
++                final MapItemSavedData mapData = MapItem.getSavedData(item, this.level());
++                if (mapData != null) {
++                    mapData.tickCarriedBy(this, item);
++                }
++            }
++            // Paper end - remove player from map on drop
+             return itemEntity;
+         }
+     }
 @@ -1955,6 +_,11 @@
              return null;
          } else {
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index df1aedb289..84c9704c87 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -497,7 +497,7 @@
              return false;
          } else if (damageSource.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
              return false;
-@@ -1116,10 +_,11 @@
+@@ -1116,47 +_,71 @@
                  amount = 0.0F;
              }
  
@@ -512,7 +512,11 @@
                  this.hurtCurrentlyUsedShield(amount);
                  f1 = amount;
                  amount = 0.0F;
-@@ -1130,33 +_,56 @@
+-                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity) {
++                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && damageSource.getDirectEntity() instanceof LivingEntity livingEntity && livingEntity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Check distance in entity interactions
+                     this.blockUsingShield(livingEntity);
+                 }
+ 
                  flag = true;
              }
  
@@ -584,9 +588,18 @@
                      this.markHurt();
                  }
  
-@@ -1186,7 +_,7 @@
+@@ -1185,8 +_,16 @@
+                         d = damageSource.getSourcePosition().x() - this.getX();
                          d1 = damageSource.getSourcePosition().z() - this.getZ();
                      }
++                    // Paper start - Check distance in entity interactions; see for loop in knockback method
++                    if (Math.abs(d) > 200) {
++                        d = Math.random() - Math.random();
++                    }
++                    if (Math.abs(d1) > 200) {
++                        d1 = Math.random() - Math.random();
++                    }
++                    // Paper end - Check distance in entity interactions
  
 -                    this.knockback(0.4F, d, d1);
 +                    this.knockback(0.4F, d, d1, damageSource.getDirectEntity(), damageSource.getDirectEntity() == null ? io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.DAMAGE : io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.ENTITY_ATTACK); // CraftBukkit // Paper - knockback events
@@ -1110,7 +1123,7 @@
 +                this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
 +                Entity entity = damageSource.getDirectEntity();
 +
-+                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity) { // Paper - Fix shield disable inconsistency
++                if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Fix shield disable inconsistency & Check distance in entity interactions
 +                    this.blockUsingShield((LivingEntity) entity);
 +                }
 +            }
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
index a5d91baad1..3d61d83b48 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/vehicle/AbstractBoat.java.patch
@@ -75,6 +75,15 @@
          this.applyEffectsFromBlocks();
          this.applyEffectsFromBlocks();
          this.tickBubbleColumn();
+@@ -598,7 +_,7 @@
+             this.waterLevel = this.getY(1.0);
+             double d2 = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
+             if (this.level().noCollision(this, this.getBoundingBox().move(0.0, d2 - this.getY(), 0.0))) {
+-                this.setPos(this.getX(), d2, this.getZ());
++                this.move(MoverType.SELF, new Vec3(0.0D, d2 - this.getY(), 0.0D)); // Paper - Fix some exploit with boats // TODO Still needed?
+                 this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
+                 this.lastYd = 0.0;
+             }
 @@ -762,11 +_,18 @@
  
      @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
index 66f1c0a2ed..9958bd72b6 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch
@@ -241,7 +241,7 @@
  
      @Override
      public boolean isClientSide() {
-@@ -167,6 +_,13 @@
+@@ -167,8 +_,15 @@
          return null;
      }
  
@@ -253,14 +253,17 @@
 +    // Paper end
 +
      public boolean isInWorldBounds(BlockPos pos) {
-         return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
+-        return !this.isOutsideBuildHeight(pos) && isInWorldBoundsHorizontal(pos);
++        return pos.isInsideBuildHeightAndWorldBoundsHorizontal(this); // Paper - Perf: Optimize isInWorldBounds
      }
+ 
+     public static boolean isInSpawnableBounds(BlockPos pos) {
 @@ -176,21 +_,84 @@
      }
  
      private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
 -        return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000;
-+        return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk()
++        return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() and isInsideBuildHeightAndWorldBoundsHorizontal
      }
  
      private static boolean isOutsideSpawnableHeight(int y) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
index baf2047d7c..d1b5769824 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkAccess.java.patch
@@ -39,6 +39,14 @@
      }
  
      private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
+@@ -123,6 +_,7 @@
+         return GameEventListenerRegistry.NOOP;
+     }
+ 
++    public abstract BlockState getBlockState(final int x, final int y, final int z); // Paper
+     @Nullable
+     public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving);
+ 
 @@ -273,6 +_,7 @@
      public boolean tryMarkSaved() {
          if (this.unsaved) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ImposterProtoChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ImposterProtoChunk.java.patch
new file mode 100644
index 0000000000..da521db813
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ImposterProtoChunk.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/chunk/ImposterProtoChunk.java
++++ b/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+@@ -56,6 +_,12 @@
+     public BlockState getBlockState(BlockPos pos) {
+         return this.wrapped.getBlockState(pos);
+     }
++    // Paper start
++    @Override
++    public final BlockState getBlockState(final int x, final int y, final int z) {
++        return this.wrapped.getBlockStateFinal(x, y, z);
++    }
++    // Paper end
+ 
+     @Override
+     public FluidState getFluidState(BlockPos pos) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
index 9988645334..d0111479a1 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ProtoChunk.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/chunk/ProtoChunk.java
 +++ b/net/minecraft/world/level/chunk/ProtoChunk.java
-@@ -85,6 +_,18 @@
+@@ -85,14 +_,32 @@
          return new ChunkAccess.PackedTicks(this.blockTicks.pack(gametime), this.fluidTicks.pack(gametime));
      }
  
@@ -18,4 +18,21 @@
 +
      @Override
      public BlockState getBlockState(BlockPos pos) {
-         int y = pos.getY();
+-        int y = pos.getY();
++        // Paper start
++        return getBlockState(pos.getX(), pos.getY(), pos.getZ());
++    }
++    public BlockState getBlockState(final int x, final int y, final int z) {
++        // Paper end
+         if (this.isOutsideBuildHeight(y)) {
+             return Blocks.VOID_AIR.defaultBlockState();
+         } else {
+-            LevelChunkSection section = this.getSection(this.getSectionIndex(y));
+-            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(pos.getX() & 15, y & 15, pos.getZ() & 15);
++            // Paper start
++            LevelChunkSection section = this.getSections()[this.getSectionIndex(y)];
++            return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(x & 15, y & 15, z & 15);
++            // Paper end
+         }
+     }
+ 
diff --git a/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
index b03e362a20..9aa3e55368 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
@@ -1,26 +1,35 @@
 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
 +++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -61,6 +_,7 @@
+     public byte scale;
+     public byte[] colors = new byte[16384];
+     public boolean locked;
++    private final org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper - Use Vanilla map renderer when possible
+     public final List<MapItemSavedData.HoldingPlayer> carriedBy = Lists.newArrayList();
+     public final Map<Player, MapItemSavedData.HoldingPlayer> carriedByPlayers = Maps.newHashMap();
+     private final Map<String, MapBanner> bannerMarkers = Maps.newHashMap();
 @@ -68,6 +_,13 @@
      private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
      private int trackedDecorationCount;
  
 +    // CraftBukkit start
 +    public final org.bukkit.craftbukkit.map.CraftMapView mapView;
-+    private org.bukkit.craftbukkit.CraftServer server;
-+    public java.util.UUID uniqueId = null;
++    private final org.bukkit.craftbukkit.CraftServer server;
++    public java.util.UUID uniqueId;
 +    public MapId id;
 +    // CraftBukkit end
 +
      public static SavedData.Factory<MapItemSavedData> factory() {
          return new SavedData.Factory<>(() -> {
              throw new IllegalStateException("Should never create an empty map saved data");
-@@ -82,6 +_,10 @@
+@@ -82,6 +_,11 @@
          this.trackingPosition = trackingPosition;
          this.unlimitedTracking = unlimitedTracking;
          this.locked = locked;
 +        // CraftBukkit start
 +        this.mapView = new org.bukkit.craftbukkit.map.CraftMapView(this);
 +        this.server = (org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer();
++        this.vanillaRender.buffer = colors; // Paper - Use Vanilla map renderer when possible
 +        // CraftBukkit end
      }
  
@@ -76,6 +85,14 @@
          int _int = tag.getInt("xCenter");
          int _int1 = tag.getInt("zCenter");
          byte b = (byte)Mth.clamp(tag.getByte("scale"), 0, 4);
+@@ -114,6 +_,7 @@
+         if (byteArray.length == 16384) {
+             mapItemSavedData.colors = byteArray;
+         }
++        mapItemSavedData.vanillaRender.buffer = byteArray; // Paper - Use Vanilla map renderer when possible
+ 
+         RegistryOps<Tag> registryOps = levelRegistry.createSerializationContext(NbtOps.INSTANCE);
+ 
 @@ -154,6 +_,25 @@
              .encodeStart(NbtOps.INSTANCE, this.dimension.location())
              .resultOrPartial(LOGGER::error)
@@ -115,6 +132,15 @@
          }
  
          MapDecorations mapDecorations = mapStack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY);
+@@ -267,7 +_,7 @@
+             this.trackedDecorationCount--;
+         }
+ 
+-        this.setDecorationsDirty();
++        if (mapDecoration != null) this.setDecorationsDirty(); // Paper - only mark dirty if a change occurs
+     }
+ 
+     public static void addTargetDecoration(ItemStack stack, BlockPos pos, String type, Holder<MapDecorationType> mapDecorationType) {
 @@ -421,7 +_,7 @@
                  return true;
              }
@@ -142,11 +168,21 @@
                  }
              }
  
-@@ -540,17 +_,27 @@
+@@ -540,17 +_,38 @@
          @Nullable
          Packet<?> nextUpdatePacket(MapId mapId) {
              MapItemSavedData.MapPatch mapPatch;
-+            org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
++            // Paper start
++            if (!this.dirtyData && this.tick % 5 != 0) {
++                // this won't end up sending, so don't render it!
++                this.tick++;
++                return null;
++            }
++
++            final boolean vanillaMaps = this.shouldUseVanillaMap();
++            // Use Vanilla map renderer when possible - much simpler/faster than the CB rendering
++            org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender;
++            // Paper end
              if (this.dirtyData) {
                  this.dirtyData = false;
 -                mapPatch = this.createPatch();
@@ -157,11 +193,12 @@
  
              Collection<MapDecoration> collection;
 -            if (this.dirtyDecorations && this.tick++ % 5 == 0) {
-+            if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet
++            if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet // TODO fix this
                  this.dirtyDecorations = false;
 -                collection = MapItemSavedData.this.decorations.values();
 +                // CraftBukkit start
 +                java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
++                if (vanillaMaps) this.addSeenPlayers(icons); // Paper
 +
 +                for (org.bukkit.map.MapCursor cursor : render.cursors) {
 +                    if (cursor.isVisible()) {
@@ -173,3 +210,27 @@
              } else {
                  collection = null;
              }
+@@ -578,6 +_,23 @@
+         private void markDecorationsDirty() {
+             this.dirtyDecorations = true;
+         }
++
++        // Paper start
++        private void addSeenPlayers(java.util.Collection<MapDecoration> icons) {
++            org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity();
++            MapItemSavedData.this.decorations.forEach((name, mapIcon) -> {
++                // If this cursor is for a player check visibility with vanish system
++                org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot
++                if (other == null || player.canSee(other)) {
++                    icons.add(mapIcon);
++                }
++            });
++        }
++
++        private boolean shouldUseVanillaMap() {
++            return mapView.getRenderers().size() == 1 && mapView.getRenderers().getFirst().getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class;
++        }
++        // Paper end
+     }
+ 
+     record MapDecorationLocation(Holder<MapDecorationType> type, byte x, byte y, byte rot) {

From 3b0b3a0aef5863b37f252bfffef7ff5b9b266445 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 13:45:04 +0100
Subject: [PATCH 273/285] and some more

---
 ...Manager-and-add-advanced-packet-sup.patch} |  12 +-
 ...02-Allow-Saving-of-Oversized-Chunks.patch} |   0
 ...=> 0003-Entity-Activation-Range-2.0.patch} |  28 +-
 ...7-Anti-Xray.patch => 0004-Anti-Xray.patch} |  10 +-
 ...city-compression-and-cipher-natives.patch} |  10 +-
 ...timize-Collision-to-not-load-chunks.patch} |   2 +-
 ...alSelector-Goal.Flag-Set-operations.patch} |   0
 ...> 0008-Optimize-Voxel-Shape-Merging.patch} |   0
 ...-type-tags-suggestions-in-selectors.patch} |   2 +-
 ...-Oversized-block-entities-in-chunks.patch} |   0
 ...11-optimize-dirt-and-snow-spreading.patch} |   0
 ...-getChunkAt-calls-for-loaded-chunks.patch} |   2 +-
 ...Optimize-Bit-Operations-by-inlining.patch} |   0
 ...> 0014-Remove-streams-from-hot-code.patch} |   0
 ...er-Remove-Streams-Optimized-collect.patch} |   0
 ...> 0016-Rewrite-dataconverter-system.patch} |  12 +-
 ... 0017-Moonrise-optimisation-patches.patch} |  87 ++---
 ...r-desync-when-new-players-are-added.patch} |   2 +-
 ...-Eigencraft-redstone-implementation.patch} |   0
 ...ate-Current-redstone-implementation.patch} |   6 +-
 ...ove-exact-choice-recipe-ingredients.patch} |   0
 ...data-to-disk-if-it-serializes-witho.patch} |   0
 ...23-Entity-load-save-limit-per-chunk.patch} |   0
 .../0023-Lag-compensation-ticks.patch         | 128 -------
 ...ulate-regionfile-header-if-it-is-co.patch} |   0
 ...Incremental-chunk-and-player-saving.patch} |   8 +-
 ...=> 0026-Optimise-general-POI-access.patch} |   0
 ...0027-Optional-per-player-mob-spawns.patch} |   2 +-
 ...g-PreCreatureSpawnEvent-with-per-pl.patch} |   2 +-
 .../0028-Improved-Watchdog-Support.patch      | 317 ------------------
 ...l-more-information-in-watchdog-dumps.patch | 223 ------------
 ...pers.patch => 0029-Optimize-Hoppers.patch} |   4 +-
 ...n-checking-in-player-move-packet-ha.patch} |   2 +-
 .../minecraft/network/Connection.java.patch   |   8 +-
 .../network/protocol/PacketUtils.java.patch   |  40 ++-
 .../net/minecraft/server/Main.java.patch      |   3 +-
 .../server/MinecraftServer.java.patch         |  76 ++++-
 .../dedicated/DedicatedServer.java.patch      |  14 +-
 .../server/level/ServerLevel.java.patch       |  54 ++-
 .../level/ServerPlayerGameMode.java.patch     |   5 +-
 .../server/players/PlayerList.java.patch      |   2 +-
 .../minecraft/world/entity/Entity.java.patch  |  68 +++-
 .../world/entity/LivingEntity.java.patch      |  54 ++-
 .../paper/util/LogManagerShutdownThread.java  |  29 ++
 44 files changed, 418 insertions(+), 794 deletions(-)
 rename paper-server/patches/features/{0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch => 0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch} (97%)
 rename paper-server/patches/features/{0005-Allow-Saving-of-Oversized-Chunks.patch => 0002-Allow-Saving-of-Oversized-Chunks.patch} (100%)
 rename paper-server/patches/features/{0006-Entity-Activation-Range-2.0.patch => 0003-Entity-Activation-Range-2.0.patch} (96%)
 rename paper-server/patches/features/{0007-Anti-Xray.patch => 0004-Anti-Xray.patch} (98%)
 rename paper-server/patches/features/{0008-Use-Velocity-compression-and-cipher-natives.patch => 0005-Use-Velocity-compression-and-cipher-natives.patch} (97%)
 rename paper-server/patches/features/{0009-Optimize-Collision-to-not-load-chunks.patch => 0006-Optimize-Collision-to-not-load-chunks.patch} (98%)
 rename paper-server/patches/features/{0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch => 0007-Optimize-GoalSelector-Goal.Flag-Set-operations.patch} (100%)
 rename paper-server/patches/features/{0011-Optimize-Voxel-Shape-Merging.patch => 0008-Optimize-Voxel-Shape-Merging.patch} (100%)
 rename paper-server/patches/features/{0012-Fix-entity-type-tags-suggestions-in-selectors.patch => 0009-Fix-entity-type-tags-suggestions-in-selectors.patch} (99%)
 rename paper-server/patches/features/{0013-Handle-Oversized-block-entities-in-chunks.patch => 0010-Handle-Oversized-block-entities-in-chunks.patch} (100%)
 rename paper-server/patches/features/{0015-optimize-dirt-and-snow-spreading.patch => 0011-optimize-dirt-and-snow-spreading.patch} (100%)
 rename paper-server/patches/features/{0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch => 0012-Optimise-getChunkAt-calls-for-loaded-chunks.patch} (96%)
 rename paper-server/patches/features/{0017-Optimize-Bit-Operations-by-inlining.patch => 0013-Optimize-Bit-Operations-by-inlining.patch} (100%)
 rename paper-server/patches/features/{0018-Remove-streams-from-hot-code.patch => 0014-Remove-streams-from-hot-code.patch} (100%)
 rename paper-server/patches/features/{0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch => 0015-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch} (100%)
 rename paper-server/patches/features/{0020-Rewrite-dataconverter-system.patch => 0016-Rewrite-dataconverter-system.patch} (99%)
 rename paper-server/patches/features/{0021-Moonrise-optimisation-patches.patch => 0017-Moonrise-optimisation-patches.patch} (99%)
 rename paper-server/patches/features/{0022-Fix-entity-tracker-desync-when-new-players-are-added.patch => 0018-Fix-entity-tracker-desync-when-new-players-are-added.patch} (98%)
 rename paper-server/patches/features/{0024-Eigencraft-redstone-implementation.patch => 0019-Eigencraft-redstone-implementation.patch} (100%)
 rename paper-server/patches/features/{0025-Add-Alternate-Current-redstone-implementation.patch => 0020-Add-Alternate-Current-redstone-implementation.patch} (99%)
 rename paper-server/patches/features/{0026-Improve-exact-choice-recipe-ingredients.patch => 0021-Improve-exact-choice-recipe-ingredients.patch} (100%)
 rename paper-server/patches/features/{0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch => 0022-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch} (100%)
 rename paper-server/patches/features/{0030-Entity-load-save-limit-per-chunk.patch => 0023-Entity-load-save-limit-per-chunk.patch} (100%)
 delete mode 100644 paper-server/patches/features/0023-Lag-compensation-ticks.patch
 rename paper-server/patches/features/{0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch => 0024-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch} (100%)
 rename paper-server/patches/features/{0032-Incremental-chunk-and-player-saving.patch => 0025-Incremental-chunk-and-player-saving.patch} (94%)
 rename paper-server/patches/features/{0033-Optimise-general-POI-access.patch => 0026-Optimise-general-POI-access.patch} (100%)
 rename paper-server/patches/features/{0034-Optional-per-player-mob-spawns.patch => 0027-Optional-per-player-mob-spawns.patch} (99%)
 rename paper-server/patches/features/{0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch => 0028-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch} (98%)
 delete mode 100644 paper-server/patches/features/0028-Improved-Watchdog-Support.patch
 delete mode 100644 paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
 rename paper-server/patches/features/{0036-Optimize-Hoppers.patch => 0029-Optimize-Hoppers.patch} (99%)
 rename paper-server/patches/features/{0037-Optimise-collision-checking-in-player-move-packet-ha.patch => 0030-Optimise-collision-checking-in-player-move-packet-ha.patch} (99%)
 create mode 100644 paper-server/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java

diff --git a/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
similarity index 97%
rename from paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
rename to paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
index df7d1df9fc..02589fdaae 100644
--- a/paper-server/patches/features/0004-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
+++ b/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch
@@ -28,7 +28,7 @@ and then catch exceptions and close if they fire.
 Part of this commit was authored by: Spottedleaf, sandtechnology
 
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7f45fdb54 100644
+index 42f44c7cb0bd55ddfacd18acb0e596e7a953870e..ad8f8428b75e37097487cdfbd0db2421ee4cbe37 100644
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
 @@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -206,7 +206,7 @@ index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7
  
      private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
      private static int joinAttemptsThisTick; // Paper - Buffer joins to world
-@@ -557,6 +647,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -563,6 +653,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
  
      public void disconnect(DisconnectionDetails disconnectionDetails) {
          this.preparing = false; // Spigot
@@ -214,7 +214,7 @@ index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7
          if (this.channel == null) {
              this.delayedDisconnect = disconnectionDetails;
          }
-@@ -745,7 +836,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -751,7 +842,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public void handleDisconnection() {
          if (this.channel != null && !this.channel.isOpen()) {
              if (this.disconnectionHandled) {
@@ -223,7 +223,7 @@ index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7
              } else {
                  this.disconnectionHandled = true;
                  PacketListener packetListener = this.getPacketListener();
-@@ -756,7 +847,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -762,7 +853,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
                      );
                      packetListener1.onDisconnect(disconnectionDetails);
                  }
@@ -232,7 +232,7 @@ index 18f25bc6ada79fd51eb7522a917236299616a371..1672bd0eeea8db44016bbbccf9a332c7
                  // Paper start - Add PlayerConnectionCloseEvent
                  if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
                      /* Player was logged in, either game listener or configuration listener */
-@@ -791,4 +882,93 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -797,4 +888,93 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      public void setBandwidthLogger(LocalSampleLogger bandwithLogger) {
          this.bandwidthDebugMonitor = new BandwidthDebugMonitor(bandwithLogger);
      }
@@ -364,7 +364,7 @@ index 65ff8b9112ec76eeac48c679044fc02ae7d4ffeb..e4789584cbe43959681a8522c66eab58
 +    // Paper end
  }
 diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
-index 18fa53903cd6500ae65d993a6fe7f49d6b069339..b68adf37af7172671163d4a8074d2bfa97724b4b 100644
+index b873af7183d9b1aabc87e63d254a4326f17b21c9..7de11ba404f0b60e7f7b7c16954811a343688219 100644
 --- a/net/minecraft/server/network/ServerConnectionListener.java
 +++ b/net/minecraft/server/network/ServerConnectionListener.java
 @@ -66,11 +66,13 @@ public class ServerConnectionListener {
diff --git a/paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch b/paper-server/patches/features/0002-Allow-Saving-of-Oversized-Chunks.patch
similarity index 100%
rename from paper-server/patches/features/0005-Allow-Saving-of-Oversized-Chunks.patch
rename to paper-server/patches/features/0002-Allow-Saving-of-Oversized-Chunks.patch
diff --git a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch
similarity index 96%
rename from paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
rename to paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch
index e4ddc69055..adff760447 100644
--- a/paper-server/patches/features/0006-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch
@@ -338,7 +338,7 @@ index 0000000000000000000000000000000000000000..bd888ef719b9bfc93bace0b1d0fb771a
 +    }
 +}
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 8569f0670c13e3f635ef529f00e636bf7bca38b5..76e59a8b07ac58a38f368386e03aaa3d8586e21d 100644
+index d95413af04121fe91ca0f3b0c70025b9808acef9..ad665c7535c615d2b03a3e7864be435f933235dd 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList;
@@ -366,7 +366,7 @@ index 8569f0670c13e3f635ef529f00e636bf7bca38b5..76e59a8b07ac58a38f368386e03aaa3d
  import org.slf4j.Logger;
  
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 2e479c02794de1eb9753b6f1e59afc2d9d6981ba..7702004b68b7735043914f93b54b4413cd21ba41 100644
+index 3164f3784131babf9a6663335517a12df7e88a7b..da8848e2a3e3745949eb2356a049451d30f763a7 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -551,6 +551,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -377,7 +377,7 @@ index 2e479c02794de1eb9753b6f1e59afc2d9d6981ba..7702004b68b7735043914f93b54b4413
              this.entityTickList
                  .forEach(
                      entity -> {
-@@ -960,16 +961,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -979,12 +980,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          entity.tickCount++;
          profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
          profilerFiller.incrementCounter("tickNonPassenger");
@@ -392,6 +392,10 @@ index 2e479c02794de1eb9753b6f1e59afc2d9d6981ba..7702004b68b7735043914f93b54b4413
 -            this.tickPassenger(entity, entity1);
 +            this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
          }
+         // Paper start - log detailed entity tick information
+         } finally {
+@@ -995,7 +999,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+         // Paper end - log detailed entity tick information
      }
  
 -    private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
@@ -399,7 +403,7 @@ index 2e479c02794de1eb9753b6f1e59afc2d9d6981ba..7702004b68b7735043914f93b54b4413
          if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
              passengerEntity.stopRiding();
          } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
-@@ -978,12 +982,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1004,12 +1008,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              ProfilerFiller profilerFiller = Profiler.get();
              profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
              profilerFiller.incrementCounter("tickPassenger");
@@ -451,7 +455,7 @@ index a9f01e616ef6b0d74caf57cd68eb371a4fd30fd5..179f4e4b9b1eb57f78bbb2f9fa34b11e
      public void aiStep() {
          super.aiStep();
 diff --git a/net/minecraft/world/entity/AreaEffectCloud.java b/net/minecraft/world/entity/AreaEffectCloud.java
-index b4a1202a9f43525caf215d2f5c86ad92ea4f6de7..47db6ac3ef23fd0da127cfb5a4d3ba9ebd2ab54d 100644
+index 24735284fda151414d99faad401d25ba60995f9a..23b342cc31c7e72ade0e1ccad86a9ccf34380f13 100644
 --- a/net/minecraft/world/entity/AreaEffectCloud.java
 +++ b/net/minecraft/world/entity/AreaEffectCloud.java
 @@ -128,6 +128,16 @@ public class AreaEffectCloud extends Entity implements TraceableEntity {
@@ -472,7 +476,7 @@ index b4a1202a9f43525caf215d2f5c86ad92ea4f6de7..47db6ac3ef23fd0da127cfb5a4d3ba9e
      public void tick() {
          super.tick();
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855844f571b 100644
+index e7889c9c7b155db46730f5e168bb7fd3d1732a8c..334859c5ff7023c730513301cc11c9837b2c7823 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -380,6 +380,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -505,7 +509,7 @@ index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855
          SynchedEntityData.Builder builder = new SynchedEntityData.Builder(this);
          builder.define(DATA_SHARED_FLAGS_ID, (byte)0);
          builder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply());
-@@ -946,6 +962,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -981,6 +997,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          } else {
              this.wasOnFire = this.isOnFire();
              if (type == MoverType.PISTON) {
@@ -514,7 +518,7 @@ index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855
                  movement = this.limitPistonMovement(movement);
                  if (movement.equals(Vec3.ZERO)) {
                      return;
-@@ -959,6 +977,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -994,6 +1012,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
                  this.stuckSpeedMultiplier = Vec3.ZERO;
                  this.setDeltaMovement(Vec3.ZERO);
              }
@@ -529,10 +533,10 @@ index 61b779fc48d88a2ba0cb7c289e7c031877bac61b..32ca912230a5999ea244cdf1c5c54855
              movement = this.maybeBackOffFromEdge(movement, type);
              Vec3 vec3 = this.collide(movement);
 diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index e76889ab9766294e64afa2b89006d2efd218027c..9bf5addb07f40cc50e80a46c0ee7944e938620fb 100644
+index a71a153a91de5a564b946091d8d3ccbb330e4b89..195e1151f7b2a32d6c4eb67edd1952e38f58b266 100644
 --- a/net/minecraft/world/entity/LivingEntity.java
 +++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -3086,6 +3086,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
+@@ -3094,6 +3094,14 @@ public abstract class LivingEntity extends Entity implements Attackable {
          return false;
      }
  
@@ -647,7 +651,7 @@ index 789fea258d70e60d38271ebb31270562dc7eb3ab..d0ab3db7bbd2942db19f473474371b20
                          }
                      }
 diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java
-index a9c7cf74174a2c998c182da149bdfeb87cca7b8d..e346edacf7705faf867430c9c44b8322a2bd9d72 100644
+index 8b034b6bda937b25dbb3d09b8293fed6d7dc512c..52a7ed0d991758bad0dcedcb7f97fb15ac6c6d04 100644
 --- a/net/minecraft/world/entity/item/ItemEntity.java
 +++ b/net/minecraft/world/entity/item/ItemEntity.java
 @@ -124,6 +124,29 @@ public class ItemEntity extends Entity implements TraceableEntity {
@@ -824,7 +828,7 @@ index c553cf0592dfa606dbbb1e6854d3377b9feb5efb..8341e7f01606fca90e69384c16fc19bb
 +
  }
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index b6342c7161c2539b316be53e72640d44ce576b79..13dbd0da80b2199519370ac6e993ace53f42c1ea 100644
+index 32f184288f6065259c921293922c1b0163df4dc4..0f346faa82b988e86834c38837f6f11bea7f31c6 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -153,6 +153,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
diff --git a/paper-server/patches/features/0007-Anti-Xray.patch b/paper-server/patches/features/0004-Anti-Xray.patch
similarity index 98%
rename from paper-server/patches/features/0007-Anti-Xray.patch
rename to paper-server/patches/features/0004-Anti-Xray.patch
index 46c23e31dd..1991ec6d25 100644
--- a/paper-server/patches/features/0007-Anti-Xray.patch
+++ b/paper-server/patches/features/0004-Anti-Xray.patch
@@ -153,7 +153,7 @@ index 3a384175f8e7f204234bbaf3081bdc20c47a0d4b..5699bc15eba92e22433a20cb8326b59f
  
      private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a43b8febcf616aa1662ea126ce60f7973799ea46..cdda7f6272cfc48638df4e0e51b496e91ed77ba5 100644
+index da8848e2a3e3745949eb2356a049451d30f763a7..192977dd661ee795ada13db895db770293e9b402 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -348,7 +348,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -166,10 +166,10 @@ index a43b8febcf616aa1662ea126ce60f7973799ea46..cdda7f6272cfc48638df4e0e51b496e9
          this.levelStorageAccess = levelStorageAccess;
          this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile());
 diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index 1178af2ba50ad71556e0a5cbde3e5dc290396148..bf2a4c03afb73367a6d2530c78ff9f7c06f7f6a6 100644
+index 47ed3ad5c0b4753f58e0bafff5e5e70b2f0bb40b..623c069f1fe079e020c6391a3db1a3d95cd3dbf5 100644
 --- a/net/minecraft/server/level/ServerPlayerGameMode.java
 +++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -298,6 +298,7 @@ public class ServerPlayerGameMode {
+@@ -299,6 +299,7 @@ public class ServerPlayerGameMode {
                  org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
              }
          }
@@ -196,7 +196,7 @@ index 342bc843c384761e883de861044f4f8930ae8763..14878690a88fd4de3e2c127086607e6c
          if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
              new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 0837c732e60d82c36793a5a030f56d9004a5a83f..b0df94fb5f933491af8b024cc8ecf8b98e448f16 100644
+index 38fb0f569ffcd96e0eb6cb6f0769155a17d62874..3d5d84c1c1d431e9b369aa727ab0876f90ff5d61 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -403,7 +403,7 @@ public abstract class PlayerList {
@@ -209,7 +209,7 @@ index 0837c732e60d82c36793a5a030f56d9004a5a83f..b0df94fb5f933491af8b024cc8ecf8b9
          }
          // Paper end - Send empty chunk
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 13dbd0da80b2199519370ac6e993ace53f42c1ea..0e4ab448755632696c4326f1df9f3855cd38a64d 100644
+index 0f346faa82b988e86834c38837f6f11bea7f31c6..771d6ed6a7c889c09efd4ff6e20298c851eaa79f 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -168,6 +168,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
diff --git a/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch b/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch
similarity index 97%
rename from paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
rename to paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch
index cf7d0a06be..9011d342f0 100644
--- a/paper-server/patches/features/0008-Use-Velocity-compression-and-cipher-natives.patch
+++ b/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch
@@ -269,10 +269,10 @@ index bc674b08a41d5529fe06c6d3f077051cf4138f73..ea8a894158c44c2e7943dea43ecd8e1f
 +    // Paper end - Use Velocity cipher
  }
 diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index 1672bd0eeea8db44016bbbccf9a332c7f45fdb54..bfdc637a750602c00919422ca0e3943ba34db832 100644
+index ad8f8428b75e37097487cdfbd0db2421ee4cbe37..208efae06c7c44f220d4192219a86ec55c98a2fe 100644
 --- a/net/minecraft/network/Connection.java
 +++ b/net/minecraft/network/Connection.java
-@@ -766,11 +766,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -772,11 +772,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
          return connection;
      }
  
@@ -299,7 +299,7 @@ index 1672bd0eeea8db44016bbbccf9a332c7f45fdb54..bfdc637a750602c00919422ca0e3943b
  
      public boolean isEncrypted() {
          return this.encrypted;
-@@ -809,16 +820,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+@@ -815,16 +826,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
      // Paper end - add proper async disconnect
      public void setupCompression(int threshold, boolean validateDecompressed) {
          if (threshold >= 0) {
@@ -321,7 +321,7 @@ index 1672bd0eeea8db44016bbbccf9a332c7f45fdb54..bfdc637a750602c00919422ca0e3943b
              this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
          } else {
 diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
-index b68adf37af7172671163d4a8074d2bfa97724b4b..9d9f1b93a68bbc3e201408a3669bba7f73006218 100644
+index 7de11ba404f0b60e7f7b7c16954811a343688219..bd07e6a5aa1883786d789ea71711a0c0c0a95c26 100644
 --- a/net/minecraft/server/network/ServerConnectionListener.java
 +++ b/net/minecraft/server/network/ServerConnectionListener.java
 @@ -108,6 +108,10 @@ public class ServerConnectionListener {
@@ -336,7 +336,7 @@ index b68adf37af7172671163d4a8074d2bfa97724b4b..9d9f1b93a68bbc3e201408a3669bba7f
              this.channels
                  .add(
 diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-index d5d1cdc0759338ce1554b19689fca90d2573189e..507c6b2628cab56e00b64fe1b21f873e717eda2d 100644
+index bb28453d230921d662ed69c8dd48021f428ef060..6689aeacf50d1498e8d23cce696fb4fecbd1cf39 100644
 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
 @@ -276,11 +276,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
diff --git a/paper-server/patches/features/0009-Optimize-Collision-to-not-load-chunks.patch b/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch
similarity index 98%
rename from paper-server/patches/features/0009-Optimize-Collision-to-not-load-chunks.patch
rename to paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch
index 940badfeb9..ca6297da96 100644
--- a/paper-server/patches/features/0009-Optimize-Collision-to-not-load-chunks.patch
+++ b/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch
@@ -14,7 +14,7 @@ movement will load only the chunk the player enters anyways and avoids loading
 massive amounts of surrounding chunks due to large AABB lookups.
 
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 2e5f1dc15ea6a20f8cbdef97ecbf00e5d56603cf..5a67aa9f1fe103e5622ed6fa93bc4bc25ddbb688 100644
+index 334859c5ff7023c730513301cc11c9837b2c7823..45f69a914d5a0565196c4105d61541047301470f 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -218,6 +218,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
diff --git a/paper-server/patches/features/0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/paper-server/patches/features/0007-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
similarity index 100%
rename from paper-server/patches/features/0010-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
rename to paper-server/patches/features/0007-Optimize-GoalSelector-Goal.Flag-Set-operations.patch
diff --git a/paper-server/patches/features/0011-Optimize-Voxel-Shape-Merging.patch b/paper-server/patches/features/0008-Optimize-Voxel-Shape-Merging.patch
similarity index 100%
rename from paper-server/patches/features/0011-Optimize-Voxel-Shape-Merging.patch
rename to paper-server/patches/features/0008-Optimize-Voxel-Shape-Merging.patch
diff --git a/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch b/paper-server/patches/features/0009-Fix-entity-type-tags-suggestions-in-selectors.patch
similarity index 99%
rename from paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
rename to paper-server/patches/features/0009-Fix-entity-type-tags-suggestions-in-selectors.patch
index 5e8077c3dd..cd578f9f57 100644
--- a/paper-server/patches/features/0012-Fix-entity-type-tags-suggestions-in-selectors.patch
+++ b/paper-server/patches/features/0009-Fix-entity-type-tags-suggestions-in-selectors.patch
@@ -60,7 +60,7 @@ index fa8c5ba4e0efd0c36613aaa8eaafba0cb70ceb87..19ccf3abf14c67f72a1ca065e4a304f5
                  }
  
 diff --git a/net/minecraft/commands/arguments/EntityArgument.java b/net/minecraft/commands/arguments/EntityArgument.java
-index ba9a7636e158222bcfb50ae8487f29df6fdbdd0c..8957b99116cb087198fe372d4d2c2b3397fed19f 100644
+index 0a01df6ebd14afe79bc76364cb1df5e0c5c08074..7a06ad9940d25e163178370bfa9ccc3bbd3d1189 100644
 --- a/net/minecraft/commands/arguments/EntityArgument.java
 +++ b/net/minecraft/commands/arguments/EntityArgument.java
 @@ -138,7 +138,7 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
diff --git a/paper-server/patches/features/0013-Handle-Oversized-block-entities-in-chunks.patch b/paper-server/patches/features/0010-Handle-Oversized-block-entities-in-chunks.patch
similarity index 100%
rename from paper-server/patches/features/0013-Handle-Oversized-block-entities-in-chunks.patch
rename to paper-server/patches/features/0010-Handle-Oversized-block-entities-in-chunks.patch
diff --git a/paper-server/patches/features/0015-optimize-dirt-and-snow-spreading.patch b/paper-server/patches/features/0011-optimize-dirt-and-snow-spreading.patch
similarity index 100%
rename from paper-server/patches/features/0015-optimize-dirt-and-snow-spreading.patch
rename to paper-server/patches/features/0011-optimize-dirt-and-snow-spreading.patch
diff --git a/paper-server/patches/features/0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/paper-server/patches/features/0012-Optimise-getChunkAt-calls-for-loaded-chunks.patch
similarity index 96%
rename from paper-server/patches/features/0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch
rename to paper-server/patches/features/0012-Optimise-getChunkAt-calls-for-loaded-chunks.patch
index 5d23cbf347..6b1a406c1e 100644
--- a/paper-server/patches/features/0016-Optimise-getChunkAt-calls-for-loaded-chunks.patch
+++ b/paper-server/patches/features/0012-Optimise-getChunkAt-calls-for-loaded-chunks.patch
@@ -7,7 +7,7 @@ bypass the need to get a player chunk, then get the either,
 then unwrap it...
 
 diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java
-index d310e7489fc4ecede8deef59241444769d87b0a1..796b5f8541b0cf84482ab2b5a60adde544d43593 100644
+index c3553385074108d686425a7637b8975076d906b9..2f49dbc919f7f5eea9abce6106723c72f5ae45fb 100644
 --- a/net/minecraft/server/level/ServerChunkCache.java
 +++ b/net/minecraft/server/level/ServerChunkCache.java
 @@ -218,6 +218,12 @@ public class ServerChunkCache extends ChunkSource {
diff --git a/paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch b/paper-server/patches/features/0013-Optimize-Bit-Operations-by-inlining.patch
similarity index 100%
rename from paper-server/patches/features/0017-Optimize-Bit-Operations-by-inlining.patch
rename to paper-server/patches/features/0013-Optimize-Bit-Operations-by-inlining.patch
diff --git a/paper-server/patches/features/0018-Remove-streams-from-hot-code.patch b/paper-server/patches/features/0014-Remove-streams-from-hot-code.patch
similarity index 100%
rename from paper-server/patches/features/0018-Remove-streams-from-hot-code.patch
rename to paper-server/patches/features/0014-Remove-streams-from-hot-code.patch
diff --git a/paper-server/patches/features/0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/paper-server/patches/features/0015-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
similarity index 100%
rename from paper-server/patches/features/0019-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
rename to paper-server/patches/features/0015-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch
diff --git a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
similarity index 99%
rename from paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
rename to paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
index 6168b4106d..1f0f67af84 100644
--- a/paper-server/patches/features/0020-Rewrite-dataconverter-system.patch
+++ b/paper-server/patches/features/0016-Rewrite-dataconverter-system.patch
@@ -30560,7 +30560,7 @@ index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98e
 +    }
 +}
 diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index 0e21efc60e7dd7d348fd024d713772069951ccd4..504a5f8626b42817f04088e2539a6941cd9c6d9d 100644
+index 729eb5d052465e4093e9d8c5d4fe8463b2efaca6..5577287398db2bb9d21f14409ef580b8ab9ea018 100644
 --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 @@ -204,6 +204,43 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -30621,11 +30621,11 @@ index 1110ca4075a1bbaa46b66686435dab91b275c945..c2218630c3074c8b3f82364e37503b12
          return structureTemplate.save(new CompoundTag());
      }
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 7b233bd4c5c373fe38585e0f8d3e6367dd357741..fd6fdb6d7e15633bd01d4f930ee3b15c0dd2ca06 100644
+index 0d65bf24f515b80701150fdc430f324a533cb478..b92a3da5c325e69f5601416d4205fb33429742b3 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -303,6 +303,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
+@@ -305,6 +305,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+     public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
  
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
 +        ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
@@ -30728,7 +30728,7 @@ index 408ba448c2f127e27e30bfcc6f35f0bdcf86d298..80533af16a59e9ad7e38d1c37b213529
      }
  
 diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
-index ed6ea17e9cf3498591f2acd232a48107564b774b..117e8a155937ed5312d544b6de748206c255c84f 100644
+index feb4cd21e26598509dc140028496a6f4254c0680..de43e54698125ce9f319d4889dd49f7029fe95e0 100644
 --- a/net/minecraft/world/level/storage/LevelStorageSource.java
 +++ b/net/minecraft/world/level/storage/LevelStorageSource.java
 @@ -227,7 +227,7 @@ public class LevelStorageSource {
@@ -30741,7 +30741,7 @@ index ed6ea17e9cf3498591f2acd232a48107564b774b..117e8a155937ed5312d544b6de748206
      }
  
 diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
-index 6ac8f16500e44069b84d862d5f0e5c5cbc07387e..5682f6d601cbf8a7eb0d76eafd52095435252579 100644
+index 6c9640f5432e9110e7811b6db246d268c6243feb..45f2800c4862a726490048576fca8e1f24252676 100644
 --- a/net/minecraft/world/level/storage/PlayerDataStorage.java
 +++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
 @@ -115,7 +115,7 @@ public class PlayerDataStorage {
diff --git a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
similarity index 99%
rename from paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
rename to paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
index 4e71dab65e..08d7f1080b 100644
--- a/paper-server/patches/features/0021-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
@@ -297,7 +297,7 @@ index 0000000000000000000000000000000000000000..1b8193587814225c2ef2c5d9e667436e
 +    }
 +}
 diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java
-index c2dfbce23af5741b7f78ddd6df9bcbce69915ae9..5955a56a63e91edafbac07ac1f0c640a4f7cbb26 100644
+index 5577287398db2bb9d21f14409ef580b8ab9ea018..4d344559a20a0c35c181e297e81788c747363ec9 100644
 --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java
 +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java
 @@ -268,7 +268,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo
@@ -310,7 +310,7 @@ index c2dfbce23af5741b7f78ddd6df9bcbce69915ae9..5955a56a63e91edafbac07ac1f0c640a
  
      @Override
 diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
-index 34b45bc11124efb22f0f3ae5b2ad8f445c719476..62a9e62711a46283931d22b0e72b2b1903d973a1 100644
+index 2a03573a6a6753f1c87dbde26ff5208c827aa13a..ece1261b67033e946dfc20a96872708755bffe0a 100644
 --- a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
 +++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java
 @@ -23,218 +23,59 @@ import java.util.function.Consumer;
@@ -677,6 +677,7 @@ index 34b45bc11124efb22f0f3ae5b2ad8f445c719476..62a9e62711a46283931d22b0e72b2b19
 +        ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player);
      }
  }
+\ No newline at end of file
 diff --git a/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java b/ca/spottedleaf/moonrise/patches/block_counting/BlockCountingBitStorage.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..93bc56daec4526f373c84763b8c7ccb4a30e800b
@@ -23539,10 +23540,10 @@ index 47b1fafd91b39e73c4e9134b0b8048000fba108a..76994c1491221c06cca5405ba239e6ff
          }
      }
 diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index 47c62090b421ebea1253ee3f1c896ed84119cea6..e738405e5112584e02e01df2d5ede2676fa1bffb 100644
+index 2b46ca9a2a046063cad422bec00d76107537b091..9aa664537cc37e44db46d5a2a64ae3116938c681 100644
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
-@@ -320,6 +320,7 @@ public class Main {
+@@ -321,6 +321,7 @@ public class Main {
              WorldData worldData = worldStem.worldData();
              levelStorageAccess.saveDataTag(frozen, worldData);
              */
@@ -23551,7 +23552,7 @@ index 47c62090b421ebea1253ee3f1c896ed84119cea6..e738405e5112584e02e01df2d5ede267
                  thread1 -> {
                      DedicatedServer dedicatedServer1 = new DedicatedServer(
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae6d15ef2c 100644
+index b92a3da5c325e69f5601416d4205fb33429742b3..d967d605c2e4227ae980c30f1c8b86edbc680d6d 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2;
@@ -23563,7 +23564,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
      private static MinecraftServer SERVER; // Paper
      public static final Logger LOGGER = LogUtils.getLogger();
      public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper
-@@ -318,6 +318,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -320,6 +320,77 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          return minecraftServer;
      }
  
@@ -23641,7 +23642,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
      public MinecraftServer(
          // CraftBukkit start
          joptsimple.OptionSet options,
-@@ -648,7 +719,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -651,7 +722,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          this.forceDifficulty();
          for (ServerLevel serverLevel : this.getAllLevels()) {
              this.prepareLevels(serverLevel.getChunkSource().chunkMap.progressListener, serverLevel);
@@ -23650,7 +23651,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
              this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld()));
          }
  
-@@ -847,6 +918,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -850,6 +921,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
      public abstract boolean shouldRconBroadcast();
  
      public boolean saveAllChunks(boolean suppressLog, boolean flush, boolean forced) {
@@ -23662,7 +23663,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
          boolean flag = false;
  
          for (ServerLevel serverLevel : this.getAllLevels()) {
-@@ -854,7 +930,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -857,7 +933,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                  LOGGER.info("Saving chunks for level '{}'/{}", serverLevel, serverLevel.dimension().location());
              }
  
@@ -23671,7 +23672,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
              flag = true;
          }
  
-@@ -945,7 +1021,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -950,7 +1026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              }
          }
  
@@ -23680,7 +23681,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
              this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
  
              for (ServerLevel serverLevelx : this.getAllLevels()) {
-@@ -956,17 +1032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -961,17 +1037,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.waitUntilNextTick();
          }
  
@@ -23699,7 +23700,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
  
          this.isSaving = false;
          this.resources.close();
-@@ -986,6 +1052,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -991,6 +1057,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
          }
          // Spigot end
@@ -23711,10 +23712,10 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
 +            ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
 +        }
 +        // Paper end - rewrite chunk system
-     }
- 
-     public String getLocalIp() {
-@@ -1150,6 +1224,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         // Paper start - Improved watchdog support - move final shutdown items here
+         Util.shutdownExecutors();
+         try {
+@@ -1175,6 +1249,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
                      profilerFiller.push("tick");
                      this.tickFrame.start();
                      this.tickServer(flag ? () -> false : this::haveTime);
@@ -23728,7 +23729,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
                      this.tickFrame.end();
                      profilerFiller.popPush("nextTickWait");
                      this.mayHaveDelayedTasks = true;
-@@ -1322,6 +1403,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1345,6 +1426,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
  
      private boolean pollTaskInternal() {
          if (super.pollTask()) {
@@ -23736,7 +23737,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
              return true;
          } else {
              boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
-@@ -2442,6 +2524,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -2472,6 +2554,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
      }
  
@@ -23750,7 +23751,7 @@ index 768d287be8040797130499b021b2cfd3970f44e5..9c5305b4542483efeeeab19a79eb8eae
      // CraftBukkit start
      public boolean isDebugging() {
 diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 72409db938babd2da64a20746911cb8d45452d7f..55d3f79af2e683b983d4d3f731bb9649dfe76f59 100644
+index f8c81d795b19e73d56d6e0196c75e441ab4c2bef..97a294d2f5c1ddf0af7ffec3e1425eb329c5751b 100644
 --- a/net/minecraft/server/dedicated/DedicatedServer.java
 +++ b/net/minecraft/server/dedicated/DedicatedServer.java
 @@ -433,7 +433,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
@@ -26734,7 +26735,7 @@ index 70f6d068b3f3665b282d9750310c883839120ab2..870b9efd445ddadb3725e88351555ad9
          if (!passengers.equals(this.lastPassengers)) {
              this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82ef1934903 100644
+index 192977dd661ee795ada13db895db770293e9b402..95a4e37a3c93f9b3c56c7a7376ed521cd46fbb6f 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -170,7 +170,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
@@ -27303,8 +27304,8 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
 +
      }
  
-     public void tickNonPassenger(Entity entity) {
-@@ -1007,6 +1290,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+     // Paper start - log detailed entity tick information
+@@ -1033,6 +1316,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      }
  
      public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
@@ -27316,7 +27317,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
          ServerChunkCache chunkSource = this.getChunkSource();
          if (!skipSave) {
              org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit
-@@ -1019,13 +1307,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1045,13 +1333,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                  progress.progressStage(Component.translatable("menu.savingChunks"));
              }
  
@@ -27340,7 +27341,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
  
          // CraftBukkit start - moved from MinecraftServer.saveChunks
          ServerLevel worldserver1 = this;
-@@ -1156,7 +1449,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1182,7 +1475,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              this.removePlayerImmediately((ServerPlayer)entity, Entity.RemovalReason.DISCARDED);
          }
  
@@ -27349,7 +27350,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
      }
  
      // CraftBukkit start
-@@ -1187,7 +1480,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1213,7 +1506,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              }
              // CraftBukkit end
  
@@ -27358,7 +27359,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
          }
      }
  
-@@ -1198,7 +1491,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1224,7 +1517,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
  
      public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
          // CraftBukkit end
@@ -27367,7 +27368,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
              return false;
          } else {
              this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
-@@ -1933,7 +2226,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1959,7 +2252,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
                  }
              }
  
@@ -27376,7 +27377,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
              bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
              bufferedWriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
              bufferedWriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
-@@ -1951,13 +2244,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -1977,13 +2270,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          Path path1 = path.resolve("chunks.csv");
  
          try (Writer bufferedWriter2 = Files.newBufferedWriter(path1)) {
@@ -27392,7 +27393,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
          }
  
          Path path3 = path.resolve("entities.csv");
-@@ -2066,8 +2359,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2092,8 +2385,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
              Locale.ROOT,
              "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s",
              this.players.size(),
@@ -27403,7 +27404,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
              this.blockEntityTickers.size(),
              getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
              this.getBlockTicks().count(),
-@@ -2099,15 +2392,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2125,15 +2418,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public LevelEntityGetter<Entity> getEntities() {
          org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot
@@ -27432,7 +27433,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
      }
  
      public void startTickingChunk(LevelChunk chunk) {
-@@ -2125,32 +2428,45 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2151,32 +2454,45 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public void close() throws IOException {
          super.close();
@@ -27485,7 +27486,7 @@ index cdda7f6272cfc48638df4e0e51b496e91ed77ba5..bbb4bb0940765a12c45a99c8234ca82e
      }
  
      @Override
-@@ -2204,7 +2520,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2230,7 +2546,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
      @Override
      public CrashReportCategory fillReportDetails(CrashReport report) {
          CrashReportCategory crashReportCategory = super.fillReportDetails(report);
@@ -27975,7 +27976,7 @@ index 4eb040006f5d41b47e5ac9df5d9f19c4315d6343..7fa41dea184b01891f45d8e404bc1cba
          this.generatingStep = generatingStep;
          this.cache = cache;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index b0df94fb5f933491af8b024cc8ecf8b98e448f16..0a2f4af548561144607d24d3bd92270ce630fc95 100644
+index 3d5d84c1c1d431e9b369aa727ab0876f90ff5d61..6abe5fd39583663af0feb98607cff75c42d96729 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -1317,7 +1317,7 @@ public abstract class PlayerList {
@@ -28371,7 +28372,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896
 +    // Paper end - block counting
  }
 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0ebdfc56a 100644
+index 45f69a914d5a0565196c4105d61541047301470f..f42bdae2f80805e5069212bd16e3ab6814aef9ee 100644
 --- a/net/minecraft/world/entity/Entity.java
 +++ b/net/minecraft/world/entity/Entity.java
 @@ -135,7 +135,7 @@ import net.minecraft.world.scores.ScoreHolder;
@@ -28622,7 +28623,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
  
      public Entity(EntityType<?> entityType, Level level) {
          this.type = entityType;
-@@ -1285,35 +1391,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1327,35 +1433,77 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return distance;
      }
  
@@ -28724,7 +28725,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
      }
  
      private static float[] collectCandidateStepUpHeights(AABB box, List<VoxelShape> colliders, float deltaY, float maxUpStep) {
-@@ -2622,23 +2770,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -2664,23 +2812,110 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public boolean isInWall() {
@@ -28848,7 +28849,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
      }
  
      public InteractionResult interact(Player player, InteractionHand hand) {
-@@ -4062,15 +4297,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4104,15 +4339,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      }
  
      public Iterable<Entity> getIndirectPassengers() {
@@ -28874,7 +28875,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
      }
  
      public int countPlayerPassengers() {
-@@ -4208,77 +4445,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4250,77 +4487,136 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          return Mth.lerp(partialTick, this.yRotO, this.yRot);
      }
  
@@ -29065,7 +29066,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
  
      public boolean touchingUnloadedChunk() {
          AABB aabb = this.getBoundingBox().inflate(1.0);
-@@ -4429,6 +4725,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4473,6 +4769,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
          this.setPosRaw(x, y, z, false);
      }
      public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -29081,7 +29082,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
          if (!checkPosition(this, x, y, z)) {
              return;
          }
-@@ -4557,6 +4862,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4603,6 +4908,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
  
      @Override
      public final void setRemoved(Entity.RemovalReason removalReason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
@@ -29094,7 +29095,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
          org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause);
          // CraftBukkit end
          final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
-@@ -4568,7 +4879,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4614,7 +4925,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
              this.stopRiding();
          }
  
@@ -29103,7 +29104,7 @@ index 2b53c5f8ff2ce1097e38ea7cb6f83fbb1d661831..c166a0ec56ff2d4885459d789ddcd5f0
          this.levelCallback.onRemove(removalReason);
          this.onRemoval(removalReason);
          // Paper start - Folia schedulers
-@@ -4602,7 +4913,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4648,7 +4959,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
      public boolean shouldBeSaved() {
          return (this.removalReason == null || this.removalReason.shouldSave())
              && !this.isPassenger()
@@ -29502,7 +29503,7 @@ index 300f3ed58109219d97846082941b860585f66fed..e81195df621159da67136f020fa7a6d3
  
      // Paper start - Affects Spawning API
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 0e4ab448755632696c4326f1df9f3855cd38a64d..aff78499b73a98f7405aead0886c51e8ac262884 100644
+index 771d6ed6a7c889c09efd4ff6e20298c851eaa79f..8331b49185500ab3b4307e9ae05126b4f83a318a 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -79,6 +79,7 @@ import net.minecraft.world.level.storage.LevelData;
diff --git a/paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch b/paper-server/patches/features/0018-Fix-entity-tracker-desync-when-new-players-are-added.patch
similarity index 98%
rename from paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch
rename to paper-server/patches/features/0018-Fix-entity-tracker-desync-when-new-players-are-added.patch
index ff28bdb87b..1059713e9f 100644
--- a/paper-server/patches/features/0022-Fix-entity-tracker-desync-when-new-players-are-added.patch
+++ b/paper-server/patches/features/0018-Fix-entity-tracker-desync-when-new-players-are-added.patch
@@ -48,7 +48,7 @@ index db31989ebe3d7021cfd2311439e9a00f819b0841..1373977b339405ef59bb3ea03d195285
              serverEntity.getLastSentYRot(),
              entity.getType(),
 diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 9faf0ece0f074b8709b4e4fad0cab3e9c2224276..d22459cb1d46ea7c969aa05cb9d7d12466e173da 100644
+index 3dff97f13586be3b52bbe786852c185f6753a019..ff6503bf8eb88d1264c3d848a89d0255b4b3ae68 100644
 --- a/net/minecraft/server/level/ChunkMap.java
 +++ b/net/minecraft/server/level/ChunkMap.java
 @@ -1208,6 +1208,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
diff --git a/paper-server/patches/features/0024-Eigencraft-redstone-implementation.patch b/paper-server/patches/features/0019-Eigencraft-redstone-implementation.patch
similarity index 100%
rename from paper-server/patches/features/0024-Eigencraft-redstone-implementation.patch
rename to paper-server/patches/features/0019-Eigencraft-redstone-implementation.patch
diff --git a/paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch b/paper-server/patches/features/0020-Add-Alternate-Current-redstone-implementation.patch
similarity index 99%
rename from paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch
rename to paper-server/patches/features/0020-Add-Alternate-Current-redstone-implementation.patch
index 07537f7485..98d1bff65a 100644
--- a/paper-server/patches/features/0025-Add-Alternate-Current-redstone-implementation.patch
+++ b/paper-server/patches/features/0020-Add-Alternate-Current-redstone-implementation.patch
@@ -2326,7 +2326,7 @@ index 0000000000000000000000000000000000000000..298076a0db4e6ee6e4775ac43bf749d9
 +    }
 +}
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 651a65aaa37f2cf26d54f75529cbda71e6fc2bc4..61347004cb5b220e5596c053181a01ba5dd2b744 100644
+index 95a4e37a3c93f9b3c56c7a7376ed521cd46fbb6f..46dfaed12c998c219a20c711a06531aed2c68012 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -214,6 +214,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -2337,7 +2337,7 @@ index 651a65aaa37f2cf26d54f75529cbda71e6fc2bc4..61347004cb5b220e5596c053181a01ba
  
      public LevelChunk getChunkIfLoaded(int x, int z) {
          return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
-@@ -2529,6 +2530,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+@@ -2555,6 +2556,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
          return this.chunkSource.getGenerator().getSeaLevel();
      }
  
@@ -2352,7 +2352,7 @@ index 651a65aaa37f2cf26d54f75529cbda71e6fc2bc4..61347004cb5b220e5596c053181a01ba
          @Override
          public void onCreated(Entity entity) {
 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 72646473019424de969756ae1d0e9f789310889b..d5d18ff7c4c75ab50b3af624b831cb077584f9d1 100644
+index 8331b49185500ab3b4307e9ae05126b4f83a318a..2bbebb4335d927f240abcac67a5b423e38dc33d7 100644
 --- a/net/minecraft/world/level/Level.java
 +++ b/net/minecraft/world/level/Level.java
 @@ -2099,6 +2099,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
diff --git a/paper-server/patches/features/0026-Improve-exact-choice-recipe-ingredients.patch b/paper-server/patches/features/0021-Improve-exact-choice-recipe-ingredients.patch
similarity index 100%
rename from paper-server/patches/features/0026-Improve-exact-choice-recipe-ingredients.patch
rename to paper-server/patches/features/0021-Improve-exact-choice-recipe-ingredients.patch
diff --git a/paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/paper-server/patches/features/0022-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
similarity index 100%
rename from paper-server/patches/features/0027-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
rename to paper-server/patches/features/0022-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch
diff --git a/paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch b/paper-server/patches/features/0023-Entity-load-save-limit-per-chunk.patch
similarity index 100%
rename from paper-server/patches/features/0030-Entity-load-save-limit-per-chunk.patch
rename to paper-server/patches/features/0023-Entity-load-save-limit-per-chunk.patch
diff --git a/paper-server/patches/features/0023-Lag-compensation-ticks.patch b/paper-server/patches/features/0023-Lag-compensation-ticks.patch
deleted file mode 100644
index 0e33a6864f..0000000000
--- a/paper-server/patches/features/0023-Lag-compensation-ticks.patch
+++ /dev/null
@@ -1,128 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Sat, 23 Sep 2023 22:05:35 -0700
-Subject: [PATCH] Lag compensation ticks
-
-Areas affected by lag comepnsation:
- - Block breaking and destroying
- - Eating food items
-
-diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 9c5305b4542483efeeeab19a79eb8eae6d15ef2c..ddd23354d68f5cbdc9f72c11246ab26a6c0bbe16 100644
---- a/net/minecraft/server/MinecraftServer.java
-+++ b/net/minecraft/server/MinecraftServer.java
-@@ -301,6 +301,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
-     public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
-     private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
-+    public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
- 
-     public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
-         ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
-@@ -1663,6 +1664,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-         for (ServerLevel serverLevel : this.getAllLevels()) {
-             serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
-             serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
-+            serverLevel.updateLagCompensationTick(); // Paper - lag compensation
-             profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
-             /* Drop global time updates
-             if (this.tickCount % 20 == 0) {
-diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index bbb4bb0940765a12c45a99c8234ca82ef1934903..7dbf6113d79ab826ba5bb9b648b557f625e2b438 100644
---- a/net/minecraft/server/level/ServerLevel.java
-+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2678,4 +2678,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         return this.server.getPlayerList().getPlayer(uuid);
-     }
-     // Paper end - check global player list where appropriate
-+
-+    // Paper start - lag compensation
-+    private long lagCompensationTick = MinecraftServer.SERVER_INIT;
-+
-+    public long getLagCompensationTick() {
-+        return this.lagCompensationTick;
-+    }
-+
-+    public void updateLagCompensationTick() {
-+        this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
-+    }
-+    // Paper end - lag compensation
- }
-diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java
-index bf2a4c03afb73367a6d2530c78ff9f7c06f7f6a6..6207f45e7b84af4e6a2e4eb7b9fabf6b1e22a63a 100644
---- a/net/minecraft/server/level/ServerPlayerGameMode.java
-+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -111,7 +111,7 @@ public class ServerPlayerGameMode {
-     }
- 
-     public void tick() {
--        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
-+        this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensation
-         if (this.hasDelayedDestroy) {
-             BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks
-             if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks
-diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index 635e6e49483194c43b0ab47b5e1bacfe84613c3a..195e1151f7b2a32d6c4eb67edd1952e38f58b266 100644
---- a/net/minecraft/world/entity/LivingEntity.java
-+++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -3828,6 +3828,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
-         this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
-     }
-     // Paper end - Properly cancel usable items
-+    // Paper start - lag compensate eating
-+    protected long eatStartTime;
-+    protected int totalEatTimeTicks;
-+    // Paper end - lag compensate eating
-     private void updatingUsingItem() {
-         if (this.isUsingItem()) {
-             if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
-@@ -3841,7 +3845,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
- 
-     protected void updateUsingItem(ItemStack usingItem) {
-         usingItem.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
--        if (--this.useItemRemaining == 0 && !this.level().isClientSide && !usingItem.useOnRelease()) {
-+        // Paper start - lag compensate eating
-+        // we add 1 to the expected time to avoid lag compensating when we should not
-+        final boolean shouldLagCompensate = this.useItem.has(DataComponents.FOOD) && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1L + this.totalEatTimeTicks) * 50L * (1000L * 1000L));
-+        if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !usingItem.useOnRelease()) {
-+            this.useItemRemaining = 0;
-+            // Paper end - lag compensate eating
-             this.completeUsingItem();
-         }
-     }
-@@ -3875,7 +3884,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
-         ItemStack itemInHand = this.getItemInHand(hand);
-         if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
-             this.useItem = itemInHand;
--            this.useItemRemaining = itemInHand.getUseDuration(this);
-+            // Paper start - lag compensate eating
-+            this.useItemRemaining = this.totalEatTimeTicks = itemInHand.getUseDuration(this);
-+            this.eatStartTime = System.nanoTime();
-+            // Paper end - lag compensate eating
-             if (!this.level().isClientSide) {
-                 this.setLivingEntityFlag(1, true);
-                 this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
-@@ -3899,7 +3911,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
-                 }
-             } else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
-                 this.useItem = ItemStack.EMPTY;
--                this.useItemRemaining = 0;
-+                // Paper start - lag compensate eating
-+                this.useItemRemaining = this.totalEatTimeTicks = 0;
-+                this.eatStartTime = -1L;
-+                // Paper end - lag compensate eating
-             }
-         }
-     }
-@@ -4023,7 +4038,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
-         }
- 
-         this.useItem = ItemStack.EMPTY;
--        this.useItemRemaining = 0;
-+        // Paper start - lag compensate eating
-+        this.useItemRemaining = this.totalEatTimeTicks = 0;
-+        this.eatStartTime = -1L;
-+        // Paper end - lag compensate eating
-     }
- 
-     public boolean isBlocking() {
diff --git a/paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/paper-server/patches/features/0024-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
similarity index 100%
rename from paper-server/patches/features/0031-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
rename to paper-server/patches/features/0024-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch
diff --git a/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
similarity index 94%
rename from paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
rename to paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
index 964ec03fd8..9d7ae4524f 100644
--- a/paper-server/patches/features/0032-Incremental-chunk-and-player-saving.patch
+++ b/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Incremental chunk and player saving
 
 
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 3f880bdfd95f7556d1d76e4602a6d24dda78438c..79d050947960b26ef375c6f1986b617ad2ff081b 100644
+index d967d605c2e4227ae980c30f1c8b86edbc680d6d..6dbae12bbfd47cd4e75bc3089561e8e226e9e604 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
 @@ -960,7 +960,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -17,7 +17,7 @@ index 3f880bdfd95f7556d1d76e4602a6d24dda78438c..79d050947960b26ef375c6f1986b617a
              var4 = this.saveAllChunks(suppressLog, flush, forced);
          } finally {
              this.isSaving = false;
-@@ -1550,9 +1550,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1533,9 +1533,29 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          }
  
          this.ticksUntilAutosave--;
@@ -50,7 +50,7 @@ index 3f880bdfd95f7556d1d76e4602a6d24dda78438c..79d050947960b26ef375c6f1986b617a
          ProfilerFiller profilerFiller = Profiler.get();
          this.runAllTasks(); // Paper - move runAllTasks() into full server tick (previously for timings)
 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 9caa06f09409d36abf9e0a770ba004f4049e8e09..fc3b0db0f0a85fb42cb5fa8a1d78d4c4691ab519 100644
+index 46dfaed12c998c219a20c711a06531aed2c68012..ebeeb63c3dca505a3ce8b88feaa5d2ca20ec24a2 100644
 --- a/net/minecraft/server/level/ServerLevel.java
 +++ b/net/minecraft/server/level/ServerLevel.java
 @@ -1316,6 +1316,28 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -95,7 +95,7 @@ index e61fe83479f095e8addbd3e8f1d5179c998ae1eb..0a7e5106a1d39150326e7c323030df5d
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
      private static final int FLY_STAT_RECORDING_SPEED = 25;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 42c1f5d0f384e0e4f348e161b4461858a6d84227..2f7f5f933cf3adb6022664a0ad9b97c8f8bc8fd4 100644
+index 6abe5fd39583663af0feb98607cff75c42d96729..65c16eddff43c875be72b08f61d6f321a6876a48 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -482,6 +482,7 @@ public abstract class PlayerList {
diff --git a/paper-server/patches/features/0033-Optimise-general-POI-access.patch b/paper-server/patches/features/0026-Optimise-general-POI-access.patch
similarity index 100%
rename from paper-server/patches/features/0033-Optimise-general-POI-access.patch
rename to paper-server/patches/features/0026-Optimise-general-POI-access.patch
diff --git a/paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch b/paper-server/patches/features/0027-Optional-per-player-mob-spawns.patch
similarity index 99%
rename from paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch
rename to paper-server/patches/features/0027-Optional-per-player-mob-spawns.patch
index 6ee8bdf5ed..500d8debd1 100644
--- a/paper-server/patches/features/0034-Optional-per-player-mob-spawns.patch
+++ b/paper-server/patches/features/0027-Optional-per-player-mob-spawns.patch
@@ -78,7 +78,7 @@ index 87d4291a3944f706a694536da6de0f28c548ab8d..5576bf1d1d70ab7a010653d3207909b5
          profiler.popPush("spawnAndTick");
          boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index a97b0b177a1fb0557af2af4d1f192513d7c0390d..4f8ef57d66a4562df0f5447988797cbdfbd3c9d5 100644
+index 0a7e5106a1d39150326e7c323030df5d32ecef1e..a63702dd7e86fc8b9f78c2ae23e23b65b6b2ee24 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -368,6 +368,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
diff --git a/paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/paper-server/patches/features/0028-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
similarity index 98%
rename from paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
rename to paper-server/patches/features/0028-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
index a3eacdf9c5..2da69ced08 100644
--- a/paper-server/patches/features/0035-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
+++ b/paper-server/patches/features/0028-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch
@@ -60,7 +60,7 @@ index 5576bf1d1d70ab7a010653d3207909b5de867e70..6540b2d6a1062d883811ce240c49d30d
              spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
          } else {
 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
-index 4f8ef57d66a4562df0f5447988797cbdfbd3c9d5..e350c6ba7bd638d27abe34afd375903e603ad682 100644
+index a63702dd7e86fc8b9f78c2ae23e23b65b6b2ee24..6d75a641431c7deb7e8ddbf02cdc919015a3a7dc 100644
 --- a/net/minecraft/server/level/ServerPlayer.java
 +++ b/net/minecraft/server/level/ServerPlayer.java
 @@ -372,6 +372,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
diff --git a/paper-server/patches/features/0028-Improved-Watchdog-Support.patch b/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
deleted file mode 100644
index 4a0d417552..0000000000
--- a/paper-server/patches/features/0028-Improved-Watchdog-Support.patch
+++ /dev/null
@@ -1,317 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <aikar@aikar.co>
-Date: Sun, 12 Apr 2020 15:50:48 -0400
-Subject: [PATCH] Improved Watchdog Support
-
-Forced Watchdog Crash support and Improve Async Shutdown
-
-If the request to shut down the server is received while we are in
-a watchdog hang, immediately treat it as a crash and begin the shutdown
-process. Shutdown process is now improved to also shutdown cleanly when
-not using restart scripts either.
-
-If a server is deadlocked, a server owner can send SIGUP (or any other signal
-the JVM understands to shut down as it currently does) and the watchdog
-will no longer need to wait until the full timeout, allowing you to trigger
-a close process and try to shut the server down gracefully, saving player and
-world data.
-
-Previously there was no way to trigger this outside of waiting for a full watchdog
-timeout, which may be set to a really long time...
-
-Additionally, fix everything to do with shutting the server down asynchronously.
-
-Previously, nearly everything about the process was fragile and unsafe. Main might
-not have actually been frozen, and might still be manipulating state.
-
-Or, some reuest might ask main to do something in the shutdown but main is dead.
-
-Or worse, other things might start closing down items such as the Console or Thread Pool
-before we are fully shutdown.
-
-This change tries to resolve all of these issues by moving everything into the stop
-method and guaranteeing only one thread is stopping the server.
-
-We then issue Thread Death to the main thread of another thread initiates the stop process.
-We have to ensure Thread Death propagates correctly though to stop main completely.
-
-This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save.
-
-This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they
-are properly accounted for and wont trip watchdog on init.
-
-diff --git a/io/papermc/paper/util/LogManagerShutdownThread.java b/io/papermc/paper/util/LogManagerShutdownThread.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3d7df554b89cff23f64da7ad48b5e4d26ac2baf7
---- /dev/null
-+++ b/io/papermc/paper/util/LogManagerShutdownThread.java
-@@ -0,0 +1,29 @@
-+package io.papermc.paper.util;
-+
-+import org.apache.logging.log4j.LogManager;
-+
-+public final class LogManagerShutdownThread extends Thread {
-+
-+    static LogManagerShutdownThread INSTANCE = new LogManagerShutdownThread();
-+
-+    public static void hook() {
-+        if (INSTANCE == null) {
-+            throw new IllegalStateException("Cannot re-hook after being unhooked");
-+        }
-+        Runtime.getRuntime().addShutdownHook(INSTANCE);
-+    }
-+
-+    public static void unhook() {
-+        Runtime.getRuntime().removeShutdownHook(INSTANCE);
-+        INSTANCE = null;
-+    }
-+
-+    private LogManagerShutdownThread() {
-+        super("Log4j2 Shutdown Thread");
-+    }
-+
-+    @Override
-+    public void run() {
-+        LogManager.shutdown();
-+    }
-+}
-diff --git a/net/minecraft/CrashReport.java b/net/minecraft/CrashReport.java
-index 3e0e88afcf010d9a3d46e48bca5cbdf98fe97544..8bd7999c17c8772451f873966f8c90969aee1482 100644
---- a/net/minecraft/CrashReport.java
-+++ b/net/minecraft/CrashReport.java
-@@ -205,6 +205,7 @@ public class CrashReport {
-     }
- 
-     public static CrashReport forThrowable(Throwable cause, String description) {
-+        if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper
-         while (cause instanceof CompletionException && cause.getCause() != null) {
-             cause = cause.getCause();
-         }
-diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index e738405e5112584e02e01df2d5ede2676fa1bffb..560d80cb1177297210646b44ce25fd2fa3766d40 100644
---- a/net/minecraft/server/Main.java
-+++ b/net/minecraft/server/Main.java
-@@ -68,6 +68,7 @@ public class Main {
-     )
-     @DontObfuscate
-     public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args)
-+        io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper
-         SharedConstants.tryDetectVersion();
-         /* CraftBukkit start - Replace everything
-         OptionParser optionParser = new OptionParser();
-diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index ddd23354d68f5cbdc9f72c11246ab26a6c0bbe16..3f880bdfd95f7556d1d76e4602a6d24dda78438c 100644
---- a/net/minecraft/server/MinecraftServer.java
-+++ b/net/minecraft/server/MinecraftServer.java
-@@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-     // Spigot end
-     public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
-     public volatile boolean abnormalExit; // Paper - Improved watchdog support
-+    public volatile Thread shutdownThread; // Paper - Improved watchdog support
-     public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
-     public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
-     private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
-@@ -466,6 +467,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-         }
-         */
-         // Paper end
-+        io.papermc.paper.util.LogManagerShutdownThread.unhook(); // Paper
-         Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
-         // CraftBukkit end
-         this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
-@@ -990,6 +992,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             this.hasStopped = true;
-         }
-         if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
-+        // Paper start - kill main thread, and kill it hard
-+        shutdownThread = Thread.currentThread();
-+        org.spigotmc.WatchdogThread.doStop(); // Paper
-+        // Paper end
-         // CraftBukkit end
-         if (this.metricsRecorder.isRecording()) {
-             this.cancelRecordingMetrics();
-@@ -1061,6 +1067,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-             ca.spottedleaf.moonrise.common.util.MoonriseCommon.haltExecutors();
-         }
-         // Paper end - rewrite chunk system
-+        // Paper start - Improved watchdog support - move final shutdown items here
-+        Util.shutdownExecutors();
-+        try {
-+            net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+        } catch (final Exception ignored) {
-+        }
-+        io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
-+        this.onServerExit();
-+        // Paper end - Improved watchdog support - move final shutdown items here
-     }
- 
-     public String getLocalIp() {
-@@ -1147,6 +1162,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
-     protected void runServer() {
-         try {
-+            long serverStartTime = Util.getNanos(); // Paper
-             if (!this.initServer()) {
-                 throw new IllegalStateException("Failed to initialize server");
-             }
-@@ -1157,6 +1173,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
-             this.server.spark.enableBeforePlugins(); // Paper - spark
-             // Spigot start
-+            // Paper start - Improved Watchdog Support
-+            LOGGER.info("Running delayed init tasks");
-+            this.server.getScheduler().mainThreadHeartbeat(); // run all 1 tick delay tasks during init,
-+            // this is going to be the first thing the tick process does anyways, so move done and run it after
-+            // everything is init before watchdog tick.
-+            // anything at 3+ won't be caught here but also will trip watchdog....
-+            // tasks are default scheduled at -1 + delay, and first tick will tick at 1
-+            final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Add total time
-+            LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Add total time
-+            org.spigotmc.WatchdogThread.tick();
-+            // Paper end - Improved Watchdog Support
-             org.spigotmc.WatchdogThread.hasStarted = true; // Paper
-             Arrays.fill(this.recentTps, 20);
-             // Paper start - further improve server tick loop
-@@ -1253,6 +1280,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-                 JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
-             }
-         } catch (Throwable var69) {
-+            // Paper start
-+            if (var69 instanceof ThreadDeath) {
-+                MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", var69);
-+                return;
-+            }
-+            // Paper end
-             LOGGER.error("Encountered an unexpected exception", var69);
-             CrashReport crashReport = constructOrExtractCrashReport(var69);
-             this.fillSystemReport(crashReport.getSystemReport());
-@@ -1275,15 +1308,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-                     this.services.profileCache().clearExecutor();
-                 }
- 
--                org.spigotmc.WatchdogThread.doStop(); // Spigot
-+                //org.spigotmc.WatchdogThread.doStop(); // Spigot // Paper - move into stop
-                 // CraftBukkit start - Restore terminal to original settings
-                 try {
--                    net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+                    //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
-                 } catch (Exception ignored) {
-                 }
-                 // CraftBukkit end
--                io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
--                this.onServerExit();
-+                //io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
-+                //this.onServerExit(); // Paper - moved into stop
-             }
-         }
-     }
-@@ -1387,6 +1420,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- 
-     @Override
-     public TickTask wrapRunnable(Runnable runnable) {
-+        // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
-+        if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
-+            runnable.run();
-+            runnable = () -> {};
-+        }
-+        // Paper end
-         return new TickTask(this.tickCount, runnable);
-     }
- 
-@@ -2184,7 +2223,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-                     this.resources.managers.updateStaticRegistryTags();
-                     this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
-                     this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
--                    this.getPlayerList().saveAll();
-+                    // Paper start
-+                    if (Thread.currentThread() != this.serverThread) {
-+                        return;
-+                    }
-+                    // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
-+                    for (ServerPlayer player : this.getPlayerList().getPlayers()) {
-+                        player.getAdvancements().save();
-+                    }
-+                    // Paper end
-                     this.getPlayerList().reloadResources();
-                     this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
-                     this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
-diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
-index 55d3f79af2e683b983d4d3f731bb9649dfe76f59..d900469dafd430ec3eba10d6f83bd8759f7a7edf 100644
---- a/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -322,7 +322,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-             this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
-             long l = Util.getNanos() - nanos;
-             String string = String.format(Locale.ROOT, "%.3fs", l / 1.0E9);
--            LOGGER.info("Done ({})! For help, type \"help\"", string);
-+            LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), string); // Paper - clarify startup log messages & add total time
-             if (properties.announcePlayerAchievements != null) {
-                 this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
-             }
-@@ -419,7 +419,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-         }
- 
-         this.hasFullyShutdown = true; // Paper - Improved watchdog support
--        System.exit(0); // CraftBukkit
-+        System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
-     }
- 
-     @Override
-@@ -753,7 +753,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
-     @Override
-     public void stopServer() {
-         super.stopServer();
--        Util.shutdownExecutors();
-+        //Util.shutdownExecutors(); // Paper - moved into super
-         SkullBlockEntity.clear();
-     }
- 
-diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 0a2f4af548561144607d24d3bd92270ce630fc95..42c1f5d0f384e0e4f348e161b4461858a6d84227 100644
---- a/net/minecraft/server/players/PlayerList.java
-+++ b/net/minecraft/server/players/PlayerList.java
-@@ -512,7 +512,7 @@ public abstract class PlayerList {
-         this.cserver.getPluginManager().callEvent(playerQuitEvent);
-         player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
- 
--        player.doTick(); // SPIGOT-924
-+        if (this.server.isSameThread()) player.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
-         // CraftBukkit end
- 
-         // Paper start - Configurable player collision; Remove from collideRule team if needed
-diff --git a/net/minecraft/util/thread/BlockableEventLoop.java b/net/minecraft/util/thread/BlockableEventLoop.java
-index 186c1b2e3599770385150eb7acdcd890aa5835eb..bfea9a2ae5e0bd5dae2873f715d192dfcbe97ee5 100644
---- a/net/minecraft/util/thread/BlockableEventLoop.java
-+++ b/net/minecraft/util/thread/BlockableEventLoop.java
-@@ -169,6 +169,6 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
-     public static boolean isNonRecoverable(Throwable error) {
-         return error instanceof ReportedException reportedException
-             ? isNonRecoverable(reportedException.getCause())
--            : error instanceof OutOfMemoryError || error instanceof StackOverflowError;
-+            : error instanceof OutOfMemoryError || error instanceof StackOverflowError || error instanceof ThreadDeath; // Paper
-     }
- }
-diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 2c7b6034852216fc5aa5c3f42a70ebd8e8317a17..3bf79eedfc358f54bfe23b5a75b3ad121558f6c6 100644
---- a/net/minecraft/world/level/Level.java
-+++ b/net/minecraft/world/level/Level.java
-@@ -1498,6 +1498,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
-         try {
-             consumerEntity.accept(entity);
-         } catch (Throwable var6) {
-+            if (var6 instanceof ThreadDeath) throw var6; // Paper
-             // Paper start - Prevent block entity and entity crashes
-             final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
-             MinecraftServer.LOGGER.error(msg, var6);
-diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
-index c1ae7755e8d6fa8501d2210dab7605d993c55722..b10890c5a7e42163e419e74596b952525c3ed3eb 100644
---- a/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -929,6 +929,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
- 
-                         profilerFiller.pop();
-                     } catch (Throwable var5) {
-+                        if (var5 instanceof ThreadDeath) throw var5; // Paper
-                         // Paper start - Prevent block entity and entity crashes
-                         final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
-                         net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);
diff --git a/paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch b/paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
deleted file mode 100644
index 0f0a58ea62..0000000000
--- a/paper-server/patches/features/0029-Detail-more-information-in-watchdog-dumps.patch
+++ /dev/null
@@ -1,223 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <Spottedleaf@users.noreply.github.com>
-Date: Thu, 26 Mar 2020 21:59:32 -0700
-Subject: [PATCH] Detail more information in watchdog dumps
-
-- Dump position, world, velocity, and uuid for currently ticking entities
-- Dump player name, player uuid, position, and world for packet handling
-
-diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java
-index 460bb584db04b582f3297ae419183f430aff1ec0..c2d2bfa3c18daf27c163e5d11e8cea1f31b86c0a 100644
---- a/io/papermc/paper/FeatureHooks.java
-+++ b/io/papermc/paper/FeatureHooks.java
-@@ -93,9 +93,6 @@ public final class FeatureHooks {
-         ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(server, isLongTimeout); // Paper - rewrite chunk system
-     }
- 
--    private static void dumpEntity(final Entity entity) {
--    }
--
-     public static org.bukkit.entity.Entity[] getChunkEntities(net.minecraft.server.level.ServerLevel world, int chunkX, int chunkZ) {
-         return world.getChunkEntities(chunkX, chunkZ); // Paper - rewrite chunk system
-     }
-@@ -184,4 +181,4 @@ public final class FeatureHooks {
-         ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setSendViewDistance(distance); // Paper - rewrite chunk system
-     }
- 
--}
-\ No newline at end of file
-+}
-diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
-index bfdc637a750602c00919422ca0e3943ba34db832..208efae06c7c44f220d4192219a86ec55c98a2fe 100644
---- a/net/minecraft/network/Connection.java
-+++ b/net/minecraft/network/Connection.java
-@@ -601,7 +601,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-             if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
-                 || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
-                 || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
-+            // Paper start - detailed watchdog information
-+            net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
-+            try {
-             tickablePacketListener.tick();
-+            } finally {
-+                net.minecraft.network.protocol.PacketUtils.packetProcessing.pop();
-+            } // Paper end - detailed watchdog information
-             } // Paper end - Buffer joins to world
-         }
- 
-diff --git a/net/minecraft/network/protocol/PacketUtils.java b/net/minecraft/network/protocol/PacketUtils.java
-index e65c62dbe4c1560ae153e4c4344e9194c783a2f4..4535858701b2bb232b9d2feb2af6551526232ddc 100644
---- a/net/minecraft/network/protocol/PacketUtils.java
-+++ b/net/minecraft/network/protocol/PacketUtils.java
-@@ -21,6 +21,8 @@ public class PacketUtils {
-     public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
-         if (!executor.isSameThread()) {
-             executor.executeIfPossible(() -> {
-+                packetProcessing.push(processor); // Paper - detailed watchdog information
-+                try { // Paper - detailed watchdog information
-                 if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
-                 if (processor.shouldHandleMessage(packet)) {
-                     try {
-@@ -35,6 +37,12 @@ public class PacketUtils {
-                 } else {
-                     LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
-                 }
-+                // Paper start - detailed watchdog information
-+                } finally {
-+                    totalMainThreadPacketsProcessed.getAndIncrement();
-+                    packetProcessing.pop();
-+                }
-+                // Paper end - detailed watchdog information
-             });
-             throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
-         }
-@@ -61,4 +69,22 @@ public class PacketUtils {
- 
-         packetListener.fillCrashReport(crashReport);
-     }
-+
-+    // Paper start - detailed watchdog information
-+    public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
-+    static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
-+
-+    public static long getTotalProcessedPackets() {
-+        return totalMainThreadPacketsProcessed.get();
-+    }
-+
-+    public static java.util.List<PacketListener> getCurrentPacketProcessors() {
-+        java.util.List<PacketListener> listeners = new java.util.ArrayList<>(4);
-+        for (PacketListener listener : packetProcessing) {
-+            listeners.add(listener);
-+        }
-+
-+        return listeners;
-+    }
-+    // Paper end - detailed watchdog information
- }
-diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 131ebdaec9ff09635689001e3b85bbe5845fbf98..9caa06f09409d36abf9e0a770ba004f4049e8e09 100644
---- a/net/minecraft/server/level/ServerLevel.java
-+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1239,7 +1239,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
- 
-     }
- 
-+    // Paper start - log detailed entity tick information
-+    // TODO replace with varhandle
-+    static final java.util.concurrent.atomic.AtomicReference<Entity> currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>();
-+
-+    public static List<Entity> getCurrentlyTickingEntities() {
-+        Entity ticking = currentlyTickingEntity.get();
-+        List<Entity> ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking });
-+
-+        return ret;
-+    }
-+    // Paper end - log detailed entity tick information
-+
-     public void tickNonPassenger(Entity entity) {
-+        // Paper start - log detailed entity tick information
-+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
-+        try {
-+            if (currentlyTickingEntity.get() == null) {
-+                currentlyTickingEntity.lazySet(entity);
-+            }
-+            // Paper end - log detailed entity tick information
-         entity.setOldPosAndRot();
-         ProfilerFiller profilerFiller = Profiler.get();
-         entity.tickCount++;
-@@ -1255,6 +1274,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
-         for (Entity entity1 : entity.getPassengers()) {
-             this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2
-         }
-+        // Paper start - log detailed entity tick information
-+        } finally {
-+            if (currentlyTickingEntity.get() == entity) {
-+                currentlyTickingEntity.lazySet(null);
-+            }
-+        }
-+        // Paper end - log detailed entity tick information
-     }
- 
-     private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
-diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 3fd7f6bcdeff271a9843b2f2454f92d92069f539..3cefe3de62e3d6af7b514eb2f3df8e63c5aa5c1f 100644
---- a/net/minecraft/world/entity/Entity.java
-+++ b/net/minecraft/world/entity/Entity.java
-@@ -1062,8 +1062,43 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-         return this.onGround;
-     }
- 
-+    // Paper start - detailed watchdog information
-+    public final Object posLock = new Object(); // Paper - log detailed entity tick information
-+
-+    private Vec3 moveVector;
-+    private double moveStartX;
-+    private double moveStartY;
-+    private double moveStartZ;
-+
-+    public final Vec3 getMoveVector() {
-+        return this.moveVector;
-+    }
-+
-+    public final double getMoveStartX() {
-+        return this.moveStartX;
-+    }
-+
-+    public final double getMoveStartY() {
-+        return this.moveStartY;
-+    }
-+
-+    public final double getMoveStartZ() {
-+        return this.moveStartZ;
-+    }
-+    // Paper end - detailed watchdog information
-+
-     public void move(MoverType type, Vec3 movement) {
-         final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
-+        // Paper start - detailed watchdog information
-+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
-+        synchronized (this.posLock) {
-+            this.moveStartX = this.getX();
-+            this.moveStartY = this.getY();
-+            this.moveStartZ = this.getZ();
-+            this.moveVector = movement;
-+        }
-+        try {
-+        // Paper end - detailed watchdog information
-         if (this.noPhysics) {
-             this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
-         } else {
-@@ -1181,6 +1216,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-                 profilerFiller.pop();
-             }
-         }
-+        // Paper start - detailed watchdog information
-+        } finally {
-+            synchronized (this.posLock) { // Paper
-+                this.moveVector = null;
-+            } // Paper
-+        }
-+        // Paper end - detailed watchdog information
-     }
- 
-     private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
-@@ -4643,7 +4685,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-     }
- 
-     public void setDeltaMovement(Vec3 deltaMovement) {
-+        synchronized (this.posLock) { // Paper
-         this.deltaMovement = deltaMovement;
-+        } // Paper
-     }
- 
-     public void addDeltaMovement(Vec3 addend) {
-@@ -4749,7 +4793,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
-         }
-         // Paper end - Fix MC-4
-         if (this.position.x != x || this.position.y != y || this.position.z != z) {
-+            synchronized (this.posLock) { // Paper
-             this.position = new Vec3(x, y, z);
-+            } // Paper
-             int floor = Mth.floor(x);
-             int floor1 = Mth.floor(y);
-             int floor2 = Mth.floor(z);
diff --git a/paper-server/patches/features/0036-Optimize-Hoppers.patch b/paper-server/patches/features/0029-Optimize-Hoppers.patch
similarity index 99%
rename from paper-server/patches/features/0036-Optimize-Hoppers.patch
rename to paper-server/patches/features/0029-Optimize-Hoppers.patch
index bb158407dc..c81544a817 100644
--- a/paper-server/patches/features/0036-Optimize-Hoppers.patch
+++ b/paper-server/patches/features/0029-Optimize-Hoppers.patch
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
 +    }
 +}
 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
-index 79d050947960b26ef375c6f1986b617ad2ff081b..12f14284655845d59d44c52c65435fa91e2d7d6f 100644
+index 6dbae12bbfd47cd4e75bc3089561e8e226e9e604..9c859025302ddb2c20cf6457fa4e4eaf7fbafdd7 100644
 --- a/net/minecraft/server/MinecraftServer.java
 +++ b/net/minecraft/server/MinecraftServer.java
-@@ -1724,6 +1724,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -1707,6 +1707,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
              serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
              serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
              serverLevel.updateLagCompensationTick(); // Paper - lag compensation
diff --git a/paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
similarity index 99%
rename from paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch
rename to paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
index a2b8f8a47e..5ab3533b9d 100644
--- a/paper-server/patches/features/0037-Optimise-collision-checking-in-player-move-packet-ha.patch
+++ b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
 Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
 
 diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index e4869774b2a8096467e5913d73af5bde93dbcccf..e3c855b9335f3d86ba933e7acdd3fa2981919c99 100644
+index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38ff73a650 100644
 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 @@ -561,7 +561,7 @@ public class ServerGamePacketListenerImpl
diff --git a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
index e0c2fff5dd..f34c32dc5a 100644
--- a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
@@ -192,7 +192,7 @@
                      } catch (RejectedExecutionException var6) {
                          this.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
                      } catch (ClassCastException var7) {
-@@ -385,10 +_,24 @@
+@@ -385,10 +_,30 @@
          }
      }
  
@@ -212,7 +212,13 @@
 +            if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
 +                || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
 +                || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
++            // Paper start - detailed watchdog information
++            net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
++            try {
              tickablePacketListener.tick();
++            } finally {
++                net.minecraft.network.protocol.PacketUtils.packetProcessing.pop();
++            } // Paper end - detailed watchdog information
 +            } // Paper end - Buffer joins to world
          }
  
diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
index 0eaf929602..b2abd03b83 100644
--- a/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
+++ b/paper-server/patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch
@@ -1,10 +1,48 @@
 --- a/net/minecraft/network/protocol/PacketUtils.java
 +++ b/net/minecraft/network/protocol/PacketUtils.java
-@@ -21,6 +_,7 @@
+@@ -21,6 +_,9 @@
      public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
          if (!executor.isSameThread()) {
              executor.executeIfPossible(() -> {
++                packetProcessing.push(processor); // Paper - detailed watchdog information
++                try { // Paper - detailed watchdog information
 +                if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
                  if (processor.shouldHandleMessage(packet)) {
                      try {
                          packet.handle(processor);
+@@ -34,6 +_,12 @@
+                 } else {
+                     LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
+                 }
++                // Paper start - detailed watchdog information
++                } finally {
++                    totalMainThreadPacketsProcessed.getAndIncrement();
++                    packetProcessing.pop();
++                }
++                // Paper end - detailed watchdog information
+             });
+             throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
+         }
+@@ -60,4 +_,22 @@
+ 
+         packetListener.fillCrashReport(crashReport);
+     }
++
++    // Paper start - detailed watchdog information
++    public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
++    static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
++
++    public static long getTotalProcessedPackets() {
++        return totalMainThreadPacketsProcessed.get();
++    }
++
++    public static java.util.List<PacketListener> getCurrentPacketProcessors() {
++        java.util.List<PacketListener> listeners = new java.util.ArrayList<>(4);
++        for (PacketListener listener : packetProcessing) {
++            listeners.add(listener);
++        }
++
++        return listeners;
++    }
++    // Paper end - detailed watchdog information
+ }
diff --git a/paper-server/patches/sources/net/minecraft/server/Main.java.patch b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
index 886690bbbd..b9d9570fdd 100644
--- a/paper-server/patches/sources/net/minecraft/server/Main.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/Main.java.patch
@@ -1,11 +1,12 @@
 --- a/net/minecraft/server/Main.java
 +++ b/net/minecraft/server/Main.java
-@@ -67,8 +_,9 @@
+@@ -67,8 +_,10 @@
          reason = "System.out needed before bootstrap"
      )
      @DontObfuscate
 -    public static void main(String[] args) {
 +    public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args)
++        io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper - Improved watchdog support
          SharedConstants.tryDetectVersion();
 +        /* CraftBukkit start - Replace everything
          OptionParser optionParser = new OptionParser();
diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
index 55497abf8a..d8518cce12 100644
--- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch
@@ -41,7 +41,7 @@
      @Nullable
      private KeyPair keyPair;
      @Nullable
-@@ -271,10 +_,35 @@
+@@ -271,10 +_,37 @@
      private final SuppressedExceptionCollector suppressedExceptions = new SuppressedExceptionCollector();
      private final DiscontinuousFrame tickFrame;
  
@@ -65,9 +65,11 @@
 +    // Spigot end
 +    public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
 +    public volatile boolean abnormalExit; // Paper - Improved watchdog support
++    public volatile Thread shutdownThread; // Paper - Improved watchdog support
 +    public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
 +    public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
 +    private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
++    public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
 +
      public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
          AtomicReference<S> atomicReference = new AtomicReference<>();
@@ -110,7 +112,7 @@
              this.tickRateManager = new ServerTickRateManager(this);
              this.progressListenerFactory = progressListenerFactory;
              this.storageSource = storageSource;
-@@ -328,6 +_,37 @@
+@@ -328,6 +_,38 @@
              this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
              this.tickFrame = TracyClient.createDiscontinuousFrame("Server Tick");
          }
@@ -142,6 +144,7 @@
 +        }
 +        */
 +        // Paper end
++        io.papermc.paper.util.LogManagerShutdownThread.unhook(); // Paper - Improved watchdog support
 +        Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
 +        // CraftBukkit end
 +        this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
@@ -595,7 +598,7 @@
          if (flush) {
              for (ServerLevel serverLevel2 : this.getAllLevels()) {
                  LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", serverLevel2.getChunkSource().chunkMap.getStorageName());
-@@ -593,18 +_,46 @@
+@@ -593,18 +_,48 @@
          this.stopServer();
      }
  
@@ -617,6 +620,8 @@
 +            this.hasStopped = true;
 +        }
 +        if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
++        shutdownThread = Thread.currentThread(); // Paper - Improved watchdog support
++        org.spigotmc.WatchdogThread.doStop(); // Paper - Improved watchdog support
 +        // CraftBukkit end
          if (this.metricsRecorder.isRecording()) {
              this.cancelRecordingMetrics();
@@ -643,7 +648,7 @@
          }
  
          LOGGER.info("Saving worlds");
-@@ -646,6 +_,16 @@
+@@ -646,6 +_,25 @@
          } catch (IOException var4) {
              LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), var4);
          }
@@ -657,6 +662,15 @@
 +            this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
 +        }
 +        // Spigot end
++        // Paper start - Improved watchdog support - move final shutdown items here
++        Util.shutdownExecutors();
++        try {
++            net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
++        } catch (final Exception ignored) {
++        }
++        io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
++        this.onServerExit();
++        // Paper end - Improved watchdog support - move final shutdown items here
      }
  
      public String getLocalIp() {
@@ -733,12 +747,23 @@
      protected void runServer() {
          try {
              if (!this.initServer()) {
-@@ -681,6 +_,24 @@
+@@ -681,6 +_,35 @@
              this.statusIcon = this.loadStatusIcon().orElse(null);
              this.status = this.buildServerStatus();
  
 +            this.server.spark.enableBeforePlugins(); // Paper - spark
 +            // Spigot start
++            // Paper start
++            LOGGER.info("Running delayed init tasks");
++            this.server.getScheduler().mainThreadHeartbeat(); // run all 1 tick delay tasks during init,
++            // this is going to be the first thing the tick process does anyways, so move done and run it after
++            // everything is init before watchdog tick.
++            // anything at 3+ won't be caught here but also will trip watchdog....
++            // tasks are default scheduled at -1 + delay, and first tick will tick at 1
++            final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Improve startup message
++            LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Improve startup message
++            org.spigotmc.WatchdogThread.tick();
++            // Paper end
 +            org.spigotmc.WatchdogThread.hasStarted = true; // Paper
 +            Arrays.fill(this.recentTps, 20);
 +            // Paper start - further improve server tick loop
@@ -798,21 +823,15 @@
                  this.nextTickTimeNanos += l;
  
                  try (Profiler.Scope scope = Profiler.use(this.createProfiler())) {
-@@ -755,6 +_,14 @@
+@@ -755,7 +_,7 @@
                      this.services.profileCache().clearExecutor();
                  }
  
-+                org.spigotmc.WatchdogThread.doStop(); // Spigot
-+                // CraftBukkit start - Restore terminal to original settings
-+                try {
-+                    net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+                } catch (Exception ignored) {
-+                }
-+                // CraftBukkit end
-+                io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
-                 this.onServerExit();
+-                this.onServerExit();
++                //this.onServerExit(); // Paper - Improved watchdog support; moved into stop
              }
          }
+     }
 @@ -807,7 +_,14 @@
      }
  
@@ -829,6 +848,19 @@
      }
  
      public static boolean throwIfFatalException() {
+@@ -852,6 +_,12 @@
+ 
+     @Override
+     public TickTask wrapRunnable(Runnable runnable) {
++        // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
++        if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
++            runnable.run();
++            runnable = () -> {};
++        }
++        // Paper end
+         return new TickTask(this.tickCount, runnable);
+     }
+ 
 @@ -871,15 +_,16 @@
          if (super.pollTask()) {
              return true;
@@ -952,7 +984,7 @@
              ObjectArrayList<GameProfile> list = new ObjectArrayList<>(min);
              int randomInt = Mth.nextInt(this.random, 0, players.size() - min);
  
-@@ -1046,17 +_,64 @@
+@@ -1046,17 +_,65 @@
      protected void tickChildren(BooleanSupplier hasTimeLeft) {
          ProfilerFiller profilerFiller = Profiler.get();
          this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
@@ -1006,6 +1038,7 @@
          for (ServerLevel serverLevel : this.getAllLevels()) {
 +            serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
 +            serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
++            serverLevel.updateLagCompensationTick(); // Paper - lag compensation
              profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location());
 +            /* Drop global time updates
              if (this.tickCount % 20 == 0) {
@@ -1168,12 +1201,19 @@
                      this.resources.close();
                      this.resources = reloadableResources;
                      this.packRepository.setSelected(selectedIds);
-@@ -1529,11 +_,23 @@
+@@ -1529,11 +_,29 @@
                      this.worldData.setDataConfiguration(worldDataConfiguration);
                      this.resources.managers.updateStaticRegistryTags();
                      this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
+-                    this.getPlayerList().saveAll();
 +                    this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
-                     this.getPlayerList().saveAll();
++                    if (Thread.currentThread() != this.serverThread) return; // Paper
++                    // Paper start - we don't need to save everything, just advancements
++                    // this.getPlayerList().saveAll();
++                    for (final ServerPlayer player : this.getPlayerList().getPlayers()) {
++                        player.getAdvancements().save();
++                    }
++                    // Paper end - we don't need to save everything, just advancements
                      this.getPlayerList().reloadResources();
                      this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
                      this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
index 2f25d642a3..a71879d2eb 100644
--- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -263,7 +263,8 @@
 +            this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
              long l = Util.getNanos() - nanos;
              String string = String.format(Locale.ROOT, "%.3fs", l / 1.0E9);
-             LOGGER.info("Done ({})! For help, type \"help\"", string);
+-            LOGGER.info("Done ({})! For help, type \"help\"", string);
++            LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), string); // Paper - Improve startup message, add total time
              if (properties.announcePlayerAchievements != null) {
 -                this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this);
 +                this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
@@ -315,7 +316,7 @@
          }
 +
 +        this.hasFullyShutdown = true; // Paper - Improved watchdog support
-+        System.exit(0); // CraftBukkit
++        System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper - Improved watchdog support
      }
  
      @Override
@@ -417,6 +418,15 @@
      }
  
      public void storeUsingWhiteList(boolean isStoreUsingWhiteList) {
+@@ -532,7 +_,7 @@
+     @Override
+     public void stopServer() {
+         super.stopServer();
+-        Util.shutdownExecutors();
++        //Util.shutdownExecutors(); // Paper - Improved watchdog support; moved into super
+         SkullBlockEntity.clear();
+     }
+ 
 @@ -626,4 +_,15 @@
              }
          }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
index 6bb71f81a0..acc43a7716 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch
@@ -456,7 +456,33 @@
      }
  
      public void resetEmptyTime() {
-@@ -753,6 +_,7 @@
+@@ -746,18 +_,45 @@
+         }
+     }
+ 
++    // Paper start - log detailed entity tick information
++    // TODO replace with varhandle
++    static final java.util.concurrent.atomic.AtomicReference<Entity> currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>();
++
++    public static List<Entity> getCurrentlyTickingEntities() {
++        Entity ticking = currentlyTickingEntity.get();
++        List<Entity> ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking });
++
++        return ret;
++    }
++    // Paper end - log detailed entity tick information
++
+     public void tickNonPassenger(Entity entity) {
++        // Paper start - log detailed entity tick information
++        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
++        try {
++            if (currentlyTickingEntity.get() == null) {
++                currentlyTickingEntity.lazySet(entity);
++            }
++            // Paper end - log detailed entity tick information
+         entity.setOldPosAndRot();
+         ProfilerFiller profilerFiller = Profiler.get();
+         entity.tickCount++;
          profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
          profilerFiller.incrementCounter("tickNonPassenger");
          entity.tick();
@@ -464,6 +490,18 @@
          profilerFiller.pop();
  
          for (Entity entity1 : entity.getPassengers()) {
+             this.tickPassenger(entity, entity1);
+         }
++        // Paper start - log detailed entity tick information
++        } finally {
++            if (currentlyTickingEntity.get() == entity) {
++                currentlyTickingEntity.lazySet(null);
++            }
++        }
++        // Paper end - log detailed entity tick information
+     }
+ 
+     private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
 @@ -770,6 +_,7 @@
              profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString());
              profilerFiller.incrementCounter("tickPassenger");
@@ -1137,7 +1175,7 @@
          }
  
          @Override
-@@ -1792,4 +_,12 @@
+@@ -1792,4 +_,24 @@
              entity.updateDynamicGameEventListener(DynamicGameEventListener::move);
          }
      }
@@ -1149,4 +1187,16 @@
 +        return this.server.getPlayerList().getPlayer(uuid);
 +    }
 +    // Paper end - check global player list where appropriate
++
++    // Paper start - lag compensation
++    private long lagCompensationTick = MinecraftServer.SERVER_INIT;
++
++    public long getLagCompensationTick() {
++        return this.lagCompensationTick;
++    }
++
++    public void updateLagCompensationTick() {
++        this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
++    }
++    // Paper end - lag compensation
  }
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
index f61fcfdd62..df18866114 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -54,12 +54,13 @@
          }
      }
  
-@@ -90,10 +_,10 @@
+@@ -90,10 +_,11 @@
      }
  
      public void tick() {
 -        this.gameTicks++;
-+        this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
++        // this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit
++        this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensate eating
          if (this.hasDelayedDestroy) {
 -            BlockState blockState = this.level.getBlockState(this.delayedDestroyPos);
 -            if (blockState.isAir()) {
diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
index 2ddcf75909..65cec1e1fc 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -371,7 +371,7 @@
 +        this.cserver.getPluginManager().callEvent(playerQuitEvent);
 +        player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
 +
-+        player.doTick(); // SPIGOT-924
++        if (this.server.isSameThread()) player.doTick(); // SPIGOT-924 // Paper - Improved watchdog support; don't tick during emergency shutdowns
 +        // CraftBukkit end
 +
 +        // Paper start - Configurable player collision; Remove from collideRule team if needed
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
index 3576351376..f1de578de1 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch
@@ -501,11 +501,47 @@
      }
  
      public boolean isFree(double x, double y, double z) {
-@@ -627,6 +_,7 @@
+@@ -626,7 +_,43 @@
+         return this.onGround;
      }
  
++    // Paper start - detailed watchdog information
++    public final Object posLock = new Object(); // Paper - log detailed entity tick information
++
++    private Vec3 moveVector;
++    private double moveStartX;
++    private double moveStartY;
++    private double moveStartZ;
++
++    public final Vec3 getMoveVector() {
++        return this.moveVector;
++    }
++
++    public final double getMoveStartX() {
++        return this.moveStartX;
++    }
++
++    public final double getMoveStartY() {
++        return this.moveStartY;
++    }
++
++    public final double getMoveStartZ() {
++        return this.moveStartZ;
++    }
++    // Paper end - detailed watchdog information
++
      public void move(MoverType type, Vec3 movement) {
 +        final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
++        // Paper start - detailed watchdog information
++        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
++        synchronized (this.posLock) {
++            this.moveStartX = this.getX();
++            this.moveStartY = this.getY();
++            this.moveStartZ = this.getZ();
++            this.moveVector = movement;
++        }
++        try {
++        // Paper end - detailed watchdog information
          if (this.noPhysics) {
              this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
          } else {
@@ -538,6 +574,20 @@
                  if (!this.level().isClientSide() || this.isControlledByLocalInstance()) {
                      Entity.MovementEmission movementEmission = this.getMovementEmission();
                      if (movementEmission.emitsAnything() && !this.isPassenger()) {
+@@ -713,6 +_,13 @@
+                 profilerFiller.pop();
+             }
+         }
++        // Paper start - detailed watchdog information
++        } finally {
++            synchronized (this.posLock) { // Paper
++                this.moveVector = null;
++            } // Paper
++        }
++        // Paper end - detailed watchdog information
+     }
+ 
+     private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
 @@ -850,7 +_,7 @@
      }
  
@@ -1601,7 +1651,17 @@
                              }
                          }
                      }
-@@ -3480,7 +_,39 @@
+@@ -3417,7 +_,9 @@
+     }
+ 
+     public void setDeltaMovement(Vec3 deltaMovement) {
++        synchronized (this.posLock) { // Paper - detailed watchdog information
+         this.deltaMovement = deltaMovement;
++        } // Paper - detailed watchdog information
+     }
+ 
+     public void addDeltaMovement(Vec3 addend) {
+@@ -3480,9 +_,43 @@
          return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale);
      }
  
@@ -1639,8 +1699,12 @@
 +        }
 +        // Paper end - Fix MC-4
          if (this.position.x != x || this.position.y != y || this.position.z != z) {
++            synchronized (this.posLock) { // Paper - detailed watchdog information
              this.position = new Vec3(x, y, z);
++            } // Paper - detailed watchdog information
              int floor = Mth.floor(x);
+             int floor1 = Mth.floor(y);
+             int floor2 = Mth.floor(z);
 @@ -3496,6 +_,12 @@
  
              this.levelCallback.onMove();
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index 84c9704c87..6e41390320 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -1535,7 +1535,7 @@
      }
  
      protected void internalSetAbsorptionAmount(float absorptionAmount) {
-@@ -3115,6 +_,11 @@
+@@ -3115,6 +_,15 @@
          return (this.entityData.get(DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
      }
  
@@ -1544,10 +1544,28 @@
 +        this.resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
 +    }
 +    // Paper end - Properly cancel usable items
++    // Paper start - lag compensate eating
++    protected long eatStartTime;
++    protected int totalEatTimeTicks;
++    // Paper end - lag compensate eating
      private void updatingUsingItem() {
          if (this.isUsingItem()) {
              if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
-@@ -3154,8 +_,13 @@
+@@ -3128,7 +_,12 @@
+ 
+     protected void updateUsingItem(ItemStack usingItem) {
+         usingItem.onUseTick(this.level(), this, this.getUseItemRemainingTicks());
+-        if (--this.useItemRemaining == 0 && !this.level().isClientSide && !usingItem.useOnRelease()) {
++        // Paper start - lag compensate eating
++        // we add 1 to the expected time to avoid lag compensating when we should not
++        final boolean shouldLagCompensate = this.useItem.has(DataComponents.FOOD) && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1L + this.totalEatTimeTicks) * 50L * (1000L * 1000L));
++        if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !usingItem.useOnRelease()) {
++            this.useItemRemaining = 0;
++            // Paper end - lag compensate eating
+             this.completeUsingItem();
+         }
+     }
+@@ -3154,10 +_,18 @@
      }
  
      public void startUsingItem(InteractionHand hand) {
@@ -1560,8 +1578,26 @@
 -        if (!itemInHand.isEmpty() && !this.isUsingItem()) {
 +        if (!itemInHand.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
              this.useItem = itemInHand;
-             this.useItemRemaining = itemInHand.getUseDuration(this);
+-            this.useItemRemaining = itemInHand.getUseDuration(this);
++            // Paper start - lag compensate eating
++            this.useItemRemaining = this.totalEatTimeTicks = itemInHand.getUseDuration(this);
++            this.eatStartTime = System.nanoTime();
++            // Paper end - lag compensate eating
              if (!this.level().isClientSide) {
+                 this.setLivingEntityFlag(1, true);
+                 this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
+@@ -3181,7 +_,10 @@
+                 }
+             } else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
+                 this.useItem = ItemStack.EMPTY;
+-                this.useItemRemaining = 0;
++                // Paper start - lag compensate eating
++                this.useItemRemaining = this.totalEatTimeTicks = 0;
++                this.eatStartTime = -1L;
++                // Paper end - lag compensate eating
+             }
+         }
+     }
 @@ -3220,12 +_,49 @@
                  this.releaseUsingItem();
              } else {
@@ -1621,6 +1657,18 @@
              this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks());
              if (this.useItem.useOnRelease()) {
                  this.updatingUsingItem();
+@@ -3267,7 +_,10 @@
+         }
+ 
+         this.useItem = ItemStack.EMPTY;
+-        this.useItemRemaining = 0;
++        // Paper start - lag compensate eating
++        this.useItemRemaining = this.totalEatTimeTicks = 0;
++        this.eatStartTime = -1L;
++        // Paper end - lag compensate eating
+     }
+ 
+     public boolean isBlocking() {
 @@ -3281,12 +_,69 @@
              if (item.getUseAnimation(this.useItem) != ItemUseAnimation.BLOCK) {
                  return null;
diff --git a/paper-server/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java b/paper-server/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java
new file mode 100644
index 0000000000..3d7df554b8
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/util/LogManagerShutdownThread.java
@@ -0,0 +1,29 @@
+package io.papermc.paper.util;
+
+import org.apache.logging.log4j.LogManager;
+
+public final class LogManagerShutdownThread extends Thread {
+
+    static LogManagerShutdownThread INSTANCE = new LogManagerShutdownThread();
+
+    public static void hook() {
+        if (INSTANCE == null) {
+            throw new IllegalStateException("Cannot re-hook after being unhooked");
+        }
+        Runtime.getRuntime().addShutdownHook(INSTANCE);
+    }
+
+    public static void unhook() {
+        Runtime.getRuntime().removeShutdownHook(INSTANCE);
+        INSTANCE = null;
+    }
+
+    private LogManagerShutdownThread() {
+        super("Log4j2 Shutdown Thread");
+    }
+
+    @Override
+    public void run() {
+        LogManager.shutdown();
+    }
+}

From 7490b311ffdd4fb2a7c446579ad06a343a404249 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 13:53:17 +0100
Subject: [PATCH 274/285] Update output jar dir references

---
 .github/workflows/build.yml | 2 +-
 README.md                   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 994ade949c..24cccf5f90 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -102,7 +102,7 @@ jobs:
         uses: actions/upload-artifact@v4
         with:
           name: paper-${{ fromJSON(steps.determine.outputs.result).pr }}
-          path: build/libs/paper-paperclip-*-mojmap.jar
+          path: paper-server/build/libs/paper-paperclip-*-mojmap.jar
   event_file:
     name: "Event File"
     # Only run on PRs if the source branch is on someone else's repo
diff --git a/README.md b/README.md
index 52165c44ee..983f2b975d 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ How To (Compiling Jar From Source)
 ------
 To compile Paper, you need JDK 21 and an internet connection.
 
-Clone this repo, run `./gradlew applyPatches`, then `./gradlew createMojmapBundlerJar` from your terminal. You can find the compiled jar in the project root's `build/libs` directory.
+Clone this repo, run `./gradlew applyPatches`, then `./gradlew createMojmapBundlerJar` from your terminal. You can find the compiled jar in the `paper-server/build/libs` directory.
 
 To get a full list of tasks, run `./gradlew tasks`.
 

From fd4c10947f891f2de2901c3b0d9af0bfd8275a58 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 14:03:02 +0100
Subject: [PATCH 275/285] Update branch name references

---
 .github/workflows/close_invalid_prs.yml | 10 +++++-----
 CONTRIBUTING.md                         |  6 +++---
 README.md                               |  2 +-
 settings.gradle.kts                     |  2 +-
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/close_invalid_prs.yml b/.github/workflows/close_invalid_prs.yml
index ef572c25c8..52ac0c1769 100644
--- a/.github/workflows/close_invalid_prs.yml
+++ b/.github/workflows/close_invalid_prs.yml
@@ -9,19 +9,19 @@ jobs:
     if: |
       github.repository != github.event.pull_request.head.repo.full_name &&
       (
-        github.head_ref == 'master' ||
+        github.head_ref == 'main' ||
         github.event.pull_request.head.repo.owner.type != 'User'
       )
     runs-on: ubuntu-latest
     steps:
       - uses: superbrothers/close-pull-request@v3
-        id: "master_branch"
-        if: github.head_ref == 'master'
+        id: "main_branch"
+        if: github.head_ref == 'main'
         with:
-          comment: "Please do not open pull requests from the `master` branch, create a new branch instead."
+          comment: "Please do not open pull requests from the `main` branch, create a new branch instead."
 
       - uses: superbrothers/close-pull-request@v3
         id: "org_account"
-        if: github.event.pull_request.head.repo.owner.type != 'User' && steps.master_branch.outcome == 'skipped'
+        if: github.event.pull_request.head.repo.owner.type != 'User' && steps.main_branch.outcome == 'skipped'
         with:
           comment: "Please do not open pull requests from non-user accounts like organizations. Create a fork on a user account instead."
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 38fb143438..74421624b1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -185,11 +185,11 @@ need to "save" the changes.
 
 ## Rebasing PRs
 
-Steps to rebase a PR to include the latest changes from `master`.  
+Steps to rebase a PR to include the latest changes from `main`.  
 These steps assume the `origin` remote is your fork of this repository and `upstream` is the official PaperMC repository.
 
-1. Pull the latest changes from upstreams master: `git switch main && git pull upstream main`.
-1. Checkout feature/fix branch and rebase on master: `git checkout patch-branch && git rebase main`.
+1. Pull the latest changes from upstreams main: `git switch main && git pull upstream main`.
+1. Checkout feature/fix branch and rebase on main: `git checkout patch-branch && git rebase main`.
 1. Apply updated patches: `./gradlew applyPatches`.
 1. If there are conflicts, fix them.
 1. If your PR creates new feature patches instead of modifying existing ones, ensure your newly-created patch is the last commit by either:
diff --git a/README.md b/README.md
index 983f2b975d..3223166baa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Paper [![Paper Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Paper/build.yml?branch=master)](https://github.com/PaperMC/Paper/actions)
+Paper [![Paper Build Status](https://img.shields.io/github/actions/workflow/status/PaperMC/Paper/build.yml?branch=main)](https://github.com/PaperMC/Paper/actions)
 [![Discord](https://img.shields.io/discord/289587909051416579.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/papermc)
 [![GitHub Sponsors](https://img.shields.io/github/sponsors/papermc?label=GitHub%20Sponsors)](https://github.com/sponsors/PaperMC)
 [![Open Collective](https://img.shields.io/opencollective/all/papermc?label=OpenCollective%20Sponsors)](https://opencollective.com/papermc)
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9503dfc6a6..2e832ab34f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -24,7 +24,7 @@ if (!file(".git").exists()) {
          Built Paper jars are available for download at
          https://papermc.io/downloads/paper
          
-         See https://github.com/PaperMC/Paper/blob/master/CONTRIBUTING.md
+         See https://github.com/PaperMC/Paper/blob/main/CONTRIBUTING.md
          for further information on building and modifying Paper.
         ===================================================
     """.trimIndent()

From 70ef43b8ddbb7c56c487f97223619068c71d08d9 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sat, 21 Dec 2024 15:53:00 +0100
Subject: [PATCH 276/285] More cleanup of var names

---
 .../world/item/FishingRodItem.java.patch      |  6 ++--
 .../item/ProjectileWeaponItem.java.patch      | 12 ++++---
 .../item/StandingAndWallBlockItem.java.patch  |  2 +-
 .../item/crafting/RecipeManager.java.patch    |  4 +--
 .../world/item/crafting/RecipeMap.java.patch  | 35 +++++--------------
 .../AbstractFurnaceBlockEntity.java.patch     |  8 ++---
 .../block/entity/BannerBlockEntity.java.patch |  8 ++---
 .../entity/BeehiveBlockEntity.java.patch      |  4 +--
 8 files changed, 30 insertions(+), 49 deletions(-)

diff --git a/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
index e9d2feeb53..d3a5d6505d 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/FishingRodItem.java.patch
@@ -29,8 +29,8 @@
                  int fishingLuckBonus = EnchantmentHelper.getFishingLuckBonus(serverLevel, itemInHand, player);
 -                Projectile.spawnProjectile(new FishingHook(player, level, fishingLuckBonus, i1), serverLevel, itemInHand);
 +                // CraftBukkit start
-+                FishingHook entityfishinghook = new FishingHook(player, level, fishingLuckBonus, i1);
-+                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) player.getBukkitEntity(), null, (org.bukkit.entity.FishHook) entityfishinghook.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.FISHING);
++                FishingHook fishingHook = new FishingHook(player, level, fishingLuckBonus, i1);
++                org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) player.getBukkitEntity(), null, (org.bukkit.entity.FishHook) fishingHook.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.FISHING);
 +                level.getCraftServer().getPluginManager().callEvent(playerFishEvent);
 +
 +                if (playerFishEvent.isCancelled()) {
@@ -47,7 +47,7 @@
 +                    0.5F,
 +                    0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
 +                );
-+                Projectile.spawnProjectile(entityfishinghook, serverLevel, itemInHand);
++                Projectile.spawnProjectile(fishingHook, serverLevel, itemInHand);
 +                // CraftBukkit end
              }
  
diff --git a/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
index 4e04b8e5b1..bfc05c761e 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/ProjectileWeaponItem.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/ProjectileWeaponItem.java
 +++ b/net/minecraft/world/item/ProjectileWeaponItem.java
-@@ -62,12 +_,25 @@
+@@ -62,12 +_,29 @@
                  float f4 = f2 + f3 * ((i + 1) / 2) * f1;
                  f3 = -f3;
                  int i1 = i;
@@ -21,9 +21,13 @@
 +                }
 +
 +                if (event.getProjectile() == projectile.getBukkitEntity()) {
-+                    if (Projectile.spawnProjectile(projectile, level, itemStack).isRemoved()) {
-+                        if (shooter instanceof net.minecraft.server.level.ServerPlayer) {
-+                            ((net.minecraft.server.level.ServerPlayer) shooter).getBukkitEntity().updateInventory();
++                    if (Projectile.spawnProjectile(
++                        projectile,
++                        level,
++                        itemStack
++                    ).isRemoved()) {
++                        if (shooter instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
++                            serverPlayer.getBukkitEntity().updateInventory();
 +                        }
 +                        return;
 +                    }
diff --git a/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
index 65b7227e59..e99c04062a 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
@@ -9,7 +9,7 @@
 +        // CraftBukkit start
 +        if (blockState != null) {
 +            boolean defaultReturn = level.isUnobstructed(blockState, clickedPos, CollisionContext.empty());
-+            org.bukkit.entity.Player player = (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
++            org.bukkit.entity.Player player = (context.getPlayer() instanceof net.minecraft.server.level.ServerPlayer serverPlayer) ? serverPlayer.getBukkitEntity() : null;
 +
 +            org.bukkit.event.block.BlockCanBuildEvent event = new org.bukkit.event.block.BlockCanBuildEvent(org.bukkit.craftbukkit.block.CraftBlock.at(context.getLevel(), clickedPos), player, org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockState), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent
 +            context.getLevel().getCraftServer().getPluginManager().callEvent(event);
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
index 03dc6953c4..dcc5613328 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeManager.java.patch
@@ -5,9 +5,9 @@
      }
  
 +    // CraftBukkit start
-+    public void addRecipe(RecipeHolder<?> irecipe) {
++    public void addRecipe(RecipeHolder<?> holder) {
 +        org.spigotmc.AsyncCatcher.catchOp("Recipe Add"); // Spigot
-+        this.recipes.addRecipe(irecipe);
++        this.recipes.addRecipe(holder);
 +        this.finalizeRecipeLoading();
 +    }
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
index ae4eba51bc..77d44ed126 100644
--- a/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/item/crafting/RecipeMap.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/item/crafting/RecipeMap.java
 +++ b/net/minecraft/world/item/crafting/RecipeMap.java
-@@ -30,8 +_,53 @@
+@@ -30,8 +_,34 @@
              builder1.put(recipeHolder.id(), recipeHolder);
          }
  
@@ -10,34 +10,18 @@
 +        return new RecipeMap(com.google.common.collect.LinkedHashMultimap.create(builder.build()), com.google.common.collect.Maps.newHashMap(builder1.build()));
 +    }
 +
-+    public void addRecipe(RecipeHolder<?> irecipe) {
-+        Collection<RecipeHolder<?>> map = this.byType.get(irecipe.value().getType());
++    public void addRecipe(RecipeHolder<?> holder) {
++        Collection<RecipeHolder<?>> recipes = this.byType.get(holder.value().getType());
 +
-+        if (this.byKey.containsKey(irecipe.id())) {
-+            throw new IllegalStateException("Duplicate recipe ignored with ID " + irecipe.id());
++        if (this.byKey.containsKey(holder.id())) {
++            throw new IllegalStateException("Duplicate recipe ignored with ID " + holder.id());
 +        } else {
-+            map.add(irecipe);
-+            this.byKey.put(irecipe.id(), irecipe);
++            recipes.add(holder);
++            this.byKey.put(holder.id(), holder);
 +        }
 +    }
-+
-+    // public boolean removeRecipe(ResourceKey<Recipe<?>> mcKey) {
-+    //     boolean removed = false;
-+    //     Iterator<RecipeHolder<?>> iter = this.byType.values().iterator();
-+    //     while (iter.hasNext()) {
-+    //         RecipeHolder<?> recipe = iter.next();
-+    //         if (recipe.id().equals(mcKey)) {
-+    //             iter.remove();
-+    //             removed = true;
-+    //         }
-+    //     }
-+    //     removed |= this.byKey.remove(mcKey) != null;
-+    //
-+    //     return removed;
-+    // }
 +    // CraftBukkit end
 +
-+
 +    // Paper start - replace removeRecipe implementation
 +    public <T extends RecipeInput> boolean removeRecipe(ResourceKey<Recipe<T>> mcKey) {
 +        //noinspection unchecked
@@ -46,10 +30,7 @@
 +            return false;
 +        }
 +        final Collection<? extends RecipeHolder<? extends Recipe<T>>> recipes = this.byType(remove.value().getType());
-+        if (recipes.remove(remove)) {
-+            return true;
-+        }
-+        return false;
++        return recipes.remove(remove);
 +        // Paper end - why are you using a loop???
 +    }
 +    // Paper end - replace removeRecipe implementation
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
index f57bc6379f..5907877ed7 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -214,12 +214,8 @@
              this.cookingTimer = 0;
              this.setChanged();
          }
-@@ -339,11 +_,11 @@
-     }
- 
-     @Override
--    public void awardUsedRecipes(Player player, List<ItemStack> items) {
-+    public void awardUsedRecipes(net.minecraft.world.entity.player.Player player, List<ItemStack> items) {
+@@ -342,8 +_,8 @@
+     public void awardUsedRecipes(Player player, List<ItemStack> items) {
      }
  
 -    public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
index 152410ce51..a398e2a015 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
@@ -61,11 +61,11 @@
      }
 +
 +    // CraftBukkit start
-+    public void setPatterns(BannerPatternLayers bannerpatternlayers) {
-+        if (bannerpatternlayers.layers().size() > 20) {
-+            bannerpatternlayers = new BannerPatternLayers(java.util.List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
++    public void setPatterns(BannerPatternLayers bannerPatternLayers) {
++        if (bannerPatternLayers.layers().size() > 20) {
++            bannerPatternLayers = new BannerPatternLayers(java.util.List.copyOf(bannerPatternLayers.layers().subList(0, 20)));
 +        }
-+        this.patterns = bannerpatternlayers;
++        this.patterns = bannerPatternLayers;
 +    }
 +    // CraftBukkit end
  }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
index 4826839013..7950dedc62 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
@@ -4,7 +4,7 @@
      private List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
      @Nullable
      public BlockPos savedFlowerPos;
-+    public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
++    public int maxBees = MAX_OCCUPANTS; // CraftBukkit - allow setting max amount of bees a hive can hold
  
      public BeehiveBlockEntity(BlockPos pos, BlockState blockState) {
          super(BlockEntityType.BEEHIVE, pos, blockState);
@@ -64,7 +64,7 @@
 +            if (this.level != null) {
 +                org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(bee.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.getBlockPos()));
 +                if (!event.callEvent()) {
-+                    bee.setStayOutOfHiveCountdown(400);
++                    bee.setStayOutOfHiveCountdown(MIN_TICKS_BEFORE_REENTERING_HIVE);
 +                    return;
 +                }
 +            }

From 286e3468d70b046b3ba290fac88ad0056754cfa6 Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Sat, 21 Dec 2024 16:03:29 +0100
Subject: [PATCH 277/285] Some more cleanup

---
 .../0017-Moonrise-optimisation-patches.patch  |  6 +++---
 ...-Incremental-chunk-and-player-saving.patch |  4 ++--
 .../server/players/PlayerList.java.patch      | 19 +++++++------------
 3 files changed, 12 insertions(+), 17 deletions(-)

diff --git a/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
index 08d7f1080b..337302aaa2 100644
--- a/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
+++ b/paper-server/patches/features/0017-Moonrise-optimisation-patches.patch
@@ -27976,10 +27976,10 @@ index 4eb040006f5d41b47e5ac9df5d9f19c4315d6343..7fa41dea184b01891f45d8e404bc1cba
          this.generatingStep = generatingStep;
          this.cache = cache;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 3d5d84c1c1d431e9b369aa727ab0876f90ff5d61..6abe5fd39583663af0feb98607cff75c42d96729 100644
+index ff0315cffdb282fdc0a1ffd15e2954caa76835c9..5e94dd9e26aa4fd6545dbaae2ae0cb51cb6f13e0 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
-@@ -1317,7 +1317,7 @@ public abstract class PlayerList {
+@@ -1312,7 +1312,7 @@ public abstract class PlayerList {
  
      public void setViewDistance(int viewDistance) {
          this.viewDistance = viewDistance;
@@ -27988,7 +27988,7 @@ index 3d5d84c1c1d431e9b369aa727ab0876f90ff5d61..6abe5fd39583663af0feb98607cff75c
  
          for (ServerLevel serverLevel : this.server.getAllLevels()) {
              if (serverLevel != null) {
-@@ -1328,7 +1328,7 @@ public abstract class PlayerList {
+@@ -1323,7 +1323,7 @@ public abstract class PlayerList {
  
      public void setSimulationDistance(int simulationDistance) {
          this.simulationDistance = simulationDistance;
diff --git a/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
index 9d7ae4524f..4ac088fbda 100644
--- a/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
+++ b/paper-server/patches/features/0025-Incremental-chunk-and-player-saving.patch
@@ -95,7 +95,7 @@ index e61fe83479f095e8addbd3e8f1d5179c998ae1eb..0a7e5106a1d39150326e7c323030df5d
      private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
      private static final int FLY_STAT_RECORDING_SPEED = 25;
 diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
-index 6abe5fd39583663af0feb98607cff75c42d96729..65c16eddff43c875be72b08f61d6f321a6876a48 100644
+index 5e94dd9e26aa4fd6545dbaae2ae0cb51cb6f13e0..03feaf0adb8ee87e33744a4615dc2507a02f92d7 100644
 --- a/net/minecraft/server/players/PlayerList.java
 +++ b/net/minecraft/server/players/PlayerList.java
 @@ -482,6 +482,7 @@ public abstract class PlayerList {
@@ -106,7 +106,7 @@ index 6abe5fd39583663af0feb98607cff75c42d96729..65c16eddff43c875be72b08f61d6f321
          this.playerIo.save(player);
          ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit
          if (serverStatsCounter != null) {
-@@ -1069,9 +1070,23 @@ public abstract class PlayerList {
+@@ -1064,9 +1065,23 @@ public abstract class PlayerList {
      }
  
      public void saveAll() {
diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
index 65cec1e1fc..e9cb8b46e9 100644
--- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch
@@ -450,12 +450,12 @@
 +        // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
 +        ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()));
 +        for (int i = 0; i < this.players.size(); i++) {
-+            ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i);
++            ServerPlayer otherPlayer = (ServerPlayer) this.players.get(i);
 +
-+            if (entityplayer2.getBukkitEntity().canSee(player.getBukkitEntity())) {
-+                entityplayer2.connection.send(packet);
++            if (otherPlayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
++                otherPlayer.connection.send(packet);
 +            } else {
-+                entityplayer2.getBukkitEntity().onEntityRemove(player);
++                otherPlayer.getBukkitEntity().onEntityRemove(player);
 +            }
 +        }
 +        // This removes the scoreboard (and player reference) for the specific player in the manager
@@ -758,7 +758,7 @@
  
          return serverPlayer;
      }
-@@ -482,24 +_,65 @@
+@@ -482,24 +_,60 @@
      }
  
      public void sendActiveEffects(LivingEntity entity, ServerGamePacketListenerImpl connection) {
@@ -791,14 +791,9 @@
 -            this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players));
 +            // CraftBukkit start
 +            for (int i = 0; i < this.players.size(); ++i) {
-+                final ServerPlayer target = (ServerPlayer) this.players.get(i);
++                final ServerPlayer target = this.players.get(i);
 +
-+                target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players.stream().filter(new Predicate<ServerPlayer>() {
-+                    @Override
-+                    public boolean test(ServerPlayer input) {
-+                        return target.getBukkitEntity().canSee(input.getBukkitEntity());
-+                    }
-+                }).collect(java.util.stream.Collectors.toList())));
++                target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), com.google.common.collect.Collections2.filter(this.players, t -> target.getBukkitEntity().canSee(t.getBukkitEntity()))));
 +            }
 +            // CraftBukkit end
              this.sendAllPlayerInfoIn = 0;

From 3bfdf6a07a9d5840d8f0e764c7bd16115af8908d Mon Sep 17 00:00:00 2001
From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com>
Date: Sat, 21 Dec 2024 19:27:59 +0100
Subject: [PATCH 278/285] fix flying too long check

---
 ...on-checking-in-player-move-packet-ha.patch | 18 ++++++------
 .../ServerGamePacketListenerImpl.java.patch   | 29 ++++++++++++++++---
 2 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
index 5ab3533b9d..dbc8cdd071 100644
--- a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
+++ b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling
 Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision
 
 diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38ff73a650 100644
+index 650126e6445c0458c6a6649c235908bfeea428cd..d248671b2e1c6256fc4d74320bdb29ca078bad0b 100644
 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
 @@ -561,7 +561,7 @@ public class ServerGamePacketListenerImpl
@@ -23,10 +23,10 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
  
                  rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
 +                boolean didCollide = toX != rootVehicle.getX() || toY != rootVehicle.getY() || toZ != rootVehicle.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be...
+                 double verticalDelta = d4; // Paper - Decompile fix, was named d11 previously, is now gone in the source
                  d3 = d - rootVehicle.getX();
                  d4 = d1 - rootVehicle.getY();
-                 if (d4 > -0.5 || d4 < 0.5) {
-@@ -581,14 +582,22 @@ public class ServerGamePacketListenerImpl
+@@ -582,14 +583,22 @@ public class ServerGamePacketListenerImpl
                  d7 = d3 * d3 + d4 * d4 + d5 * d5;
                  boolean flag2 = false;
                  if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot
@@ -52,7 +52,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
                      rootVehicle.absMoveTo(x, y, z, f, f1);
                      this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
                      this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle));
-@@ -666,9 +675,32 @@ public class ServerGamePacketListenerImpl
+@@ -667,9 +676,32 @@ public class ServerGamePacketListenerImpl
      }
  
      private boolean noBlocksAround(Entity entity) {
@@ -88,7 +88,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
      }
  
      @Override
-@@ -1360,7 +1392,7 @@ public class ServerGamePacketListenerImpl
+@@ -1361,7 +1393,7 @@ public class ServerGamePacketListenerImpl
                                  }
                              }
  
@@ -97,7 +97,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
                              d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above
                              d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above
                              d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above
-@@ -1399,6 +1431,7 @@ public class ServerGamePacketListenerImpl
+@@ -1400,6 +1432,7 @@ public class ServerGamePacketListenerImpl
                              boolean flag1 = this.player.verticalCollisionBelow;
                              this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
                              this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
@@ -105,7 +105,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
                              // Paper start - prevent position desync
                              if (this.awaitingPositionFromClient != null) {
                                  return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1430,7 +1463,17 @@ public class ServerGamePacketListenerImpl
+@@ -1432,7 +1465,17 @@ public class ServerGamePacketListenerImpl
                              }
  
                              // Paper start - Add fail move event
@@ -124,7 +124,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
                              if (teleportBack) {
                                  io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK,
                                      toX, toY, toZ, toYaw, toPitch, false);
-@@ -1566,7 +1609,7 @@ public class ServerGamePacketListenerImpl
+@@ -1568,7 +1611,7 @@ public class ServerGamePacketListenerImpl
  
      private boolean updateAwaitingTeleport() {
          if (this.awaitingPositionFromClient != null) {
@@ -133,7 +133,7 @@ index 96d36b2e285ec0a518b15ccdff4d7d1850b0a7e2..9a0a2a628d76a4e0410839d919a8bf38
                  this.awaitingTeleportTime = this.tickCount;
                  this.teleport(
                      this.awaitingPositionFromClient.x,
-@@ -1585,6 +1628,33 @@ public class ServerGamePacketListenerImpl
+@@ -1587,6 +1630,33 @@ public class ServerGamePacketListenerImpl
          }
      }
  
diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
index 5c3d9c3714..37ab47c80b 100644
--- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -256,7 +256,7 @@
                      LOGGER.warn(
                          "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5
                      );
-@@ -417,9 +_,9 @@
+@@ -417,15 +_,16 @@
                  }
  
                  boolean flag = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
@@ -269,6 +269,13 @@
                  boolean flag1 = rootVehicle.verticalCollisionBelow;
                  if (rootVehicle instanceof LivingEntity livingEntity && livingEntity.onClimbable()) {
                      livingEntity.resetFallDistance();
+                 }
+ 
+                 rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
++                double verticalDelta = d4; // Paper - Decompile fix, was named d11 previously, is now gone in the source
+                 d3 = d - rootVehicle.getX();
+                 d4 = d1 - rootVehicle.getY();
+                 if (d4 > -0.5 || d4 < 0.5) {
 @@ -435,18 +_,71 @@
                  d5 = d2 - rootVehicle.getZ();
                  d7 = d3 * d3 + d4 * d4 + d5 * d5;
@@ -342,6 +349,15 @@
  
                  this.player.serverLevel().getChunkSource().move(this.player);
                  rootVehicle.recordMovementThroughBlocks(new Vec3(x, y, z), rootVehicle.position());
+@@ -455,7 +_,7 @@
+                 rootVehicle.setOnGroundWithMovement(packet.onGround(), vec3);
+                 rootVehicle.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.onGround());
+                 this.player.checkMovementStatistics(vec3.x, vec3.y, vec3.z);
+-                this.clientVehicleIsFloating = d4 >= -0.03125
++                this.clientVehicleIsFloating = verticalDelta >= -0.03125 // Paper - Decompile fix
+                     && !flag1
+                     && !this.server.isFlightAllowed()
+                     && !rootVehicle.isNoGravity()
 @@ -478,12 +_,12 @@
          PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
          if (packet.getId() == this.awaitingTeleport) {
@@ -694,7 +710,7 @@
                          if (this.player.isSleeping()) {
                              if (d7 > 1.0) {
                                  this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
-@@ -936,32 +_,104 @@
+@@ -936,32 +_,105 @@
                              if (serverLevel.tickRateManager().runsNormally()) {
                                  this.receivedMovePacketCount++;
                                  int i = this.receivedMovePacketCount - this.knownMovePacketCount;
@@ -805,10 +821,11 @@
 +                                return; // ... thanks Mojang for letting move calls teleport across dimensions.
 +                            }
 +                            // Paper end - prevent position desync
++                            double verticalDelta = d4; // Paper - Decompile fix, was named d11 previously, is now gone in the source
                              d3 = d - this.player.getX();
                              d4 = d1 - this.player.getY();
                              if (d4 > -0.5 || d4 < 0.5) {
-@@ -970,20 +_,101 @@
+@@ -970,23 +_,104 @@
  
                              d5 = d2 - this.player.getZ();
                              d7 = d3 * d3 + d4 * d4 + d5 * d5;
@@ -918,7 +935,11 @@
 +                                // CraftBukkit end
                                  this.player.absMoveTo(d, d1, d2, f, f1);
                                  boolean isAutoSpinAttack = this.player.isAutoSpinAttack();
-                                 this.clientIsFloating = d4 >= -0.03125
+-                                this.clientIsFloating = d4 >= -0.03125
++                                this.clientIsFloating = verticalDelta >= -0.03125 // Paper - Decompile fix
+                                     && !flag1
+                                     && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR
+                                     && !this.server.isFlightAllowed()
 @@ -1019,7 +_,7 @@
                                  this.lastGoodY = this.player.getY();
                                  this.lastGoodZ = this.player.getZ();

From 54debf494f467a71c561cf8765c4f21725c99dd8 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sat, 21 Dec 2024 13:51:42 -0800
Subject: [PATCH 279/285] Update paperweight to 2.0.0-beta.8 and remove
 deprecated VM arg from runs

---
 .gitignore                    | 2 +-
 build.gradle.kts              | 2 +-
 paper-server/build.gradle.kts | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index e08033d541..53a798d16e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,4 +48,4 @@ test-plugin.settings.gradle.kts
 paper-api-generator.settings.gradle.kts
 
 # Don't track patched vanilla submodules
-paper-server/src/vanilla/
+paper-server/src/minecraft/
diff --git a/build.gradle.kts b/build.gradle.kts
index 23e8eafd5d..f21b88e818 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,7 +12,7 @@ import java.nio.file.Path
 import kotlin.random.Random
 
 plugins {
-    id("io.papermc.paperweight.core") version "2.0.0-beta.7" apply false
+    id("io.papermc.paperweight.core") version "2.0.0-beta.8" apply false
 }
 
 subprojects {
diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index dc2bce7b5a..f36c81c083 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -271,7 +271,7 @@ fun TaskContainer.registerRunTask(
         languageVersion.set(JavaLanguageVersion.of(21))
         vendor.set(JvmVendorSpec.JETBRAINS)
     })
-    jvmArgs("-XX:+AllowEnhancedClassRedefinition", "-XX:+AllowRedefinitionToAddDeleteMethods")
+    jvmArgs("-XX:+AllowEnhancedClassRedefinition")
 
     if (rootProject.childProjects["test-plugin"] != null) {
         val testPluginJar = rootProject.project(":test-plugin").tasks.jar.flatMap { it.archiveFile }

From 46c6f497c7e5e3f6d7ee9e226837d44211737590 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 21 Dec 2024 14:56:01 -0800
Subject: [PATCH 280/285] Fix Registry#getKey implementation

---
 .../io/papermc/paper/util/Holderable.java     |  12 +-
 .../papermc/paper/util/OldEnumHolderable.java |  88 ++++++++++++++
 .../java/org/bukkit/craftbukkit/CraftArt.java | 109 +++---------------
 .../craftbukkit/CraftMusicInstrument.java     |   4 +-
 .../org/bukkit/craftbukkit/CraftRegistry.java |   8 +-
 .../org/bukkit/craftbukkit/CraftSound.java    |  91 +--------------
 .../block/banner/CraftPatternType.java        |  90 +--------------
 .../generator/structure/CraftStructure.java   |   1 -
 .../inventory/trim/CraftTrimMaterial.java     |   4 +-
 .../inventory/trim/CraftTrimPattern.java      |   4 +-
 .../registry/RegistryConversionTest.java      |   5 +-
 .../io/papermc/testplugin/TestPlugin.java     |   8 ++
 .../testplugin/TestPluginBootstrap.java       |  17 +++
 13 files changed, 164 insertions(+), 277 deletions(-)
 create mode 100644 paper-server/src/main/java/io/papermc/paper/util/OldEnumHolderable.java

diff --git a/paper-server/src/main/java/io/papermc/paper/util/Holderable.java b/paper-server/src/main/java/io/papermc/paper/util/Holderable.java
index 3401d0073f..c0115e0a31 100644
--- a/paper-server/src/main/java/io/papermc/paper/util/Holderable.java
+++ b/paper-server/src/main/java/io/papermc/paper/util/Holderable.java
@@ -7,7 +7,7 @@ import com.mojang.serialization.JsonOps;
 import net.kyori.adventure.key.Key;
 import net.minecraft.core.Holder;
 import net.minecraft.resources.RegistryOps;
-import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
 import org.bukkit.Registry;
 import org.bukkit.craftbukkit.CraftRegistry;
 import org.bukkit.craftbukkit.util.Handleable;
@@ -25,7 +25,7 @@ public interface Holderable<M> extends Handleable<M> {
         return this.getHolder().value();
     }
 
-    static <T extends Keyed, M> @Nullable T fromBukkitSerializationObject(final Object deserialized, final Codec<? extends Holder<M>> codec, final Registry<T> registry) { // TODO remove Keyed
+    static <T extends org.bukkit.Keyed, M> @Nullable T fromBukkitSerializationObject(final Object deserialized, final Codec<? extends Holder<M>> codec, final Registry<T> registry) { // TODO remove Keyed
         return switch (deserialized) {
             case @Subst("key:value") final String string -> {
                 if (!(Key.parseable(string))) {
@@ -75,4 +75,12 @@ public interface Holderable<M> extends Handleable<M> {
     default String implToString() {
         return "%s{holder=%s}".formatted(this.getClass().getSimpleName(), this.getHolder().toString());
     }
+
+    default @Nullable NamespacedKey getKeyOrNull() {
+        return this.getHolder().unwrapKey().map(MCUtil::fromResourceKey).orElse(null);
+    }
+
+    default NamespacedKey getKey() {
+        return MCUtil.fromResourceKey(this.getHolder().unwrapKey().orElseThrow(() -> new IllegalStateException("Cannot get key for this registry item, because it is not registered.")));
+    }
 }
diff --git a/paper-server/src/main/java/io/papermc/paper/util/OldEnumHolderable.java b/paper-server/src/main/java/io/papermc/paper/util/OldEnumHolderable.java
new file mode 100644
index 0000000000..1e70146847
--- /dev/null
+++ b/paper-server/src/main/java/io/papermc/paper/util/OldEnumHolderable.java
@@ -0,0 +1,88 @@
+package io.papermc.paper.util;
+
+import com.google.common.base.Preconditions;
+import java.util.Locale;
+import net.minecraft.core.Holder;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.util.OldEnum;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@SuppressWarnings("removal")
+@Deprecated
+@NullMarked
+public abstract class OldEnumHolderable<A extends OldEnum<A>, M> implements Holderable<M>, OldEnum<A>, Keyed {
+
+    private final Holder<M> holder;
+    private final int ordinal;
+    private final @Nullable String name;
+
+    protected OldEnumHolderable(final Holder<M> holder, final int ordinal) {
+        this.holder = holder;
+        this.ordinal = ordinal;
+        if (holder instanceof final Holder.Reference<M> reference) {
+            // For backwards compatibility, minecraft values will stile return the uppercase name without the namespace,
+            // in case plugins use for example the name as key in a config file to receive registry item specific values.
+            // Custom registry items will return the key with namespace. For a plugin this should look than like a new registry item
+            // (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
+            if (NamespacedKey.MINECRAFT.equals(reference.key().location().getNamespace())) {
+                this.name = reference.key().location().getPath().toUpperCase(Locale.ROOT);
+            } else {
+                this.name = reference.key().location().toString();
+            }
+        } else {
+            this.name = null;
+        }
+    }
+
+    @Override
+    public Holder<M> getHolder() {
+        return this.holder;
+    }
+
+    @Override
+    @Deprecated
+    public int compareTo(A other) {
+        this.checkIsReference();
+        return this.ordinal - other.ordinal();
+    }
+
+    @Override
+    @Deprecated
+    public String name() {
+        this.checkIsReference();
+        return this.name;
+    }
+
+    @Override
+    @Deprecated
+    public int ordinal() {
+        this.checkIsReference();
+        return this.ordinal;
+    }
+
+    private void checkIsReference() {
+        Preconditions.checkState(this.holder.kind() == Holder.Kind.REFERENCE, "Cannot call method for this registry item, because it is not registered.");
+    }
+
+    @Override
+    public NamespacedKey getKey() {
+        return MCUtil.fromResourceKey(this.holder.unwrapKey().orElseThrow(() -> new IllegalStateException("Cannot get key for this registry item, because it is not registered.")));
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return this.implEquals(obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.implHashCode();
+    }
+
+    @Override
+    public String toString() {
+        return this.implToString();
+    }
+}
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java
index 0207c7c507..d4edaea715 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java
@@ -1,17 +1,15 @@
 package org.bukkit.craftbukkit;
 
-import com.google.common.base.Preconditions;
-import java.util.Locale;
+import io.papermc.paper.adventure.PaperAdventure;
+import io.papermc.paper.util.OldEnumHolderable;
+import net.kyori.adventure.text.Component;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.world.entity.decoration.PaintingVariant;
 import org.bukkit.Art;
-import org.bukkit.NamespacedKey;
 import org.bukkit.Registry;
-import org.bukkit.craftbukkit.util.Handleable;
-import org.jetbrains.annotations.NotNull;
 
-public class CraftArt implements Art, Handleable<PaintingVariant> {
+public class CraftArt extends OldEnumHolderable<Art, PaintingVariant> implements Art {
 
     private static int count = 0;
 
@@ -20,7 +18,7 @@ public class CraftArt implements Art, Handleable<PaintingVariant> {
     }
 
     public static Art minecraftHolderToBukkit(Holder<PaintingVariant> minecraft) {
-        return CraftArt.minecraftToBukkit(minecraft.value());
+        return CraftRegistry.minecraftHolderToBukkit(minecraft, Registry.ART);
     }
 
     public static PaintingVariant bukkitToMinecraft(Art bukkit) {
@@ -28,118 +26,41 @@ public class CraftArt implements Art, Handleable<PaintingVariant> {
     }
 
     public static Holder<PaintingVariant> bukkitToMinecraftHolder(Art bukkit) {
-        Preconditions.checkArgument(bukkit != null);
-
-        net.minecraft.core.Registry<PaintingVariant> registry = CraftRegistry.getMinecraftRegistry(Registries.PAINTING_VARIANT);
-
-        if (registry.wrapAsHolder(CraftArt.bukkitToMinecraft(bukkit)) instanceof Holder.Reference<PaintingVariant> holder) {
-            return holder;
-        }
-
-        throw new IllegalArgumentException("No Reference holder found for " + bukkit
-                + ", this can happen if a plugin creates its own painting variant with out properly registering it.");
+        return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.PAINTING_VARIANT);
     }
 
-    private final NamespacedKey key;
-    private final PaintingVariant paintingVariant;
-    private final String name;
-    private final int ordinal;
-
-    public CraftArt(NamespacedKey key, PaintingVariant paintingVariant) {
-        this.key = key;
-        this.paintingVariant = paintingVariant;
-        // For backwards compatibility, minecraft values will stile return the uppercase name without the namespace,
-        // in case plugins use for example the name as key in a config file to receive art specific values.
-        // Custom arts will return the key with namespace. For a plugin this should look than like a new art
-        // (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
-        if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
-            this.name = key.getKey().toUpperCase(Locale.ROOT);
-        } else {
-            this.name = key.toString();
-        }
-        this.ordinal = CraftArt.count++;
-    }
-
-    @Override
-    public PaintingVariant getHandle() {
-        return this.paintingVariant;
+    public CraftArt(Holder<PaintingVariant> paintingVariant) {
+        super(paintingVariant, count++);
     }
 
     @Override
     public int getBlockWidth() {
-        return this.paintingVariant.width();
+        return this.getHandle().width();
     }
 
     @Override
     public int getBlockHeight() {
-        return this.paintingVariant.height();
+        return this.getHandle().height();
     }
 
     // Paper start - Expand Art API
     @Override
-    public net.kyori.adventure.text.Component title() {
-        return this.paintingVariant.title().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).orElse(null);
+    public Component title() {
+        return this.getHandle().title().map(PaperAdventure::asAdventure).orElse(null);
     }
 
     @Override
     public net.kyori.adventure.text.Component author() {
-        return this.paintingVariant.author().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).orElse(null);
+        return this.getHandle().author().map(PaperAdventure::asAdventure).orElse(null);
     }
 
     public net.kyori.adventure.key.Key assetId() {
-        return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.paintingVariant.assetId());
+        return PaperAdventure.asAdventure(this.getHandle().assetId());
     }
     // Paper end - Expand Art API
 
     @Override
     public int getId() {
-        return CraftRegistry.getMinecraftRegistry(Registries.PAINTING_VARIANT).getId(this.paintingVariant);
-    }
-
-    @NotNull
-    @Override
-    public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.ART.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
-    }
-
-    @Override
-    public int compareTo(@NotNull Art art) {
-        return this.ordinal - art.ordinal();
-    }
-
-    @NotNull
-    @Override
-    public String name() {
-        return this.name;
-    }
-
-    @Override
-    public int ordinal() {
-        return this.ordinal;
-    }
-
-    @Override
-    public String toString() {
-        // For backwards compatibility
-        return this.name();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof CraftArt otherArt)) {
-            return false;
-        }
-
-        return this.getKey().equals(otherArt.getKey());
-    }
-
-    @Override
-    public int hashCode() {
-        return this.getKey().hashCode();
+        return CraftRegistry.getMinecraftRegistry(Registries.PAINTING_VARIANT).getId(this.getHandle());
     }
 }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
index aa19ac7513..520c3e2e1d 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
@@ -1,6 +1,7 @@
 package org.bukkit.craftbukkit;
 
 import com.google.common.base.Preconditions;
+import io.papermc.paper.util.Holderable;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.world.item.Instrument;
@@ -75,8 +76,7 @@ public class CraftMusicInstrument extends MusicInstrument implements io.papermc.
     @NotNull
     @Override
     public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.INSTRUMENT.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
+        return Holderable.super.getKey();
     }
 
     // Paper start - add translationKey methods
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
index a7e745bc13..a94cebfd26 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
@@ -1,6 +1,7 @@
 package org.bukkit.craftbukkit;
 
 import com.google.common.base.Preconditions;
+import io.papermc.paper.util.Holderable;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -197,7 +198,6 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
 
     private final Class<?> bukkitClass; // Paper - relax preload class
     private final Map<NamespacedKey, B> cache = new HashMap<>();
-    private final Map<B, NamespacedKey> byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry
     private final net.minecraft.core.Registry<M> minecraftRegistry;
     private final io.papermc.paper.registry.entry.RegistryTypeMapper<M, B> minecraftToBukkit; // Paper - switch to Holder
     private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater; // Paper - rename to make it *clear* what it is *only* for
@@ -251,7 +251,6 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
         }
 
         this.cache.put(namespacedKey, bukkit);
-        this.byValue.put(bukkit, namespacedKey); // Paper - improve Registry
 
         return bukkit;
     }
@@ -298,7 +297,10 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
     // Paper start - improve Registry
     @Override
     public NamespacedKey getKey(final B value) {
-        return this.byValue.get(value);
+        if (value instanceof Holderable<?> holderable) {
+            return holderable.getKeyOrNull();
+        }
+        return Registry.super.getKey(value);
     }
     // Paper end - improve Registry
 
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java
index e24d784674..451fe79b1e 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java
@@ -1,17 +1,13 @@
 package org.bukkit.craftbukkit;
 
-import com.google.common.base.Preconditions;
-import java.util.Locale;
+import io.papermc.paper.util.OldEnumHolderable;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.sounds.SoundEvent;
-import org.bukkit.NamespacedKey;
 import org.bukkit.Registry;
 import org.bukkit.Sound;
-import org.bukkit.craftbukkit.util.Handleable;
-import org.jetbrains.annotations.NotNull;
 
-public class CraftSound implements Sound, Handleable<SoundEvent> {
+public class CraftSound extends OldEnumHolderable<Sound, SoundEvent> implements Sound {
 
     private static int count = 0;
 
@@ -24,88 +20,11 @@ public class CraftSound implements Sound, Handleable<SoundEvent> {
     }
 
     public static Holder<SoundEvent> bukkitToMinecraftHolder(Sound bukkit) {
-        Preconditions.checkArgument(bukkit != null);
-
-        net.minecraft.core.Registry<SoundEvent> registry = CraftRegistry.getMinecraftRegistry(Registries.SOUND_EVENT);
-
-        if (registry.wrapAsHolder(CraftSound.bukkitToMinecraft(bukkit)) instanceof Holder.Reference<SoundEvent> holder) {
-            return holder;
-        }
-
-        throw new IllegalArgumentException("No Reference holder found for " + bukkit
-                + ", this can happen if a plugin creates its own sound effect with out properly registering it.");
+        return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.SOUND_EVENT);
     }
 
-    private final NamespacedKey key;
-    private final SoundEvent soundEffect;
-    private final String name;
-    private final int ordinal;
-
-    public CraftSound(NamespacedKey key, SoundEvent soundEffect) {
-        this.key = key;
-        this.soundEffect = soundEffect;
-        // For backwards compatibility, minecraft values will stile return the uppercase name without the namespace,
-        // in case plugins use for example the name as key in a config file to receive sound specific values.
-        // Custom sounds will return the key with namespace. For a plugin this should look than like a new sound
-        // (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
-        if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
-            this.name = key.getKey().toUpperCase(Locale.ROOT).replace('.', '_');
-        } else {
-            this.name = key.toString();
-        }
-        this.ordinal = CraftSound.count++;
-    }
-
-    @Override
-    public SoundEvent getHandle() {
-        return this.soundEffect;
-    }
-
-    @NotNull
-    @Override
-    public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.SOUNDS.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
-    }
-
-    @Override
-    public int compareTo(@NotNull Sound sound) {
-        return this.ordinal - sound.ordinal();
-    }
-
-    @NotNull
-    @Override
-    public String name() {
-        return this.name;
-    }
-
-    @Override
-    public int ordinal() {
-        return this.ordinal;
-    }
-
-    @Override
-    public String toString() {
-        // For backwards compatibility
-        return this.name();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof CraftSound otherSound)) {
-            return false;
-        }
-
-        return this.getKey().equals(otherSound.getKey());
-    }
-
-    @Override
-    public int hashCode() {
-        return this.getKey().hashCode();
+    public CraftSound(Holder<SoundEvent> soundEffect) {
+        super(soundEffect, count++);
     }
 
     // Paper start
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java
index 3addb382d0..749024b259 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java
@@ -1,17 +1,14 @@
 package org.bukkit.craftbukkit.block.banner;
 
-import com.google.common.base.Preconditions;
-import java.util.Locale;
+import io.papermc.paper.util.OldEnumHolderable;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.world.level.block.entity.BannerPattern;
-import org.bukkit.NamespacedKey;
 import org.bukkit.Registry;
 import org.bukkit.block.banner.PatternType;
 import org.bukkit.craftbukkit.CraftRegistry;
-import org.bukkit.craftbukkit.util.Handleable;
 
-public class CraftPatternType implements PatternType, Handleable<BannerPattern> {
+public class CraftPatternType extends OldEnumHolderable<PatternType, BannerPattern> implements PatternType {
 
     private static int count = 0;
 
@@ -20,7 +17,7 @@ public class CraftPatternType implements PatternType, Handleable<BannerPattern>
     }
 
     public static PatternType minecraftHolderToBukkit(Holder<BannerPattern> minecraft) {
-        return CraftPatternType.minecraftToBukkit(minecraft.value());
+        return CraftRegistry.minecraftHolderToBukkit(minecraft, Registry.BANNER_PATTERN);
     }
 
     public static BannerPattern bukkitToMinecraft(PatternType bukkit) {
@@ -28,86 +25,11 @@ public class CraftPatternType implements PatternType, Handleable<BannerPattern>
     }
 
     public static Holder<BannerPattern> bukkitToMinecraftHolder(PatternType bukkit) {
-        Preconditions.checkArgument(bukkit != null);
-
-        net.minecraft.core.Registry<BannerPattern> registry = CraftRegistry.getMinecraftRegistry(Registries.BANNER_PATTERN);
-
-        if (registry.wrapAsHolder(CraftPatternType.bukkitToMinecraft(bukkit)) instanceof Holder.Reference<BannerPattern> holder) {
-            return holder;
-        }
-
-        throw new IllegalArgumentException("No Reference holder found for " + bukkit
-                + ", this can happen if a plugin creates its own banner pattern without properly registering it.");
+        return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.BANNER_PATTERN);
     }
 
-    private final NamespacedKey key;
-    private final BannerPattern bannerPatternType;
-    private final String name;
-    private final int ordinal;
-
-    public CraftPatternType(NamespacedKey key, BannerPattern bannerPatternType) {
-        this.key = key;
-        this.bannerPatternType = bannerPatternType;
-        // For backwards compatibility, minecraft values will stile return the uppercase name without the namespace,
-        // in case plugins use for example the name as key in a config file to receive pattern type specific values.
-        // Custom pattern types will return the key with namespace. For a plugin this should look than like a new pattern type
-        // (which can always be added in new minecraft versions and the plugin should therefore handle it accordingly).
-        if (NamespacedKey.MINECRAFT.equals(key.getNamespace())) {
-            this.name = key.getKey().toUpperCase(Locale.ROOT);
-        } else {
-            this.name = key.toString();
-        }
-        this.ordinal = CraftPatternType.count++;
-    }
-
-    @Override
-    public BannerPattern getHandle() {
-        return this.bannerPatternType;
-    }
-
-    @Override
-    public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.BANNER_PATTERN.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
-    }
-
-    @Override
-    public int compareTo(PatternType patternType) {
-        return this.ordinal - patternType.ordinal();
-    }
-
-    @Override
-    public String name() {
-        return this.name;
-    }
-
-    @Override
-    public int ordinal() {
-        return this.ordinal;
-    }
-
-    @Override
-    public String toString() {
-        // For backwards compatibility
-        return this.name();
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof CraftPatternType)) {
-            return false;
-        }
-
-        return this.getKey().equals(((PatternType) other).getKey());
-    }
-
-    @Override
-    public int hashCode() {
-        return this.getKey().hashCode();
+    public CraftPatternType(Holder<BannerPattern> bannerPatternType) {
+       super(bannerPatternType, count++);
     }
 
     @Override
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/generator/structure/CraftStructure.java b/paper-server/src/main/java/org/bukkit/craftbukkit/generator/structure/CraftStructure.java
index 7ef679fdd2..09fd006244 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/generator/structure/CraftStructure.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/generator/structure/CraftStructure.java
@@ -42,7 +42,6 @@ public class CraftStructure extends Structure implements Handleable<net.minecraf
 
     @Override
     public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.STRUCTURE.getKey(this), () -> this + " doesn't have a key"); // Paper
         return this.key;
     }
 }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
index afee459c4d..9b7488cb45 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
@@ -1,6 +1,7 @@
 package org.bukkit.craftbukkit.inventory.trim;
 
 import com.google.common.base.Preconditions;
+import io.papermc.paper.util.Holderable;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.network.chat.contents.TranslatableContents;
@@ -77,8 +78,7 @@ public class CraftTrimMaterial implements TrimMaterial, io.papermc.paper.util.Ho
     @Override
     @NotNull
     public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.TRIM_MATERIAL.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
+        return Holderable.super.getKey();
     }
 
     @NotNull
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
index da951bd7e4..b5b209d16a 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
@@ -1,6 +1,7 @@
 package org.bukkit.craftbukkit.inventory.trim;
 
 import com.google.common.base.Preconditions;
+import io.papermc.paper.util.Holderable;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.network.chat.contents.TranslatableContents;
@@ -77,8 +78,7 @@ public class CraftTrimPattern implements TrimPattern, io.papermc.paper.util.Hold
     @Override
     @NotNull
     public NamespacedKey getKey() {
-        if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.TRIM_PATTERN.getKey(this), () -> this + " doesn't have a key"); // Paper
-        return this.key;
+        return Holderable.super.getKey();
     }
 
     @NotNull
diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java
index 092b88e0ac..293af3511c 100644
--- a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java
+++ b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java
@@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assumptions.*;
 import static org.mockito.Mockito.*;
 import com.google.common.base.Joiner;
+import io.papermc.paper.registry.RegistryKey;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -259,6 +260,8 @@ public class RegistryConversionTest {
                 Joiner.on('\n').withKeyValueSeparator(" got: ").join(notMatching)));
     }
 
+    static final Set<RegistryKey<?>> IGNORE_FOR_DIRECT_HOLDER = Set.of(RegistryKey.TRIM_MATERIAL, RegistryKey.TRIM_PATTERN, RegistryKey.INSTRUMENT, RegistryKey.PAINTING_VARIANT, RegistryKey.BANNER_PATTERN, RegistryKey.SOUND_EVENT); // Paper
+
     /**
      * Minecraft registry can return a default key / value
      * when the passed minecraft value is not registry in this case, we want it to throw an error.
@@ -269,7 +272,7 @@ public class RegistryConversionTest {
                                                       Class<? extends Keyed> craftClazz, Class<?> minecraftClazz) throws IllegalAccessException {
         this.checkValidMinecraftToBukkit(clazz);
 
-        if (type == io.papermc.paper.registry.RegistryKey.TRIM_MATERIAL || type == io.papermc.paper.registry.RegistryKey.TRIM_PATTERN || type == io.papermc.paper.registry.RegistryKey.INSTRUMENT) return; // Paper - manually skip for now
+        assumeFalse(IGNORE_FOR_DIRECT_HOLDER.contains(type), "skipped because these types support direct holders"); // Paper - manually skip for now
         try {
 
             Object minecraft = mock(minecraftClazz);
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
index 671c37fa40..c640638154 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
@@ -1,5 +1,9 @@
 package io.papermc.testplugin;
 
+import io.papermc.paper.registry.RegistryAccess;
+import io.papermc.paper.registry.RegistryKey;
+import org.bukkit.Art;
+import org.bukkit.Registry;
 import org.bukkit.event.Listener;
 import org.bukkit.plugin.java.JavaPlugin;
 
@@ -10,6 +14,10 @@ public final class TestPlugin extends JavaPlugin implements Listener {
         this.getServer().getPluginManager().registerEvents(this, this);
 
         // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
+
+        final Registry<Art> registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.PAINTING_VARIANT);
+        final Art art = registry.get(TestPluginBootstrap.NEW);
+        System.out.println(art.getKey());
     }
 
 }
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
index fe2b287b25..5d3ec9f604 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
@@ -2,13 +2,30 @@ package io.papermc.testplugin;
 
 import io.papermc.paper.plugin.bootstrap.BootstrapContext;
 import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.registry.event.RegistryEvents;
+import io.papermc.paper.registry.keys.PaintingVariantKeys;
+import net.kyori.adventure.key.Key;
+import org.bukkit.Art;
 import org.jetbrains.annotations.NotNull;
 
+import static net.kyori.adventure.text.Component.text;
+
 public class TestPluginBootstrap implements PluginBootstrap {
 
+    static final TypedKey<Art> NEW = PaintingVariantKeys.create(Key.key("test:test"));
     @Override
     public void bootstrap(@NotNull BootstrapContext context) {
         // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
+
+        context.getLifecycleManager().registerEventHandler(RegistryEvents.PAINTING_VARIANT.freeze(), event -> {
+            event.registry().register(NEW, builder -> {
+                builder.assetId(Key.key("wind"))
+                    .author(text("ME"))
+                    .width(2)
+                    .height(2);
+            });
+        });
     }
 
 }

From 4c6ea8416baa16c741737310571f6cac08271417 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sat, 21 Dec 2024 15:10:08 -0800
Subject: [PATCH 281/285] move junit platform dep to -server and -api

---
 build.gradle.kts              | 4 ----
 paper-api/build.gradle.kts    | 1 +
 paper-server/build.gradle.kts | 1 +
 3 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index f21b88e818..75fc6a21af 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -25,10 +25,6 @@ subprojects {
         }
     }
 
-    dependencies {
-        "testRuntimeOnly"("org.junit.platform:junit-platform-launcher")
-    }
-
     tasks.withType<AbstractArchiveTask>().configureEach {
         isPreserveFileTimestamps = false
         isReproducibleFileOrder = true
diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts
index 7dc323ba31..7dd682b4b9 100644
--- a/paper-api/build.gradle.kts
+++ b/paper-api/build.gradle.kts
@@ -92,6 +92,7 @@ dependencies {
     testImplementation("org.mockito:mockito-core:5.14.1")
     testImplementation("org.ow2.asm:asm-tree:9.7.1")
     mockitoAgent("org.mockito:mockito-core:5.14.1") { isTransitive = false } // Paper - configure mockito agent that is needed in newer java versions
+    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
 }
 
 // Paper start
diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts
index f36c81c083..266795c487 100644
--- a/paper-server/build.gradle.kts
+++ b/paper-server/build.gradle.kts
@@ -13,6 +13,7 @@ val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
 dependencies {
     mache("io.papermc:mache:1.21.4+build.6")
     paperclip("io.papermc:paperclip:3.0.3")
+    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
 }
 
 paperweight {

From c31d95eccb5f8fccf293333d0fecbd986bd9061d Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 22 Dec 2024 11:44:49 -0800
Subject: [PATCH 282/285] remove test plugin diff accidentally committed

---
 .../java/io/papermc/testplugin/TestPlugin.java  |  8 --------
 .../papermc/testplugin/TestPluginBootstrap.java | 17 -----------------
 2 files changed, 25 deletions(-)

diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
index c640638154..671c37fa40 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
@@ -1,9 +1,5 @@
 package io.papermc.testplugin;
 
-import io.papermc.paper.registry.RegistryAccess;
-import io.papermc.paper.registry.RegistryKey;
-import org.bukkit.Art;
-import org.bukkit.Registry;
 import org.bukkit.event.Listener;
 import org.bukkit.plugin.java.JavaPlugin;
 
@@ -14,10 +10,6 @@ public final class TestPlugin extends JavaPlugin implements Listener {
         this.getServer().getPluginManager().registerEvents(this, this);
 
         // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
-
-        final Registry<Art> registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.PAINTING_VARIANT);
-        final Art art = registry.get(TestPluginBootstrap.NEW);
-        System.out.println(art.getKey());
     }
 
 }
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
index 5d3ec9f604..fe2b287b25 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
@@ -2,30 +2,13 @@ package io.papermc.testplugin;
 
 import io.papermc.paper.plugin.bootstrap.BootstrapContext;
 import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
-import io.papermc.paper.registry.TypedKey;
-import io.papermc.paper.registry.event.RegistryEvents;
-import io.papermc.paper.registry.keys.PaintingVariantKeys;
-import net.kyori.adventure.key.Key;
-import org.bukkit.Art;
 import org.jetbrains.annotations.NotNull;
 
-import static net.kyori.adventure.text.Component.text;
-
 public class TestPluginBootstrap implements PluginBootstrap {
 
-    static final TypedKey<Art> NEW = PaintingVariantKeys.create(Key.key("test:test"));
     @Override
     public void bootstrap(@NotNull BootstrapContext context) {
         // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
-
-        context.getLifecycleManager().registerEventHandler(RegistryEvents.PAINTING_VARIANT.freeze(), event -> {
-            event.registry().register(NEW, builder -> {
-                builder.assetId(Key.key("wind"))
-                    .author(text("ME"))
-                    .width(2)
-                    .height(2);
-            });
-        });
     }
 
 }

From 90d50aafc5a82d727d6cb304cbbd630bf5df9b2d Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 22 Dec 2024 11:48:32 -0800
Subject: [PATCH 283/285] update .editorconfig

---
 .editorconfig | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/.editorconfig b/.editorconfig
index 198db6e8a1..9c12172107 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -32,10 +32,7 @@ ij_java_generate_final_parameters = true
 ij_java_method_parameters_new_line_after_left_paren = true
 ij_java_method_parameters_right_paren_on_new_line = true
 
-[test-plugin/**/*.java]
-ij_java_use_fq_class_names = false
-
-[Paper-Server/src/main/resources/data/**/*.json]
+[paper-server/src/minecraft/resources/data/**/*.json]
 indent_size = 2
 
 [paper-api-generator/generated/**/*.java]

From 8ad15d64f00ecf1f11be4cbd715c229daa0209de Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 22 Dec 2024 11:45:15 +0100
Subject: [PATCH 284/285] Update minecraft dir references

---
 CONTRIBUTING.md  | 10 +++++-----
 build.gradle.kts |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 74421624b1..d793b54e6a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -66,16 +66,16 @@ Assuming you have already forked the repository:
 2. Type `./gradlew applyPatches` in a terminal to apply the changes from upstream.
 On Windows, replace the `./` with `.\` at the beginning for all `gradlew` commands;
 3. cd into `paper-server` for server changes, and `paper-api` for API changes.
-**Only changes made in `paper-server/src/vanilla` have to deal with the patch system.**
+**Only changes made in `paper-server/src/minecraft` have to deal with the patch system.**
 
-`paper-server/src/vanilla` is not a git repositories in the traditional sense. Its
-initial commits are the decompiled and deobfuscated Vanilla source files. The per-file
+`paper-server/src/minecraft` is not a git repositories in the traditional sense. Its
+initial commits are the decompiled and deobfuscated Minecraft source files. The per-file
 patches are applied on top of these files as a single, large commit, which is then followed
 by the individual feature-patch commits.
 
-### Modifying (per-file) Vanilla patches
+### Modifying (per-file) Minecraft patches
 
-This is generally what you need to do when editing Vanilla files. Updating our
+This is generally what you need to do when editing Minecraft files. Updating our
 per-file patches is as easy as making your changes and then running
 # TODO
 in the root directory. If nothing went wrong, you can rebuild patches with
diff --git a/build.gradle.kts b/build.gradle.kts
index 75fc6a21af..03fd12ce48 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -145,7 +145,7 @@ tasks.register("checkWork") {
 
     val input = providers.fileContents(layout.projectDirectory.file("$CACHE_PATH/last-updating-folder")).asText.map { it.trim() }
     val patchFolder = layout.projectDirectory.dir("paper-server/patches/sources").dir(input)
-    val sourceFolder = layout.projectDirectory.dir("paper-server/src/vanilla/java").dir(input)
+    val sourceFolder = layout.projectDirectory.dir("paper-server/src/minecraft/java").dir(input)
     val targetFolder = providers.gradleProperty("cleanPaperRepo").map {
         expandUserHome(it).resolve(input.get())
     }

From 083c083188825b6c758e700023541d4db7804502 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 22 Dec 2024 13:50:00 -0800
Subject: [PATCH 285/285] Fix a bunch more issues arising from mutable types
 (#11769)

---
 .../paper/event/block/BlockDestroyEvent.java  |  2 +-
 .../paper/event/player/PlayerJumpEvent.java   |  2 +-
 .../event/player/PlayerSetSpawnEvent.java     |  2 +-
 .../paper/event/entity/EntityMoveEvent.java   |  4 ++--
 .../border/WorldBorderCenterChangeEvent.java  |  2 +-
 .../io/papermc/paper/potion/PotionMix.java    | 12 +++++------
 .../src/main/java/org/bukkit/Vibration.java   |  4 ++--
 .../event/block/BlockDispenseEvent.java       |  2 +-
 .../event/block/FluidLevelChangeEvent.java    |  2 +-
 .../event/entity/EntityKnockbackEvent.java    |  2 +-
 .../event/entity/EntityTeleportEvent.java     |  4 ++--
 .../event/player/PlayerInteractEvent.java     |  2 +-
 .../bukkit/event/player/PlayerMoveEvent.java  |  4 ++--
 .../event/player/PlayerRespawnEvent.java      |  2 +-
 .../event/player/PlayerVelocityEvent.java     |  2 +-
 .../event/world/AsyncStructureSpawnEvent.java |  2 +-
 .../java/org/bukkit/loot/LootContext.java     |  6 +++---
 .../java/org/bukkit/util/Transformation.java  | 20 +++++++++----------
 .../player/PlayerSpawnLocationEvent.java      |  2 +-
 19 files changed, 39 insertions(+), 39 deletions(-)

diff --git a/paper-api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java b/paper-api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java
index 122ccdef02..78084f4c90 100644
--- a/paper-api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java
+++ b/paper-api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java
@@ -55,7 +55,7 @@ public class BlockDestroyEvent extends BlockExpEvent implements Cancellable {
      * @param effectBlock block effect
      */
     public void setEffectBlock(final BlockData effectBlock) {
-        this.effectBlock = effectBlock;
+        this.effectBlock = effectBlock.clone();
     }
 
     /**
diff --git a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java
index 1d07c3d6bf..e3d4c3f912 100644
--- a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java
+++ b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java
@@ -79,7 +79,7 @@ public class PlayerJumpEvent extends PlayerEvent implements Cancellable {
     public void setFrom(final Location from) {
         Preconditions.checkArgument(from != null, "Cannot use null from location!");
         Preconditions.checkArgument(from.getWorld() != null, "Cannot use from location with null world!");
-        this.from = from;
+        this.from = from.clone();
     }
 
     /**
diff --git a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.java b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.java
index 41d42d73cf..1688c2c4ee 100644
--- a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.java
+++ b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerSetSpawnEvent.java
@@ -66,7 +66,7 @@ public class PlayerSetSpawnEvent extends PlayerEvent implements Cancellable {
      * @param location the spawn location, or {@code null} to remove the spawn location
      */
     public void setLocation(final @Nullable Location location) {
-        this.location = location;
+        this.location = location != null ? location.clone() : null;
     }
 
     /**
diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityMoveEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityMoveEvent.java
index 49ace39539..00fef88d56 100644
--- a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityMoveEvent.java
+++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityMoveEvent.java
@@ -53,7 +53,7 @@ public class EntityMoveEvent extends EntityEvent implements Cancellable {
      */
     public void setFrom(final Location from) {
         this.validateLocation(from);
-        this.from = from;
+        this.from = from.clone();
     }
 
     /**
@@ -72,7 +72,7 @@ public class EntityMoveEvent extends EntityEvent implements Cancellable {
      */
     public void setTo(final Location to) {
         this.validateLocation(to);
-        this.to = to;
+        this.to = to.clone();
     }
 
     /**
diff --git a/paper-api/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java b/paper-api/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java
index 74fe5ad505..5f43aefe7a 100644
--- a/paper-api/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java
+++ b/paper-api/src/main/java/io/papermc/paper/event/world/border/WorldBorderCenterChangeEvent.java
@@ -52,7 +52,7 @@ public class WorldBorderCenterChangeEvent extends WorldBorderEvent implements Ca
      * @param newCenter the new center
      */
     public void setNewCenter(final Location newCenter) {
-        this.newCenter = newCenter;
+        this.newCenter = newCenter.clone();
     }
 
     @Override
diff --git a/paper-api/src/main/java/io/papermc/paper/potion/PotionMix.java b/paper-api/src/main/java/io/papermc/paper/potion/PotionMix.java
index 01b2a7c7a6..41152c98c0 100644
--- a/paper-api/src/main/java/io/papermc/paper/potion/PotionMix.java
+++ b/paper-api/src/main/java/io/papermc/paper/potion/PotionMix.java
@@ -30,9 +30,9 @@ public final class PotionMix implements Keyed {
      */
     public PotionMix(final NamespacedKey key, final ItemStack result, final RecipeChoice input, final RecipeChoice ingredient) {
         this.key = key;
-        this.result = result;
-        this.input = input;
-        this.ingredient = ingredient;
+        this.result = result.clone();
+        this.input = input.clone();
+        this.ingredient = ingredient.clone();
     }
 
     /**
@@ -58,7 +58,7 @@ public final class PotionMix implements Keyed {
      * @return the result itemstack
      */
     public ItemStack getResult() {
-        return this.result;
+        return this.result.clone();
     }
 
     /**
@@ -67,7 +67,7 @@ public final class PotionMix implements Keyed {
      * @return the bottom 3 slot ingredients
      */
     public RecipeChoice getInput() {
-        return this.input;
+        return this.input.clone();
     }
 
     /**
@@ -76,7 +76,7 @@ public final class PotionMix implements Keyed {
      * @return the top slot input
      */
     public RecipeChoice getIngredient() {
-        return this.ingredient;
+        return this.ingredient.clone();
     }
 
     @Override
diff --git a/paper-api/src/main/java/org/bukkit/Vibration.java b/paper-api/src/main/java/org/bukkit/Vibration.java
index bbc01e7c19..2c0e18e8dd 100644
--- a/paper-api/src/main/java/org/bukkit/Vibration.java
+++ b/paper-api/src/main/java/org/bukkit/Vibration.java
@@ -79,7 +79,7 @@ public class Vibration {
             private final Location block;
 
             public BlockDestination(@NotNull Location block) {
-                this.block = block;
+                this.block = block.clone();
             }
 
             public BlockDestination(@NotNull Block block) {
@@ -88,7 +88,7 @@ public class Vibration {
 
             @NotNull
             public Location getLocation() {
-                return block;
+                return block.clone();
             }
 
             @NotNull
diff --git a/paper-api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java b/paper-api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java
index 14d1eb5d93..e8ed75ba7d 100644
--- a/paper-api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java
@@ -65,7 +65,7 @@ public class BlockDispenseEvent extends BlockEvent implements Cancellable {
      * @param vel the velocity of the item being dispensed
      */
     public void setVelocity(@NotNull Vector vel) {
-        velocity = vel;
+        velocity = vel.clone();
     }
 
     @Override
diff --git a/paper-api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java b/paper-api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java
index 9bd0440c37..c61f6b1bd5 100644
--- a/paper-api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java
@@ -43,7 +43,7 @@ public class FluidLevelChangeEvent extends BlockEvent implements Cancellable {
         Preconditions.checkArgument(newData != null, "newData null");
         Preconditions.checkArgument(this.newData.getMaterial().equals(newData.getMaterial()), "Cannot change fluid type");
 
-        this.newData = newData;
+        this.newData = newData.clone();
     }
 
     @Override
diff --git a/paper-api/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java
index 0d465629ec..6c0c2f78de 100644
--- a/paper-api/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java
@@ -99,7 +99,7 @@ public class EntityKnockbackEvent extends EntityEvent implements Cancellable {
     public void setFinalKnockback(@NotNull Vector knockback) {
         Preconditions.checkArgument(knockback != null, "Knockback cannot be null");
 
-        this.knockback = knockback;
+        this.knockback = knockback.clone();
     }
 
     @Override
diff --git a/paper-api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java b/paper-api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java
index a7918049ae..bffad21c02 100644
--- a/paper-api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java
@@ -52,7 +52,7 @@ public class EntityTeleportEvent extends EntityEvent implements Cancellable {
      * @param from New location this entity moved from
      */
     public void setFrom(@NotNull Location from) {
-        this.from = from;
+        this.from = from.clone();
     }
 
     /**
@@ -71,7 +71,7 @@ public class EntityTeleportEvent extends EntityEvent implements Cancellable {
      * @param to New Location this entity moved to
      */
     public void setTo(@Nullable Location to) {
-        this.to = to;
+        this.to = to != null ? to.clone() : null;
     }
 
     @NotNull
diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java
index 69c800d367..5e52f4dc26 100644
--- a/paper-api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java
@@ -241,7 +241,7 @@ public class PlayerInteractEvent extends PlayerEvent implements Cancellable {
     @Nullable
     @Deprecated // Paper
     public Vector getClickedPosition() {
-        return clickedPosistion;
+        return clickedPosistion.clone();
     }
 
     // Paper start
diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java
index b484abf3b0..237d654773 100644
--- a/paper-api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java
@@ -70,7 +70,7 @@ public class PlayerMoveEvent extends PlayerEvent implements Cancellable {
      */
     public void setFrom(@NotNull Location from) {
         validateLocation(from);
-        this.from = from;
+        this.from = from.clone();
     }
 
     /**
@@ -90,7 +90,7 @@ public class PlayerMoveEvent extends PlayerEvent implements Cancellable {
      */
     public void setTo(@NotNull Location to) {
         validateLocation(to);
-        this.to = to;
+        this.to = to.clone();
     }
 
     // Paper start - PlayerMoveEvent improvements
diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java
index 4d925774f7..d1dd5cf0ad 100644
--- a/paper-api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java
@@ -69,7 +69,7 @@ public class PlayerRespawnEvent extends PlayerEvent {
         Preconditions.checkArgument(respawnLocation != null, "Respawn location can not be null");
         Preconditions.checkArgument(respawnLocation.getWorld() != null, "Respawn world can not be null");
 
-        this.respawnLocation = respawnLocation;
+        this.respawnLocation = respawnLocation.clone();
     }
 
     /**
diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java
index 61e098d94a..87b15dcabf 100644
--- a/paper-api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java
@@ -45,7 +45,7 @@ public class PlayerVelocityEvent extends PlayerEvent implements Cancellable {
      * @param velocity The velocity vector that will be sent to the player
      */
     public void setVelocity(@NotNull Vector velocity) {
-        this.velocity = velocity;
+        this.velocity = velocity.clone();
     }
 
     @NotNull
diff --git a/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureSpawnEvent.java b/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureSpawnEvent.java
index 5f05b32bdf..978790b97b 100644
--- a/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureSpawnEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureSpawnEvent.java
@@ -45,7 +45,7 @@ public class AsyncStructureSpawnEvent extends WorldEvent implements Cancellable
      */
     @NotNull
     public BoundingBox getBoundingBox() {
-        return boundingBox;
+        return boundingBox.clone();
     }
 
     /**
diff --git a/paper-api/src/main/java/org/bukkit/loot/LootContext.java b/paper-api/src/main/java/org/bukkit/loot/LootContext.java
index 4a8b2538a8..470f712e8d 100644
--- a/paper-api/src/main/java/org/bukkit/loot/LootContext.java
+++ b/paper-api/src/main/java/org/bukkit/loot/LootContext.java
@@ -24,7 +24,7 @@ public final class LootContext {
     private LootContext(@NotNull Location location, float luck, int lootingModifier, @Nullable Entity lootedEntity, @Nullable HumanEntity killer) {
         Preconditions.checkArgument(location != null, "LootContext location cannot be null");
         Preconditions.checkArgument(location.getWorld() != null, "LootContext World cannot be null");
-        this.location = location;
+        this.location = location.clone();
         this.luck = luck;
         this.lootingModifier = lootingModifier;
         this.lootedEntity = lootedEntity;
@@ -38,7 +38,7 @@ public final class LootContext {
      */
     @NotNull
     public Location getLocation() {
-        return location;
+        return location.clone();
     }
 
     /**
@@ -110,7 +110,7 @@ public final class LootContext {
          * @param location the location the LootContext should use
          */
         public Builder(@NotNull Location location) {
-            this.location = location;
+            this.location = location.clone();
         }
 
         /**
diff --git a/paper-api/src/main/java/org/bukkit/util/Transformation.java b/paper-api/src/main/java/org/bukkit/util/Transformation.java
index 39f9e50c7d..a23ed4193b 100644
--- a/paper-api/src/main/java/org/bukkit/util/Transformation.java
+++ b/paper-api/src/main/java/org/bukkit/util/Transformation.java
@@ -27,9 +27,9 @@ public class Transformation {
         Preconditions.checkArgument(scale != null, "scale cannot be null");
         Preconditions.checkArgument(rightRotation != null, "rightRotation cannot be null");
 
-        this.translation = translation;
+        this.translation = new Vector3f(translation);
         this.leftRotation = new Quaternionf(leftRotation);
-        this.scale = scale;
+        this.scale = new Vector3f(scale);
         this.rightRotation = new Quaternionf(rightRotation);
     }
 
@@ -39,10 +39,10 @@ public class Transformation {
         Preconditions.checkArgument(scale != null, "scale cannot be null");
         Preconditions.checkArgument(rightRotation != null, "rightRotation cannot be null");
 
-        this.translation = translation;
-        this.leftRotation = leftRotation;
-        this.scale = scale;
-        this.rightRotation = rightRotation;
+        this.translation = new Vector3f(translation);
+        this.leftRotation = new Quaternionf(leftRotation);
+        this.scale = new Vector3f(scale);
+        this.rightRotation = new Quaternionf(rightRotation);
     }
 
     /**
@@ -52,7 +52,7 @@ public class Transformation {
      */
     @NotNull
     public Vector3f getTranslation() {
-        return this.translation;
+        return new Vector3f(this.translation);
     }
 
     /**
@@ -62,7 +62,7 @@ public class Transformation {
      */
     @NotNull
     public Quaternionf getLeftRotation() {
-        return this.leftRotation;
+        return new Quaternionf(this.leftRotation);
     }
 
     /**
@@ -72,7 +72,7 @@ public class Transformation {
      */
     @NotNull
     public Vector3f getScale() {
-        return this.scale;
+        return new Vector3f(this.scale);
     }
 
     /**
@@ -82,7 +82,7 @@ public class Transformation {
      */
     @NotNull
     public Quaternionf getRightRotation() {
-        return this.rightRotation;
+        return new Quaternionf(this.rightRotation);
     }
 
     @Override
diff --git a/paper-api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java b/paper-api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java
index 2515887c20..eeedfedc47 100644
--- a/paper-api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java
+++ b/paper-api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java
@@ -37,7 +37,7 @@ public class PlayerSpawnLocationEvent extends PlayerEvent {
      * @param location the spawn location
      */
     public void setSpawnLocation(@NotNull Location location) {
-        this.spawnLocation = location;
+        this.spawnLocation = location.clone();
     }
 
     @NotNull